Merge pull request #19007 from rootbeer/glibc-c-test

test/link/glibc_compat: Add C test case for glibc versions
This commit is contained in:
Andrew Kelley 2024-06-08 15:57:35 -04:00 committed by GitHub
commit 7ae9d8089d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 226 additions and 3 deletions

View File

@ -31,7 +31,9 @@ The GNU C Library supports a very wide set of platforms and architectures.
The current Zig support for glibc only includes Linux.
Zig supports glibc versions back to v2.17 (2012) as the Zig standard
library depends on symbols that were introduced in 2.17.
library depends on symbols that were introduced in 2.17. When used as a C
or C++ compiler (i.e., `zig cc`) zig supports glibc versions back to
v2.2.5.
## Glibc stubs

View File

@ -12,7 +12,7 @@ pub const available_libcs = [_]ArchOsAbi{
.{ .arch = .aarch64_be, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 17, .patch = 0 } },
.{ .arch = .aarch64_be, .os = .linux, .abi = .musl },
.{ .arch = .aarch64_be, .os = .windows, .abi = .gnu },
.{ .arch = .aarch64, .os = .linux, .abi = .gnu },
.{ .arch = .aarch64, .os = .linux, .abi = .gnu, .glibc_min = .{ .major = 2, .minor = 17, .patch = 0 } },
.{ .arch = .aarch64, .os = .linux, .abi = .musl },
.{ .arch = .aarch64, .os = .windows, .abi = .gnu },
.{ .arch = .aarch64, .os = .macos, .abi = .none, .os_ver = .{ .major = 11, .minor = 0, .patch = 0 } },

View File

@ -28,7 +28,114 @@ pub fn build(b: *std.Build) void {
test_step.dependOn(&exe.step);
}
// Build & run against a sampling of supported glibc versions
// Build & run a C test case against a sampling of supported glibc versions
for ([_][]const u8{
// "native-linux-gnu.2.0", // fails with a pile of missing symbols.
"native-linux-gnu.2.2.5",
"native-linux-gnu.2.4",
"native-linux-gnu.2.12",
"native-linux-gnu.2.16",
"native-linux-gnu.2.22",
"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;
// only build test if glibc version supports the architecture
if (target.result.cpu.arch.isAARCH64()) {
if (glibc_ver.order(.{ .major = 2, .minor = 17, .patch = 0 }) == .lt) {
continue;
}
}
const exe = b.addExecutable(.{
.name = t,
.target = target,
});
exe.addCSourceFile(.{ .file = b.path("glibc_runtime_check.c") });
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 fstat redirects through __fxstat, afterwards its a
// normal dynamic symbol
check.checkInDynamicSymtab();
if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstat");
check.checkInSymtab();
check.checkContains("FUNC LOCAL HIDDEN fstat");
} else {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstat");
check.checkInSymtab();
check.checkNotPresent("__fxstat");
}
// before v2.26 reallocarray is not supported
check.checkInDynamicSymtab();
if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
check.checkNotPresent("reallocarray");
} else {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray");
}
// before v2.38 strlcpy is not supported
check.checkInDynamicSymtab();
if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
check.checkNotPresent("strlcpy");
} else {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy");
}
// v2.16 introduced getauxval()
check.checkInDynamicSymtab();
if (glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) {
check.checkNotPresent("getauxval");
} else {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval");
}
// Always have dynamic "exit", "pow", and "powf" references
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit");
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT pow");
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT powf");
// 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);
}
// Build & run a Zig test case 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",

View File

@ -0,0 +1,114 @@
/*
* Exercise complicating glibc symbols from C code. Complicating symbols
* are ones that have moved between glibc versions, or use floating point
* parameters, or have otherwise tripped up the Zig glibc compatibility
* code.
*/
#include <assert.h>
#include <errno.h>
#include <features.h>
#include <fcntl.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/auxv.h>
#include <sys/stat.h>
#include <unistd.h>
/* errno is compilcated (thread-local, dynamically provided, etc). */
static void check_errno()
{
int invalid_fd = open("/doesnotexist", O_RDONLY);
assert(invalid_fd == -1);
assert(errno == ENOENT);
}
/* fstat has moved around in glibc (between libc_nonshared and libc) */
static void check_fstat()
{
int self_fd = open("/proc/self/exe", O_RDONLY);
struct stat statbuf = {0};
int rc = fstat(self_fd, &statbuf);
assert(rc == 0);
assert(statbuf.st_dev != 0);
assert(statbuf.st_ino != 0);
assert(statbuf.st_mode != 0);
assert(statbuf.st_size > 0);
assert(statbuf.st_blocks > 0);
assert(statbuf.st_ctim.tv_sec > 0);
close(self_fd);
}
/* Some targets have a complicated ABI for floats and doubles */
static void check_fp_abi()
{
// Picked "pow" as it takes and returns doubles
assert(pow(10.0, 10.0) == 10000000000.0);
assert(powf(10.0f, 10.0f) == 10000000000.0f);
}
/* strlcpy introduced in glibc 2.38 */
static void check_strlcpy()
{
#if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 38) || (__GLIBC__ > 2)
char target[4] = {0};
strlcpy(target, "this is a source string", 4);
assert(strcmp(target, "thi") == 0);
#endif
}
/* reallocarray introduced in glibc 2.26 */
static void check_reallocarray()
{
#if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 26) || (__GLIBC__ > 2)
const size_t el_size = 32;
void* base = reallocarray(NULL, 10, el_size);
void* grown = reallocarray(base, 100, el_size);
assert(base != NULL);
assert(grown != NULL);
free(grown);
#endif
}
/* getauxval introduced in glibc 2.16 */
static void check_getauxval()
{
#if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 16) || (__GLIBC__ > 2)
int pgsz = getauxval(AT_PAGESZ);
assert(pgsz >= 4*1024);
#endif
}
/* atexit() is part of libc_nonshared */
static void force_exit_0()
{
exit(0);
}
static void check_atexit()
{
int rc = atexit(force_exit_0);
assert(rc == 0);
}
int main() {
int rc;
check_errno();
check_fstat();
check_fp_abi();
check_strlcpy();
check_reallocarray();
check_getauxval();
check_atexit();
exit(99); // exit code overridden by atexit handler
}