How to properly cross compile without any runtime / dependencies?

I am having a lot of issues trying to properly cross compile Rust for UEFI platforms.

UEFI platforms work the same as x64 Windows platforms, except that there is no standard library, and the subsystem flag in the binary is set to a different value.

My workflow currently exists out of compiling a static library for Windows, and using the LLVM linker (lld-link) to change the subsystem and setting the flag manually.

I would like to have rustc do the extra linking step for me, but this is way harder than I initially hoped it would be. I have created a x86_64-uefi.json target specification, which is mostly based on x86_64-pc-windows-msvc, with a few differences.

  • I am trying to use lld-link as my linker.
  • I have is-like-msvc set to false (the default) as that makes rustc convinced it has to use link.exe., which doesn't exist on a Linux system.
  • I removed some options which don't particularly matter.

This does not compile at all, it complains about a lot of duplicate symbols regarding __rustc_debug which is apparently defined twice in the crate I am currently trying to compile.

lld-link: error: duplicate symbol: __rustc_debug_gdb_scripts_section__ in /home/jeroen/documents/code/krnl/target/x86_64-uefi/debug/deps/bootloader-ec23bf905be24708.170lmw1sqqfe80qo.rcgu.o andin /home/jeroen/documents/code/krnl/target/x86_64-uefi/debug/deps/bootloader-ec23bf905be24708.4ypvbwho0bu5tnww.rcgu.o

These are removed by compiling with --release, which lead rustc to complain about things like memcpy not working. I don't use that function, but I understand that core requires it, which I had to pull in as a dependency to get it to even compile ("panic_fmt" language item depends on core). An inclusion of rlibc fixed this.

The error now moved on to the following:

   = note: lld-link: warning: <root>: undefined symbol: mainCRTStartup
          lld-link: warning: libcore-05daa184684cea15.rlib(core-05daa184684cea15.core0-27b18eff33e66842f5421e6bbff77609.rs.rcgu.o): undefined symbol: __udivti3
          error: link failed

This is an error I cannot solve, and I have no idea how this is possible either. The mainCRTStartup symbol is presumably called by rustc to start the C runtime, which I don't want. The __udivti3 symbol is apparently used by the core crate, and unsure how to fix that.

TL;DR: I want to get rust to compile some code without any runtime or any dependencies. Just bare metal. How do I achieve this?

Getting Rust to build for bare-metal execution can be quite tricky. Maybe you could start from one of the existing Rust UEFI examples, study how it works, and adapt it to your needs? I am personally a big fan of https://github.com/GabrielMajeri/uefi-rs .

2 Likes

Thanks for the reply!

I am aware of some other UEFI examples. The code I am working with is about a year old now, and the one you linked was a very good resource for how to get it to manually link.

The issue is that I don't want to manually link. As I started off the OP, I already have a workflow on how to get it to manually link, but I want to be able to replace the runtime Rust provides with my own, and that won't work without setting a custom target, which is what I am trying to do.

I've looked a bit into how the compiler handles things internally and it seems to all be a little bit hacky, overriding config values based on other config values, and some hard coded assumptions about the runtime and execution environment in general, so I am quite wary I will have to end up doing a compiler fork.

I figure before doing such a massive undertaking I first want to be able to run on the bare metal, so I have something to build upwards from.

1 Like

@Binero I'm very much interested in supporting your use case. I think UEFI is a great target for Rust. Portability to non-mainstream platforms is not super convenient right now as you've found out. This would be a good subject to bring up with the newly-established portability working group. I'm still getting settled into my new role as WG leader, and in the future we'll have a better way to track issues such as yours. For now, I encourage you to contribute to the discussion in A vision for portability in Rust - #55 by ratmice - Rust Internals.

2 Likes

__udivti3 can be found in LLVM’s compiler-rt (the GCC equivalent is libgcc). According to GCC docs:

Runtime Function: unsigned int __udivsi3 (unsigned int a, unsigned int b)
Runtime Function: unsigned long __udivdi3 (unsigned long a, unsigned long b)
Runtime Function: unsigned long long __udivti3 (unsigned long long a, unsigned long long b)
These functions return the quotient of the unsigned division of a and b.

compiler-rt functions typically substitute for missing hardware functionality. I’m not sure why LLVM is generating calls to __udivti3 on x86-64, considering that the hardware div instruction should handle that out of the box; that may suggest something is misconfigured. Or maybe this is related to u128 support somehow, even though the documentation suggests u64 (aka long long)? Anyway, as a general rule, LLVM does expect -rt symbols to be available, so you should probably link to it.

On the other hand, I have no idea why you’re getting duplicate symbols. That’s almost certainly a bug, though I can’t guess whether it’s on Rust’s end or LLVM’s.

What causes Rust to not link against compiler-rt itself? I could probably add a manual flag to that in my target definition, but it doesn't seem like any of the built in ones do, so I am wondering what is the difference.

I don’t have experience with that part, but here are some relevant-seeming links: