How to define a new target for building against L4Re microkernel

At work we are building systems based on the L4Re microkernel.

It is possible to build applications for that system using regular C files like this hello world example.

The build system is quite complex, but at the end the Code is simply build against a special version of uclibc, which uses a special c-backend which translates regular system calls to appropriate IPC messages, which is the only way to call to the microkernel.

Now I would really like to build applications for that system using Rust. In theory this should probably simply work by something like rustc --target x86_64-l4re-uclibc hello.rs.

However for this to work I first need to define that target and build the stdlib for it.

I already found the documentation for target specification files on rust-cross. However I am not sure if this is really the right way and what exactly I need to specify in my case.

I also found target definitions in the rust source, which look more promising. Maybe adapting these might be the better way.

Can you give me a hint on what I should do next in order to define an appropriate target?


The above mentioned hello world example is internally build this way:

$ make -n hello
[...]
echo -e "  [hello] ... Compiling main.o"
gcc -c -MD -MP -MF ./.main.o.d                -DSYSTEM_amd64_K8_l4f -DARCH_amd64 -DCPUTYPE_K8 -DL4API_l4f  -D_GNU_SOURCE    -I/l4/obj/l4/include/contrib/libstdc++-v3 -I/l4/obj/l4/include/amd64/l4f -I/l4/obj/l4/include/amd64 -I/l4/obj/l4/include -isystem /l4/obj/l4/include/sys/amd64/l4f -isystem /l4/obj/l4/include/sys/l4f -isystem /l4/obj/l4/include/sys/amd64 -isystem /l4/obj/l4/include/sys  -nostdinc -I/l4/obj/l4/include/contrib/libstdc++-v3 -I/l4/obj/l4/include/uclibc  -I/path/to/l4re/include/uclibc -isystem /usr/lib/gcc/x86_64-linux-gnu/4.9/include -isystem /usr/lib/gcc/x86_64-linux-gnu/4.9/include-fixed -fno-omit-frame-pointer        -g -O2 -fno-strict-aliasing -Wextra -Wall -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations        -fno-common -std=gnu99 -m64 -mno-red-zone -march=k8  -fstack-protector  /l4/src/l4/pkg/hello/server/src/main.c -o main.o
echo -e "  [hello] ==> Linking hello"
LD_PRELOAD=libgendep.so LD_LIBRARY_PATH=/l4/obj/l4/tool/gendep/64:/l4/obj/l4/tool/gendep/32 GENDEP_TARGET=hello GENDEP_BINARY=ld  GENDEP_BINARY_ALT1=ld ld -m elf_x86_64 -o hello /l4/obj/l4/lib/amd64_K8/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.9/crtbeginT.o /l4/obj/l4/lib/amd64_K8/crt1.o \
            --whole-archive  main.o --no-whole-archive \
            -Bstatic --defsym=__executable_start=0x01000000 -m elf_x86_64 -z max-page-size=0x1000 -z common-page-size=0x1000  --defsym __L4_KIP_ADDR__=0x6ffff000 --defsym __L4_STACK_ADDR__=0x70000000 -L/l4/obj/l4/lib/amd64_K8/l4f -L/l4/obj/l4/lib/amd64_K8 -L/l4/obj/l4/lib -T/l4/obj/l4/lib/amd64_K8/main_stat.ld --start-group  -static -nostdlib  /l4/obj/l4/lib/amd64_K8/l4f/libgcc.a /usr/lib/gcc/x86_64-linux-gnu/4.9/libgcc_eh.a  -l4re-util -lc_be_l4refile -lsupc++ -luc_c -luc_c_nonshared.p -lssp_nonshared.p -lc_be_l4re -l4re -ll4util -ll4sys -ll4re-vfs.o --end-group --warn-common --defsym=__executable_start=0x01000000 -gc-sections /usr/lib/gcc/x86_64-linux-gnu/4.9/crtend.o /l4/obj/l4/lib/amd64_K8/crtn.o
echo -e '\033[34;1m''  [hello] ==> hello built''\033[0m'

This creates a statically linked ELF binary with a custom programm header (using the -T linker script). It also links against modified crt files, the already mentioned c-backend layer (*_be_*), and the l4re libraries, which are the interface to the microkernel.

At the end no big magic. Shouldn't it be possible to tell rust to build the same way?

You can use Xargo and a target specification file to build std on the fly. This is faster and easier than using rustbuild (Rust build system).

In principle, you'll have to do something like this:

$ cargo new --bin hello && cd $_

$ edit Xargo.toml && cat $_
[dependencies.std]
features = ["panic_abort"]  # see src/libstd/Cargo.toml in rust-lang/rust

$ xargo build --target x86_64-l4re-uclibc

You can base the target specification file on this one.

This creates a statically linked ELF binary with a custom programm header (using the -T linker script). It also links against modified crt files, the already mentioned c-backend layer (be), and the l4re libraries, which are the interface to the microkernel.

All this can be done using the pre-link-args / post-link-args fields in the target specification file.

Do note that std depends on the libc crate which doesn't support uclibc. You'll have to add support to it. Also note that, even if you don't add uclibc support to libc, it will still compile for uclibc targets but the definitions (like O_CLOEXEC) will be wrong and that could lead to binaries that segfault at runtime.

1 Like

Hello japaric,

Thanks a lot for your help.
I did not work with xargo before.
I set up a suitable docker environment and gave it a try.

You can run docker run -it --rm michas2/l4-rust to check it out or have a look at the latest build for Dockerfile and output.

cargo build --target x86_64-unknown-linux-musl works fine, but of course does not use xargo.

xargo build --target x86_64-unknown-linux-musl fails with "could not find native static library 'c'". What exactly is the problem here?

xargo build --target x86_64-l4re-uclibc with your original target specification builds fine, but creates a dynamically linked binary. What is the correct way to produce a statically linked binary?

If I understood correctly, dynamic_linking should default to false, and even the pre-link-args explicitly enable static linking. Why does it still produce a dynamically linked binary? How can I see the actual linker command used to produce that binary?

At the end the idea is to produce a binary, which can be run with the local run command. The c example can be run with run /l4/obj/l4/bin/amd64_K8/l4f/hello (ctrl-a x will quit qemu). The aim is to be able to run target/x86_64-l4re-uclibc/debug/hello.

xargo build --target x86_64-unknown-linux-musl fails with "could not find native static library 'c'". What exactly is the problem here?

The musl target is a bit special in the sense in that it expects to host system to provide (MUSL) libc.a and libunwind.a. I'm not quite sure how one is supposed to pass the path to those when directly compiling std using Cargo/Xargo. The Rust build system has a musl-root option for this so perhaps the libc crate expects some env variable to be set. libc build script should give more information about this.

What is the correct way to produce a statically linked binary?

The steed target specification file does produce statically linked pure Rust binaries but in your case probably some crate, like libc, is asking some C library to be linked dynamically. Look for #[link] attributes in the crate or println statements that print cargo:...link.... Those should use static to force static linking.

How can I see the actual linker command used to produce that binary?

Use -Z print-link-args as in xargo rustc (..) -- -Z print-link-args to see the linker invocation.