diff --git a/test/link/build.zig.zon b/test/link/build.zig.zon index ab44726091eddf978caeb4553f3ee69ceb22abfc..4005a7d55be3f0ea416bf89bfb819366b6719bf1 100644 --- a/test/link/build.zig.zon +++ b/test/link/build.zig.zon @@ -15,9 +15,6 @@ .static_libs_from_object_files = .{ .path = "static_libs_from_object_files", }, - .glibc_compat = .{ - .path = "glibc_compat", - }, // WASM Cases .wasm_archive = .{ .path = "wasm/archive", diff --git a/test/link/glibc_compat/build.zig b/test/link/glibc_compat/build.zig deleted file mode 100644 index 6c2c924cae0a40f93a7ad81f4fa2bbbc54539186..0000000000000000000000000000000000000000 --- a/test/link/glibc_compat/build.zig +++ /dev/null @@ -1,262 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); - -// To run executables linked against a specific glibc version, the -// run-time glibc version needs to be new enough. Check the host's glibc -// version. Note that this does not allow for translation/vm/emulation -// services to run these tests. -const running_glibc_ver = builtin.os.versionRange().gnuLibCVersion(); - -pub fn build(b: *std.Build) void { - const test_step = b.step("test", "Test"); - b.default_step = test_step; - - for ([_][]const u8{ "aarch64-linux-gnu.2.27", "aarch64-linux-gnu.2.34" }) |t| { - const exe = b.addExecutable(.{ - .name = t, - .root_module = b.createModule(.{ - .root_source_file = null, - .target = b.resolveTargetQuery(std.Target.Query.parse( - .{ .arch_os_abi = t }, - ) catch unreachable), - .link_libc = true, - }), - }); - // We disable UBSAN for these tests as the libc being tested here is - // so old, it doesn't even support compiling our UBSAN implementation. - exe.bundle_ubsan_rt = false; - exe.root_module.sanitize_c = .off; - exe.root_module.addCSourceFile(.{ .file = b.path("main.c") }); - // TODO: actually test the output - _ = exe.getEmittedBin(); - test_step.dependOn(&exe.step); - } - - // Build & run a C test case against a sampling of supported glibc versions - 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; - - // only build test if glibc version supports the architecture - for (std.zig.target.available_libcs) |libc| { - if (libc.arch != target.result.cpu.arch or - libc.os != target.result.os.tag or - libc.abi != target.result.abi) - continue; - - if (libc.glibc_min) |min| { - if (glibc_ver.order(min) == .lt) continue :versions; - } - } - - const exe = b.addExecutable(.{ - .name = t, - .root_module = b.createModule(.{ - .root_source_file = null, - .target = target, - .link_libc = true, - }), - }); - // We disable UBSAN for these tests as the libc being tested here is - // so old, it doesn't even support compiling our UBSAN implementation. - exe.bundle_ubsan_rt = false; - exe.root_module.sanitize_c = .off; - exe.root_module.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") }); - - // 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 - versions: for ([_][]const u8{ - "native-linux-gnu.2.17", // Currently oldest supported, see #17769 - "native-linux-gnu.2.23", - "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; - - // only build test if glibc version supports the architecture - for (std.zig.target.available_libcs) |libc| { - if (libc.arch != target.result.cpu.arch or - libc.os != target.result.os.tag or - libc.abi != target.result.abi) - continue; - - if (libc.glibc_min) |min| { - if (glibc_ver.order(min) == .lt) continue :versions; - } - } - - const exe = b.addExecutable(.{ - .name = t, - .root_module = b.createModule(.{ - .root_source_file = b.path("glibc_runtime_check.zig"), - .target = target, - .link_libc = true, - }), - }); - // We disable UBSAN for these tests as the libc being tested here is - // so old, it doesn't even support compiling our UBSAN implementation. - exe.bundle_ubsan_rt = false; - exe.root_module.sanitize_c = .off; - - // 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 fstatat redirects through __fxstatat, afterwards its a - // normal dynamic symbol - if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) { - check.checkInDynamicSymtab(); - check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstatat"); - - check.checkInSymtab(); - check.checkContains("FUNC LOCAL HIDDEN fstatat"); - } else { - check.checkInDynamicSymtab(); - check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstatat"); - - check.checkInSymtab(); - check.checkNotPresent("FUNC LOCAL HIDDEN fstatat"); - } - - // before v2.26 reallocarray is not supported - if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) { - check.checkInDynamicSymtab(); - check.checkNotPresent("reallocarray"); - } else { - check.checkInDynamicSymtab(); - check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray"); - } - - // before v2.38 strlcpy is not supported - if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) { - check.checkInDynamicSymtab(); - check.checkNotPresent("strlcpy"); - } else { - check.checkInDynamicSymtab(); - check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy"); - } - - // v2.16 introduced getauxval(), so always present - check.checkInDynamicSymtab(); - check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval"); - - // Always have a dynamic "exit" reference - check.checkInDynamicSymtab(); - check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit"); - - // 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); - } -} diff --git a/test/link/glibc_compat/glibc_runtime_check.c b/test/link/glibc_compat/glibc_runtime_check.c deleted file mode 100644 index b65bb06e60225ef6a5a035aa7fb96ba75c3d10c4..0000000000000000000000000000000000000000 --- a/test/link/glibc_compat/glibc_runtime_check.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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 -} diff --git a/test/link/glibc_compat/glibc_runtime_check.zig b/test/link/glibc_compat/glibc_runtime_check.zig deleted file mode 100644 index 30265a3adce947615d9edcd14783be4304f0fcfe..0000000000000000000000000000000000000000 --- a/test/link/glibc_compat/glibc_runtime_check.zig +++ /dev/null @@ -1,116 +0,0 @@ -// A zig test case that exercises some glibc symbols that have uncovered -// problems in the past. This test must be compiled against a glibc. -// -// The build.zig tests the binary built from this source to see that -// symbols are statically or dynamically linked, as expected. - -const std = @import("std"); -const builtin = @import("builtin"); -const assert = std.debug.assert; - -const c_malloc = @cImport( - @cInclude("malloc.h"), // for reallocarray -); - -const c_stdlib = @cImport( - @cInclude("stdlib.h"), // for atexit -); - -const c_string = @cImport( - @cInclude("string.h"), // for strlcpy -); - -// Version of glibc this test is being built to run against -const glibc_ver = builtin.os.versionRange().gnuLibCVersion().?; - -// PR #17034 - fstat moved between libc_nonshared and libc -fn checkStat() !void { - const cwdFd = std.fs.cwd().fd; - - var stat = std.mem.zeroes(std.c.Stat); - var result = std.c.fstatat(cwdFd, "a_file_that_definitely_does_not_exist", &stat, 0); - assert(result == -1); - assert(std.posix.errno(result) == .NOENT); - - result = std.c.stat("a_file_that_definitely_does_not_exist", &stat); - assert(result == -1); - assert(std.posix.errno(result) == .NOENT); -} - -// PR #17607 - reallocarray not visible in headers -fn checkReallocarray() !void { - // reallocarray was introduced in v2.26 - if (comptime glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) { - if (@hasDecl(c_malloc, "reallocarray")) { - @compileError("Before v2.26 'malloc.h' does not define 'reallocarray'"); - } - } else { - return try checkReallocarray_v2_26(); - } -} - -fn checkReallocarray_v2_26() !void { - const size = 16; - const tenX = c_malloc.reallocarray(c_malloc.NULL, 10, size); - const elevenX = c_malloc.reallocarray(tenX, 11, size); - - assert(tenX != c_malloc.NULL); - assert(elevenX != c_malloc.NULL); -} - -// getauxval introduced in v2.16 -fn checkGetAuxVal() !void { - if (comptime glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) { - if (@hasDecl(std.c, "getauxval")) { - @compileError("Before v2.16 glibc does not define 'getauxval'"); - } - } else { - try checkGetAuxVal_v2_16(); - } -} - -fn checkGetAuxVal_v2_16() !void { - const base = std.c.getauxval(std.elf.AT_BASE); - const pgsz = std.c.getauxval(std.elf.AT_PAGESZ); - - assert(base != 0); - assert(pgsz != 0); -} - -// strlcpy introduced in v2.38, which is newer than many installed glibcs -fn checkStrlcpy() !void { - if (comptime glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) { - if (@hasDecl(c_string, "strlcpy")) { - @compileError("Before v2.38 glibc does not define 'strlcpy'"); - } - } else { - try checkStrlcpy_v2_38(); - } -} - -fn checkStrlcpy_v2_38() !void { - var buf: [99]u8 = undefined; - const used = c_string.strlcpy(&buf, "strlcpy works!", buf.len); - assert(used == 14); -} - -// atexit is part of libc_nonshared, so ensure its linked in correctly -fn forceExit0Callback() callconv(.c) void { - std.c.exit(0); // Override the main() exit code -} - -fn checkAtExit() !void { - const result = c_stdlib.atexit(forceExit0Callback); - assert(result == 0); -} - -pub fn main() !u8 { - try checkStat(); - try checkReallocarray(); - try checkStrlcpy(); - - try checkGetAuxVal(); - try checkAtExit(); - - std.c.exit(1); // overridden by atexit() callback -} diff --git a/test/link/glibc_compat/main.c b/test/link/glibc_compat/main.c deleted file mode 100644 index 29ec920a58b291a63f032cd43befc4399f7f2919..0000000000000000000000000000000000000000 --- a/test/link/glibc_compat/main.c +++ /dev/null @@ -1,40 +0,0 @@ -#define _FILE_OFFSET_BITS 64 -#include -#include -#include -#include - -int main() { - /* in glibc 2.28+ and _FILE_OFFSET_BITS=64 fcntl is #define'd to fcntl64 - * Thus headers say `fcntl64` exists, but libc.so.6 (the old one) - * disagrees, resulting in a linking error unless headers are made - * backwards-compatible. - * - * Glibc 2.28+: - * FUNC GLOBAL DEFAULT UND fcntl64@GLIBC_2.28 (3): - * - * Glibc 2.27 or older: - * FUNC GLOBAL DEFAULT UND fcntl@GLIBC_2.2.5 - */ - printf("address to fcntl: %p\n", fcntl); - - /* The following functions became symbols of their own right with glibc - * 2.34+. Before 2.34 resolv.h would #define res_search __res_search; and - * __res_search is a valid symbol since the beginning of time. - * - * On glibc 2.34+ these symbols are linked this way: - * FUNC GLOBAL DEFAULT UND res_search@GLIBC_2.34 (2) - * - * Pre-glibc 2.34: - * FUNC GLOBAL DEFAULT UND __res_search@GLIBC_2.2.5 (4) - */ - printf("address to res_search: %p\n", res_search); - printf("address to res_nsearch: %p\n", res_nsearch); - printf("address to res_query: %p\n", res_query); - printf("address to res_nquery: %p\n", res_nquery); - printf("address to res_querydomain: %p\n", res_querydomain); - printf("address to res_nquerydomain: %p\n", res_nquerydomain); - printf("address to dn_skipname: %p\n", dn_skipname); - printf("address to dn_comp: %p\n", dn_comp); - printf("address to dn_expand: %p\n", dn_expand); -} diff --git a/test/standalone/build.zig.zon b/test/standalone/build.zig.zon index b5778a34dbe3c946df5b4559549bb0e009dbe6a4..bf02cce0a5b2cad88ac4f2c0f69f57fc9f49a154 100644 --- a/test/standalone/build.zig.zon +++ b/test/standalone/build.zig.zon @@ -49,6 +49,9 @@ .pkg_import = .{ .path = "pkg_import", }, + .glibc_compat = .{ + .path = "glibc_compat", + }, .install_raw_hex = .{ .path = "install_raw_hex", }, diff --git a/test/standalone/glibc_compat/build.zig b/test/standalone/glibc_compat/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..6c2c924cae0a40f93a7ad81f4fa2bbbc54539186 --- /dev/null +++ b/test/standalone/glibc_compat/build.zig @@ -0,0 +1,262 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +// To run executables linked against a specific glibc version, the +// run-time glibc version needs to be new enough. Check the host's glibc +// version. Note that this does not allow for translation/vm/emulation +// services to run these tests. +const running_glibc_ver = builtin.os.versionRange().gnuLibCVersion(); + +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test"); + b.default_step = test_step; + + for ([_][]const u8{ "aarch64-linux-gnu.2.27", "aarch64-linux-gnu.2.34" }) |t| { + const exe = b.addExecutable(.{ + .name = t, + .root_module = b.createModule(.{ + .root_source_file = null, + .target = b.resolveTargetQuery(std.Target.Query.parse( + .{ .arch_os_abi = t }, + ) catch unreachable), + .link_libc = true, + }), + }); + // We disable UBSAN for these tests as the libc being tested here is + // so old, it doesn't even support compiling our UBSAN implementation. + exe.bundle_ubsan_rt = false; + exe.root_module.sanitize_c = .off; + exe.root_module.addCSourceFile(.{ .file = b.path("main.c") }); + // TODO: actually test the output + _ = exe.getEmittedBin(); + test_step.dependOn(&exe.step); + } + + // Build & run a C test case against a sampling of supported glibc versions + 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; + + // only build test if glibc version supports the architecture + for (std.zig.target.available_libcs) |libc| { + if (libc.arch != target.result.cpu.arch or + libc.os != target.result.os.tag or + libc.abi != target.result.abi) + continue; + + if (libc.glibc_min) |min| { + if (glibc_ver.order(min) == .lt) continue :versions; + } + } + + const exe = b.addExecutable(.{ + .name = t, + .root_module = b.createModule(.{ + .root_source_file = null, + .target = target, + .link_libc = true, + }), + }); + // We disable UBSAN for these tests as the libc being tested here is + // so old, it doesn't even support compiling our UBSAN implementation. + exe.bundle_ubsan_rt = false; + exe.root_module.sanitize_c = .off; + exe.root_module.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") }); + + // 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 + versions: for ([_][]const u8{ + "native-linux-gnu.2.17", // Currently oldest supported, see #17769 + "native-linux-gnu.2.23", + "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; + + // only build test if glibc version supports the architecture + for (std.zig.target.available_libcs) |libc| { + if (libc.arch != target.result.cpu.arch or + libc.os != target.result.os.tag or + libc.abi != target.result.abi) + continue; + + if (libc.glibc_min) |min| { + if (glibc_ver.order(min) == .lt) continue :versions; + } + } + + const exe = b.addExecutable(.{ + .name = t, + .root_module = b.createModule(.{ + .root_source_file = b.path("glibc_runtime_check.zig"), + .target = target, + .link_libc = true, + }), + }); + // We disable UBSAN for these tests as the libc being tested here is + // so old, it doesn't even support compiling our UBSAN implementation. + exe.bundle_ubsan_rt = false; + exe.root_module.sanitize_c = .off; + + // 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 fstatat redirects through __fxstatat, afterwards its a + // normal dynamic symbol + if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) { + check.checkInDynamicSymtab(); + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstatat"); + + check.checkInSymtab(); + check.checkContains("FUNC LOCAL HIDDEN fstatat"); + } else { + check.checkInDynamicSymtab(); + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstatat"); + + check.checkInSymtab(); + check.checkNotPresent("FUNC LOCAL HIDDEN fstatat"); + } + + // before v2.26 reallocarray is not supported + if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) { + check.checkInDynamicSymtab(); + check.checkNotPresent("reallocarray"); + } else { + check.checkInDynamicSymtab(); + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray"); + } + + // before v2.38 strlcpy is not supported + if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) { + check.checkInDynamicSymtab(); + check.checkNotPresent("strlcpy"); + } else { + check.checkInDynamicSymtab(); + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy"); + } + + // v2.16 introduced getauxval(), so always present + check.checkInDynamicSymtab(); + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval"); + + // Always have a dynamic "exit" reference + check.checkInDynamicSymtab(); + check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit"); + + // 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); + } +} diff --git a/test/standalone/glibc_compat/glibc_runtime_check.c b/test/standalone/glibc_compat/glibc_runtime_check.c new file mode 100644 index 0000000000000000000000000000000000000000..b65bb06e60225ef6a5a035aa7fb96ba75c3d10c4 --- /dev/null +++ b/test/standalone/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 +} diff --git a/test/standalone/glibc_compat/glibc_runtime_check.zig b/test/standalone/glibc_compat/glibc_runtime_check.zig new file mode 100644 index 0000000000000000000000000000000000000000..30265a3adce947615d9edcd14783be4304f0fcfe --- /dev/null +++ b/test/standalone/glibc_compat/glibc_runtime_check.zig @@ -0,0 +1,116 @@ +// A zig test case that exercises some glibc symbols that have uncovered +// problems in the past. This test must be compiled against a glibc. +// +// The build.zig tests the binary built from this source to see that +// symbols are statically or dynamically linked, as expected. + +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; + +const c_malloc = @cImport( + @cInclude("malloc.h"), // for reallocarray +); + +const c_stdlib = @cImport( + @cInclude("stdlib.h"), // for atexit +); + +const c_string = @cImport( + @cInclude("string.h"), // for strlcpy +); + +// Version of glibc this test is being built to run against +const glibc_ver = builtin.os.versionRange().gnuLibCVersion().?; + +// PR #17034 - fstat moved between libc_nonshared and libc +fn checkStat() !void { + const cwdFd = std.fs.cwd().fd; + + var stat = std.mem.zeroes(std.c.Stat); + var result = std.c.fstatat(cwdFd, "a_file_that_definitely_does_not_exist", &stat, 0); + assert(result == -1); + assert(std.posix.errno(result) == .NOENT); + + result = std.c.stat("a_file_that_definitely_does_not_exist", &stat); + assert(result == -1); + assert(std.posix.errno(result) == .NOENT); +} + +// PR #17607 - reallocarray not visible in headers +fn checkReallocarray() !void { + // reallocarray was introduced in v2.26 + if (comptime glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) { + if (@hasDecl(c_malloc, "reallocarray")) { + @compileError("Before v2.26 'malloc.h' does not define 'reallocarray'"); + } + } else { + return try checkReallocarray_v2_26(); + } +} + +fn checkReallocarray_v2_26() !void { + const size = 16; + const tenX = c_malloc.reallocarray(c_malloc.NULL, 10, size); + const elevenX = c_malloc.reallocarray(tenX, 11, size); + + assert(tenX != c_malloc.NULL); + assert(elevenX != c_malloc.NULL); +} + +// getauxval introduced in v2.16 +fn checkGetAuxVal() !void { + if (comptime glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) { + if (@hasDecl(std.c, "getauxval")) { + @compileError("Before v2.16 glibc does not define 'getauxval'"); + } + } else { + try checkGetAuxVal_v2_16(); + } +} + +fn checkGetAuxVal_v2_16() !void { + const base = std.c.getauxval(std.elf.AT_BASE); + const pgsz = std.c.getauxval(std.elf.AT_PAGESZ); + + assert(base != 0); + assert(pgsz != 0); +} + +// strlcpy introduced in v2.38, which is newer than many installed glibcs +fn checkStrlcpy() !void { + if (comptime glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) { + if (@hasDecl(c_string, "strlcpy")) { + @compileError("Before v2.38 glibc does not define 'strlcpy'"); + } + } else { + try checkStrlcpy_v2_38(); + } +} + +fn checkStrlcpy_v2_38() !void { + var buf: [99]u8 = undefined; + const used = c_string.strlcpy(&buf, "strlcpy works!", buf.len); + assert(used == 14); +} + +// atexit is part of libc_nonshared, so ensure its linked in correctly +fn forceExit0Callback() callconv(.c) void { + std.c.exit(0); // Override the main() exit code +} + +fn checkAtExit() !void { + const result = c_stdlib.atexit(forceExit0Callback); + assert(result == 0); +} + +pub fn main() !u8 { + try checkStat(); + try checkReallocarray(); + try checkStrlcpy(); + + try checkGetAuxVal(); + try checkAtExit(); + + std.c.exit(1); // overridden by atexit() callback +} diff --git a/test/standalone/glibc_compat/main.c b/test/standalone/glibc_compat/main.c new file mode 100644 index 0000000000000000000000000000000000000000..29ec920a58b291a63f032cd43befc4399f7f2919 --- /dev/null +++ b/test/standalone/glibc_compat/main.c @@ -0,0 +1,40 @@ +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include + +int main() { + /* in glibc 2.28+ and _FILE_OFFSET_BITS=64 fcntl is #define'd to fcntl64 + * Thus headers say `fcntl64` exists, but libc.so.6 (the old one) + * disagrees, resulting in a linking error unless headers are made + * backwards-compatible. + * + * Glibc 2.28+: + * FUNC GLOBAL DEFAULT UND fcntl64@GLIBC_2.28 (3): + * + * Glibc 2.27 or older: + * FUNC GLOBAL DEFAULT UND fcntl@GLIBC_2.2.5 + */ + printf("address to fcntl: %p\n", fcntl); + + /* The following functions became symbols of their own right with glibc + * 2.34+. Before 2.34 resolv.h would #define res_search __res_search; and + * __res_search is a valid symbol since the beginning of time. + * + * On glibc 2.34+ these symbols are linked this way: + * FUNC GLOBAL DEFAULT UND res_search@GLIBC_2.34 (2) + * + * Pre-glibc 2.34: + * FUNC GLOBAL DEFAULT UND __res_search@GLIBC_2.2.5 (4) + */ + printf("address to res_search: %p\n", res_search); + printf("address to res_nsearch: %p\n", res_nsearch); + printf("address to res_query: %p\n", res_query); + printf("address to res_nquery: %p\n", res_nquery); + printf("address to res_querydomain: %p\n", res_querydomain); + printf("address to res_nquerydomain: %p\n", res_nquerydomain); + printf("address to dn_skipname: %p\n", dn_skipname); + printf("address to dn_comp: %p\n", dn_comp); + printf("address to dn_expand: %p\n", dn_expand); +}