master
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 = builtin.os.versionRange().gnuLibCVersion();
9
10pub fn build(b: *std.Build) void {
11 const test_step = b.step("test", "Test");
12 b.default_step = test_step;
13
14 for ([_][]const u8{ "aarch64-linux-gnu.2.27", "aarch64-linux-gnu.2.34" }) |t| {
15 const exe = b.addExecutable(.{
16 .name = t,
17 .root_module = b.createModule(.{
18 .root_source_file = null,
19 .target = b.resolveTargetQuery(std.Target.Query.parse(
20 .{ .arch_os_abi = t },
21 ) catch unreachable),
22 .link_libc = true,
23 }),
24 });
25 // We disable UBSAN for these tests as the libc being tested here is
26 // so old, it doesn't even support compiling our UBSAN implementation.
27 exe.bundle_ubsan_rt = false;
28 exe.root_module.sanitize_c = .off;
29 exe.root_module.addCSourceFile(.{ .file = b.path("main.c") });
30 // TODO: actually test the output
31 _ = exe.getEmittedBin();
32 test_step.dependOn(&exe.step);
33 }
34
35 // Build & run a C test case against a sampling of supported glibc versions
36 versions: for ([_][]const u8{
37 // "native-linux-gnu.2.0", // fails with a pile of missing symbols.
38 "native-linux-gnu.2.2.5",
39 "native-linux-gnu.2.4",
40 "native-linux-gnu.2.12",
41 "native-linux-gnu.2.16",
42 "native-linux-gnu.2.22",
43 "native-linux-gnu.2.28",
44 "native-linux-gnu.2.33",
45 "native-linux-gnu.2.38",
46 "native-linux-gnu",
47 }) |t| {
48 const target = b.resolveTargetQuery(std.Target.Query.parse(
49 .{ .arch_os_abi = t },
50 ) catch unreachable);
51
52 const glibc_ver = target.result.os.version_range.linux.glibc;
53
54 // only build test if glibc version supports the architecture
55 for (std.zig.target.available_libcs) |libc| {
56 if (libc.arch != target.result.cpu.arch or
57 libc.os != target.result.os.tag or
58 libc.abi != target.result.abi)
59 continue;
60
61 if (libc.glibc_min) |min| {
62 if (glibc_ver.order(min) == .lt) continue :versions;
63 }
64 }
65
66 const exe = b.addExecutable(.{
67 .name = t,
68 .root_module = b.createModule(.{
69 .root_source_file = null,
70 .target = target,
71 .link_libc = true,
72 }),
73 });
74 // We disable UBSAN for these tests as the libc being tested here is
75 // so old, it doesn't even support compiling our UBSAN implementation.
76 exe.bundle_ubsan_rt = false;
77 exe.root_module.sanitize_c = .off;
78 exe.root_module.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") });
79
80 // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig
81 // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453
82 if (running_glibc_ver) |running_ver| {
83 if (glibc_ver.order(running_ver) == .lt) {
84 const run_cmd = b.addRunArtifact(exe);
85 run_cmd.skip_foreign_checks = true;
86 run_cmd.expectExitCode(0);
87
88 test_step.dependOn(&run_cmd.step);
89 }
90 }
91 const check = exe.checkObject();
92
93 // __errno_location is always a dynamically linked symbol
94 check.checkInDynamicSymtab();
95 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __errno_location");
96
97 // before v2.32 fstat redirects through __fxstat, afterwards its a
98 // normal dynamic symbol
99 check.checkInDynamicSymtab();
100 if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) {
101 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstat");
102
103 check.checkInSymtab();
104 check.checkContains("FUNC LOCAL HIDDEN fstat");
105 } else {
106 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstat");
107
108 check.checkInSymtab();
109 check.checkNotPresent("__fxstat");
110 }
111
112 // before v2.26 reallocarray is not supported
113 check.checkInDynamicSymtab();
114 if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
115 check.checkNotPresent("reallocarray");
116 } else {
117 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray");
118 }
119
120 // before v2.38 strlcpy is not supported
121 check.checkInDynamicSymtab();
122 if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
123 check.checkNotPresent("strlcpy");
124 } else {
125 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy");
126 }
127
128 // v2.16 introduced getauxval()
129 check.checkInDynamicSymtab();
130 if (glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) {
131 check.checkNotPresent("getauxval");
132 } else {
133 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval");
134 }
135
136 // Always have dynamic "exit", "pow", and "powf" references
137 check.checkInDynamicSymtab();
138 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit");
139 check.checkInDynamicSymtab();
140 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT pow");
141 check.checkInDynamicSymtab();
142 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT powf");
143
144 if (target.result.cpu.arch != .s390x) {
145 // An atexit local symbol is defined, and depends on undefined dynamic
146 // __cxa_atexit.
147 check.checkInSymtab();
148 check.checkContains("FUNC LOCAL HIDDEN atexit");
149 check.checkInDynamicSymtab();
150 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __cxa_atexit");
151 }
152
153 test_step.dependOn(&check.step);
154 }
155
156 // Build & run a Zig test case against a sampling of supported glibc versions
157 versions: for ([_][]const u8{
158 "native-linux-gnu.2.17", // Currently oldest supported, see #17769
159 "native-linux-gnu.2.23",
160 "native-linux-gnu.2.28",
161 "native-linux-gnu.2.33",
162 "native-linux-gnu.2.38",
163 "native-linux-gnu",
164 }) |t| {
165 const target = b.resolveTargetQuery(std.Target.Query.parse(
166 .{ .arch_os_abi = t },
167 ) catch unreachable);
168
169 const glibc_ver = target.result.os.version_range.linux.glibc;
170
171 // only build test if glibc version supports the architecture
172 for (std.zig.target.available_libcs) |libc| {
173 if (libc.arch != target.result.cpu.arch or
174 libc.os != target.result.os.tag or
175 libc.abi != target.result.abi)
176 continue;
177
178 if (libc.glibc_min) |min| {
179 if (glibc_ver.order(min) == .lt) continue :versions;
180 }
181 }
182
183 const exe = b.addExecutable(.{
184 .name = t,
185 .root_module = b.createModule(.{
186 .root_source_file = b.path("glibc_runtime_check.zig"),
187 .target = target,
188 .link_libc = true,
189 }),
190 });
191 // We disable UBSAN for these tests as the libc being tested here is
192 // so old, it doesn't even support compiling our UBSAN implementation.
193 exe.bundle_ubsan_rt = false;
194 exe.root_module.sanitize_c = .off;
195
196 // Only try running the test if the host glibc is known to be good enough. Ideally, the Zig
197 // test runner would be able to check this, but see https://github.com/ziglang/zig/pull/17702#issuecomment-1831310453
198 if (running_glibc_ver) |running_ver| {
199 if (glibc_ver.order(running_ver) == .lt) {
200 const run_cmd = b.addRunArtifact(exe);
201 run_cmd.skip_foreign_checks = true;
202 run_cmd.expectExitCode(0);
203
204 test_step.dependOn(&run_cmd.step);
205 }
206 }
207 const check = exe.checkObject();
208
209 // __errno_location is always a dynamically linked symbol
210 check.checkInDynamicSymtab();
211 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __errno_location");
212
213 // before v2.32 fstatat redirects through __fxstatat, afterwards its a
214 // normal dynamic symbol
215 if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) {
216 check.checkInDynamicSymtab();
217 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstatat");
218
219 check.checkInSymtab();
220 check.checkContains("FUNC LOCAL HIDDEN fstatat");
221 } else {
222 check.checkInDynamicSymtab();
223 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstatat");
224
225 check.checkInSymtab();
226 check.checkNotPresent("FUNC LOCAL HIDDEN fstatat");
227 }
228
229 // before v2.26 reallocarray is not supported
230 if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
231 check.checkInDynamicSymtab();
232 check.checkNotPresent("reallocarray");
233 } else {
234 check.checkInDynamicSymtab();
235 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray");
236 }
237
238 // before v2.38 strlcpy is not supported
239 if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
240 check.checkInDynamicSymtab();
241 check.checkNotPresent("strlcpy");
242 } else {
243 check.checkInDynamicSymtab();
244 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy");
245 }
246
247 // v2.16 introduced getauxval(), so always present
248 check.checkInDynamicSymtab();
249 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval");
250
251 // Always have a dynamic "exit" reference
252 check.checkInDynamicSymtab();
253 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit");
254
255 if (target.result.cpu.arch != .s390x) {
256 // An atexit local symbol is defined, and depends on undefined dynamic
257 // __cxa_atexit.
258 check.checkInSymtab();
259 check.checkContains("FUNC LOCAL HIDDEN atexit");
260 check.checkInDynamicSymtab();
261 check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __cxa_atexit");
262 }
263
264 test_step.dependOn(&check.step);
265 }
266}