Commit 42d7b69d81

Pat Tullmann <pat.github@tullmann.org>
2023-10-25 06:44:55
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.
1 parent 1564cb0
Changed files (2)
test
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);
+    }
 }
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
+}