-
Notifications
You must be signed in to change notification settings - Fork 12.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add x86_64-unknown-uefi target #56769
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
// This defines a base target-configuration for native UEFI systems. The UEFI specification has | ||
// quite detailed sections on the ABI of all the supported target architectures. In almost all | ||
// cases it simply follows what Microsoft Windows does. Hence, whenever in doubt, see the MSDN | ||
// documentation. | ||
// 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 | ||
// 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. | ||
|
||
use spec::{LinkArgs, LinkerFlavor, LldFlavor, PanicStrategy, TargetOptions}; | ||
use std::default::Default; | ||
|
||
pub fn opts() -> TargetOptions { | ||
let mut pre_link_args = LinkArgs::new(); | ||
|
||
pre_link_args.insert(LinkerFlavor::Lld(LldFlavor::Link), vec![ | ||
// Suppress the verbose logo and authorship debugging output, which would needlessly | ||
// clog any log files. | ||
"/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. | ||
"/NXCOMPAT".to_string(), | ||
|
||
// There is no runtime for UEFI targets, prevent them from being linked. UEFI targets | ||
// must be freestanding. | ||
"/nodefaultlib".to_string(), | ||
|
||
// Non-standard subsystems have no default entry-point in PE+ files. We have to define | ||
// one. "efi_main" seems to be a common choice amongst other implementations and the | ||
// spec. | ||
"/entry:efi_main".to_string(), | ||
|
||
// COFF images have a "Subsystem" field in their header, which defines what kind of | ||
// program it is. UEFI has 3 fields reserved, which are EFI_APPLICATION, | ||
// EFI_BOOT_SERVICE_DRIVER, and EFI_RUNTIME_DRIVER. We default to EFI_APPLICATION, | ||
// which is very likely the most common option. Individual projects can override this | ||
// with custom linker flags. | ||
// The subsystem-type only has minor effects on the application. It defines the memory | ||
// regions the application is loaded into (runtime-drivers need to be put into | ||
// reserved areas), as well as whether a return from the entry-point is treated as | ||
// exit (default for applications). | ||
"/subsystem:efi_application".to_string(), | ||
]); | ||
|
||
TargetOptions { | ||
dynamic_linking: false, | ||
executables: true, | ||
disable_redzone: true, | ||
exe_suffix: ".efi".to_string(), | ||
allows_weak_linkage: false, | ||
panic_strategy: PanicStrategy::Abort, | ||
singlethread: true, | ||
emit_debug_gdb_scripts: false, | ||
|
||
linker: Some("lld-link".to_string()), | ||
lld_flavor: LldFlavor::Link, | ||
pre_link_args, | ||
|
||
.. Default::default() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
// This defines the amd64 target for UEFI systems as described in the UEFI specification. See the | ||
// uefi-base module for generic UEFI options. On x86_64 systems (mostly called "x64" in the spec) | ||
// UEFI systems always run in long-mode, have the interrupt-controller pre-configured and force a | ||
// single-CPU execution. | ||
// The win64 ABI is used. It differs from the sysv64 ABI, so we must use a windows target with | ||
// LLVM. "x86_64-unknown-windows" is used to get the minimal subset of windows-specific features. | ||
|
||
use spec::{LinkerFlavor, LldFlavor, Target, TargetResult}; | ||
|
||
pub fn target() -> TargetResult { | ||
let mut base = super::uefi_base::opts(); | ||
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. | ||
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 | ||
// LLVM to expect code to reference any address in the address-space. The "large" code-model | ||
// 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: x64 UEFI uses the exact same msabi (aka. x64) calling convention that windows uses. There is not a single difference (and therefore “mostly” is not applicable). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure about that? UEFI explicitly disallows passing values bigger than 64bit by value (both as argument or return value; see There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Other than XMM/floating point registers, nothing larger than 64-bit is passed by-register in x64 msabi. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Well, in registers, yes, but is that true for the stack as well? Does msabi require you to put objects bigger than 64bit in caller allocated memory and pass it by reference? Because that is how I read the UEFI spec, even for arguments passed on the stack.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. Either way, this is proved by the fact that it wasn’t necessary for you to do anything special to implement UEFI’s ABI: it was already implemented! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, the MSDN docs are almost identical. Thanks a lot for the link! I certainly expecteded the ABIs to be compatible. I just wasn't sure whether the UEFI ABI is a subset (e.g., their coding-convention forbids returning anything but ints, which I expected to be rooted in a restricted ABI). I am confident using the msabi will just work, given that's what tianocore does as well. |
||
// 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 { | ||
llvm_target: "x86_64-unknown-windows".to_string(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems debatable to me. Are you sure this is correct (as opposed to just "x86_64"?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You have to trick llvm into creating COFF objects, rather than ELF objects. In theory this shouldn't matter. You should be able to use any available target compiler to get your object files, and then in the end tell your linker about the target format. However, this sadly does not work. The different object files have different capabilities, or specific annotations are only supported in one, not the other, etc. So I never succeeded in linking coff-objects into ELF-executables, or vice versa. Preferably, I would use Selecting the windows target might cause LLVM to optimize for something that will not apply to LLVM. However, I don't think anyone can apply any assumptions on a generic target like the one I picked, because it has to be backwards-compatible to all the existing windows versions. So I believe this will be safe. I contacted colleagues of mine here at Red Hat, maybe they have a suggestion. Sadly, it is all gcc, not LLVM ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense then. It appears that |
||
target_endian: "little".to_string(), | ||
target_pointer_width: "64".to_string(), | ||
target_c_int_width: "32".to_string(), | ||
data_layout: "e-m:w-i64:64-f80:128-n8:16:32:64-S128".to_string(), | ||
target_os: "uefi".to_string(), | ||
target_env: "".to_string(), | ||
target_vendor: "unknown".to_string(), | ||
arch: "x86_64".to_string(), | ||
linker_flavor: LinkerFlavor::Lld(LldFlavor::Link), | ||
|
||
options: base, | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this work for application writers?
Are they responsible for defining an
efi_main
symbol? Perhaps not the best approach then, as we already have#[start]
and expect function annotated with that attribute to get invoked at start-up even in#[no_std]
contexts.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some crate in your application must provide the
efi_main
symbol. If we portstd
, we could make the#[start]
logic do this. However, I don't thinkcore
should provide wrappers around the entry-point. We explicitly want a way to link applications with full control over the entry-point. With this target, you need:pub extern fn efi_main(_h: efi::Handle, st: *mut efi::SystemTable) -> efi::Status
..to exist somewhere in your application.
I expect applications to pull in helper crates that wrap the UEFI API with a safe rust interface. Those crates can then provide the entry-point and retain the system-table somehow. However, note that due to
ExitBootServices()
, it is non-trivial to safely wrap all of the UEFI API (at least it requires some thought). By blockingExitBootServices()
and the VM-remapping, it is at least easier to imagine a safe wrapper.We already have a public crate that simply provides the symbols from the UEFI spec as rust symbols (really just copying the definitions, not wrapper code in there) at https://github.com/r-util/r-efi
Our safe wrappers are still being worked on.