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}