Skip to content
Alex Rønne Petersen edited this page Dec 8, 2024 · 71 revisions

Here's how to update the libc files that Zig bundles.

glibc

Warning: Last time I went through this guide, I experienced a bug in glibc/scripts/build-many-glibcs.py that made it incorrectly mark compilers which had successfully finished building as "UNRESOLVED", which ruined everything. I worked around the issue by doing this process on Debian instead of on NixOS.

Make sure these dependencies are installed. If the scripts below fail, I'm not aware of a way to make them resume; each command deletes everything and starts over.

  • python3
  • subversion
  • gawk
  • autoconf
  • automake
  • libtool
  • flex
  • bison
  • build-essential
  • texinfo

Next, git clone glibc.

git clone git://sourceware.org/git/glibc.git
cd glibc
git checkout glibc-2.40 # the tag of the version to update to

Assuming the path of that is ~/glibc, make a new directory and go to it. Then run the Python commands, each of which uses all CPU cores and takes a long time. If any of them fail, look at the logs to find out why, correct it, and then start the command again. Unfortunately each command will delete its own previous progress and start over.

mkdir multi
cd multi
python3 ~/glibc/scripts/build-many-glibcs.py . checkout
cd src/glibc
git checkout glibc-2.40 # the tag of the version to update to
cd -
python3 ~/glibc/scripts/build-many-glibcs.py . host-libraries # took 51s with 32 CPU cores
python3 ~/glibc/scripts/build-many-glibcs.py . compilers # took 2h11m with 64 CPU cores
python3 ~/glibc/scripts/build-many-glibcs.py . glibcs # took 2h with 64 CPU cores

Next, make sure that the list of architectures in tools/process_headers.zig is complete in that it lists all of the glibc targets and maps them to Zig targets. Any additional targets you add, add to the available_libcs global constant in lib/std/zig/target.zig.

Next, from the "build" directory of zig git source, use tools/process_headers.zig:

zig run ../tools/process_headers.zig -- --search-path ~/glibc/multi/install/glibcs --out hdrs --abi glibc

Inspect the hdrs directory that the tool just created. If it looks good, then:

