| author | |
| committer | |
| log | 7ae9d8089d49d3bf04b5f248caf46fc095a71290 |
| tree | a65bf3336ee5b846e723d2d991c380f0d543cea8 |
| parent | 24f28753e6df3d0bea28afaccc02d0c9f50cd04f |
| parent | ed795a907d329487b5fdc6f21e2bace11998e5bf |
| signature |
test/link/glibc_compat: Add C test case for glibc versions4 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. |
| 31 | The current Zig support for glibc only includes Linux. | 31 | The current Zig support for glibc only includes Linux. |
| 32 | 32 | ||
| 33 | Zig supports glibc versions back to v2.17 (2012) as the Zig standard | 33 | 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. | ||
| 35 | 37 | ||
| 36 | ## Glibc stubs | 38 | ## Glibc stubs |
| 37 | 39 |
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 | } |
| 30 | 30 | ||
| 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 | ||
| 32 | for ([_][]const u8{ | 139 | for ([_][]const u8{ |
| 33 | "native-linux-gnu.2.17", // Currently oldest supported, see #17769 | 140 | "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). */ | ||
| 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 | } | ||