ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.57k stars 2.53k forks source link

zig detects wrong libc version #6469

Closed ifreund closed 2 years ago

ifreund commented 4 years ago

My system libc version is 2.30 but zig detects and builds 2.17 which causes linking errors with expat for example:

const std = @import("std");
const c = @cImport({
    @cInclude("expat.h");
});
pub fn main() void {
    std.debug.print("Take off every Zig\n", .{});
}
zig-git build-exe -lc -lexpat test.zig --verbose-link
lld -error-limit=0 -z stack-size=16777216 --gc-sections -m elf_x86_64 -o test /home/ifreund/.cache/zig/o/e078967572054b1e20fb7e32da06f238/Scrt1.o /home/ifreund/.cache/zig/o/9a9a917500aa09fcd6cbafb708b1d6e3/crti.o -L /usr/local/lib64 -L /usr/local/lib -L /usr/lib/x86_64-linux-gnu -L /lib64 -L /lib -L /usr/lib64 -L /usr/lib -L /lib/x86_64-linux-gnu -dynamic-linker /lib/ld-linux-x86-64.so.2 zig-cache/o/d6ab4d674788932c736c310c43b963b3/test.o /home/ifreund/.cache/zig/o/cac0c8a5e22159f852a543a37241f19a/libcompiler_rt.a -lexpat /home/ifreund/.cache/zig/o/ef893c3d724376328171551dc3f416d7/libunwind.a /home/ifreund/.cache/zig/o/513bf1524998e06b2a99a61649abfd7d/libc.so.6 /home/ifreund/.cache/zig/o/513bf1524998e06b2a99a61649abfd7d/libm.so.6 /home/ifreund/.cache/zig/o/513bf1524998e06b2a99a61649abfd7d/libpthread.so.0 /home/ifreund/.cache/zig/o/513bf1524998e06b2a99a61649abfd7d/libdl.so.2 /home/ifreund/.cache/zig/o/513bf1524998e06b2a99a61649abfd7d/librt.so.1 /home/ifreund/.cache/zig/o/513bf1524998e06b2a99a61649abfd7d/libld.so.2 /home/ifreund/.cache/zig/o/513bf1524998e06b2a99a61649abfd7d/libutil.so.1 /home/ifreund/.cache/zig/o/66a177e6febdd316bcf2348b13964c28/libc_nonshared.a /home/ifreund/.cache/zig/o/5d923f6790009188502400b46c05b3cf/crtn.o
lld: error: /lib64/libexpat.so: undefined reference to getrandom
error: LLDReportedFailure

Compiling with --show-builtin gives the following os struct, demonstrating that the wrong glibc version is detected.

pub const os = Os{
    .tag = .linux,
    .version_range = .{ .linux = .{
        .range = .{
            .min = .{
                .major = 3,
                .minor = 16,
                .patch = 0,
            },
            .max = .{
                .major = 5,
                .minor = 5,
                .patch = 5,
            },
        },
        .glibc = .{
            .major = 2,
            .minor = 17,
            .patch = 0,
        },
    }},
};
Snektron commented 4 years ago

I am also running void linux, but Zig does detect the right version:

pub const os = Os{
    .tag = .linux,
    .version_range = .{ .linux = .{
        .range = .{
            .min = .{
                .major = 3,
                .minor = 16,
                .patch = 0,
            },
            .max = .{
                .major = 5,
                .minor = 5,
                .patch = 5,
            },
        },
        .glibc = .{
            .major = 2,
            .minor = 30,
            .patch = 0,
        },
    }},
};
andrewrk commented 4 years ago

@ifreund can you double check with valgrind that zig is not doing anything fishy?

Snektron commented 4 years ago

Excuses, i hadn't built the most recent Zig compiler. It now also detects 2.17 on my system, so this seems to be a regression.

ifreund commented 4 years ago

@ifreund can you double check with valgrind that zig is not doing anything fishy?