rm -rf $(find ../lib/libc/include/ -name "*linux-gnu*")
rm -rf ../lib/libc/include/generic-glibc
mv hdrs/* ../lib/libc/include/

Inspect git status and make sure the changes look good. Make a commit with only the updated glibc headers in it.

Next, use glibc-abi-tool to update the lib/libc/glibc/abilists file. The instructions for how to do that are in the README.

Finally, update the rest of the files in lib/libc/glibc/ besides abilists, to match the new glibc version, using the tool:

./zig run ../tools/update_glibc.zig -- ~/src/glibc ..

You definitely need to inspect the full diff and look for patches that Zig has on top of these files and put them back in place.

If any new features were added to features.h they probably need to be gated by the glibc version with an #ifdef.

If you keep your glibc build artifacts, you can use it with zig build test -fqemu --glibc-runtimes /foo/glibc/multi/install/glibcs.

musl

git clone git://git.musl-libc.org/musl
git checkout v1.2.5 # the tag of the version to update to
rm -rf obj/ && make DESTDIR=build-all/aarch64     install-headers ARCH=aarch64     prefix=/usr/local/musl
rm -rf obj/ && make DESTDIR=build-all/arm         install-headers ARCH=arm         prefix=/usr/local/musl
rm -rf obj/ && make DESTDIR=build-all/i386        install-headers ARCH=i386        prefix=/usr/local/musl
rm -rf obj/ && make DESTDIR=build-all/loongarch64 install-headers ARCH=loongarch64 prefix=/usr/local/musl
rm -rf obj/ && make DESTDIR=build-all/m68k        install-headers ARCH=m68k        prefix=/usr/local/musl
rm -rf obj/ && make DESTDIR=build-all/mips        install-headers ARCH=mips        prefix=/usr/local/musl
rm -rf obj/ && make DESTDIR=build-all/mips64      install-headers ARCH=mips64      prefix=/usr/local/musl
rm -rf obj/ && make DESTDIR=build-all/mipsn32     install-headers ARCH=mipsn32     prefix=/usr/local/musl
rm -rf obj/ && make DESTDIR=build-all/powerpc     install-headers ARCH=powerpc     prefix=/usr/local/musl
rm -rf obj/ && make DESTDIR=build-all/powerpc64   install-headers ARCH=powerpc64   prefix=/usr/local/musl
rm -rf obj/ && make DESTDIR=build-all/riscv32     install-headers ARCH=riscv32     prefix=/usr/local/musl
rm -rf obj/ && make DESTDIR=build-all/riscv64     install-headers ARCH=riscv64     prefix=/usr/local/musl
rm -rf obj/ && make DESTDIR=build-all/s390x       install-headers ARCH=s390x       prefix=/usr/local/musl
rm -rf obj/ && make DESTDIR=build-all/x32         install-headers ARCH=x32         prefix=/usr/local/musl
rm -rf obj/ && make DESTDIR=build-all/x86_64      install-headers ARCH=x86_64      prefix=/usr/local/musl

Make sure the list of architectures in tools/process_headers.zig is complete in that it lists all of the musl targets which have corresponding Zig targets. Any additional targets you add, add to the available_libcs variable in lib/std/zig/target.zig.

Next, use tools/process_headers.zig, with these parameters:

  • --abi musl
  • --search-path parameter will be the path to the build-all directory.
  • --out to a temporary directory if you want to inspect the output first, or lib/libc/include if you are confident in the output.

To update musl source code:

cd lib/libc/musl
rm -rf arch crt compat src include
cp -r ~/src/musl/arch ./
cp -r ~/src/musl/crt ./
cp -r ~/src/musl/compat ./
cp -r ~/src/musl/src ./
cp -r ~/src/musl/include ./
cp ~/src/musl/COPYRIGHT .

Remove the non-supported architectures:

rm -rf arch/microblaze
rm -rf arch/or1k
rm -rf arch/sh

Next, look at a git status and git diff and make sure everything looks OK. There are some chores to do below:

Update the contents of libc/musl/src/internal/version.h to the correct musl version number. This file will have been deleted by the above process and you will need to create it now (look at the git diff to see the contents).

Take note of the .mak files. These will show up in the "Untracked files" section, and should be deleted:

rm arch/arm/arch.mak
rm arch/i386/arch.mak
rm arch/m68k/arch.mak
rm arch/mips/arch.mak
rm arch/mipsn32/arch.mak
rm arch/powerpc/arch.mak

If there are any new ones not covered in this list, support needs to be added in src/musl.zig, where there is special handling for these. Look for time32_compat_arch_list.

Update src_files in src/musl.zig to be a complete list, e.g. with find musl/src -type f -name "*.c" -o -iname "*.s". Similarly, update compat_time32_files, e.g. with find musl/compat/time32 -type f -name "*.c" -o -iname "*.s". Lexically sort the resulting lists in src/musl.zig to keep the diff clean.

If musl added any new architectures, add them to musl_arch_names in src/musl.zig. These can be found by ls arch/ in the musl source directory.

Updating the libc.S file

lib/libc/musl/libc.S contains stubs for all the dynamic symbols of musl's libc.so. It is generated from tools/gen_stubs.zig.

First, apply this patch to musl's build system:

diff --git a/Makefile b/Makefile
index 3ad88b35..e1c6e55e 100644
--- a/Makefile
+++ b/Makefile
@@ -40,11 +40,11 @@ IMPH = $(addprefix $(srcdir)/, src/internal/stdio_impl.h src/internal/pthread_im

 LDFLAGS =
 LDFLAGS_AUTO =
-LIBCC = -lgcc
+#LIBCC = -lgcc
 CPPFLAGS =
 CFLAGS =
 CFLAGS_AUTO = -Os -pipe
diff --git a/configure b/configure
index bc9fbe48..fe86ba5c 100755
--- a/configure
+++ b/configure
@@ -600,7 +600,7 @@ tryldflag LDFLAGS_AUTO -Wl,--hash-style=both
 # libc.so will crash at runtime during relocation processing.
 # The common way this can happen is failure to link the compiler
 # runtime library; implementation error is also a possibility.
-tryldflag LDFLAGS_AUTO -Wl,--no-undefined
+#tryldflag LDFLAGS_AUTO -Wl,--no-undefined

 # Avoid exporting symbols from compiler runtime libraries. They
 # should be hidden anyway, but some toolchains including old gcc
@@ -615,13 +615,13 @@ tryldflag LDFLAGS_AUTO -Wl,--exclude-libs=ALL
 tryldflag LDFLAGS_AUTO -Wl,--dynamic-list="$srcdir/dynamic.list"

 # Find compiler runtime library
-test -z "$LIBCC" && tryldflag LIBCC -lgcc && tryldflag LIBCC -lgcc_eh
-test -z "$LIBCC" && tryldflag LIBCC -lcompiler_rt
-test -z "$LIBCC" && try_libcc=`$CC -print-libgcc-file-name 2>/dev/null` \
-                 && tryldflag LIBCC "$try_libcc"
-test -z "$LIBCC" && try_libcc=`$CC -print-file-name=libpcc.a 2>/dev/null` \
-                 && tryldflag LIBCC "$try_libcc"
-printf "using compiler runtime libraries: %s\n" "$LIBCC"
+#test -z "$LIBCC" && tryldflag LIBCC -lgcc && tryldflag LIBCC -lgcc_eh
+#test -z "$LIBCC" && tryldflag LIBCC -lcompiler_rt
+#test -z "$LIBCC" && try_libcc=`$CC -print-libgcc-file-name 2>/dev/null` \
+#                 && tryldflag LIBCC "$try_libcc"
+#test -z "$LIBCC" && try_libcc=`$CC -print-file-name=libpcc.a 2>/dev/null` \
+#                 && tryldflag LIBCC "$try_libcc"
+#printf "using compiler runtime libraries: %s\n" "$LIBCC"

 # Figure out arch variants for archs with variants
 SUBARCH=

This ensures that musl can build with -rtlib=none, despite the fact that this results in undefined symbols that would normally be fulfilled by compiler-rt. (We don't want those symbols showing up as defined in the resulting libc.so.)

Next, cross-compile musl for all the supported architectures:

mkdir -p build-all
echo -e '#!/bin/sh\nzig cc -target aarch64-linux-musl $@' > $(pwd)/build-all/zcc-aarch64
echo -e '#!/bin/sh\nzig cc -target arm-linux-musleabi $@' > $(pwd)/build-all/zcc-arm
echo -e '#!/bin/sh\nzig cc -target x86-linux-musl $@' > $(pwd)/build-all/zcc-i386
echo -e '#!/bin/sh\nzig cc -target loongarch64-linux-musl $@' > $(pwd)/build-all/zcc-loongarch64
echo -e '#!/bin/sh\nzig cc -target mips-linux-musleabi $@' > $(pwd)/build-all/zcc-mips
echo -e '#!/bin/sh\nzig cc -target mips64-linux-muslabi64 $@' > $(pwd)/build-all/zcc-mips64
echo -e '#!/bin/sh\nzig cc -target mips64-linux-muslabin32 $@' > $(pwd)/build-all/zcc-mipsn32
echo -e '#!/bin/sh\nzig cc -target powerpc-linux-musleabi $@' > $(pwd)/build-all/zcc-powerpc
echo -e '#!/bin/sh\nzig cc -target powerpc64-linux-musl $@' > $(pwd)/build-all/zcc-powerpc64
echo -e '#!/bin/sh\nzig cc -target riscv32-linux-musl $@' > $(pwd)/build-all/zcc-riscv32
echo -e '#!/bin/sh\nzig cc -target riscv64-linux-musl $@' > $(pwd)/build-all/zcc-riscv64
echo -e '#!/bin/sh\nzig cc -target s390x-linux-musl $@' > $(pwd)/build-all/zcc-s390x
echo -e '#!/bin/sh\nzig cc -target x86_64-linux-muslx32 $@' > $(pwd)/build-all/zcc-x32
echo -e '#!/bin/sh\nzig cc -target x86_64-linux-musl $@' > $(pwd)/build-all/zcc-x86_64
chmod +x $(pwd)/build-all/zcc-*
export PATH=$(pwd)/build-all:$PATH

make distclean && \
    ./configure --prefix=$(pwd)/build-all/aarch64 --target=aarch64-linux-musl --disable-static AR="zig ar" CC="zcc-aarch64" RANLIB="zig ranlib" CFLAGS="-rtlib=none" && \
    make -j$(nproc) && \
    make install
make distclean && \
    ./configure --prefix=$(pwd)/build-all/arm --target=arm-linux-musleabi --disable-static AR="zig ar" CC="zcc-arm" RANLIB="zig ranlib" CFLAGS="-rtlib=none" && \
    make -j$(nproc) && \
    make install
make distclean && \
    ./configure --prefix=$(pwd)/build-all/i386 --target=i386-linux-musl --disable-static AR="zig ar" CC="zcc-i386" RANLIB="zig ranlib" CFLAGS="-rtlib=none" && \
    make -j$(nproc) && \
    make install
make distclean && \
    ./configure --prefix=$(pwd)/build-all/loongarch64 --target=loongarch64-linux-musl --disable-static AR="zig ar" CC="zcc-loongarch64" RANLIB="zig ranlib" CFLAGS="-rtlib=none" && \
    make -j$(nproc) && \
    make install
make distclean && \
    ./configure --prefix=$(pwd)/build-all/mips --target=mips-linux-musleabi --disable-static AR="zig ar" CC="zcc-mips" RANLIB="zig ranlib" CFLAGS="-rtlib=none" && \
    make -j$(nproc) && \
    make install
make distclean && \
    ./configure --prefix=$(pwd)/build-all/mips64 --target=mips64-linux-muslabi64 --disable-static AR="zig ar" CC="zcc-mips64" RANLIB="zig ranlib" CFLAGS="-rtlib=none" && \
    make -j$(nproc) && \
    make install
make distclean && \
    ./configure --prefix=$(pwd)/build-all/mipsn32 --target=mips64-linux-muslabin32 --disable-static AR="zig ar" CC="zcc-mipsn32" RANLIB="zig ranlib" CFLAGS="-rtlib=none" && \
    make -j$(nproc) && \
    make install
make distclean && \
    ./configure --prefix=$(pwd)/build-all/powerpc --target=powerpc-linux-musleabi --disable-static AR="zig ar" CC="zcc-powerpc" RANLIB="zig ranlib" CFLAGS="-rtlib=none" && \
    make -j$(nproc) && \
    make install
make distclean && \
    ./configure --prefix=$(pwd)/build-all/powerpc64 --target=powerpc64-linux-musl --disable-static AR="zig ar" CC="zcc-powerpc64" RANLIB="zig ranlib" CFLAGS="-rtlib=none" && \
    make -j$(nproc) && \
    make install
make distclean && \
    ./configure --prefix=$(pwd)/build-all/riscv32 --target=riscv32-linux-musl --disable-static AR="zig ar" CC="zcc-riscv32" RANLIB="zig ranlib" CFLAGS="-rtlib=none" && \
    make -j$(nproc) && \
    make install
make distclean && \
    ./configure --prefix=$(pwd)/build-all/riscv64 --target=riscv64-linux-musl --disable-static AR="zig ar" CC="zcc-riscv64" RANLIB="zig ranlib" CFLAGS="-rtlib=none" && \
    make -j$(nproc) && \
    make install
make distclean && \
    ./configure --prefix=$(pwd)/build-all/s390x --target=s390x-linux-musl --disable-static AR="zig ar" CC="zcc-s390x" RANLIB="zig ranlib" CFLAGS="-rtlib=none" && \
    make -j$(nproc) && \
    make install
# x32 is currently broken due to relocation errors.
make distclean && \
    ./configure --prefix=$(pwd)/build-all/x32 --target=x86_64-linux-muslx32 --disable-static AR="zig ar" CC="zcc-x32" RANLIB="zig ranlib" CFLAGS="-rtlib=none" && \
    make -j$(nproc) && \
    make install
make distclean && \
    ./configure --prefix=$(pwd)/build-all/x86_64 --target=x86_64-linux-musl --disable-static AR="zig ar" CC="zcc-x86_64" RANLIB="zig ranlib" CFLAGS="-rtlib=none" && \
    make -j$(nproc) && \
    make install

Some of these didn't work last time I tried them, as you can see with the comments. These are bugs that should be fixed in zig or in clang. However, fixing those bugs is a separate issue that won't block the musl upgrade process. If any of them start working, then add the newly working target in tools/gen_stubs.zig.

You should now have libc.so built for multiple architectures:

andy@ark ~/src/musl ((v1.2.5))> find -name "libc.so"
./build-all/aarch64/lib/libc.so
./build-all/arm/lib/libc.so
./build-all/i386/lib/libc.so
./build-all/loongarch64/lib/libc.so
./build-all/mips/lib/libc.so
./build-all/mips64/lib/libc.so
./build-all/mipsn32/lib/libc.so
./build-all/powerpc/lib/libc.so
./build-all/powerpc64/lib/libc.so
./build-all/riscv32/lib/libc.so
./build-all/riscv64/lib/libc.so
./build-all/s390x/lib/libc.so
./build-all/x86_64/lib/libc.so

From the root of the zig source repository:

zig run tools/gen_stubs.zig -- ~/src/musl/build-all >lib/libc/musl/libc.S

Pay attention to the stderr output of this command. It may reveal an issue has occurred that will require you to massage the data by editing tools/gen_stubs.zig.

To verify that the stub libc.so matches the "real" musl libc.so first build a zig hello world that dynamically links musl:

zig build-exe -lc -dynamic -target x86_64-linux-musl hello.zig --verbose-link

Copy the path to the stub libc.so in the global cache from the link command logged and then run the following commands (with the proper paths):

objdump --dynamic-syms /home/ifreund/.cache/zig/o/94cd8ea1da001f912f2f7d259446424d/libc.so | sed -E -e 's/[0-9a-f]{16}//g' | sort > stubs.txt
objdump --dynamic-syms /path/to/musl/lib/libc.so | sed -E -e 's/[0-9a-f]{16}//g' | sort > musl.txt                                                
diff musl.txt stubs.txt

If all is well, the only output of the diff command should be the line containing the filename. For example:

1702c1702
< ../musl/lib/libc.so:     file format elf64-x86-64
---
> /home/ifreund/.cache/zig/o//libc.so:     file format elf64-x86-64

Linux

Install rsync and build-essential.

Clone the linux stable tree (note that it is different from the torvalds tree):

git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git

... and checkout the latest stable patch release. Stable kernel version is prominently displayed in the front-page of kernel.org. Run the following commands:

make SHELL=/bin/sh ARCH=arc        INSTALL_HDR_PATH=dest/arc        headers_install
make SHELL=/bin/sh ARCH=arm        INSTALL_HDR_PATH=dest/arm        headers_install
make SHELL=/bin/sh ARCH=arm64      INSTALL_HDR_PATH=dest/arm64      headers_install
make SHELL=/bin/sh ARCH=csky       INSTALL_HDR_PATH=dest/csky       headers_install
make SHELL=/bin/sh ARCH=hexagon    INSTALL_HDR_PATH=dest/hexagon    headers_install
make SHELL=/bin/sh ARCH=loongarch  INSTALL_HDR_PATH=dest/loongarch  headers_install
make SHELL=/bin/sh ARCH=m68k       INSTALL_HDR_PATH=dest/m68k       headers_install
make SHELL=/bin/sh ARCH=mips       INSTALL_HDR_PATH=dest/mips       headers_install
make SHELL=/bin/sh ARCH=powerpc    INSTALL_HDR_PATH=dest/powerpc    headers_install
make SHELL=/bin/sh ARCH=riscv      INSTALL_HDR_PATH=dest/riscv      headers_install
make SHELL=/bin/sh ARCH=s390       INSTALL_HDR_PATH=dest/s390       headers_install
make SHELL=/bin/sh ARCH=sparc      INSTALL_HDR_PATH=dest/sparc      headers_install
make SHELL=/bin/sh ARCH=x86        INSTALL_HDR_PATH=dest/x86        headers_install
make SHELL=/bin/sh ARCH=xtensa     INSTALL_HDR_PATH=dest/xtensa     headers_install

Eyeball the linux_targets variable inside tools/update-linux-headers.zig.

rm -rf lib/libc/include/*-linux-any
zig run tools/update-linux-headers.zig -- --search-path ~/Downloads/linux/dest --out lib/libc/include

FreeBSD

TODO

OpenBSD

TODO

NetBSD

TODO

Darwin

Follow the directions on the README of ziglang/fetch-them-macos-headers.

The "fetch" command has to be run natively on 6 different macOS computers:

  • x86_64-macos-13.x.x (Ventura)
  • x86_64-macos-14.x.x (Sonoma)
  • x86_64-macos-15.x.x (Sequoia)
  • aarch64-macos-13.x.x (Ventura)
  • aarch64-macos-14.x.x (Sonoma)
  • aarch64-macos-15.x.x (Sequoia)

Be sure to run all system updates (other than upgrading to the next major OS version) before running the "fetch" command.

After each fetch command, commit the changes to the repository. Finally, after all fetches have been completed, run the "generate" command, using the destination path of the Zig source repository.

MinGW-w64 (Windows)

The process-headers tool is not needed for these headers because MinGW-w64 headers are already multi-architecture.

git clone git://git.code.sf.net/p/mingw-w64/mingw-w64

Check out the latest master branch commit.

ZIGSRC=$HOME/Downloads/zig
rm -rf $ZIGSRC/lib/libc/include/any-windows-any /tmp/prefix
cd mingw-w64-headers
./configure --prefix=/tmp/prefix --with-default-msvcrt=ucrt
make install
mv /tmp/prefix/include $ZIGSRC/lib/libc/include/any-windows-any

Next, navigate back to your Zig checkout and update the CRT files:

zig build update-mingw -Dmingw-src=$HOME/Downloads/mingw-w64

wasi-libc

Fetch the wasi-libc repository:

git clone https://github.com/WebAssembly/wasi-libc

The src/wasi_libc.zig contains the code to build it from Zig. It should mirror the wasi-libc Makefile. The order of include directories is especially important.

So, start by inspecting the Makefile's history and update wasi_libc.zig accordingly. Also check for breaking changes in libc-bottom-half/crt.

Then, copy the content of wasi-libc's libc-bottom-half/headers/public/wasi into lib/zig/libc/include/wasm-wasi-musl/wasi.

Next, sync the content of the following directories into lib/libc/wasi:

  • emmalloc
  • libc-bottom-half
  • libc-top-half

The libc-bottom-half/headers/public subdirectory should ignored, as its content was already copied to lib/zig/libc/include/wasm-wasi-musl/wasi.

Finally, run the usual test suite and watch for regressions.

By the way, wasi-libc can be compiled with zig cc (make sure that Zig's wasm-wasi-musl/wasi directory is up to date beforehand):

env AR="zig ar" CC="zig cc -target wasm32-freestanding" make