rust-osdev/uefi-rs

Ensure ABI is correct for all targets

Closed this issue ยท 8 comments

All the UEFI functions are declared with the extern "C" calling convention, which matches the specification.

However, this raises an issue regarding the ABI on x64: extern "C" refers to MS's ABI when compiling for Windows-like targets, but refers to the System-V ABI when compiling for UNIX targets.

This is OK when building with the example target.json, since it tells LLVM to compile for the x86_64-pc-windows-gnu target, which makes it use the MS ABI.

This can cause an issue if the calling executable does not use the MS ABI: if an OS kernel were to be built as an ELF executable, and use this crate for calling UEFI runtime services, it would lead to undefined behavior, since the ELF ABI, gnuabi, is not the same as UEFI's ABI, msabi.

With C compilers we could make a distinction with __attribute__((msabi)) and __attribute__((gnuabi)), but Rust doesn't seem to make that kind of distinction.

Basically, we need to find a way to have functions declared as extern "C" when compiling for all targets except x86_64, where we'll have to use extern "win64" (because otherwise extern "C" becomes extern "sysv64", which is incorrect).

This might be part of a bigger issue with supporting 32-bit UEFI. I've tried building for IA32 and it looks like there are a lot of other issues with it (issues with linker / name mangling, lack of compiler-builtins support for some functions).

I think the easiest solution for now would be to simply use extern "win64" everywhere, and drop support for 32-bit.

Related (unfortunately stalled) discussion: rust-lang/rust#54527

gurry commented

Hi guys. We're writing a crate called efi which is very similar to uefi-rs and has almost the same goals. Just like you we're also planning an x86 build and have run into the problem of missing compiler-builtins for 32-bit. Do you happen to know the reasons for them not being implemented? Is it simply that 32-bit is low priority and nobody got to implementing them or there's a technical reason? I know you're not the maintainers of compiler_builtins, but I figured you might already have done some research on this topic :D.

We're building on Windows, by the way, and did not run into any tooling issues like @GabrielMajeri mentioned above fortunately. That's probably because Windows is a more natural home for UEFI development given its heritage.

cc @imor

Is it simply that 32-bit is low priority

Unless somebody comes and contributes implementations for the specific functions, there will be no 32-bit support. compiler-builtins is, like most projects, a community-driven effort.

there's a technical reason

In practice there are very few PCs with 32-bit-only UEFI implementations, and it's likely they will become even rarer as time goes on. 32-bit UEFI is also more likely to be buggy, due to less testing being done on it.

gurry commented

Thanks Gabriel ๐Ÿ‘. UEFI 32-bit being rare is very useful info for us. As we continue further work on our crate, we'll see if one of us can find time to implement missing builtins.

I personally need x86 support. As it stands, uefi-rs doesn't support anything that is not x86_64. This means no support for ARM, 32-bit x86, or Itanium. I have two questions:

  • Would simply adding an EFICALL ABI in upstream rust be sufficient? I was thinking that adding an ABI to the list of ABIs and have it select the right underlying ABI in adjust_abi would be enough for our purposes.
  • In the absence of the above, would a proc macro not work? Using syn's VisitorMut, we could make a very simple proc macro like this playground. This macro could be an attribute macro tacked on structs and functions alike, handling both in the same way, which makes for a fairly elegant solution.

@roblabla The idea of adding a new ABI to the compiler sounds good to me. The discussion over on rust-lang/rust stalled because the compiler team wanted to move ABI definitions to a JSON format, but that's beyond the scope of the issue. I'd say you could come up with a PR for `extern "uefi``.

As for the proc macro, sure, we could do that, but the initial idea was to get this integrated in the compiler to ensure consistency across different UEFI crates.

Submitted a PR to rust-lang/rust: rust-lang/rust#65809