mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
test/link/glibc_compat: Add C test case for glibc versions
glibc_runtime_check.c is a simple test case that exercises glibc functions that might smoke out linking problems with Zig's C compiler. The build.zig compiles it against a variety of glibc versions. Also document and test glibc v2.2.5 (from 2002) as the oldest working glibc target for C binaries.
This commit is contained in:
parent
a4e01074b5
commit
8bee879fc2
3 changed files with 218 additions and 2 deletions
4
lib/libc/glibc/README.md
vendored
4
lib/libc/glibc/README.md
vendored
|
|
@ -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.
|
The current Zig support for glibc only includes Linux.
|
||||||
|
|
||||||
Zig supports glibc versions back to v2.17 (2012) as the Zig standard
|
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
|
## Glibc stubs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,107 @@ pub fn build(b: *std.Build) void {
|
||||||
test_step.dependOn(&exe.step);
|
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;
|
||||||
|
|
||||||
|
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{
|
for ([_][]const u8{
|
||||||
"native-linux-gnu.2.17", // Currently oldest supported, see #17769
|
"native-linux-gnu.2.17", // Currently oldest supported, see #17769
|
||||||
"native-linux-gnu.2.23",
|
"native-linux-gnu.2.23",
|
||||||
|
|
|
||||||
114
test/link/glibc_compat/glibc_runtime_check.c
Normal file
114
test/link/glibc_compat/glibc_runtime_check.c
Normal 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
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue