authorgravatar for pat.github@tullmann.orgPat Tullmann <pat.github@tullmann.org> 2024-02-19 12:51:04-08:00
committergravatar for pat.github@tullmann.orgPat Tullmann <pat.github@tullmann.org> 2024-06-06 09:36:16-07:00
log8bee879fc247e9885202f690aac677717e708402
tree42ec3bda58405f22fac1917248fa8dd091db1e11
parenta4e01074b5cec7da7990188bf0163e8a1fb18483

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

glibc_runtime_check.c is a simple test case that exercises glibc functions that might smoke out linking problems with Zig's C compiler. The build.zig compiles it against a variety of glibc versions. Also document and test glibc v2.2.5 (from 2002) as the oldest working glibc target for C binaries.

3 files changed, 218 insertions(+), 2 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
test/link/glibc_compat/build.zig+101-1
...@@ -28,7 +28,107 @@ pub fn build(b: *std.Build) void {...@@ -28,7 +28,107 @@ 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 const exe = b.addExecutable(.{
51 .name = t,
52 .target = target,
53 });
54 exe.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") });
55 exe.linkLibC();
56
57 // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig
58 // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453
59 if (running_glibc_ver) |running_ver| {
60 if (glibc_ver.order(running_ver) == .lt) {
61 const run_cmd = b.addRunArtifact(exe);
62 run_cmd.skip_foreign_checks = true;
63 run_cmd.expectExitCode(0);
64
65 test_step.dependOn(&run_cmd.step);
66 }
67 }
68 const check = exe.checkObject();
69
70 // __errno_location is always a dynamically linked symbol
71 check.checkInDynamicSymtab();
72 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __errno_location");
73
74 // before v2.32 fstat redirects through __fxstat, afterwards its a
75 // normal dynamic symbol
76 check.checkInDynamicSymtab();
77 if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) {
78 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstat");
79
80 check.checkInSymtab();
81 check.checkContains("FUNC LOCAL HIDDEN fstat");
82 } else {
83 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstat");
84
85 check.checkInSymtab();
86 check.checkNotPresent("__fxstat");
87 }
88
89 // before v2.26 reallocarray is not supported
90 check.checkInDynamicSymtab();
91 if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
92 check.checkNotPresent("reallocarray");
93 } else {
94 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray");
95 }
96
97 // before v2.38 strlcpy is not supported
98 check.checkInDynamicSymtab();
99 if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
100 check.checkNotPresent("strlcpy");
101 } else {
102 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy");
103 }
104
105 // v2.16 introduced getauxval()
106 check.checkInDynamicSymtab();
107 if (glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) {
108 check.checkNotPresent("getauxval");
109 } else {
110 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval");
111 }
112
113 // Always have dynamic "exit", "pow", and "powf" references
114 check.checkInDynamicSymtab();
115 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit");
116 check.checkInDynamicSymtab();
117 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT pow");
118 check.checkInDynamicSymtab();
119 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT powf");
120
121 // An atexit local symbol is defined, and depends on undefined dynamic
122 // __cxa_atexit.
123 check.checkInSymtab();
124 check.checkContains("FUNC LOCAL HIDDEN atexit");
125 check.checkInDynamicSymtab();
126 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __cxa_atexit");
127
128 test_step.dependOn(&check.step);
129 }
130
131 // Build & run a Zig test case against a sampling of supported glibc versions
32 for ([_][]const u8{132 for ([_][]const u8{
33 "native-linux-gnu.2.17", // Currently oldest supported, see #17769133 "native-linux-gnu.2.17", // Currently oldest supported, see #17769
34 "native-linux-gnu.2.23",134 "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}