zig icon indicating copy to clipboard operation
zig copied to clipboard

zig detects wrong libc version

Open ifreund opened this issue 4 years ago • 22 comments

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,
        },
    }},
};

ifreund avatar Sep 30 '20 09:09 ifreund

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,
        },
    }},
};

Snektron avatar Sep 30 '20 09:09 Snektron

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

andrewrk avatar Sep 30 '20 09:09 andrewrk

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.

Snektron avatar Sep 30 '20 09:09 Snektron

@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<char, std::char_traits<char>, std::allocator<char> >*) (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::object::ELFType<(llvm::support::endianness)1, true> >(llvm::opt::InputArgList&) (in /home/ifreund/projects/zig/build/zig)
==19445==    by 0x8CE842: lld::elf::LinkerDriver::main(llvm::ArrayRef<char const*>) (in /home/ifreund/projects/zig/build/zig)
==19445==    by 0x343ADAA: lld::elf::link(llvm::ArrayRef<char const*>, 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)

ifreund avatar Sep 30 '20 10:09 ifreund

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.

LemonBoy avatar Sep 30 '20 11:09 LemonBoy

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

Snektron avatar Sep 30 '20 11:09 Snektron

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.

FireFox317 avatar Sep 30 '20 11:09 FireFox317

--- 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.

andrewrk avatar Sep 30 '20 19:09 andrewrk

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?

ifreund avatar Sep 30 '20 22:09 ifreund

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

aniljava avatar Oct 01 '20 06:10 aniljava

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.

LemonBoy avatar Oct 01 '20 09:10 LemonBoy

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 avatar Oct 04 '20 06:10 andrewrk

@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.

andrewrk avatar Oct 04 '20 06:10 andrewrk

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) }

ifreund avatar Oct 05 '20 21:10 ifreund

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

vesim987 avatar Dec 02 '20 11:12 vesim987

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

LemonBoy avatar Dec 02 '20 19:12 LemonBoy

@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```

vesim987 avatar Dec 02 '20 19:12 vesim987

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.

LemonBoy avatar Dec 03 '20 11:12 LemonBoy

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

No. I veto adding a dependency on ldd.

andrewrk avatar Nov 24 '21 21:11 andrewrk

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.

BratishkaErik avatar Jun 04 '22 18:06 BratishkaErik

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.

topolarity avatar Jul 18 '22 00:07 topolarity

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

BratishkaErik avatar Jul 30 '22 13:07 BratishkaErik

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

andrewrk avatar Sep 09 '22 01:09 andrewrk