Skip to content
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

Merged
merged 1 commit into from
Dec 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/librustc_target/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ mod linux_musl_base;
mod openbsd_base;
mod netbsd_base;
mod solaris_base;
mod uefi_base;
mod windows_base;
mod windows_msvc_base;
mod thumb_base;
Expand Down Expand Up @@ -419,6 +420,8 @@ supported_targets! {
("aarch64-unknown-none", aarch64_unknown_none),

("x86_64-fortanix-unknown-sgx", x86_64_fortanix_unknown_sgx),

("x86_64-unknown-uefi", x86_64_unknown_uefi),
}

/// Everything `rustc` knows about how to compile for a specific target.
Expand Down
74 changes: 74 additions & 0 deletions src/librustc_target/spec/uefi_base.rs
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(),
Copy link
Member

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.

Copy link
Contributor Author

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 port std, we could make the #[start] logic do this. However, I don't think core 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 blocking ExitBootServices() 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.


// 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()
}
}
58 changes: 58 additions & 0 deletions src/librustc_target/spec/x86_64_unknown_uefi.rs
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
Copy link
Member

Choose a reason for hiding this comment

The 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).

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 UEFI-2.7 2.3.4.2). Isn't this allowed in msabi?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UEFI explicitly disallows passing values bigger than 64bit by value (both as argument or return value; see UEFI-2.7 2.3.4.2). Isn't this allowed in msabi?

Other than XMM/floating point registers, nothing larger than 64-bit is passed by-register in x64 msabi.

Copy link
Contributor Author

@dvdhrm dvdhrm Dec 17, 2018

Choose a reason for hiding this comment

The 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.

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.

The caller passes arrays and strings via a pointer to memory allocated by the
caller. The caller passes structures and unions of size 8, 16, 32, or 64 bits as if
they were integers of the same size. The caller is not allowed to pass structures
and unions of other than these sizes and must pass these unions and structures
via a pointer.

Copy link
Member

Choose a reason for hiding this comment

The 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!

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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(),
Copy link
Member

Choose a reason for hiding this comment

The 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"?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 x86_64-unknown-coff, but that does not exit. If anyone has better suggestions, I would gladly switch over.

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 ;)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense then. It appears that llc has no way to specify format of the produced object file either, which most likely makes the target specification like this the best approach.

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,
})
}