Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test/link/glibc_compat: Add C test case for glibc versions #19007

Merged
merged 2 commits into from
Jun 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/libc/glibc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion lib/std/zig/target.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 } },
Expand Down
109 changes: 108 additions & 1 deletion test/link/glibc_compat/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
114 changes: 114 additions & 0 deletions test/link/glibc_compat/glibc_runtime_check.c
Original file line number Diff line number Diff line change
@@ -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
}