valgrind log ``` valgrind --leak-check=full zig-git build-exe -lc -lexpat test.zig ==19445== Memcheck, a memory error detector ==19445== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==19445== Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info ==19445== Command: zig-git build-exe -lc -lexpat test.zig ==19445== lld: error: /lib64/libexpat.so: undefined reference to getrandom error: LLDReportedFailure ==19445== ==19445== HEAP SUMMARY: ==19445== in use at exit: 200,937 bytes in 1,971 blocks ==19445== total heap usage: 7,099 allocs, 5,128 frees, 4,419,028 bytes allocated ==19445== ==19445== 59 bytes in 1 blocks are definitely lost in loss record 1,707 of 1,968 ==19445== at 0x48B777F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==19445== by 0x8D3434: std.heap.cAlloc (heap.zig:47) ==19445== by 0x96B516: std.mem.Allocator.allocAdvancedWithRetAddr.363 (Allocator.zig:294) ==19445== by 0x96B2BA: std.mem.Allocator.alloc.362 (Allocator.zig:186) ==19445== by 0x96BD85: std.fs.path.joinSep (path.zig:58) ==19445== by 0x8D3714: std.fs.path.joinPosix (path.zig:93) ==19445== by 0x9F1968: glibc.buildSharedObjects (glibc.zig:888) ==19445== by 0x9BE5A8: Compilation.performAllTheWork (Compilation.zig:1247) ==19445== by 0x9B8EE6: Compilation.update (Compilation.zig:1028) ==19445== by 0x95F596: main.updateModule (main.zig:1761) ==19445== by 0x8FC452: main.buildOutputType (main.zig:1637) ==19445== by 0x8D6189: main.mainArgs (main.zig:126) ==19445== ==19445== 192 bytes in 1 blocks are definitely lost in loss record 1,957 of 1,968 ==19445== at 0x48B777F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==19445== by 0x8D3434: std.heap.cAlloc (heap.zig:47) ==19445== by 0xBD2986: std.mem.Allocator.allocAdvancedWithRetAddr.3436 (Allocator.zig:294) ==19445== by 0xAAEDC6: std.mem.Allocator.allocAdvancedWithRetAddr.1806 (Allocator.zig:277) ==19445== by 0x994E6D: std.mem.Allocator.reallocAdvancedWithRetAddr.617 (Allocator.zig:375) ==19445== by 0x994D68: std.mem.Allocator.reallocAtLeast.616 (Allocator.zig:348) ==19445== by 0x9946FA: std.array_list.ArrayListAlignedUnmanaged(std.array_hash_map.Entry,null).ensureCapacity (array_list.zig:546) ==19445== by 0x98A68D: std.array_hash_map.ArrayHashMapUnmanaged([]const u8,void,std.array_hash_map.hashString,std.array_hash_map.eqlString,true).ensureCapacity (array_hash_map.zig:404) ==19445== by 0x95A241: Compilation.create (Compilation.zig:735) ==19445== by 0x8FB7EA: main.buildOutputType (main.zig:1533) ==19445== by 0x8D6189: main.mainArgs (main.zig:126) ==19445== by 0x8D5E6A: main (stage1.zig:42) ==19445== ==19445== 67,584 bytes in 1 blocks are definitely lost in loss record 1,968 of 1,968 ==19445== at 0x48B777F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==19445== by 0x534E983: ??? (in /usr/lib/libLLVM-10.so) ==19445== by 0x534F27E: llvm::sys::RemoveFileOnSignal(llvm::StringRef, std::__cxx11::basic_string, std::allocator >*) (in /usr/lib/libLLVM-10.so) ==19445== by 0x53479AD: llvm::sys::fs::TempFile::create(llvm::Twine const&, unsigned int) (in /usr/lib/libLLVM-10.so) ==19445== by 0x529B8E5: llvm::FileOutputBuffer::create(llvm::StringRef, unsigned long, unsigned int) (in /usr/lib/libLLVM-10.so) ==19445== by 0x3778C8D: lld::tryCreateFile(llvm::StringRef) (in /home/ifreund/projects/zig/build/zig) ==19445== by 0x3436416: void lld::elf::LinkerDriver::link >(llvm::opt::InputArgList&) (in /home/ifreund/projects/zig/build/zig) ==19445== by 0x8CE842: lld::elf::LinkerDriver::main(llvm::ArrayRef) (in /home/ifreund/projects/zig/build/zig) ==19445== by 0x343ADAA: lld::elf::link(llvm::ArrayRef, bool, llvm::raw_ostream&, llvm::raw_ostream&) (in /home/ifreund/projects/zig/build/zig) ==19445== by 0x13357A7: ZigLLDLink (zig_llvm.cpp:1064) ==19445== by 0xA145AC: link.Elf.linkWithLLD (Elf.zig:1608) ==19445== by 0xA03573: link.Elf.flush (Elf.zig:719) ==19445== ==19445== LEAK SUMMARY: ==19445== definitely lost: 67,835 bytes in 3 blocks ==19445== indirectly lost: 0 bytes in 0 blocks ==19445== possibly lost: 0 bytes in 0 blocks ==19445== still reachable: 133,102 bytes in 1,968 blocks ==19445== suppressed: 0 bytes in 0 blocks ==19445== Reachable blocks (those to which a pointer was found) are not shown. ==19445== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==19445== ==19445== For lists of detected and suppressed errors, rerun with: -s ==19445== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0) ```
LemonBoy commented 4 years ago

