From 42d7b69d8130bd3dba05dd75e671fcbf07177930 Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Tue, 24 Oct 2023 21:44:55 -0700 Subject: [PATCH] test/link/glibc_compat: test various older glibc versions Compile, link and run a test case against various glibc versions. Exercise symbols that have been probelmatic in the past. --- test/link/glibc_compat/build.zig | 95 +++++++++++++++++++ .../link/glibc_compat/glibc_runtime_check.zig | 94 ++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 test/link/glibc_compat/glibc_runtime_check.zig diff --git a/test/link/glibc_compat/build.zig b/test/link/glibc_compat/build.zig index bb8d5d056d..cb2a906c2c 100644 --- a/test/link/glibc_compat/build.zig +++ b/test/link/glibc_compat/build.zig @@ -1,4 +1,14 @@ 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: ?std.SemanticVersion = switch (builtin.os.tag) { + .linux => builtin.os.version_range.linux.glibc, + else => null, +}; pub fn build(b: *std.Build) void { const test_step = b.step("test", "Test"); @@ -17,4 +27,89 @@ pub fn build(b: *std.Build) void { _ = exe.getEmittedBin(); test_step.dependOn(&exe.step); } + + // Build & run 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", + "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, + .root_source_file = .{ .path = "glibc_runtime_check.zig" }, + .target = target, + }); + 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 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"); + } else { + check.checkInDynamicSymtab(); + check.checkNotPresent("reallocarray"); + } + + // 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.zig b/test/link/glibc_compat/glibc_runtime_check.zig new file mode 100644 index 0000000000..2866fefc67 --- /dev/null +++ b/test/link/glibc_compat/glibc_runtime_check.zig @@ -0,0 +1,94 @@ +// 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 +); + +// Version of glibc this test is being built to run against +const glibc_ver = builtin.target.os.version_range.linux.glibc; + +// 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.c.getErrno(result) == .NOENT); + + result = std.c.stat("a_file_that_definitely_does_not_exist", &stat); + assert(result == -1); + assert(std.c.getErrno(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); +} + +// 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 checkGetAuxVal(); + try checkAtExit(); + + std.c.exit(1); // overridden by atexit() callback +}