| author | |
| committer | |
| log | 8bee879fc247e9885202f690aac677717e708402 |
| tree | 42ec3bda58405f22fac1917248fa8dd091db1e11 |
| parent | a4e01074b5cec7da7990188bf0163e8a1fb18483 |
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 | 31 | The current Zig support for glibc only includes Linux. |
| 32 | 32 | |
| 33 | 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 | 38 | ## Glibc stubs |
| 37 | 39 |
test/link/glibc_compat/build.zig+101-1| ... | ... | @@ -28,7 +28,107 @@ pub fn build(b: *std.Build) void { |
| 28 | 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 | 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 | 132 | for ([_][]const u8{ |
| 33 | 133 | "native-linux-gnu.2.17", // Currently oldest supported, see #17769 |
| 34 | 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). */ | |
| 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 | } |