authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-09-16 16:41:17+02:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-09-16 23:39:29+02:00
loga5bb7108a945cbc12fcba3d3bfe5eb3e2c9e9286
tree80443808285c9fbf1749ac8e142ccd0387bba917
parentab1946de924c7fe107299042c5312fa31f34b93e
signaturebadge-check Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

test: move glibc_compat from link to standalone tests

This is not really testing the linker.

10 files changed, 535 insertions(+), 535 deletions(-)

test/link/build.zig.zon-3
...@@ -15,9 +15,6 @@...@@ -15,9 +15,6 @@
15 .static_libs_from_object_files = .{15 .static_libs_from_object_files = .{
16 .path = "static_libs_from_object_files",16 .path = "static_libs_from_object_files",
17 },17 },
18 .glibc_compat = .{
19 .path = "glibc_compat",
20 },
21 // WASM Cases18 // WASM Cases
22 .wasm_archive = .{19 .wasm_archive = .{
23 .path = "wasm/archive",20 .path = "wasm/archive",
test/link/glibc_compat/build.zig deleted-262
...@@ -1,262 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4// To run executables linked against a specific glibc version, the
5// run-time glibc version needs to be new enough. Check the host's glibc
6// version. Note that this does not allow for translation/vm/emulation
7// services to run these tests.
8const running_glibc_ver = builtin.os.versionRange().gnuLibCVersion();
9
10pub fn build(b: *std.Build) void {
11 const test_step = b.step("test", "Test");
12 b.default_step = test_step;
13
14 for ([_][]const u8{ "aarch64-linux-gnu.2.27", "aarch64-linux-gnu.2.34" }) |t| {
15 const exe = b.addExecutable(.{
16 .name = t,
17 .root_module = b.createModule(.{
18 .root_source_file = null,
19 .target = b.resolveTargetQuery(std.Target.Query.parse(
20 .{ .arch_os_abi = t },
21 ) catch unreachable),
22 .link_libc = true,
23 }),
24 });
25 // We disable UBSAN for these tests as the libc being tested here is
26 // so old, it doesn't even support compiling our UBSAN implementation.
27 exe.bundle_ubsan_rt = false;
28 exe.root_module.sanitize_c = .off;
29 exe.root_module.addCSourceFile(.{ .file = b.path("main.c") });
30 // TODO: actually test the output
31 _ = exe.getEmittedBin();
32 test_step.dependOn(&exe.step);
33 }
34
35 // Build & run a C test case against a sampling of supported glibc versions
36 versions: for ([_][]const u8{
37 // "native-linux-gnu.2.0", // fails with a pile of missing symbols.
38 "native-linux-gnu.2.2.5",
39 "native-linux-gnu.2.4",
40 "native-linux-gnu.2.12",
41 "native-linux-gnu.2.16",
42 "native-linux-gnu.2.22",
43 "native-linux-gnu.2.28",
44 "native-linux-gnu.2.33",
45 "native-linux-gnu.2.38",
46 "native-linux-gnu",
47 }) |t| {
48 const target = b.resolveTargetQuery(std.Target.Query.parse(
49 .{ .arch_os_abi = t },
50 ) catch unreachable);
51
52 const glibc_ver = target.result.os.version_range.linux.glibc;
53
54 // only build test if glibc version supports the architecture
55 for (std.zig.target.available_libcs) |libc| {
56 if (libc.arch != target.result.cpu.arch or
57 libc.os != target.result.os.tag or
58 libc.abi != target.result.abi)
59 continue;
60
61 if (libc.glibc_min) |min| {
62 if (glibc_ver.order(min) == .lt) continue :versions;
63 }
64 }
65
66 const exe = b.addExecutable(.{
67 .name = t,
68 .root_module = b.createModule(.{
69 .root_source_file = null,
70 .target = target,
71 .link_libc = true,
72 }),
73 });
74 // We disable UBSAN for these tests as the libc being tested here is
75 // so old, it doesn't even support compiling our UBSAN implementation.
76 exe.bundle_ubsan_rt = false;
77 exe.root_module.sanitize_c = .off;
78 exe.root_module.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") });
79
80 // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig
81 // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453
82 if (running_glibc_ver) |running_ver| {
83 if (glibc_ver.order(running_ver) == .lt) {
84 const run_cmd = b.addRunArtifact(exe);
85 run_cmd.skip_foreign_checks = true;
86 run_cmd.expectExitCode(0);
87
88 test_step.dependOn(&run_cmd.step);
89 }
90 }
91 const check = exe.checkObject();
92
93 // __errno_location is always a dynamically linked symbol
94 check.checkInDynamicSymtab();
95 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __errno_location");
96
97 // before v2.32 fstat redirects through __fxstat, afterwards its a
98 // normal dynamic symbol
99 check.checkInDynamicSymtab();
100 if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) {
101 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstat");
102
103 check.checkInSymtab();
104 check.checkContains("FUNC LOCAL HIDDEN fstat");
105 } else {
106 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstat");
107
108 check.checkInSymtab();
109 check.checkNotPresent("__fxstat");
110 }
111
112 // before v2.26 reallocarray is not supported
113 check.checkInDynamicSymtab();
114 if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
115 check.checkNotPresent("reallocarray");
116 } else {
117 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray");
118 }
119
120 // before v2.38 strlcpy is not supported
121 check.checkInDynamicSymtab();
122 if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
123 check.checkNotPresent("strlcpy");
124 } else {
125 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy");
126 }
127
128 // v2.16 introduced getauxval()
129 check.checkInDynamicSymtab();
130 if (glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) {
131 check.checkNotPresent("getauxval");
132 } else {
133 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval");
134 }
135
136 // Always have dynamic "exit", "pow", and "powf" references
137 check.checkInDynamicSymtab();
138 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit");
139 check.checkInDynamicSymtab();
140 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT pow");
141 check.checkInDynamicSymtab();
142 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT powf");
143
144 // An atexit local symbol is defined, and depends on undefined dynamic
145 // __cxa_atexit.
146 check.checkInSymtab();
147 check.checkContains("FUNC LOCAL HIDDEN atexit");
148 check.checkInDynamicSymtab();
149 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __cxa_atexit");
150
151 test_step.dependOn(&check.step);
152 }
153
154 // Build & run a Zig test case against a sampling of supported glibc versions
155 versions: for ([_][]const u8{
156 "native-linux-gnu.2.17", // Currently oldest supported, see #17769
157 "native-linux-gnu.2.23",
158 "native-linux-gnu.2.28",
159 "native-linux-gnu.2.33",
160 "native-linux-gnu.2.38",
161 "native-linux-gnu",
162 }) |t| {
163 const target = b.resolveTargetQuery(std.Target.Query.parse(
164 .{ .arch_os_abi = t },
165 ) catch unreachable);
166
167 const glibc_ver = target.result.os.version_range.linux.glibc;
168
169 // only build test if glibc version supports the architecture
170 for (std.zig.target.available_libcs) |libc| {
171 if (libc.arch != target.result.cpu.arch or
172 libc.os != target.result.os.tag or
173 libc.abi != target.result.abi)
174 continue;
175
176 if (libc.glibc_min) |min| {
177 if (glibc_ver.order(min) == .lt) continue :versions;
178 }
179 }
180
181 const exe = b.addExecutable(.{
182 .name = t,
183 .root_module = b.createModule(.{
184 .root_source_file = b.path("glibc_runtime_check.zig"),
185 .target = target,
186 .link_libc = true,
187 }),
188 });
189 // We disable UBSAN for these tests as the libc being tested here is
190 // so old, it doesn't even support compiling our UBSAN implementation.
191 exe.bundle_ubsan_rt = false;
192 exe.root_module.sanitize_c = .off;
193
194 // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig
195 // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453
196 if (running_glibc_ver) |running_ver| {
197 if (glibc_ver.order(running_ver) == .lt) {
198 const run_cmd = b.addRunArtifact(exe);
199 run_cmd.skip_foreign_checks = true;
200 run_cmd.expectExitCode(0);
201
202 test_step.dependOn(&run_cmd.step);
203 }
204 }
205 const check = exe.checkObject();
206
207 // __errno_location is always a dynamically linked symbol
208 check.checkInDynamicSymtab();
209 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __errno_location");
210
211 // before v2.32 fstatat redirects through __fxstatat, afterwards its a
212 // normal dynamic symbol
213 if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) {
214 check.checkInDynamicSymtab();
215 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstatat");
216
217 check.checkInSymtab();
218 check.checkContains("FUNC LOCAL HIDDEN fstatat");
219 } else {
220 check.checkInDynamicSymtab();
221 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstatat");
222
223 check.checkInSymtab();
224 check.checkNotPresent("FUNC LOCAL HIDDEN fstatat");
225 }
226
227 // before v2.26 reallocarray is not supported
228 if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
229 check.checkInDynamicSymtab();
230 check.checkNotPresent("reallocarray");
231 } else {
232 check.checkInDynamicSymtab();
233 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray");
234 }
235
236 // before v2.38 strlcpy is not supported
237 if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
238 check.checkInDynamicSymtab();
239 check.checkNotPresent("strlcpy");
240 } else {
241 check.checkInDynamicSymtab();
242 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy");
243 }
244
245 // v2.16 introduced getauxval(), so always present
246 check.checkInDynamicSymtab();
247 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval");
248
249 // Always have a dynamic "exit" reference
250 check.checkInDynamicSymtab();
251 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit");
252
253 // An atexit local symbol is defined, and depends on undefined dynamic
254 // __cxa_atexit.
255 check.checkInSymtab();
256 check.checkContains("FUNC LOCAL HIDDEN atexit");
257 check.checkInDynamicSymtab();
258 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __cxa_atexit");
259
260 test_step.dependOn(&check.step);
261 }
262}
test/link/glibc_compat/glibc_runtime_check.c deleted-114
...@@ -1,114 +0,0 @@
1/*
2 * Exercise complicating glibc symbols from C code. Complicating symbols
3 * are ones that have moved between glibc versions, or use floating point
4 * parameters, or have otherwise tripped up the Zig glibc compatibility
5 * code.
6 */
7#include <assert.h>
8#include <errno.h>
9#include <features.h>
10#include <fcntl.h>
11#include <math.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <sys/auxv.h>
16#include <sys/stat.h>
17#include <unistd.h>
18
19/* errno is compilcated (thread-local, dynamically provided, etc). */
20static void check_errno()
21{
22 int invalid_fd = open("/doesnotexist", O_RDONLY);
23 assert(invalid_fd == -1);
24 assert(errno == ENOENT);
25}
26
27/* fstat has moved around in glibc (between libc_nonshared and libc) */
28static void check_fstat()
29{
30 int self_fd = open("/proc/self/exe", O_RDONLY);
31
32 struct stat statbuf = {0};
33 int rc = fstat(self_fd, &statbuf);
34
35 assert(rc == 0);
36
37 assert(statbuf.st_dev != 0);
38 assert(statbuf.st_ino != 0);
39 assert(statbuf.st_mode != 0);
40 assert(statbuf.st_size > 0);
41 assert(statbuf.st_blocks > 0);
42 assert(statbuf.st_ctim.tv_sec > 0);
43
44 close(self_fd);
45}
46
47/* Some targets have a complicated ABI for floats and doubles */
48static void check_fp_abi()
49{
50 // Picked "pow" as it takes and returns doubles
51 assert(pow(10.0, 10.0) == 10000000000.0);
52 assert(powf(10.0f, 10.0f) == 10000000000.0f);
53}
54
55/* strlcpy introduced in glibc 2.38 */
56static void check_strlcpy()
57{
58#if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 38) || (__GLIBC__ > 2)
59 char target[4] = {0};
60 strlcpy(target, "this is a source string", 4);
61
62 assert(strcmp(target, "thi") == 0);
63#endif
64}
65
66/* reallocarray introduced in glibc 2.26 */
67static void check_reallocarray()
68{
69#if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 26) || (__GLIBC__ > 2)
70 const size_t el_size = 32;
71 void* base = reallocarray(NULL, 10, el_size);
72 void* grown = reallocarray(base, 100, el_size);
73
74 assert(base != NULL);
75 assert(grown != NULL);
76
77 free(grown);
78#endif
79}
80
81/* getauxval introduced in glibc 2.16 */
82static void check_getauxval()
83{
84#if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 16) || (__GLIBC__ > 2)
85 int pgsz = getauxval(AT_PAGESZ);
86 assert(pgsz >= 4*1024);
87#endif
88}
89
90/* atexit() is part of libc_nonshared */
91static void force_exit_0()
92{
93 exit(0);
94}
95
96static void check_atexit()
97{
98 int rc = atexit(force_exit_0);
99 assert(rc == 0);
100}
101
102int main() {
103 int rc;
104
105 check_errno();
106 check_fstat();
107 check_fp_abi();
108 check_strlcpy();
109 check_reallocarray();
110 check_getauxval();
111 check_atexit();
112
113 exit(99); // exit code overridden by atexit handler
114}
test/link/glibc_compat/glibc_runtime_check.zig deleted-116
...@@ -1,116 +0,0 @@
1// A zig test case that exercises some glibc symbols that have uncovered
2// problems in the past. This test must be compiled against a glibc.
3//
4// The build.zig tests the binary built from this source to see that
5// symbols are statically or dynamically linked, as expected.
6
7const std = @import("std");
8const builtin = @import("builtin");
9const assert = std.debug.assert;
10
11const c_malloc = @cImport(
12 @cInclude("malloc.h"), // for reallocarray
13);
14
15const c_stdlib = @cImport(
16 @cInclude("stdlib.h"), // for atexit
17);
18
19const c_string = @cImport(
20 @cInclude("string.h"), // for strlcpy
21);
22
23// Version of glibc this test is being built to run against
24const glibc_ver = builtin.os.versionRange().gnuLibCVersion().?;
25
26// PR #17034 - fstat moved between libc_nonshared and libc
27fn checkStat() !void {
28 const cwdFd = std.fs.cwd().fd;
29
30 var stat = std.mem.zeroes(std.c.Stat);
31 var result = std.c.fstatat(cwdFd, "a_file_that_definitely_does_not_exist", &stat, 0);
32 assert(result == -1);
33 assert(std.posix.errno(result) == .NOENT);
34
35 result = std.c.stat("a_file_that_definitely_does_not_exist", &stat);
36 assert(result == -1);
37 assert(std.posix.errno(result) == .NOENT);
38}
39
40// PR #17607 - reallocarray not visible in headers
41fn checkReallocarray() !void {
42 // reallocarray was introduced in v2.26
43 if (comptime glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
44 if (@hasDecl(c_malloc, "reallocarray")) {
45 @compileError("Before v2.26 'malloc.h' does not define 'reallocarray'");
46 }
47 } else {
48 return try checkReallocarray_v2_26();
49 }
50}
51
52fn checkReallocarray_v2_26() !void {
53 const size = 16;
54 const tenX = c_malloc.reallocarray(c_malloc.NULL, 10, size);
55 const elevenX = c_malloc.reallocarray(tenX, 11, size);
56
57 assert(tenX != c_malloc.NULL);
58 assert(elevenX != c_malloc.NULL);
59}
60
61// getauxval introduced in v2.16
62fn checkGetAuxVal() !void {
63 if (comptime glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) {
64 if (@hasDecl(std.c, "getauxval")) {
65 @compileError("Before v2.16 glibc does not define 'getauxval'");
66 }
67 } else {
68 try checkGetAuxVal_v2_16();
69 }
70}
71
72fn checkGetAuxVal_v2_16() !void {
73 const base = std.c.getauxval(std.elf.AT_BASE);
74 const pgsz = std.c.getauxval(std.elf.AT_PAGESZ);
75
76 assert(base != 0);
77 assert(pgsz != 0);
78}
79
80// strlcpy introduced in v2.38, which is newer than many installed glibcs
81fn checkStrlcpy() !void {
82 if (comptime glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
83 if (@hasDecl(c_string, "strlcpy")) {
84 @compileError("Before v2.38 glibc does not define 'strlcpy'");
85 }
86 } else {
87 try checkStrlcpy_v2_38();
88 }
89}
90
91fn checkStrlcpy_v2_38() !void {
92 var buf: [99]u8 = undefined;
93 const used = c_string.strlcpy(&buf, "strlcpy works!", buf.len);
94 assert(used == 14);
95}
96
97// atexit is part of libc_nonshared, so ensure its linked in correctly
98fn forceExit0Callback() callconv(.c) void {
99 std.c.exit(0); // Override the main() exit code
100}
101
102fn checkAtExit() !void {
103 const result = c_stdlib.atexit(forceExit0Callback);
104 assert(result == 0);
105}
106
107pub fn main() !u8 {
108 try checkStat();
109 try checkReallocarray();
110 try checkStrlcpy();
111
112 try checkGetAuxVal();
113 try checkAtExit();
114
115 std.c.exit(1); // overridden by atexit() callback
116}
test/link/glibc_compat/main.c deleted-40
...@@ -1,40 +0,0 @@
1#define _FILE_OFFSET_BITS 64
2#include <unistd.h>
3#include <fcntl.h>
4#include <stdio.h>
5#include <resolv.h>
6
7int main() {
8 /* in glibc 2.28+ and _FILE_OFFSET_BITS=64 fcntl is #define'd to fcntl64
9 * Thus headers say `fcntl64` exists, but libc.so.6 (the old one)
10 * disagrees, resulting in a linking error unless headers are made
11 * backwards-compatible.
12 *
13 * Glibc 2.28+:
14 * FUNC GLOBAL DEFAULT UND fcntl64@GLIBC_2.28 (3):
15 *
16 * Glibc 2.27 or older:
17 * FUNC GLOBAL DEFAULT UND fcntl@GLIBC_2.2.5
18 */
19 printf("address to fcntl: %p\n", fcntl);
20
21 /* The following functions became symbols of their own right with glibc
22 * 2.34+. Before 2.34 resolv.h would #define res_search __res_search; and
23 * __res_search is a valid symbol since the beginning of time.
24 *
25 * On glibc 2.34+ these symbols are linked this way:
26 * FUNC GLOBAL DEFAULT UND res_search@GLIBC_2.34 (2)
27 *
28 * Pre-glibc 2.34:
29 * FUNC GLOBAL DEFAULT UND __res_search@GLIBC_2.2.5 (4)
30 */
31 printf("address to res_search: %p\n", res_search);
32 printf("address to res_nsearch: %p\n", res_nsearch);
33 printf("address to res_query: %p\n", res_query);
34 printf("address to res_nquery: %p\n", res_nquery);
35 printf("address to res_querydomain: %p\n", res_querydomain);
36 printf("address to res_nquerydomain: %p\n", res_nquerydomain);
37 printf("address to dn_skipname: %p\n", dn_skipname);
38 printf("address to dn_comp: %p\n", dn_comp);
39 printf("address to dn_expand: %p\n", dn_expand);
40}
test/standalone/build.zig.zon+3
...@@ -49,6 +49,9 @@...@@ -49,6 +49,9 @@
49 .pkg_import = .{49 .pkg_import = .{
50 .path = "pkg_import",50 .path = "pkg_import",
51 },51 },
52 .glibc_compat = .{
53 .path = "glibc_compat",
54 },
52 .install_raw_hex = .{55 .install_raw_hex = .{
53 .path = "install_raw_hex",56 .path = "install_raw_hex",
54 },57 },
test/standalone/glibc_compat/build.zig created+262
...@@ -0,0 +1,262 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4// To run executables linked against a specific glibc version, the
5// run-time glibc version needs to be new enough. Check the host's glibc
6// version. Note that this does not allow for translation/vm/emulation
7// services to run these tests.
8const running_glibc_ver = builtin.os.versionRange().gnuLibCVersion();
9
10pub fn build(b: *std.Build) void {
11 const test_step = b.step("test", "Test");
12 b.default_step = test_step;
13
14 for ([_][]const u8{ "aarch64-linux-gnu.2.27", "aarch64-linux-gnu.2.34" }) |t| {
15 const exe = b.addExecutable(.{
16 .name = t,
17 .root_module = b.createModule(.{
18 .root_source_file = null,
19 .target = b.resolveTargetQuery(std.Target.Query.parse(
20 .{ .arch_os_abi = t },
21 ) catch unreachable),
22 .link_libc = true,
23 }),
24 });
25 // We disable UBSAN for these tests as the libc being tested here is
26 // so old, it doesn't even support compiling our UBSAN implementation.
27 exe.bundle_ubsan_rt = false;
28 exe.root_module.sanitize_c = .off;
29 exe.root_module.addCSourceFile(.{ .file = b.path("main.c") });
30 // TODO: actually test the output
31 _ = exe.getEmittedBin();
32 test_step.dependOn(&exe.step);
33 }
34
35 // Build & run a C test case against a sampling of supported glibc versions
36 versions: for ([_][]const u8{
37 // "native-linux-gnu.2.0", // fails with a pile of missing symbols.
38 "native-linux-gnu.2.2.5",
39 "native-linux-gnu.2.4",
40 "native-linux-gnu.2.12",
41 "native-linux-gnu.2.16",
42 "native-linux-gnu.2.22",
43 "native-linux-gnu.2.28",
44 "native-linux-gnu.2.33",
45 "native-linux-gnu.2.38",
46 "native-linux-gnu",
47 }) |t| {
48 const target = b.resolveTargetQuery(std.Target.Query.parse(
49 .{ .arch_os_abi = t },
50 ) catch unreachable);
51
52 const glibc_ver = target.result.os.version_range.linux.glibc;
53
54 // only build test if glibc version supports the architecture
55 for (std.zig.target.available_libcs) |libc| {
56 if (libc.arch != target.result.cpu.arch or
57 libc.os != target.result.os.tag or
58 libc.abi != target.result.abi)
59 continue;
60
61 if (libc.glibc_min) |min| {
62 if (glibc_ver.order(min) == .lt) continue :versions;
63 }
64 }
65
66 const exe = b.addExecutable(.{
67 .name = t,
68 .root_module = b.createModule(.{
69 .root_source_file = null,
70 .target = target,
71 .link_libc = true,
72 }),
73 });
74 // We disable UBSAN for these tests as the libc being tested here is
75 // so old, it doesn't even support compiling our UBSAN implementation.
76 exe.bundle_ubsan_rt = false;
77 exe.root_module.sanitize_c = .off;
78 exe.root_module.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") });
79
80 // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig
81 // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453
82 if (running_glibc_ver) |running_ver| {
83 if (glibc_ver.order(running_ver) == .lt) {
84 const run_cmd = b.addRunArtifact(exe);
85 run_cmd.skip_foreign_checks = true;
86 run_cmd.expectExitCode(0);
87
88 test_step.dependOn(&run_cmd.step);
89 }
90 }
91 const check = exe.checkObject();
92
93 // __errno_location is always a dynamically linked symbol
94 check.checkInDynamicSymtab();
95 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __errno_location");
96
97 // before v2.32 fstat redirects through __fxstat, afterwards its a
98 // normal dynamic symbol
99 check.checkInDynamicSymtab();
100 if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) {
101 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstat");
102
103 check.checkInSymtab();
104 check.checkContains("FUNC LOCAL HIDDEN fstat");
105 } else {
106 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstat");
107
108 check.checkInSymtab();
109 check.checkNotPresent("__fxstat");
110 }
111
112 // before v2.26 reallocarray is not supported
113 check.checkInDynamicSymtab();
114 if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
115 check.checkNotPresent("reallocarray");
116 } else {
117 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray");
118 }
119
120 // before v2.38 strlcpy is not supported
121 check.checkInDynamicSymtab();
122 if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
123 check.checkNotPresent("strlcpy");
124 } else {
125 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy");
126 }
127
128 // v2.16 introduced getauxval()
129 check.checkInDynamicSymtab();
130 if (glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) {
131 check.checkNotPresent("getauxval");
132 } else {
133 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval");
134 }
135
136 // Always have dynamic "exit", "pow", and "powf" references
137 check.checkInDynamicSymtab();
138 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit");
139 check.checkInDynamicSymtab();
140 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT pow");
141 check.checkInDynamicSymtab();
142 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT powf");
143
144 // An atexit local symbol is defined, and depends on undefined dynamic
145 // __cxa_atexit.
146 check.checkInSymtab();
147 check.checkContains("FUNC LOCAL HIDDEN atexit");
148 check.checkInDynamicSymtab();
149 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __cxa_atexit");
150
151 test_step.dependOn(&check.step);
152 }
153
154 // Build & run a Zig test case against a sampling of supported glibc versions
155 versions: for ([_][]const u8{
156 "native-linux-gnu.2.17", // Currently oldest supported, see #17769
157 "native-linux-gnu.2.23",
158 "native-linux-gnu.2.28",
159 "native-linux-gnu.2.33",
160 "native-linux-gnu.2.38",
161 "native-linux-gnu",
162 }) |t| {
163 const target = b.resolveTargetQuery(std.Target.Query.parse(
164 .{ .arch_os_abi = t },
165 ) catch unreachable);
166
167 const glibc_ver = target.result.os.version_range.linux.glibc;
168
169 // only build test if glibc version supports the architecture
170 for (std.zig.target.available_libcs) |libc| {
171 if (libc.arch != target.result.cpu.arch or
172 libc.os != target.result.os.tag or
173 libc.abi != target.result.abi)
174 continue;
175
176 if (libc.glibc_min) |min| {
177 if (glibc_ver.order(min) == .lt) continue :versions;
178 }
179 }
180
181 const exe = b.addExecutable(.{
182 .name = t,
183 .root_module = b.createModule(.{
184 .root_source_file = b.path("glibc_runtime_check.zig"),
185 .target = target,
186 .link_libc = true,
187 }),
188 });
189 // We disable UBSAN for these tests as the libc being tested here is
190 // so old, it doesn't even support compiling our UBSAN implementation.
191 exe.bundle_ubsan_rt = false;
192 exe.root_module.sanitize_c = .off;
193
194 // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig
195 // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453
196 if (running_glibc_ver) |running_ver| {
197 if (glibc_ver.order(running_ver) == .lt) {
198 const run_cmd = b.addRunArtifact(exe);
199 run_cmd.skip_foreign_checks = true;
200 run_cmd.expectExitCode(0);
201
202 test_step.dependOn(&run_cmd.step);
203 }
204 }
205 const check = exe.checkObject();
206
207 // __errno_location is always a dynamically linked symbol
208 check.checkInDynamicSymtab();
209 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __errno_location");
210
211 // before v2.32 fstatat redirects through __fxstatat, afterwards its a
212 // normal dynamic symbol
213 if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) {
214 check.checkInDynamicSymtab();
215 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstatat");
216
217 check.checkInSymtab();
218 check.checkContains("FUNC LOCAL HIDDEN fstatat");
219 } else {
220 check.checkInDynamicSymtab();
221 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstatat");
222
223 check.checkInSymtab();
224 check.checkNotPresent("FUNC LOCAL HIDDEN fstatat");
225 }
226
227 // before v2.26 reallocarray is not supported
228 if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
229 check.checkInDynamicSymtab();
230 check.checkNotPresent("reallocarray");
231 } else {
232 check.checkInDynamicSymtab();
233 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray");
234 }
235
236 // before v2.38 strlcpy is not supported
237 if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
238 check.checkInDynamicSymtab();
239 check.checkNotPresent("strlcpy");
240 } else {
241 check.checkInDynamicSymtab();
242 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy");
243 }
244
245 // v2.16 introduced getauxval(), so always present
246 check.checkInDynamicSymtab();
247 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval");
248
249 // Always have a dynamic "exit" reference
250 check.checkInDynamicSymtab();
251 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit");
252
253 // An atexit local symbol is defined, and depends on undefined dynamic
254 // __cxa_atexit.
255 check.checkInSymtab();
256 check.checkContains("FUNC LOCAL HIDDEN atexit");
257 check.checkInDynamicSymtab();
258 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __cxa_atexit");
259
260 test_step.dependOn(&check.step);
261 }
262}
test/standalone/glibc_compat/glibc_runtime_check.c created+114
...@@ -0,0 +1,114 @@
1/*
2 * Exercise complicating glibc symbols from C code. Complicating symbols
3 * are ones that have moved between glibc versions, or use floating point
4 * parameters, or have otherwise tripped up the Zig glibc compatibility
5 * code.
6 */
7#include <assert.h>
8#include <errno.h>
9#include <features.h>
10#include <fcntl.h>
11#include <math.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <sys/auxv.h>
16#include <sys/stat.h>
17#include <unistd.h>
18
19/* errno is compilcated (thread-local, dynamically provided, etc). */
20static void check_errno()
21{
22 int invalid_fd = open("/doesnotexist", O_RDONLY);
23 assert(invalid_fd == -1);
24 assert(errno == ENOENT);
25}
26
27/* fstat has moved around in glibc (between libc_nonshared and libc) */
28static void check_fstat()
29{
30 int self_fd = open("/proc/self/exe", O_RDONLY);
31
32 struct stat statbuf = {0};
33 int rc = fstat(self_fd, &statbuf);
34
35 assert(rc == 0);
36
37 assert(statbuf.st_dev != 0);
38 assert(statbuf.st_ino != 0);
39 assert(statbuf.st_mode != 0);
40 assert(statbuf.st_size > 0);
41 assert(statbuf.st_blocks > 0);
42 assert(statbuf.st_ctim.tv_sec > 0);
43
44 close(self_fd);
45}
46
47/* Some targets have a complicated ABI for floats and doubles */
48static void check_fp_abi()
49{
50 // Picked "pow" as it takes and returns doubles
51 assert(pow(10.0, 10.0) == 10000000000.0);
52 assert(powf(10.0f, 10.0f) == 10000000000.0f);
53}
54
55/* strlcpy introduced in glibc 2.38 */
56static void check_strlcpy()
57{
58#if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 38) || (__GLIBC__ > 2)
59 char target[4] = {0};
60 strlcpy(target, "this is a source string", 4);
61
62 assert(strcmp(target, "thi") == 0);
63#endif
64}
65
66/* reallocarray introduced in glibc 2.26 */
67static void check_reallocarray()
68{
69#if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 26) || (__GLIBC__ > 2)
70 const size_t el_size = 32;
71 void* base = reallocarray(NULL, 10, el_size);
72 void* grown = reallocarray(base, 100, el_size);
73
74 assert(base != NULL);
75 assert(grown != NULL);
76
77 free(grown);
78#endif
79}
80
81/* getauxval introduced in glibc 2.16 */
82static void check_getauxval()
83{
84#if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 16) || (__GLIBC__ > 2)
85 int pgsz = getauxval(AT_PAGESZ);
86 assert(pgsz >= 4*1024);
87#endif
88}
89
90/* atexit() is part of libc_nonshared */
91static void force_exit_0()
92{
93 exit(0);
94}
95
96static void check_atexit()
97{
98 int rc = atexit(force_exit_0);
99 assert(rc == 0);
100}
101
102int main() {
103 int rc;
104
105 check_errno();
106 check_fstat();
107 check_fp_abi();
108 check_strlcpy();
109 check_reallocarray();
110 check_getauxval();
111 check_atexit();
112
113 exit(99); // exit code overridden by atexit handler
114}
test/standalone/glibc_compat/glibc_runtime_check.zig created+116
...@@ -0,0 +1,116 @@
1// A zig test case that exercises some glibc symbols that have uncovered
2// problems in the past. This test must be compiled against a glibc.
3//
4// The build.zig tests the binary built from this source to see that
5// symbols are statically or dynamically linked, as expected.
6
7const std = @import("std");
8const builtin = @import("builtin");
9const assert = std.debug.assert;
10
11const c_malloc = @cImport(
12 @cInclude("malloc.h"), // for reallocarray
13);
14
15const c_stdlib = @cImport(
16 @cInclude("stdlib.h"), // for atexit
17);
18
19const c_string = @cImport(
20 @cInclude("string.h"), // for strlcpy
21);
22
23// Version of glibc this test is being built to run against
24const glibc_ver = builtin.os.versionRange().gnuLibCVersion().?;
25
26// PR #17034 - fstat moved between libc_nonshared and libc
27fn checkStat() !void {
28 const cwdFd = std.fs.cwd().fd;
29
30 var stat = std.mem.zeroes(std.c.Stat);
31 var result = std.c.fstatat(cwdFd, "a_file_that_definitely_does_not_exist", &stat, 0);
32 assert(result == -1);
33 assert(std.posix.errno(result) == .NOENT);
34
35 result = std.c.stat("a_file_that_definitely_does_not_exist", &stat);
36 assert(result == -1);
37 assert(std.posix.errno(result) == .NOENT);
38}
39
40// PR #17607 - reallocarray not visible in headers
41fn checkReallocarray() !void {
42 // reallocarray was introduced in v2.26
43 if (comptime glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
44 if (@hasDecl(c_malloc, "reallocarray")) {
45 @compileError("Before v2.26 'malloc.h' does not define 'reallocarray'");
46 }
47 } else {
48 return try checkReallocarray_v2_26();
49 }
50}
51
52fn checkReallocarray_v2_26() !void {
53 const size = 16;
54 const tenX = c_malloc.reallocarray(c_malloc.NULL, 10, size);
55 const elevenX = c_malloc.reallocarray(tenX, 11, size);
56
57 assert(tenX != c_malloc.NULL);
58 assert(elevenX != c_malloc.NULL);
59}
60
61// getauxval introduced in v2.16
62fn checkGetAuxVal() !void {
63 if (comptime glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) {
64 if (@hasDecl(std.c, "getauxval")) {
65 @compileError("Before v2.16 glibc does not define 'getauxval'");
66 }
67 } else {
68 try checkGetAuxVal_v2_16();
69 }
70}
71
72fn checkGetAuxVal_v2_16() !void {
73 const base = std.c.getauxval(std.elf.AT_BASE);
74 const pgsz = std.c.getauxval(std.elf.AT_PAGESZ);
75
76 assert(base != 0);
77 assert(pgsz != 0);
78}
79
80// strlcpy introduced in v2.38, which is newer than many installed glibcs
81fn checkStrlcpy() !void {
82 if (comptime glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
83 if (@hasDecl(c_string, "strlcpy")) {
84 @compileError("Before v2.38 glibc does not define 'strlcpy'");
85 }
86 } else {
87 try checkStrlcpy_v2_38();
88 }
89}
90
91fn checkStrlcpy_v2_38() !void {
92 var buf: [99]u8 = undefined;
93 const used = c_string.strlcpy(&buf, "strlcpy works!", buf.len);
94 assert(used == 14);
95}
96
97// atexit is part of libc_nonshared, so ensure its linked in correctly
98fn forceExit0Callback() callconv(.c) void {
99 std.c.exit(0); // Override the main() exit code
100}
101
102fn checkAtExit() !void {
103 const result = c_stdlib.atexit(forceExit0Callback);
104 assert(result == 0);
105}
106
107pub fn main() !u8 {
108 try checkStat();
109 try checkReallocarray();
110 try checkStrlcpy();
111
112 try checkGetAuxVal();
113 try checkAtExit();
114
115 std.c.exit(1); // overridden by atexit() callback
116}
test/standalone/glibc_compat/main.c created+40
...@@ -0,0 +1,40 @@
1#define _FILE_OFFSET_BITS 64
2#include <unistd.h>
3#include <fcntl.h>
4#include <stdio.h>
5#include <resolv.h>
6
7int main() {
8 /* in glibc 2.28+ and _FILE_OFFSET_BITS=64 fcntl is #define'd to fcntl64
9 * Thus headers say `fcntl64` exists, but libc.so.6 (the old one)
10 * disagrees, resulting in a linking error unless headers are made
11 * backwards-compatible.
12 *
13 * Glibc 2.28+:
14 * FUNC GLOBAL DEFAULT UND fcntl64@GLIBC_2.28 (3):
15 *
16 * Glibc 2.27 or older:
17 * FUNC GLOBAL DEFAULT UND fcntl@GLIBC_2.2.5
18 */
19 printf("address to fcntl: %p\n", fcntl);
20
21 /* The following functions became symbols of their own right with glibc
22 * 2.34+. Before 2.34 resolv.h would #define res_search __res_search; and
23 * __res_search is a valid symbol since the beginning of time.
24 *
25 * On glibc 2.34+ these symbols are linked this way:
26 * FUNC GLOBAL DEFAULT UND res_search@GLIBC_2.34 (2)
27 *
28 * Pre-glibc 2.34:
29 * FUNC GLOBAL DEFAULT UND __res_search@GLIBC_2.2.5 (4)
30 */
31 printf("address to res_search: %p\n", res_search);
32 printf("address to res_nsearch: %p\n", res_nsearch);
33 printf("address to res_query: %p\n", res_query);
34 printf("address to res_nquery: %p\n", res_nquery);
35 printf("address to res_querydomain: %p\n", res_querydomain);
36 printf("address to res_nquerydomain: %p\n", res_nquerydomain);
37 printf("address to dn_skipname: %p\n", dn_skipname);
38 printf("address to dn_comp: %p\n", dn_comp);
39 printf("address to dn_expand: %p\n", dn_expand);
40}