Two problems here, the first being that getSelfExeSharedLibPaths returns an empty array because builtin.link_mode==static, even though the final zig binary is not static. The second is trickier as /usr/bin/env has no DT_RUNPATH on my system (Debian 10 Buster), so even the second detection method fails.

Snektron commented 4 years ago

Does the latter problem mean Zig should search the other places like DT_RPATH, LD_LIBRARY_PATH, /lib and /usr/lib similar to ld?

FireFox317 commented 4 years ago

Regarding the builtin.link_mode, when building some .zig file and adding -dynamic to the command, the builtin.link_mode changes to .Dynamic. Thus this is actually not the link_mode of the zig executable, but rather of the one that we are building currently.

andrewrk commented 4 years ago
--- a/src/stage1/codegen.cpp
+++ b/src/stage1/codegen.cpp
@@ -8815,7 +8815,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
     buf_append_str(contents, "/// Deprecated: use `std.Target.current.cpu.arch.endian()`\n");
     buf_append_str(contents, "pub const endian = Target.current.cpu.arch.endian();\n");
     buf_appendf(contents, "pub const output_mode = OutputMode.Obj;\n");
-    buf_appendf(contents, "pub const link_mode = LinkMode.Static;\n");
+    buf_appendf(contents, "pub const link_mode = LinkMode.Dynamic;\n");
     buf_appendf(contents, "pub const is_test = false;\n");
     buf_appendf(contents, "pub const single_threaded = %s;\n", bool_to_str(g->is_single_threaded));
     buf_appendf(contents, "pub const abi = Abi.%s;\n", cur_abi);

try this. should be OK because this applies only to building zig1.o

I'm not sure why the uname didn't get the correct linux version though, that's another mystery here.

ifreund commented 4 years ago

try this. should be OK because this applies only to building zig1.o

Tried and confirmed that it fixed the issue for me at least. Should this be PR'd?

aniljava commented 4 years ago

Is libc bundled with zig or there are two versions in my computer.

/ws/projects/zig/sqlite :: # ldd --version
ldd (Ubuntu GLIBC 2.31-0ubuntu9) 2.31

pub const os = Os{
    .tag = .linux,
    .version_range = .{ .linux = .{
        .range = .{
            .min = .{
                .major = 5,
                .minor = 4,
                .patch = 0,
            },
            .max = .{
                .major = 5,
                .minor = 4,
                .patch = 0,
            },
        },
        .glibc = .{
            .major = 2,
            .minor = 17,
            .patch = 0,
        },
    }},
};

