Commit 8bee879fc2
Changed files (3)
lib
libc
glibc
test
link
glibc_compat
lib/libc/glibc/README.md
@@ -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
test/link/glibc_compat/build.zig
@@ -28,7 +28,107 @@ 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;
+
+ 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",
test/link/glibc_compat/glibc_runtime_check.c
@@ -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
+}