2510rs,稳定裸函数
原文
稳定裸函数
Rust1.88.0稳定了用来定义裸函数的#[unsafe(naked)]属性和naked_asm!宏.
裸函数是标有#[unsafe(naked)]属性,主体由单个naked_asm!调用组成的.如:
//安全:遵守`64`位`SystemVABI`.
#[unsafe(naked)]
pub extern "sysv64" fn wrapping_add(a: u64, b: u64) -> u64 {//相当于`"a.wrapping_add(b)"`.core::arch::naked_asm!("lea rax, [rdi + rsi]","ret");
}
裸函数的特别之处,是因为手写汇编块定义了整个函数体.与非裸函数不同,编译器不会特殊处理参数或返回值.
此功能是替代使用global_asm!定义函数的,他更符合人体工程学.裸函数用在低级设置,如Rust的编译器内置,操作系统和嵌入式应用.
为什么要使用裸函数
但是等等,如果裸函数只是global_asm!的语法糖,为什么要首先添加它们?
为了了解好处,来使用global_asm!重写介绍中的wrapping_add示例:
//安全:`此模块`中定义了`"wrapping_add"`,并期望`64`位`SystemVABI`.
unsafe extern "sysv64" {safe fn wrapping_add(a: u64, b: u64) -> u64
}
core::arch::global_asm!(r#"//构成相关平台的指令的函数..section .text.wrapping_add,"ax",@progbits.p2align 2.globl wrapping_add.type wrapping_add,@function
wrapping_add:lea rax, [rdi + rsi]ret
.Ltmp0:.size wrapping_add, .Ltmp0wrapping_add"#
);
汇编块以定义函数期望指令(.section,.p2align等)开始和结束.这些指令是机械的,但它们在目标文件格式之间有所不同.
裸函数将自动发出正确的指令.
接着,wrapping_add名是硬编码的,不会参与Rust名混杂.这使得编写跨平台代码更难,因为不同目标有不同名混杂方案(如x86_64,macOS在符号前缀为_,但林操没有).
解混杂的符号也是全局可见的,这样外块可找到它,从而导致解析符号冲突.裸函数名确实参与了名混杂,且不会遇见这些问题.
此例未显示的另一个限制是,使用全局汇编定义的函数不能使用模板.特别是常模板与汇编结合使用很有用.
最后,只有一个定义可为(安全)文档和属性提供一致的位置,降低它们过时的风险.适当的安全注释对裸函数至关重要.
因为ABI(这里为sysv64),裸属性是不安全的,签名和实现必须一致.
extern"``自定义"函数
裸函数一般带extern"C"``调用约定.但一般,该调用约定是谎言.许多时候,裸函数不会实现Rust知道的ABI.相反,它们使用相关该函数的一些自定义的调用约定.
abi_custom功能添加了extern"``自定义"函数和块,这使可从编译器内置中,正确编写像以下示例一样的代码:
#![feature(abi_custom)]///使用`Arm`的非标准`ABI`对`两个数字`除法和模数`.
/// ```c
/// typedef struct { int quot; int rem; } idiv_return;
/// __value_in_regs idiv_return __aeabi_idivmod(int num, int denom);
/// ```
//安全:`汇编实现了期望的`ABI`,`"custom"`确保无法`直接调用`此函数. #[unsafe(naked)]
pub unsafe extern "custom" fn __aeabi_idivmod() {core::arch::naked_asm!("push {{r0, r1, r4, lr}}",//备份垃圾."bl {trampoline}",//为`a/b`调用`"extern"C"`函数."pop {{r1, r2}}","muls r2, r2, r0",//执行模."subs r1, r1, r2","pop {{r4, pc}}",//恢复`clobbers`,通过设置`'pc'`隐式返回.trampoline = sym crate::arm::__aeabi_idiv,);
}
使用自定义调用约定的结果是,不能使用Rust调用式调用此类函数;编译器根本不知道如何为此类调用生成正确的代码.
相反,当程序试调用extern"``自定义"函数时,编译器会出错,执行该函数只能使用内联汇编.
在线汇编上的配置
cfg_asm功能增加了,可用#[cfg(...)]或#[cfg_attr(...,...)]注解汇编块的各行的功能.配置汇编的特定部分帮助使汇编依赖目标,目标特征或特征标志等.如:
#![feature(cfg_asm)]
global_asm!(//...如果允许,请初化`SP`.这一般由`CPU`自身或`引导加载器`初化,但在重置目标时,某些`调试器`无法设置它,从而导致栈崩溃.#[cfg(feature = "setsp")]"ldr r0, =_stack_startmsr msp, r0",//...
)
此例来自,当前必须使用在每次使用#[cfg(...)]时复制整个汇编块的自定义宏的cortex-m.有了cfg_asm,将不再需要它.