6227 #5882

LemonBoy commented 4 years ago

Does the latter problem mean Zig should search the other places like DT_RPATH, LD_LIBRARY_PATH, /lib and /usr/lib similar to ld?

Shit hits the fan pretty quickly if you want to do things right, here's a quote from ld.so manpage:


       When resolving shared object dependencies, the dynamic linker first
       inspects each dependency string to see if it contains a slash (this
       can occur if a shared object pathname containing slashes was
       specified at link time).  If a slash is found, then the dependency
       string is interpreted as a (relative or absolute) pathname, and the
       shared object is loaded using that pathname.

       If a shared object dependency does not contain a slash, then it is
       searched for in the following order:

       o  Using the directories specified in the DT_RPATH dynamic section
          attribute of the binary if present and DT_RUNPATH attribute does
          not exist.  Use of DT_RPATH is deprecated.

       o  Using the environment variable LD_LIBRARY_PATH, unless the
          executable is being run in secure-execution mode (see below), in
          which case this variable is ignored.

       o  Using the directories specified in the DT_RUNPATH dynamic section
          attribute of the binary if present.  Such directories are searched
          only to find those objects required by DT_NEEDED (direct
          dependencies) entries and do not apply to those objects' children,
          which must themselves have their own DT_RUNPATH entries.  This is
          unlike DT_RPATH, which is applied to searches for all children in
          the dependency tree.

       o  From the cache file /etc/ld.so.cache, which contains a compiled
          list of candidate shared objects previously found in the augmented
          library path.  If, however, the binary was linked with the -z
          nodeflib linker option, shared objects in the default paths are
          skipped.  Shared objects installed in hardware capability
          directories (see below) are preferred to other shared objects.

       o  In the default path /lib, and then /usr/lib.  (On some 64-bit
          architectures, the default paths for 64-bit shared objects are
          /lib64, and then /usr/lib64.)  If the binary was linked with the
          -z nodeflib linker option, this step is skipped.
andrewrk commented 4 years ago

The regression should be fixed in 2de53592a1d84a1476f662e20d7339d25d4716fe. I'd like to leave this issue open until the logic is improved to match ld behavior that @LemonBoy posted above.

andrewrk commented 4 years ago

@ifreund one thing I still want to figure out, is why did zig get the wrong OS version? 3.16.0...5.5.5 means it failed to extract the value from the uname syscall.

ifreund commented 4 years ago

Here's what I get from the uname syscall on my machine using the following code:

