From 8bee879fc247e9885202f690aac677717e708402 Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Mon, 19 Feb 2024 12:51:04 -0800 Subject: [PATCH 1/2] 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. --- lib/libc/glibc/README.md | 4 +- test/link/glibc_compat/build.zig | 102 ++++++++++++++++- test/link/glibc_compat/glibc_runtime_check.c | 114 +++++++++++++++++++ 3 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 test/link/glibc_compat/glibc_runtime_check.c diff --git a/lib/libc/glibc/README.md b/lib/libc/glibc/README.md index 0bcf23ae343d..7fba7cc994ac 100644 --- a/lib/libc/glibc/README.md +++ b/lib/libc/glibc/README.md @@ -31,7 +31,9 @@ The GNU C Library supports a very wide set of platforms and architectures. The current Zig support for glibc only includes Linux. Zig supports glibc versions back to v2.17 (2012) as the Zig standard -library depends on symbols that were introduced in 2.17. +library depends on symbols that were introduced in 2.17. When used as a C +or C++ compiler (i.e., `zig cc`) zig supports glibc versions back to +v2.2.5. ## Glibc stubs diff --git a/test/link/glibc_compat/build.zig b/test/link/glibc_compat/build.zig index 67a58200c3cc..1e88702d68b2 100644 --- a/test/link/glibc_compat/build.zig +++ b/test/link/glibc_compat/build.zig @@ -28,7 +28,107 @@ pub fn build(b: *std.Build) void { test_step.dependOn(&exe.step); } - // Build & run against a sampling of supported glibc versions + // Build & run a C test case against a sampling of supported glibc versions + for ([_][]const u8{ + // "native-linux-gnu.2.0", // fails with a pile of missing symbols. + "native-linux-gnu.2.2.5", + "native-linux-gnu.2.4", + "native-linux-gnu.2.12", + "native-linux-gnu.2.16", + "native-linux-gnu.2.22", + "native-linux-gnu.2.28", + "native-linux-gnu.2.33", + "native-linux-gnu.2.38", + "native-linux-gnu", + }) |t| { + const target = b.resolveTargetQuery(std.Target.Query.parse( + .{ .arch_os_abi = t }, + ) catch unreachable); + + const glibc_ver = target.result.os.version_range.linux.glibc; + + const exe = b.addExecutable(.{ + .name = t, + .target = target, + }); + exe.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") }); + exe.linkLibC(); + + // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig + // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453 + if (running_glibc_ver) |running_ver| { + if (glibc_ver.order(running_ver) == .lt) { + const run_cmd = b.addRunArtifact(exe); + run_cmd.skip_foreign_checks = true; + run_cmd.expectExitCode(0); + + test_step.dependOn(&run_cmd.step); + } + } + const check = exe.checkObject(); + + // __errno_location is always a dynamically linked symbol + check.checkInDynamicSymtab(); + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __errno_location"); + + // before v2.32 fstat redirects through __fxstat, afterwards its a + // normal dynamic symbol + check.checkInDynamicSymtab(); + if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) { + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstat"); + + check.checkInSymtab(); + check.checkContains("FUNC LOCAL HIDDEN fstat"); + } else { + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstat"); + + check.checkInSymtab(); + check.checkNotPresent("__fxstat"); + } + + // before v2.26 reallocarray is not supported + check.checkInDynamicSymtab(); + if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) { + check.checkNotPresent("reallocarray"); + } else { + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray"); + } + + // before v2.38 strlcpy is not supported + check.checkInDynamicSymtab(); + if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) { + check.checkNotPresent("strlcpy"); + } else { + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy"); + } + + // v2.16 introduced getauxval() + check.checkInDynamicSymtab(); + if (glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) { + check.checkNotPresent("getauxval"); + } else { + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval"); + } + + // Always have dynamic "exit", "pow", and "powf" references + check.checkInDynamicSymtab(); + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit"); + check.checkInDynamicSymtab(); + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT pow"); + check.checkInDynamicSymtab(); + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT powf"); + + // An atexit local symbol is defined, and depends on undefined dynamic + // __cxa_atexit. + check.checkInSymtab(); + check.checkContains("FUNC LOCAL HIDDEN atexit"); + check.checkInDynamicSymtab(); + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __cxa_atexit"); + + test_step.dependOn(&check.step); + } + + // Build & run a Zig test case against a sampling of supported glibc versions for ([_][]const u8{ "native-linux-gnu.2.17", // Currently oldest supported, see #17769 "native-linux-gnu.2.23", diff --git a/test/link/glibc_compat/glibc_runtime_check.c b/test/link/glibc_compat/glibc_runtime_check.c new file mode 100644 index 000000000000..b65bb06e6022 --- /dev/null +++ b/test/link/glibc_compat/glibc_runtime_check.c @@ -0,0 +1,114 @@ +/* + * Exercise complicating glibc symbols from C code. Complicating symbols + * are ones that have moved between glibc versions, or use floating point + * parameters, or have otherwise tripped up the Zig glibc compatibility + * code. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* errno is compilcated (thread-local, dynamically provided, etc). */ +static void check_errno() +{ + int invalid_fd = open("/doesnotexist", O_RDONLY); + assert(invalid_fd == -1); + assert(errno == ENOENT); +} + +/* fstat has moved around in glibc (between libc_nonshared and libc) */ +static void check_fstat() +{ + int self_fd = open("/proc/self/exe", O_RDONLY); + + struct stat statbuf = {0}; + int rc = fstat(self_fd, &statbuf); + + assert(rc == 0); + + assert(statbuf.st_dev != 0); + assert(statbuf.st_ino != 0); + assert(statbuf.st_mode != 0); + assert(statbuf.st_size > 0); + assert(statbuf.st_blocks > 0); + assert(statbuf.st_ctim.tv_sec > 0); + + close(self_fd); +} + +/* Some targets have a complicated ABI for floats and doubles */ +static void check_fp_abi() +{ + // Picked "pow" as it takes and returns doubles + assert(pow(10.0, 10.0) == 10000000000.0); + assert(powf(10.0f, 10.0f) == 10000000000.0f); +} + +/* strlcpy introduced in glibc 2.38 */ +static void check_strlcpy() +{ +#if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 38) || (__GLIBC__ > 2) + char target[4] = {0}; + strlcpy(target, "this is a source string", 4); + + assert(strcmp(target, "thi") == 0); +#endif +} + +/* reallocarray introduced in glibc 2.26 */ +static void check_reallocarray() +{ +#if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 26) || (__GLIBC__ > 2) + const size_t el_size = 32; + void* base = reallocarray(NULL, 10, el_size); + void* grown = reallocarray(base, 100, el_size); + + assert(base != NULL); + assert(grown != NULL); + + free(grown); +#endif +} + +/* getauxval introduced in glibc 2.16 */ +static void check_getauxval() +{ +#if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 16) || (__GLIBC__ > 2) + int pgsz = getauxval(AT_PAGESZ); + assert(pgsz >= 4*1024); +#endif +} + +/* atexit() is part of libc_nonshared */ +static void force_exit_0() +{ + exit(0); +} + +static void check_atexit() +{ + int rc = atexit(force_exit_0); + assert(rc == 0); +} + +int main() { + int rc; + + check_errno(); + check_fstat(); + check_fp_abi(); + check_strlcpy(); + check_reallocarray(); + check_getauxval(); + check_atexit(); + + exit(99); // exit code overridden by atexit handler +} From ed795a907d329487b5fdc6f21e2bace11998e5bf Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Mon, 19 Feb 2024 17:30:39 -0800 Subject: [PATCH 2/2] target.zig: Constrain aarch64 to glibc version 2.17 or later Had constrained the `aarch64_be` target, but not `aarch64`. This constraint is necessary because earlier versions of glibc do not support the aarch64 architecture. Also, skip unsupported test cases. --- lib/std/zig/target.zig | 2 +- test/link/glibc_compat/build.zig | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/std/zig/target.zig b/lib/std/zig/target.zig index 1f7ab95907ac..eb86055eef28 100644 --- a/lib/std/zig/target.zig +++ b/lib/std/zig/target.zig @@ -12,7 +12,7 @@ pub const available_libcs = [_]ArchOsAbi{ .{ .arch = .aarch64_be, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 17, .patch = 0 } }, .{ .arch = .aarch64_be, .os = .linux, .abi = .musl }, .{ .arch = .aarch64_be, .os = .windows, .abi = .gnu }, - .{ .arch = .aarch64, .os = .linux, .abi = .gnu }, + .{ .arch = .aarch64, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 17, .patch = 0 } }, .{ .arch = .aarch64, .os = .linux, .abi = .musl }, .{ .arch = .aarch64, .os = .windows, .abi = .gnu }, .{ .arch = .aarch64, .os = .macos, .abi = .none, .os_ver = .{ .major = 11, .minor = 0, .patch = 0 } }, diff --git a/test/link/glibc_compat/build.zig b/test/link/glibc_compat/build.zig index 1e88702d68b2..29df30151b49 100644 --- a/test/link/glibc_compat/build.zig +++ b/test/link/glibc_compat/build.zig @@ -47,6 +47,13 @@ pub fn build(b: *std.Build) void { const glibc_ver = target.result.os.version_range.linux.glibc; + // only build test if glibc version supports the architecture + if (target.result.cpu.arch.isAARCH64()) { + if (glibc_ver.order(.{ .major = 2, .minor = 17, .patch = 0 }) == .lt) { + continue; + } + } + const exe = b.addExecutable(.{ .name = t, .target = target,