Skip to content

Commit 7ae9d80

Browse files
authored
Merge pull request #19007 from rootbeer/glibc-c-test
test/link/glibc_compat: Add C test case for glibc versions
2 parents 24f2875 + ed795a9 commit 7ae9d80

File tree

4 files changed

+226
-3
lines changed

4 files changed

+226
-3
lines changed

lib/libc/glibc/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ The GNU C Library supports a very wide set of platforms and architectures.
3131
The current Zig support for glibc only includes Linux.
3232

3333
Zig supports glibc versions back to v2.17 (2012) as the Zig standard
34-
library depends on symbols that were introduced in 2.17.
34+
library depends on symbols that were introduced in 2.17. When used as a C
35+
or C++ compiler (i.e., `zig cc`) zig supports glibc versions back to
36+
v2.2.5.
3537

3638
## Glibc stubs
3739

lib/std/zig/target.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub const available_libcs = [_]ArchOsAbi{
1212
.{ .arch = .aarch64_be, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 17, .patch = 0 } },
1313
.{ .arch = .aarch64_be, .os = .linux, .abi = .musl },
1414
.{ .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 } },
1616
.{ .arch = .aarch64, .os = .linux, .abi = .musl },
1717
.{ .arch = .aarch64, .os = .windows, .abi = .gnu },
1818
.{ .arch = .aarch64, .os = .macos, .abi = .none, .os_ver = .{ .major = 11, .minor = 0, .patch = 0 } },

test/link/glibc_compat/build.zig

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,114 @@ pub fn build(b: *std.Build) void {
2828
test_step.dependOn(&exe.step);
2929
}
3030

31-
// Build & run against a sampling of supported glibc versions
31+
// 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
32139
for ([_][]const u8{
33140
"native-linux-gnu.2.17", // Currently oldest supported, see #17769
34141
"native-linux-gnu.2.23",
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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). */
20+
static 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) */
28+
static 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 */
48+
static 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 */
56+
static 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 */
67+
static 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 */
82+
static 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 */
91+
static void force_exit_0()
92+
{
93+
exit(0);
94+
}
95+
96+
static void check_atexit()
97+
{
98+
int rc = atexit(force_exit_0);
99+
assert(rc == 0);
100+
}
101+
102+
int 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+
}

0 commit comments

Comments
 (0)