const std = @import("std");
pub fn main() !void {
    std.debug.print("{}\n", .{std.os.uname()});
}
utsname{ .sysname = Linux, .nodename = trantor, .release = 5.8.12_1, .version = #1 SMP Sat Sep 26 18:03:25 UTC 2020, .machine = x86_64, .domainname = (none) }
vesim987 commented 3 years ago

Hi, I have the same issue on Archlinux. When I try to build

const std = @import("std");

const mman = @cImport({
    @cDefine("_GNU_SOURCE", {});
    @cInclude("sys/mman.h");
});

pub fn main() void {
    var fd = mman.memfd_create("test", 0);
    std.
    std.debug.print("fd = {}\n", .{fd});
}

I get linking error:

lld: error: undefined symbol: memfd_create
>>> referenced by main.zig:9
>>>               /home/vesim/pro/zsmrt/zig-cache/o/2367e7736b5f96ca670c5e9d97d3d0de/init-exe.o:(main.0)
>>> did you mean: memfd_create@GLIBC_2.27
>>> defined in: /home/vesim/.cache/zig/o/54b93fc35ead95cbaefb74de730f28a8/libc.so.6
error: LLDReportedFailure

Enviroment: zig build --show-builtin:

pub const os = Os{
    .tag = .linux,
    .version_range = .{ .linux = .{
        .range = .{
            .min = .{
                .major = 5,
                .minor = 9,
                .patch = 10,
            },
            .max = .{
                .major = 5,
                .minor = 9,
                .patch = 10,
            },
        },
        .glibc = .{
            .major = 2,
            .minor = 17,
            .patch = 0,
        },
    }},
};

zig version:

0.7.0+39336fd2e

Packages versions:

glibc 2.32-5
llvm 11.0.0-1
gcc 10.2.0-3
LemonBoy commented 3 years ago

@vesim987, whats the output of readelf -d /usr/bin/env?

vesim987 commented 3 years ago

@LemonBoy


Dynamic section at offset 0xabd8 contains 27 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000c (INIT)               0x2000
 0x000000000000000d (FINI)               0x7184
 0x0000000000000019 (INIT_ARRAY)         0xb9d0
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0xb9d8
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x308
 0x0000000000000005 (STRTAB)             0xb10
 0x0000000000000006 (SYMTAB)             0x390
 0x000000000000000a (STRSZ)              902 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0xbdc8
 0x0000000000000002 (PLTRELSZ)           24 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x1928
 0x0000000000000007 (RELA)               0xf98
 0x0000000000000008 (RELASZ)             2448 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x0000000000000018 (BIND_NOW)
 0x000000006ffffffb (FLAGS_1)            Flags: NOW PIE
 0x000000006ffffffe (VERNEED)            0xf38
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0xe96
 0x000000006ffffff9 (RELACOUNT)          28
 0x0000000000000000 (NULL)               0x0```
LemonBoy commented 3 years ago

Perhaps it's time to reconsider whether the extra-smart, super-complex and fallible heuristic used here is pulling its own weight. A simpler detection method would be to invoke ldd --version and scrape the output, it's not that nice but sounds better than misdetecting the libc version and/or having to re-implement the whole ld lookup logic.

andrewrk commented 2 years ago

A simpler detection method would be to invoke ldd --version and scrape the output,

No. I veto adding a dependency on ldd.

BratishkaErik commented 2 years ago

Looks like there is a regression:

zig version: 0.10.0 ldd --version:

ldd (Gentoo 2.35-r5 p7) 2.35
...

zig build-exe --show-builtin:

...
pub const os = std.Target.Os{
    .tag = .linux,
    .version_range = .{ .linux = .{
        .range = .{
            .min = .{
                .major = 5,
                .minor = 18,
                .patch = 1,
            },
            .max = .{
                .major = 5,
                .minor = 18,
                .patch = 1,
            },
        },
        .glibc = .{
            .major = 2,
            .minor = 19,
            .patch = 0,
        },
    }},
};
...

Same on Arch Linux.

topolarity commented 2 years ago

Problem is that on Arch /lib64/ld-linux-x86-64.so.2 is not a symlink after all

For that matter, neither is /lib64/libc.so.6:

$ file /lib64/ld-linux-x86-64.so.2
/lib64/ld-linux-x86-64.so.2: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), static-pie linked, BuildID[sha1]=0effd0e43efa4468d3c31871c93af0b7f3005673, stripped
$ file /lib64/libc.so.6
/lib64/libc.so.6: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /usr/lib/ld-linux-x86-64.so.2, BuildID[sha1]=60df1df31f02a7b23da83e8ef923359885b81492, for GNU/Linux 4.4.0, stripped

They do both support being executed with a --version flag, but the output is not really intended to be machine parseable:

$ /lib64/ld-linux-x86-64.so.2 --version
ld.so (GNU libc) stable release version 2.35.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
BratishkaErik commented 2 years ago

Maybe we can use confstr() with name _CS_GNU_LIBC_VERSION for detecting glibc version? (upd: oh, it requires linking libc)

upd: or execute getconf GNU_LIBC_VERSION https://pubs.opengroup.org/onlinepubs/000095399/utilities/getconf.html

andrewrk commented 2 years ago

Could I get someone affected by this issue to verify that #12788 indeed solves it?