diff --git a/src/attributes.md b/src/attributes.md index e5ca10879..d6cbed613 100644 --- a/src/attributes.md +++ b/src/attributes.md @@ -44,6 +44,7 @@ The following attributes are unsafe: * [`export_name`] * [`link_section`] +* [`naked`] * [`no_mangle`] r[attributes.kind] @@ -290,6 +291,7 @@ The following is an index of all built-in attributes. - Code generation - [`inline`] --- Hint to inline code. - [`cold`] --- Hint that a function is unlikely to be called. + - [`naked`] - Prevent the compiler from emitting a function prologue. - [`no_builtins`] --- Disables use of certain built-in functions. - [`target_feature`] --- Configure platform-specific code generation. - [`track_caller`] --- Pass the parent call location to `std::panic::Location::caller()`. @@ -360,6 +362,7 @@ The following is an index of all built-in attributes. [`macro_export`]: macros-by-example.md#path-based-scope [`macro_use`]: macros-by-example.md#the-macro_use-attribute [`must_use`]: attributes/diagnostics.md#the-must_use-attribute +[`naked`]: attributes/codegen.md#the-naked-attribute [`no_builtins`]: attributes/codegen.md#the-no_builtins-attribute [`no_implicit_prelude`]: names/preludes.md#the-no_implicit_prelude-attribute [`no_link`]: items/extern-crates.md#the-no_link-attribute diff --git a/src/attributes/codegen.md b/src/attributes/codegen.md index 4f9be65bc..025b7cc3e 100644 --- a/src/attributes/codegen.md +++ b/src/attributes/codegen.md @@ -46,6 +46,42 @@ r[attributes.codegen.cold] The *`cold` [attribute]* suggests that the attributed function is unlikely to be called. +r[attributes.codegen.naked] +## The `naked` attribute + +r[attributes.codegen.naked.intro] +The *`naked` [attribute]* prevents the compiler from emitting a function prologue and epilogue for the attributed function. + +r[attributes.codegen.naked.body] +The [function body] must consist of exactly one [`naked_asm!`] macro invocation. + +r[attributes.codegen.naked.prologue-epilogue] +No function prologue or epilogue is generated for the attributed function. The assembly code in the `naked_asm!` block constitutes the full body of a naked function. + +r[attributes.codegen.naked.unsafe-attribute] +The `naked` attribute is an [unsafe attribute]. Annotating a function with `#[unsafe(naked)]` comes with the safety obligation that the body must respect the function's calling convention, uphold its signature, and either return or diverge (i.e., not fall through past the end of the assembly code). + +r[attributes.codegen.naked.call-stack] +The assembly code may assume that the call stack and register state are valid on entry as per the signature and calling convention of the function. + +r[attributes.codegen.naked.no-duplication] +The assembly code may not be duplicated by the compiler except when monomorphizing polymorphic functions. + +> [!NOTE] +> Guaranteeing when the assembly code may or may not be duplicated is important for naked functions that define symbols. + +r[attributes.codegen.naked.unused-variables] +The [`unused_variables`] lint is suppressed within naked functions. + +r[attributes.codegen.naked.inline] +The [`inline`](#the-inline-attribute) attribute cannot by applied to a naked function. + +r[attributes.codegen.naked.track_caller] +The [`track_caller`](#the-track_caller-attribute) attribute cannot be applied to a naked function. + +r[attributes.codegen.naked.testing] +The [testing attributes](testing.md) cannot be applied to a naked function. + r[attributes.codegen.no_builtins] ## The `no_builtins` attribute @@ -497,15 +533,23 @@ trait object whose methods are attributed. [`-C target-cpu`]: ../../rustc/codegen-options/index.html#target-cpu [`-C target-feature`]: ../../rustc/codegen-options/index.html#target-feature +[`inline`]: #the-inline-attribute [`is_x86_feature_detected`]: ../../std/arch/macro.is_x86_feature_detected.html [`is_aarch64_feature_detected`]: ../../std/arch/macro.is_aarch64_feature_detected.html +[`naked_asm!`]: ../inline-assembly.md [`target_feature` conditional compilation option]: ../conditional-compilation.md#target_feature +[`track_caller`]: #the-track-caller-attribute +[`unused_variables`]: ../../rustc/lints/listing/warn-by-default.html#unused-variables [attribute]: ../attributes.md [attributes]: ../attributes.md +[FFI-safe]: ../../rustc/lints/listing/warn-by-default.html#improper-ctypes-definitions +[function body]: ../items/functions.md#function-body [functions]: ../items/functions.md +[rules for inline assembly]: ../inline-assembly.md#rules-for-inline-assembly [target architecture]: ../conditional-compilation.md#target_arch [trait]: ../items/traits.md [undefined behavior]: ../behavior-considered-undefined.md +[unsafe attribute]: ../attributes.md#r-attributes.safety [rust-abi]: ../items/external-blocks.md#abi [`Location`]: core::panic::Location diff --git a/src/inline-assembly.md b/src/inline-assembly.md index 094d2f43e..0a6e21fe8 100644 --- a/src/inline-assembly.md +++ b/src/inline-assembly.md @@ -2,10 +2,11 @@ r[asm] # Inline assembly r[asm.intro] -Support for inline assembly is provided via the [`asm!`] and [`global_asm!`] macros. +Support for inline assembly is provided via the [`asm!`], [`naked_asm!`], and [`global_asm!`] macros. It can be used to embed handwritten assembly in the assembly output generated by the compiler. [`asm!`]: core::arch::asm +[`naked_asm!`]: core::arch::naked_asm [`global_asm!`]: core::arch::global_asm r[asm.stable-targets] @@ -58,6 +59,7 @@ option := "pure" / "nomem" / "readonly" / "preserves_flags" / "noreturn" / "nost options := "options(" option *("," option) [","] ")" operand := reg_operand / clobber_abi / options asm := "asm!(" format_string *("," format_string) *("," operand) [","] ")" +naked_asm := "naked_asm!(" format_string *("," format_string) *("," operand) [","] ")" global_asm := "global_asm!(" format_string *("," format_string) *("," operand) [","] ")" ``` @@ -65,7 +67,7 @@ r[asm.scope] ## Scope r[asm.scope.intro] -Inline assembly can be used in one of two ways. +Inline assembly can be used in one of three ways. r[asm.scope.asm] With the `asm!` macro, the assembly code is emitted in a function scope and integrated into the compiler-generated assembly code of a function. @@ -78,6 +80,18 @@ unsafe { core::arch::asm!("/* {} */", in(reg) 0); } # } ``` +r[asm.scope.naked_asm] +With the `naked_asm!` macro, the assembly code is emitted in a function scope and constitutes the full assembly code of a function. The `naked_asm!` macro is only allowed in [naked functions](attributes/codegen.md#the-naked-attribute). + +```rust +# #[cfg(target_arch = "x86_64")] { +# #[unsafe(naked)] +# extern "C" fn wrapper() { +core::arch::naked_asm!("/* {} */", const 0); +# } +# } +``` + r[asm.scope.global_asm] With the `global_asm!` macro, the assembly code is emitted in a global scope, outside a function. This can be used to hand-write entire functions using assembly code, and generally provides much more freedom to use arbitrary registers and assembler directives. @@ -384,8 +398,11 @@ assert_eq!(y, 1); # } ``` +r[asm.operand-type.naked_asm-restriction] +Because `naked_asm!` defines a whole function body and the compiler cannot emit any additional code to handle operands, it can only use `sym` and `const` operands. + r[asm.operand-type.global_asm-restriction] -Since `global_asm!` exists outside a function, it can only use `sym` and `const` operands. +Because `global_asm!` exists outside a function, it can only use `sym` and `const` operands. ```rust,compile_fail # fn main() {} @@ -1206,9 +1223,11 @@ unsafe { core::arch::asm!("mov {:e}, 1", out(reg) z, options(noreturn)); } # #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch"); ``` +r[asm.options.naked_asm-restriction] +`naked_asm!` only supports the `att_syntax` and `raw` options. The remaining options are not meaningful because the inline assembly defines the whole function body. + r[asm.options.global_asm-restriction] -`global_asm!` only supports the `att_syntax` and `raw` options. -The remaining options are not meaningful for global-scope inline assembly +`global_asm!` only supports the `att_syntax` and `raw` options. The remaining options are not meaningful for global-scope inline assembly. ```rust,compile_fail # fn main() {} @@ -1242,7 +1261,6 @@ r[asm.rules.unwind] r[asm.rules.mem-same-as-ffi] - The set of memory locations that assembly code is allowed to read and write are the same as those allowed for an FFI function. - - Refer to the unsafe code guidelines for the exact rules. - If the `readonly` option is set, then only memory reads are allowed. - If the `nomem` option is set then no reads or writes to memory are allowed. - These rules do not apply to memory which is private to the assembly code, such as stack space allocated within it. @@ -1362,6 +1380,106 @@ r[asm.rules.preserves_flags] > [!NOTE] > As a general rule, the flags covered by `preserves_flags` are those which are *not* preserved when performing a function call. +r[asm.naked-rules] +## Rules for naked inline assembly + +r[asm.naked-rules.intro] +To avoid undefined behavior, these rules must be followed when using function-scope inline assembly in naked functions (`naked_asm!`): + +r[asm.naked-rules.reg-not-input] +- Any registers not used for function inputs according to the calling convention and function signature will contain an undefined value on entry to the `naked_asm!` block. + - An "undefined value" in the context of inline assembly means that the register can (non-deterministically) have any one of the possible values allowed by the architecture. Notably it is not the same as an LLVM `undef` which can have a different value every time you read it (since such a concept does not exist in assembly code). + +r[asm.naked-rules.reg-not-output] +- Any callee-saved registers must have the same value upon return as they had on entry. + - Caller-saved registers may be used freely, even if they are not used for the return value. + +r[asm.naked-rules.noreturn] +- Behavior is undefined if execution falls through past the end of the assembly code. + - Every path through the assembly code is expected to terminate with a return instruction or to diverge. + +r[asm.naked-rules.mem-same-as-ffi] +- The set of memory locations that assembly code is allowed to read and write are the same as those allowed for an FFI function. + +r[asm.naked-rules.black-box] +- The compiler cannot assume that the instructions in the `naked_asm!` block are the ones that will actually be executed. + - This effectively means that the compiler must treat the `naked_asm!` as a black box and only take the interface specification into account, not the instructions themselves. + - Runtime code patching is allowed, via target-specific mechanisms. + +r[asm.naked-rules.unwind] +- Unwinding out of a `naked_asm!` block is allowed. + - For correct behavior, the appropriate assembler directives that emit unwinding metadata must be used. + +```rust +# #[cfg(target_arch = "x86_64")] { +#[unsafe(naked)] +extern "sysv64-unwind" fn unwinding_naked() { + core::arch::naked_asm!( + // "CFI" here stands for "call frame information". + ".cfi_startproc", + // The CFA (canonical frame address) is the value of `rsp` + // before the `call`, i.e. before the return address, `rip`, + // was pushed to `rsp`, so it's eight bytes higher in memory + // than `rsp` upon function entry (after `rip` has been + // pushed). + // + // This is the default, so we don't have to write it. + //".cfi_def_cfa rsp, 8", + // + // The traditional thing to do is to preserve the base + // pointer, so we'll do that. + "push rbp", + // Since we've now extended the stack downward by 8 bytes in + // memory, we need to adjust the offset to the CFA from `rsp` + // by another 8 bytes. + ".cfi_adjust_cfa_offset 8", + // We also then annotate where we've stored the caller's value + // of `rbp`, relative to the CFA, so that when unwinding into + // the caller we can find it, in case we need it to calculate + // the caller's CFA relative to it. + // + // Here, we've stored the caller's `rbp` starting 16 bytes + // below the CFA. I.e., starting from the CFA, there's first + // the `rip` (which starts 8 bytes below the CFA), then + // there's the caller's `rbp` that we just pushed. + ".cfi_offset rbp, -16", + // As is traditional, we set the base pointer to the value of + // the stack pointer. This way, the base pointer stays the + // same throughout the function body. + "mov rbp, rsp", + // We can now track the offset to the CFA from the base + // pointer. This means we don't need to make any further + // adjustments until the end, as we don't change `rbp`. + ".cfi_def_cfa_register rbp", + // We can now call a function that may panic. + "call {f}", + // Upon return, we restore `rbp` in preparation for returning + // ourselves. + "pop rbp", + // Now that we've restored `rbp`, we must specify the offset + // to the CFA again in terms of `rsp`. + ".cfi_def_cfa rsp, 8", + // Now we can return. + "ret", + ".cfi_endproc", + f = sym may_panic, + ) +} + +extern "sysv64-unwind" fn may_panic() { + panic!("unwind!"); +} +# } +``` + +> [!NOTE] +> +> For more information on the `cfi` assembler directives above, see these resources: +> +> - [Using `as` - CFI directives](https://sourceware.org/binutils/docs/as/CFI-directives.html) +> - [DWARF Debugging Information Format Version 5](https://dwarfstd.org/doc/DWARF5.pdf) +> - [ImperialViolet - CFI directives in assembly files](https://www.imperialviolet.org/2017/01/18/cfi.html) + r[asm.validity] ### Correctness and Validity