You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
zig cc: detect -static and treat -l options as static library names #4986
So I'm not sure if I'm doing something incorrectly, but I can't really figure out, so I came with this reproducible example.
Consider we have a C file:
We want to build it for arm-linux-musleabi as a static binary and link to libressl, so we do this:
wget https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-3.1.0.tar.gz
tar xvf libressl-3.1.0.tar.gz
cd libressl-3.1.0
export CC="zig cc -target arm-linux-musleabi -static"
./configure --host=arm-linux-musleabi --disable-shared --prefix=/some/dir
make -j16 -C crypto
make -j16 -C ssl
make -j16 -C crypto install
make -j16 -C ssl install
$ file hello
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-arm.so.1, not stripped
Or even if we do strings hello, /lib/ld-musl-arm.so.1 will be there too.
And we won't be able to run it on any ARM-based machine which doesn't have that file:
$ qemu-arm ./hello
/lib/ld-musl-arm.so.1: No such file or directory
(Maybe I just used some compiler options incorrectly, but I'm not really experienced with C compiler arguments)
Also, on an unrelated note - is it possible to do "--strip" (like in zig build-exe) with zig cc?
--strip (like in zig build-exe) is enabled by default for zig cc, and turns off if you pass -g or other similar debug options.
For the other problem-
I looked through the code a bit and I believe the problem is -lssl and -lcrypto making zig think you are dynamic linking against those libraries. So this is a missing feature of zig cc, to detect -static and make it look for .a files instead.
If you are looking for a workaround in the meantime, it is to pass the .a files as positional arguments instead of -l parameters.
changed the title [-]zig cc makes a dynamic library even when told not to[/-][+]zig cc: detect -static and treat -l options as static library names[/+]on Apr 9, 2020
Not sure if this is the same issue, but zig cc has the same problem with -Wl,-Bstatic which sets all the -l libs after it as statically linked. They end up being dynamically linked anyway. This happens without -static being specified. Running the same command with clang doesn't have this issue.
Edit: Also, Zig doesn't even seem to know the options:
-static is not recognized by zig cc, it doesn't complain about it when provided like some other options do with error: Unknown Clang option. This issue is asking for zig cc to detect and handle -static properly.
As shown below when providing the static *.a libraries explicitly as a suggested workaround above, Zig is still configuring a dynamic linker / interpreter which is causing the segfault?
I looked through the code a bit and I believe the problem is -lssl and -lcrypto making zig think you are dynamic linking against those libraries. So this is a missing feature of zig cc, to detect -static and make it look for .a files instead.
If you are looking for a workaround in the meantime, it is to pass the .a files as positional arguments instead of -l parameters.
Just building the hello world example without any -l parameters still ignores -static for a glibc (-gnu) target:
# zig 0.11.0 (Aug 2023) via Fedora 40 Docker image:
$ zig cc -static -o hello hello.c
$ file hellohello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, with debug_info, not stripped
$ ldd hello linux-vdso.so.1 (0x00007fff22ad0000) libc.so.6 => /lib64/libc.so.6 (0x00007f59ddc91000) /lib64/ld-linux-x86-64.so.2 (0x00007f59dde83000)
# Explicit target, same output except for`GNU/Linux 2.0.0`:
$ zig cc -static -target x86_64-linux-gnu -o hello hello.c
$ file hellohello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.0.0, with debug_info, not stripped
# Musl target is static, unlike glibc/gnu:
$ zig cc -static -target x86_64-linux-musl -o hello hello.c
$ file hellohello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, with debug_info, not stripped
$ ldd hello not a dynamic executable
I imagine that's the actual issue here?
UPDATE: Nevermind, I understand the advice/discussion now 😅
To successfully static link the hello world on a -gnu target, you need to reference the absolute path for libc.a, which then fails with:
LLD Link... ld.lld: error: undefined symbol: _Unwind_Resume
>>> referenced by iofputs.o:(_IO_fputs.cold) in archive /usr/lib64/libc.a
>> referenced by fileops.o:(_IO_new_file_underflow.cold) in archive /usr/lib64/libc.a
>> referenced by wfileops.o:(_IO_wfile_underflow.cold) in archive /usr/lib64/libc.a
>> referenced 4 more times
ld.lld: error: undefined symbol: __gcc_personality_v0
>>> referenced by iofputs.o:(DW.ref.__gcc_personality_v0) in archive /usr/lib64/libc.a
So you need to also reference libgcc_eh.a explicitly too.
On fedora these can be found:
# Find a package that provides this file, in this case it's `gcc`:
$ dnf provides *gcc_eh.agcc-14.0.1-0.7.fc40.x86_64 : Various compilers (C, C++, Objective-C, ...)Repo : @SystemMatched from:Other : *gcc_eh.a
# Query the package for the actual install path:
$ dnf repoquery -l gcc | grep gcc_eh.a/usr/lib/gcc/x86_64-redhat-linux/14/32/libgcc_eh.a/usr/lib/gcc/x86_64-redhat-linux/14/libgcc_eh.a
# Same process for`libc.a`:
$ dnf provides *libc.aglibc-static-2.39-2.fc40.x86_64 : C library static libraries for -static linking.Repo : @SystemMatched from:Other : *libc.a
$ dnf repoquery -l glibc-static | grep libc.a/usr/lib/libc.a/usr/lib64/libc.a
So until this issue is fixed, it'll vary based on build host:
# Split to multi-line for readability:
$ zig cc -static -target x86_64-linux-gnu -o hello \ hello.c \ /usr/lib64/libc.a \ /usr/lib/gcc/x86_64-redhat-linux/14/libgcc_eh.a
$ ldd hello statically linked
# Despite that, seems to still be dynamically linked?
$ file hellohello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.0.0, with debug_info, not stripped
$ nm -an hello | grep dl_open0000000000290e60 t dl_open_worker0000000000291190 t _dl_open00000000002914c0 t dl_open_worker_begin
Perhaps it needs something else for file to not treated it as dynamically linked?
I assume that's due to the _dl_open. I'm aware that glibc can be statically linked and still have _dl_open, so it may be due to that.
I'm more familiar with Rust where the equivalent glibc static link build does not have the extra dynamic output from file / nm commands.
EDIT: I probably should have run it first, the above approach segfaults 😂 (despite being run on the same build host) so I'm definitely missing something else, or doing something wrong?
Use the Dockerfile below to build a container with a version of Zig for testing. It embeds the main.c hello world program and a test-zig script for convenience, you can just run the container to build, or shell in to inspect.
FROM fedora:42
ARG ZIG_VERSION=master # 0.14.0
RUN <<"HEREDOC"
dnf --setopt=install_weak_deps=0 install -yq file jq gcc glibc-static
# Install Zig:
ZIG_RELEASE=$(curl -fsSL https://ziglang.org/download/index.json | jq -rc ".[\"${ZIG_VERSION}\"].[\"x86_64-linux\"].tarball")
ZIG_DIR="/opt/zig-${ZIG_VERSION}"# Prefer mkdir with `--strip-components=1` otherwise archive top-level directory is the longer archive filename from URL# Use `--no-same-owner` because ownership is otherwise 1000:1000
mkdir "${ZIG_DIR}"
curl -fsSL "${ZIG_RELEASE}" | tar --extract --xz --directory "${ZIG_DIR}" --strip-components=1 --no-same-owner
ln -s "${ZIG_DIR}/zig" /usr/local/bin/zig
HEREDOC
# Defaults for convenience:WORKDIR /example
CMD ["test-zig-static"]
# Remainder of this file embeds content:# Example program to test with:COPY <<HEREDOC /example/main.c
#include <stdio.h>
int main()
{
printf("Hello World!\n");
}
HEREDOC
COPY --chmod=755 <<HEREDOC /usr/local/bin/test-zig-static
#!/bin/sh
zig cc main.c -static -target x86_64-linux-gnu -o example /usr/lib64/libc.a /usr/lib/gcc/x86_64-redhat-linux/15/libgcc_eh.a
HEREDOC
# Same as prior `test-zig-static` file, except no `-static` flag:COPY --chmod=755 <<HEREDOC /usr/local/bin/test-zig
#!/bin/sh
zig cc main.c -target x86_64-linux-gnu -o example /usr/lib64/libc.a /usr/lib/gcc/x86_64-redhat-linux/15/libgcc_eh.a
HEREDOC
Here's the failure scenario from my last comment:
docker run --rm -it localhost/example:0.13.0 bash
# Build the example:
$ test-zig
# Notice it segfaults:
$ ./exampleSegmentation fault (core dumped)
$ ldd example statically linked
$ file exampleexample: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.0.0, with debug_info, not stripped, too many notes (256)
Newer versions of Zig since fail to build it, master (to become 0.15.0? Tested with this PR) additionally blocks the usage of -static with a new error message:
# Only from zig-master:
$ docker run --rm -it localhost/example:mastererror: libc of the specified target requires dynamic linking
# Both Zig 0.14.0 (with or without `-static`) and master (without `-static`):
$ docker run --rm -it localhost/example:0.14.0ld.lld: error: undefined hidden symbol: _init>>> referenced by libc-start.o:(__libc_start_main_impl) in archive /usr/lib64/libc.ald.lld: error: undefined hidden symbol: _fini>>> referenced by libc-start.o:(call_fini) in archive /usr/lib64/libc.a
# Does not support cross-compilation target (which apparently includes a `-target` that matches the host)
# https://github.com/ziglang/zig/pull/23752#issue-3037284792
$ zig cc main.c -o example -static
# ✅ Nothing linked:
$ ldd ./example not a dynamic executable
# ✅ No interpreter:
$ file ./example./example: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, with debug_info, not stripped
# ✅ No segfault:./exampleHello World!
Activity
andrewrk commentedon Apr 9, 2020
--strip(like inzig build-exe) is enabled by default forzig cc, and turns off if you pass-gor other similar debug options.For the other problem-
I looked through the code a bit and I believe the problem is
-lssland-lcryptomaking zig think you are dynamic linking against those libraries. So this is a missing feature ofzig cc, to detect-staticand make it look for.afiles instead.If you are looking for a workaround in the meantime, it is to pass the .a files as positional arguments instead of
-lparameters.[-]zig cc makes a dynamic library even when told not to[/-][+]zig cc: detect -static and treat -l options as static library names[/+]andrewrk commentedon Apr 10, 2020
Is that different than
zig build-exethough? The strip functionality is not fully implemented; currently it only omits debug info.makew0rld commentedon Sep 14, 2021
Not sure if this is the same issue, but
zig cchas the same problem with-Wl,-Bstaticwhich sets all the-llibs after it as statically linked. They end up being dynamically linked anyway. This happens without-staticbeing specified. Running the same command withclangdoesn't have this issue.Edit: Also, Zig doesn't even seem to know the options:
SoftwareApe commentedon Oct 13, 2021
Facing the same issue as @makeworld-the-better-one, also doesn't work when using lld as a linker.
nuald commentedon Oct 23, 2021
As @andrewrk suggested, there is a workaround by using object files (
addObjectFilemethod), i.e.YugoCode commentedon Jul 2, 2023
Could someone please provide a workaround for using
zig ccinstead ofzig build?I tried following command, but still get
error: undefined symbolfor every libcurl function:This command builds without a problem:
polarathene commentedon Mar 7, 2024
UPDATE: I misunderstood this issue:
-staticis not recognized byzig cc, it doesn't complain about it when provided like some other options do witherror: Unknown Clang option. This issue is asking forzig ccto detect and handle-staticproperly.*.alibraries explicitly as a suggested workaround above, Zig is still configuring a dynamic linker / interpreter which is causing the segfault?Just building the hello world example without any
-lparameters still ignores-staticfor aglibc(-gnu) target:hello.c:I imagine that's the actual issue here?
UPDATE: Nevermind, I understand the advice/discussion now 😅
To successfully static link the hello world on a
-gnutarget, you need to reference the absolute path forlibc.a, which then fails with:So you need to also reference
libgcc_eh.aexplicitly too.On fedora these can be found:
So until this issue is fixed, it'll vary based on build host:
Perhaps it needs something else for
fileto not treated it as dynamically linked?_dl_open. I'm aware that glibc can be statically linked and still have_dl_open, so it may be due to that.file/nmcommands.Related issues:
cargo zigbuilddoesn't support static glibc builds with an explicit--targetrust-cross/cargo-zigbuild#231 (comment)polarathene commentedon May 2, 2025
Follow-up to my previous comment.
Reproduction
Use the
Dockerfilebelow to build a container with a version of Zig for testing. It embeds themain.chello world program and atest-zigscript for convenience, you can just run the container to build, or shell in to inspect.Dockerfile:Here's the failure scenario from my last comment:
Newer versions of Zig since fail to build it,
master(to become0.15.0? Tested with this PR) additionally blocks the usage of-staticwith a new error message:The
_init/_finierrors seems to be due tocrt0.o+crtend.ohttps://stackoverflow.com/questions/8410383/linker-error-undefined-reference-to-fini/8410815#8410815Those specific files do not appear to be available on Fedora or Alpine, nor does Zig or Rust toolchains seem to provide them.
Here's the Rust toolchain where only the musl target (due to that targets
-C link-self-contained=yesdefault) has these files for static linking:polarathene commentedon May 5, 2025
Another update. While the original issue may not be resolved, the related one for static glibc will be for
0.15.0, except only when you build without specifying-target(even if it matches the same build host, but-target nativeshould be valid).Full reproduction (
Dockerfile): #23572 (comment)