From 15e4bd3bf460a4a943465534132a9d3eda5eb798 Mon Sep 17 00:00:00 2001 From: David Rheinsberg Date: Wed, 13 Feb 2019 11:50:09 +0100 Subject: [PATCH] target/uefi: clarify documentation This clarifies why FP-units are disabled on UEFI targets, as well as why we must opt into the NXCOMPAT feature. I did find some time to investigate why GRUB and friends disable FP on UEFI. The specification explicitly allows using MMX/SSE/AVX, but as it turns out it does not mandate enabling the instruction sets explicitly. Hence, any use of these instructions will trigger CPU exceptions, unless an application explicitly enables them (which is not an option, as these are global flags that better be controlled by the kernel/firmware). Furthermore, UEFI systems are allowed to mark any non-code page as non-executable. Hence, we must make sure to never place code on the stack or heap. So we better pass /NXCOMPAT to the linker for it to complain if it ever places code in non-code pages. Lastly, this fixes some typos in related comments. --- src/librustc_target/spec/uefi_base.rs | 7 ++++-- .../spec/x86_64_unknown_uefi.rs | 22 ++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/librustc_target/spec/uefi_base.rs b/src/librustc_target/spec/uefi_base.rs index 5078d500679d1..631966c09a498 100644 --- a/src/librustc_target/spec/uefi_base.rs +++ b/src/librustc_target/spec/uefi_base.rs @@ -5,7 +5,7 @@ // UEFI uses COFF/PE32+ format for binaries. All binaries must be statically linked. No dynamic // linker is supported. As native to COFF, binaries are position-dependent, but will be relocated // by the loader if the pre-chosen memory location is already in use. -// UEFI forbids running code on anything but the boot-CPU. Not interrupts are allowed other than +// UEFI forbids running code on anything but the boot-CPU. No interrupts are allowed other than // the timer-interrupt. Device-drivers are required to use polling-based models. Furthermore, all // code runs in the same environment, no process separation is supported. @@ -21,7 +21,10 @@ pub fn opts() -> TargetOptions { "/NOLOGO".to_string(), // UEFI is fully compatible to non-executable data pages. Tell the compiler that - // non-code sections can be marked as non-executable, including stack pages. + // non-code sections can be marked as non-executable, including stack pages. In fact, + // firmware might enforce this, so we better let the linker know about this, so it + // will fail if the compiler ever tries placing code on the stack (e.g., trampoline + // constructs and alike). "/NXCOMPAT".to_string(), // There is no runtime for UEFI targets, prevent them from being linked. UEFI targets diff --git a/src/librustc_target/spec/x86_64_unknown_uefi.rs b/src/librustc_target/spec/x86_64_unknown_uefi.rs index 9ac17a1693fb5..443479f55f04a 100644 --- a/src/librustc_target/spec/x86_64_unknown_uefi.rs +++ b/src/librustc_target/spec/x86_64_unknown_uefi.rs @@ -12,13 +12,15 @@ pub fn target() -> TargetResult { base.cpu = "x86-64".to_string(); base.max_atomic_width = Some(64); - // We disable MMX and SSE for now. UEFI does not prevent these from being used, but there have - // been reports to GRUB that some firmware does not initialize the FP exception handlers - // properly. Therefore, using FP coprocessors will end you up at random memory locations when - // you throw FP exceptions. - // To be safe, we disable them for now and force soft-float. This can be revisited when we - // have more test coverage. Disabling FP served GRUB well so far, so it should be good for us - // as well. + // We disable MMX and SSE for now, even though UEFI allows using them. Problem is, you have to + // enable these CPU features explicitly before their first use, otherwise their instructions + // will trigger an exception. Rust does not inject any code that enables AVX/MMX/SSE + // instruction sets, so this must be done by the firmware. However, existing firmware is known + // to leave these uninitialized, thus triggering exceptions if we make use of them. Which is + // why we avoid them and instead use soft-floats. This is also what GRUB and friends did so + // far. + // If you initialize FP units yourself, you can override these flags with custom linker + // arguments, thus giving you access to full MMX/SSE acceleration. base.features = "-mmx,-sse,+soft-float".to_string(); // UEFI systems run without a host OS, hence we cannot assume any code locality. We must tell @@ -26,9 +28,9 @@ pub fn target() -> TargetResult { // places no locality-restrictions, so it fits well here. base.code_model = Some("large".to_string()); - // UEFI mostly mirrors the calling-conventions used on windows. In case of x86-64 this means - // small structs will be returned as int. This shouldn't matter much, since the restrictions - // placed by the UEFI specifications forbid any ABI to return structures. + // UEFI mirrors the calling-conventions used on windows. In case of x86-64 this means small + // structs will be returned as int. This shouldn't matter much, since the restrictions placed + // by the UEFI specifications forbid any ABI to return structures. base.abi_return_struct_as_int = true; Ok(Target {