authorgravatar for pat.github@tullmann.orgPat Tullmann <pat.github@tullmann.org> 2023-10-24 21:44:55-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-04 17:12:07-07:00
log42d7b69d8130bd3dba05dd75e671fcbf07177930
tree0b99a336cc480637240013d6a67c98dcd8ac4d14
parent1564cb0ab978b904e612a056c013bce8d6663a1b

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.

2 files changed, 189 insertions(+), 0 deletions(-)

test/link/glibc_compat/build.zig+95
...@@ -1,4 +1,14 @@...@@ -1,4 +1,14 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");
3
4// To run executables linked against a specific glibc version, the
5// run-time glibc version needs to be new enough. Check the host's glibc
6// version. Note that this does not allow for translation/vm/emulation
7// services to run these tests.
8const running_glibc_ver: ?std.SemanticVersion = switch (builtin.os.tag) {
9 .linux => builtin.os.version_range.linux.glibc,
10 else => null,
11};
212
3pub fn build(b: *std.Build) void {13pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");14 const test_step = b.step("test", "Test");
...@@ -17,4 +27,89 @@ pub fn build(b: *std.Build) void {...@@ -17,4 +27,89 @@ pub fn build(b: *std.Build) void {
17 _ = exe.getEmittedBin();27 _ = exe.getEmittedBin();
18 test_step.dependOn(&exe.step);28 test_step.dependOn(&exe.step);
19 }29 }
30
31 // Build & run against a sampling of supported glibc versions
32 for ([_][]const u8{
33 "native-linux-gnu.2.17", // Currently oldest supported, see #17769
34 "native-linux-gnu.2.23",
35 "native-linux-gnu.2.28",
36 "native-linux-gnu.2.33",
37 "native-linux-gnu.2.38",
38 "native-linux-gnu",
39 }) |t| {
40 const target = b.resolveTargetQuery(std.Target.Query.parse(
41 .{ .arch_os_abi = t },
42 ) catch unreachable);
43
44 const glibc_ver = target.result.os.version_range.linux.glibc;
45
46 const exe = b.addExecutable(.{
47 .name = t,
48 .root_source_file = .{ .path = "glibc_runtime_check.zig" },
49 .target = target,
50 });
51 exe.linkLibC();
52
53 // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig
54 // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453
55 if (running_glibc_ver) |running_ver| {
56 if (glibc_ver.order(running_ver) == .lt) {
57 const run_cmd = b.addRunArtifact(exe);
58 run_cmd.skip_foreign_checks = true;
59 run_cmd.expectExitCode(0);
60
61 test_step.dependOn(&run_cmd.step);
62 }
63 }
64 const check = exe.checkObject();
65
66 // __errno_location is always a dynamically linked symbol
67 check.checkInDynamicSymtab();
68 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __errno_location");
69
70 // before v2.32 fstatat redirects through __fxstatat, afterwards its a
71 // normal dynamic symbol
72 if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) {
73 check.checkInDynamicSymtab();
74 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstatat");
75
76 check.checkInSymtab();
77 check.checkContains("FUNC LOCAL HIDDEN fstatat");
78 } else {
79 check.checkInDynamicSymtab();
80 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstatat");
81
82 check.checkInSymtab();
83 check.checkNotPresent("FUNC LOCAL HIDDEN fstatat");
84 }
85
86 // before v2.26 reallocarray is not supported
87 if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
88 check.checkInDynamicSymtab();
89 check.checkNotPresent("reallocarray");
90 } else {
91 check.checkInDynamicSymtab();
92 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray");
93 } else {
94 check.checkInDynamicSymtab();
95 check.checkNotPresent("reallocarray");
96 }
97
98 // v2.16 introduced getauxval(), so always present
99 check.checkInDynamicSymtab();
100 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval");
101
102 // Always have a dynamic "exit" reference
103 check.checkInDynamicSymtab();
104 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit");
105
106 // An atexit local symbol is defined, and depends on undefined dynamic
107 // __cxa_atexit.
108 check.checkInSymtab();
109 check.checkContains("FUNC LOCAL HIDDEN atexit");
110 check.checkInDynamicSymtab();
111 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __cxa_atexit");
112
113 test_step.dependOn(&check.step);
114 }
20}115}
test/link/glibc_compat/glibc_runtime_check.zig created+94
...@@ -0,0 +1,94 @@
1// A zig test case that exercises some glibc symbols that have uncovered
2// problems in the past. This test must be compiled against a glibc.
3//
4// The build.zig tests the binary built from this source to see that
5// symbols are statically or dynamically linked, as expected.
6
7const std = @import("std");
8const builtin = @import("builtin");
9const assert = std.debug.assert;
10
11const c_malloc = @cImport(
12 @cInclude("malloc.h"), // for reallocarray
13);
14
15const c_stdlib = @cImport(
16 @cInclude("stdlib.h"), // for atexit
17);
18
19// Version of glibc this test is being built to run against
20const glibc_ver = builtin.target.os.version_range.linux.glibc;
21
22// PR #17034 - fstat moved between libc_nonshared and libc
23fn checkStat() !void {
24 const cwdFd = std.fs.cwd().fd;
25
26 var stat = std.mem.zeroes(std.c.Stat);
27 var result = std.c.fstatat(cwdFd, "a_file_that_definitely_does_not_exist", &stat, 0);
28 assert(result == -1);
29 assert(std.c.getErrno(result) == .NOENT);
30
31 result = std.c.stat("a_file_that_definitely_does_not_exist", &stat);
32 assert(result == -1);
33 assert(std.c.getErrno(result) == .NOENT);
34}
35
36// PR #17607 - reallocarray not visible in headers
37fn checkReallocarray() !void {
38 // reallocarray was introduced in v2.26
39 if (comptime glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
40 if (@hasDecl(c_malloc, "reallocarray")) {
41 @compileError("Before v2.26 'malloc.h' does not define 'reallocarray'");
42 }
43 } else {
44 return try checkReallocarray_v2_26();
45 }
46}
47
48fn checkReallocarray_v2_26() !void {
49 const size = 16;
50 const tenX = c_malloc.reallocarray(c_malloc.NULL, 10, size);
51 const elevenX = c_malloc.reallocarray(tenX, 11, size);
52
53 assert(tenX != c_malloc.NULL);
54 assert(elevenX != c_malloc.NULL);
55}
56
57// getauxval introduced in v2.16
58fn checkGetAuxVal() !void {
59 if (comptime glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) {
60 if (@hasDecl(std.c, "getauxval")) {
61 @compileError("Before v2.16 glibc does not define 'getauxval'");
62 }
63 } else {
64 try checkGetAuxVal_v2_16();
65 }
66}
67
68fn checkGetAuxVal_v2_16() !void {
69 const base = std.c.getauxval(std.elf.AT_BASE);
70 const pgsz = std.c.getauxval(std.elf.AT_PAGESZ);
71
72 assert(base != 0);
73 assert(pgsz != 0);
74}
75
76// atexit is part of libc_nonshared, so ensure its linked in correctly
77fn forceExit0Callback() callconv(.C) void {
78 std.c.exit(0); // Override the main() exit code
79}
80
81fn checkAtExit() !void {
82 const result = c_stdlib.atexit(forceExit0Callback);
83 assert(result == 0);
84}
85
86pub fn main() !u8 {
87 try checkStat();
88 try checkReallocarray();
89
90 try checkGetAuxVal();
91 try checkAtExit();
92
93 std.c.exit(1); // overridden by atexit() callback
94}