authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-06-08 15:57:35-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-06-08 15:57:35-04:00
log7ae9d8089d49d3bf04b5f248caf46fc095a71290
treea65bf3336ee5b846e723d2d991c380f0d543cea8
parent24f28753e6df3d0bea28afaccc02d0c9f50cd04f
parented795a907d329487b5fdc6f21e2bace11998e5bf
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #19007 from rootbeer/glibc-c-test

test/link/glibc_compat: Add C test case for glibc versions

4 files changed, 226 insertions(+), 3 deletions(-)

lib/libc/glibc/README.md+3-1
...@@ -31,7 +31,9 @@ The GNU C Library supports a very wide set of platforms and architectures....@@ -31,7 +31,9 @@ The GNU C Library supports a very wide set of platforms and architectures.
31The current Zig support for glibc only includes Linux.31The current Zig support for glibc only includes Linux.
3232
33Zig supports glibc versions back to v2.17 (2012) as the Zig standard33Zig supports glibc versions back to v2.17 (2012) as the Zig standard
34library depends on symbols that were introduced in 2.17.34library depends on symbols that were introduced in 2.17. When used as a C
35or C++ compiler (i.e., `zig cc`) zig supports glibc versions back to
36v2.2.5.
3537
36## Glibc stubs38## Glibc stubs
3739
lib/std/zig/target.zig+1-1
...@@ -12,7 +12,7 @@ pub const available_libcs = [_]ArchOsAbi{...@@ -12,7 +12,7 @@ pub const available_libcs = [_]ArchOsAbi{
12 .{ .arch = .aarch64_be, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 17, .patch = 0 } },12 .{ .arch = .aarch64_be, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 17, .patch = 0 } },
13 .{ .arch = .aarch64_be, .os = .linux, .abi = .musl },13 .{ .arch = .aarch64_be, .os = .linux, .abi = .musl },
14 .{ .arch = .aarch64_be, .os = .windows, .abi = .gnu },14 .{ .arch = .aarch64_be, .os = .windows, .abi = .gnu },
15 .{ .arch = .aarch64, .os = .linux, .abi = .gnu },15 .{ .arch = .aarch64, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 17, .patch = 0 } },
16 .{ .arch = .aarch64, .os = .linux, .abi = .musl },16 .{ .arch = .aarch64, .os = .linux, .abi = .musl },
17 .{ .arch = .aarch64, .os = .windows, .abi = .gnu },17 .{ .arch = .aarch64, .os = .windows, .abi = .gnu },
18 .{ .arch = .aarch64, .os = .macos, .abi = .none, .os_ver = .{ .major = 11, .minor = 0, .patch = 0 } },18 .{ .arch = .aarch64, .os = .macos, .abi = .none, .os_ver = .{ .major = 11, .minor = 0, .patch = 0 } },
test/link/glibc_compat/build.zig+108-1
...@@ -28,7 +28,114 @@ pub fn build(b: *std.Build) void {...@@ -28,7 +28,114 @@ pub fn build(b: *std.Build) void {
28 test_step.dependOn(&exe.step);28 test_step.dependOn(&exe.step);
29 }29 }
3030
31 // Build & run against a sampling of supported glibc versions31 // Build & run a C test case against a sampling of supported glibc versions
32 for ([_][]const u8{
33 // "native-linux-gnu.2.0", // fails with a pile of missing symbols.
34 "native-linux-gnu.2.2.5",
35 "native-linux-gnu.2.4",
36 "native-linux-gnu.2.12",
37 "native-linux-gnu.2.16",
38 "native-linux-gnu.2.22",
39 "native-linux-gnu.2.28",
40 "native-linux-gnu.2.33",
41 "native-linux-gnu.2.38",
42 "native-linux-gnu",
43 }) |t| {
44 const target = b.resolveTargetQuery(std.Target.Query.parse(
45 .{ .arch_os_abi = t },
46 ) catch unreachable);
47
48 const glibc_ver = target.result.os.version_range.linux.glibc;
49
50 // only build test if glibc version supports the architecture
51 if (target.result.cpu.arch.isAARCH64()) {
52 if (glibc_ver.order(.{ .major = 2, .minor = 17, .patch = 0 }) == .lt) {
53 continue;
54 }
55 }
56
57 const exe = b.addExecutable(.{
58 .name = t,
59 .target = target,
60 });
61 exe.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") });
62 exe.linkLibC();
63
64 // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig
65 // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453
66 if (running_glibc_ver) |running_ver| {
67 if (glibc_ver.order(running_ver) == .lt) {
68 const run_cmd = b.addRunArtifact(exe);
69 run_cmd.skip_foreign_checks = true;
70 run_cmd.expectExitCode(0);
71
72 test_step.dependOn(&run_cmd.step);
73 }
74 }
75 const check = exe.checkObject();
76
77 // __errno_location is always a dynamically linked symbol
78 check.checkInDynamicSymtab();
79 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __errno_location");
80
81 // before v2.32 fstat redirects through __fxstat, afterwards its a
82 // normal dynamic symbol
83 check.checkInDynamicSymtab();
84 if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) {
85 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstat");
86
87 check.checkInSymtab();
88 check.checkContains("FUNC LOCAL HIDDEN fstat");
89 } else {
90 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstat");
91
92 check.checkInSymtab();
93 check.checkNotPresent("__fxstat");
94 }
95
96 // before v2.26 reallocarray is not supported
97 check.checkInDynamicSymtab();
98 if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
99 check.checkNotPresent("reallocarray");
100 } else {
101 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray");
102 }
103
104 // before v2.38 strlcpy is not supported
105 check.checkInDynamicSymtab();
106 if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
107 check.checkNotPresent("strlcpy");
108 } else {
109 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy");
110 }
111
112 // v2.16 introduced getauxval()
113 check.checkInDynamicSymtab();
114 if (glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) {
115 check.checkNotPresent("getauxval");
116 } else {
117 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval");
118 }
119
120 // Always have dynamic "exit", "pow", and "powf" references
121 check.checkInDynamicSymtab();
122 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit");
123 check.checkInDynamicSymtab();
124 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT pow");
125 check.checkInDynamicSymtab();
126 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT powf");
127
128 // An atexit local symbol is defined, and depends on undefined dynamic
129 // __cxa_atexit.
130 check.checkInSymtab();
131 check.checkContains("FUNC LOCAL HIDDEN atexit");
132 check.checkInDynamicSymtab();
133 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __cxa_atexit");
134
135 test_step.dependOn(&check.step);
136 }
137
138 // Build & run a Zig test case against a sampling of supported glibc versions
32 for ([_][]const u8{139 for ([_][]const u8{
33 "native-linux-gnu.2.17", // Currently oldest supported, see #17769140 "native-linux-gnu.2.17", // Currently oldest supported, see #17769
34 "native-linux-gnu.2.23",141 "native-linux-gnu.2.23",
test/link/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}