Commit 42d7b69d81
Changed files (2)
test
link
glibc_compat
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
+}