ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.

Home Page:https://ziglang.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

glibc 2.27 or older: fcntl64 not found, but zig's glibc headers refer it

motiejus opened this issue · comments

TLDR: zig is using "too new" glibc headers, which sometimes references undefined symbols. This fails compilation for at least sqlite and libuv when older glibc is selected, because it redefines fcntl to fcntl64, which is present only in newer glibcs.

main.c

#define _FILE_OFFSET_BITS 64
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main() {
    printf("address to fcntl: %p\n", fcntl);
}

This fails when target is glibc 2.27 or older:

$ zig cc --target=x86_64-linux-gnu.2.27  main.c
ld.lld: error: undefined symbol: fcntl64
>>> referenced by main.c
>>>               /home/motiejus/.cache/zig/o/921a4d8978936f8450e53a6103470e2c/main.o:(main)
>>> did you mean: fcntl64@GLIBC_2.28
>>> defined in: /home/motiejus/.cache/zig/o/6dd7f9446c261fd02c47b1aad02ab90b/libc.so.6

And works with glibc 2.28+:

$ zig cc --target=x86_64-linux-gnu.2.28  main.c
$ ./a.out 
address to fcntl: 0x7f46a328e330

This task gives a small reproducible test case; the problem was well explained in #5882 (comment) , includes a workaround (for x86_64 only though). While the workaround works, it may be nicer if zig provided headers of the requested version, and make this problem go away?

if https://patchwork.sourceware.org/project/glibc/patch/20220129023727.1496360-1-andrew@ziglang.org/ gets merged, I will attempt do to an equivalent thing for fcntl64 for glibc <= 2.27.

The solution provided in 5882 didn't fully work for me when building CPython. Therefore, I tried something similar to 39083c3:

--- zig_linux_x86.orig/lib/libc/include/generic-glibc/fcntl.h	2022-02-15 03:47:43.000000000 +0100
+++ zig_linux_x86.custom/lib/libc/include/generic-glibc/fcntl.h	2022-06-22 12:50:07.530393034 +0200
@@ -173,7 +173,7 @@
    This function is a cancellation point and therefore not marked with
    __THROW.  */
 #ifndef __USE_TIME_BITS64
-# ifndef __USE_FILE_OFFSET64
+# if (__GLIBC__ == 2 && __GLIBC_MINOR__ < 28) || !defined(__USE_FILE_OFFSET64)
 extern int fcntl (int __fd, int __cmd, ...);
 # else
 #  ifdef __REDIRECT

And the linking issue disappeared. However, the resulting binary behaves erratically (for instance, when the mkdir syscall fails, errno returns 0); but I can't confirm any relationship with the linking problem described in this issue.

This issue not only impacts fcntl64. For instance, memfd_create was added in 2.27; but when linking in a system with an older glibc (e.g. Amazon Linux 2), memfd_create fails to resolve because it is missing in the installed Glibc but present in Zig's bundled headers.

I would like to know if Glibc headers are included via automation or are hand tuned sometimes. Because the preprocessor conditionals may be the easiest way to solve this. In the case of memfd_create, surrounding it by something like #if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 27) || __GLIBC__ > 2.

This issue not only impacts fcntl64. For instance, memfd_create was added in 2.27; but when linking in a system with an older glibc (e.g. Amazon Linux 2), memfd_create fails to resolve because it is missing in the installed Glibc but present in Zig's bundled headers.

Good point.

I would like to know if Glibc headers are included via automation or are hand tuned sometimes. Because the preprocessor conditionals may be the easiest way to solve this. In the case of memfd_create, surrounding it by something like #if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 27) || __GLIBC__ > 2.

There is a precedent in 39083c3 ; but it's dangerous and it would be best to keep it at zero, since that will make glibc header updates error prone.

I know @marler8997 started working on a proper solution to glibc headers, and this issue is high in our wishlist (but to my knowledge nobody in ZSF has prioritized it yet).

While this issue is being considered. Is there a way to tell the Zig C driver to use the system's libc headers instead of the bundled ones?

While this issue is being considered. Is there a way to tell the Zig C driver to use the system's libc headers instead of the bundled ones?

Yes. Just don't pass --target:

main.c

#define _FILE_OFFSET_BITS 64
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main() {
    printf("address to fcntl: %p\n", fcntl);
}

Compile:

$ strace -f -e openat zig cc main.c |& grep fcntl.h
[pid 2239029] openat(AT_FDCWD, "/usr/include/fcntl.h", O_RDONLY|O_NOCTTY|O_LARGEFILE|O_CLOEXEC) = 29
[pid 2239029] openat(AT_FDCWD, "/usr/include/x86_64-linux-gnu/bits/fcntl.h", O_RDONLY|O_NOCTTY|O_LARGEFILE|O_CLOEXEC) = 29

This workaround seems to "work-for-me":

redirect_fnctl64_hack.zig:

// work around glibc headers >= 2.28 no linking against older runtime library
// more info: https://microeducate.tech/how-to-force-linkage-to-older-libc-fcntl-instead-of-fcntl64/

extern fn fcntl() callconv(.Naked) i32;

pub export fn fcntl_zig_trampoline() callconv(.Naked) noreturn {
    const builtin = @import("builtin");
    if (builtin.target.isGnuLibC()) {
        const ver = builtin.os.version_range.linux.glibc;
        if (comptime ver.order(.{ .major = 2, .minor = 28, .patch = 0 }) == .lt) {
            @export(fcntl_zig_trampoline, .{ .name = "fcntl64", .linkage = .Weak });
            if (builtin.target.cpu.arch == .x86_64) {
                asm volatile (
                    \\ jmp fcntl
                );
            } else {
                @compileError("TODO");
            }
        }
    }

    unreachable;
}

and add this to the build.zig script:

        const obj = b.addObject("fnctl64_hack", "redirect_fnctl64_hack.zig");
        obj.setTarget(target);
        obj.setBuildMode(.ReleaseFast);
        exe.addObject(obj);

(or I guess it's similarly possible with other build systems by using zig build-obj and including the .o file to the linker command)

I would like to know if Glibc headers are included via automation or are hand tuned sometimes. Because the preprocessor conditionals may be the easiest way to solve this.

I discussed this offline with @andrewrk and we agreed to do header ifdefs until universal-headers project is merged.

#15101 adds header conditionals to fix fcntl64 and a few more symbols from resolv.h.