indexpost archiveatom feed syndication feed icon

A Neat Workflow for Testing Changes

2023-01-10

I found myself on a bit of a yak shaving exercise trying to get a C library linked during the build process of a Hare program. These are a few notes on testing out potentially breaking changes with little interuption to my development machine.

Scene Setting

Hare is able to link to C libraries. The way to do it is by making a forward declaration using the symbol and prototype for the library function and linking during the build process. For example, if the following is cmd/example/main.ha:

use strings;

@symbol("printf") fn printf(fmt: *const char, ...) int;

export fn main() void = {
      printf(strings::to_c("hola mundo\n"));
};

The build process is simply:

hare build -lc cmd/example

This works out of the box on both Debian and Alpine, where I followed the standard installation instructions. I ran into an issue doing the same on Fedora though. Trying the above results in several opaque error messages:

$ hare build -lc cmd/example
/usr/lib/gcc/x86_64-redhat-linux/12/crtbegin.o: in function `deregister_tm_clones':
(.text+0x7): relocation truncated to fit: R_X86_64_32S against `.tm_clone_table'
/usr/lib/gcc/x86_64-redhat-linux/12/crtbegin.o: in function `register_tm_clones':
(.text+0x38): relocation truncated to fit: R_X86_64_32S against `.tm_clone_table'
collect2: error: ld returned 1 exit status
Error: cc: exited with status 1
hare build: build failed

As I understand it the linker is attempting to use an (AMD 64 ABI) 32-bit relocation type, R_X86_64_32S while the provided address lies outside the space.

Knowing the what is a little interesting but I still had no idea why I was seeing this only under Fedora. It turns out GCC will happily tell you how it was configured when built though, so I decided to check the 3 environments:

Debian

# gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/10/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 10.2.1-6' --with-bugurl=file:///usr/share/doc/gcc-10/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-10 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-10-Km9U7s/gcc-10-10.2.1/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-10-Km9U7s/gcc-10-10.2.1/debian/tmp-gcn/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-mutex
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 10.2.1 20210110 (Debian 10.2.1-6)

Alpine

# gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-alpine-linux-musl/12.2.1/lto-wrapper
Target: x86_64-alpine-linux-musl
Configured with: /home/buildozer/aports/main/gcc/src/gcc-12-20220924/configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --build=x86_64-alpine-linux-musl --host=x86_64-alpine-linux-musl --target=x86_64-alpine-linux-musl --enable-checking=release --disable-fixed-point --disable-libstdcxx-pch --disable-multilib --disable-nls --disable-werror --disable-symvers --enable-__cxa_atexit --enable-default-pie --enable-default-ssp --enable-languages=c,c++,d,objc,go,fortran,ada --disable-libssp --disable-libsanitizer --enable-shared --enable-threads --enable-tls --with-bugurl=https://gitlab.alpinelinux.org/alpine/aports/-/issues --with-system-zlib --with-linker-hash-style=gnu --with-pkgversion='Alpine 12.2.1_git20220924-r4'
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 12.2.1 20220924 (Alpine 12.2.1_git20220924-r4) 

Fedora

$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/12/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-redhat-linux
Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,fortran,objc,obj-c++,ada,go,d,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --enable-libstdcxx-backtrace --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --with-isl=/builddir/build/BUILD/gcc-12.2.1-20220819/obj-x86_64-redhat-linux/isl-install --enable-offload-targets=nvptx-none --without-cuda-driver --enable-offload-defaulted --enable-gnu-indirect-function --enable-cet --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux --with-build-config=bootstrap-lto --enable-link-serialization=1
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 12.2.1 20220819 (Red Hat 12.2.1-2) (GCC)

Obviously I recommend using diff unless you're really good at I Spy. The thing that jumped out at me was the flag --enable-default-pie, where PIE is position independent executable a feature that allows for ASLR.

I tried in vain to coerce this feature in my build through the CFLAGS environment variable to no effect. I ended up deciding to try a custom build of GCC on Fedora with this flag to test the idea. How bad could it be?

Building GCC

This was almost uninteresting - there is good documentation and I have the other necessary configuration options used previously in order to make my GCC as close to stock as possible. I did a bit of copy-paste and then invoked make and threw 16 cores at the thing. It took over an hour for a clean build and only then did I realize my error.

By default GCC will prefix itself into /usr/local (which turns into /usr/local/bin), whereas Fedora builds it prefixed into /usr. If I wanted to install my new toolchain I would be overwriting my package installation! I am obviously not quite so confident, having never done this before. I had almost resigned myself to reconfiguring and rebuilding (and thus another ~80 minute wait). Thankfully I remembered systemd-nspawn and ephemeral containers. This is almost exactly the use case it was designed for!

By creating a container with a throw-away snapshot of my system I can install my new GCC build without risking altering my development machine negatively. All it requires is this:

$ sudo systemd-nspawn -x -D /

Btrfs ensures it launches efficiently and I'm free to poke and prod things to my hearts content, with the whole thing being torn down on exit.

Testing It Out

After running make install inside my container I was able to verify the change worked! The issue really was in the difference in GCC build-time configuration between distros. With this in mind I was also able to do more focused searching for the necessary incantation to make a custom GCC build unnecessary. It turns out I was close with my attempts at CFLAGS. Instead I only need to export LDFLAGS='-pie' and the linker will gracefully handle the relocations.

"YMMV"

During this process I tried soliciting ideas from smarter people on IRC. Unfortunately the response I got was that Hare doesn't support linking to shared objects and does not itself support PIE. While I had coerced my build into producing a running program it sounds as though this is only incidentally working, rather than working as designed. The solution is to use static libraries, which should be possible but entails some more work which I haven't delved into yet. Ho-hum.

I know I'm not the only person in the world linking shared objects successfully into Hare programs but it sounds like we may all find ourselves doing the right thing sooner or later as Hare proceeds in development. I imagine I'll keep plodding along to find how to link static libraries on Fedora and Debian. Both platforms cheerily pull in all sorts of shared objects by default, so far as I can tell, using static libraries is a bit off the beaten path.