Rust的各种花式汇编操作
使用nightly rust的asm!宏
Rust的内联汇编基础语法如下(需要启用#!(feature(asm))
)
asm!(
assembly template
: 输出操作数
: 输入操作数
: Clobber
: 选项
);
assembly template
assembly template是唯一需要的参数并且必须是原始字符串例如asm!("nop")
,该指令不需要任何参数,因此省略了
因为Rust内联汇编还处于Unstable,因此我们需要使用#![feature(asm)]
放在lib.rs
文件对开头部分或main.rs
中,例如
// in src/main.rs
#![feature(asm)]
fn main(){
}
使用时需要添加unsafe块或函数需要添加unsafe关键字,例如
pub unsafe fn nop(){
asm!(
"xor %eax, %eax"
:
:
: "{eax}"
:
);
}
或者
pub fn nop(){
unsafe{
asm!(
"xor %eax, %eax"
:
:
: "{eax}"
:
);
}
}
在调用unsafe函数时需要使用unsafe块
fn main(){
unsafe{
nop();
}
}
如果一个函数只有一个内联汇编操作的话,建议将该函数声明为unsafe的,为了提高执行效率可以在函数上添加#[inline]
宏(与C语言的#inline
宏类似),这在编译时起到了优化的效果
#[inline]
pub unsafe fn xor(){
// 有空格也没关系
asm!("xor %eax, %eax" ::: "{eax}");
}
在调用该函数时我们可以添加#[cfg(target_arch = "x86_64")]
来指定要编译的目标系统的架构,
模板字符串支持使用$后跟一个数字的参数替换例如$0
,以指示由约束字符串指定的给定寄存器/内存位置的替换。${NUM:MODIFIER}也可以使用,其中MODIFIER是如何打印操作数的特定于目标的注释
字符 可 以 在 模 板 中 使 用 ‘ 可以在模板中使用` 可以在模板中使用‘$`。要在输出中包含其他特殊字符,可以使用通常的“\XX”转义符,就像在其他字符串中一样。
约束
约束列表是逗号分隔的字符串,每个元素包含一个或多个约束代码,例如:“约束1”(表达式1),“约束2”(表达式2)...
,
对于约束列表中的每个元素,将选择一个适当的寄存器或内存操作数,并且将对$0列表中的第一个约束,$1第二个等将使其可用于组件模板字符串扩展。
输出约束
输出约束由“=”前缀(例如“=r”)指定。这表示程序集将写入此操作数,然后操作数将作为asm表达式的返回值提供。输出约束不会消耗调用指令中的参数。
LLVM输出约束的原文如下
通常,在读取所有输入之前,预计没有输出位置被汇编表达式写入。因此,LLVM可以将相同的寄存器分配给输出和输入。如果这不安全(例如,如果程序集包含两条指令,其中第一条写入一个输出,第二条读取输入并写入第二条输出),则必须使用“&”修饰符(例如“=&r”)来指定输出是“早期破坏”输出。将输出标记为“early-clobber”可确保LLVM不会对任何输入(除了与此输出关联的输入)使用相同的寄存器。
例如:
将cs寄存器的值移动到ax变量中
let ax: u16;
asm!(
"movw %cs, %ax"
: "={ax}"(ax)
:
:
);
输入约束
输入约束没有前缀 只是约束代码。每个输入约束将从调用指令中消耗一个参数。asm不允许写入任何输入寄存器或存储单元(除非该输入连接到输出)。还要注意,如果LLVM可以确定它们必然都包含相同的值,则可以将多个输入全部分配给相同的寄存器。
通过提供一个整数作为约束字符串,输入约束可以将它们自己绑定到输出约束,而不是提供约束代码。被绑定的输入仍然会从调用指令中消耗一个参数,并且按照通常的方式在asm模板编号中占据一个位置
它们将被简单地限制为始终使用与其绑定的输出相同的寄存器。例如,一个约束字符串“=r,0”表示为输出分配一个寄存器,并将该寄存器用作输入(它是第0个约束)
例如,将0x23移动到ss寄存器
asm!(
"movw $0, %ss"
:
: "r"(0x23)
: "memory"
);
指定寄存器名,可以使用多个参数
asm!("outb %al,%dx"
:
:"{dx}"(0x21),"{al}"(0x21)
:
);
所有目标通常都支持一些约束代码:
约束 | 解释 |
---|---|
r | 目标通用寄存器类中的寄存器 |
m | 存储器地址操作数。它支持哪些寻址模式,典型的例子是寄存器,寄存器+寄存器偏移量,或寄存器+直接偏移量(某些目标特定的大小) |
i | 一个整数常量(目标特定宽度)。允许简单的即时或可重定位的值 |
n | 一个整数常量 – 不包括可重定位值 |
s | 一个整数常量,但只允许重定位值 |
X | 允许任何类型的操作数,不受任何限制。通常用于为asm分支或call传递标签 |
{register-name} | 需要完整的指定物理寄存器 |
Clobber约束
clobber不会消耗输入操作数,也不会输出操作数。
一些指令修改的寄存器可能保存有不同的值,所以我们使用覆盖列表来告诉编译器不要假设任何装载在这些寄存器的值是有效的
“memory”表示程序写入任意未声明的内存位置 不仅是由声明的间接输出指向的内存。
请注意,输出约束中存在的clobbering命名寄存器是不合法的。
约束代码可以是单个字母(例如“r”),“^”字符后跟两个字母(例如“^wc”)或“{”寄存器名称“ }”(例如“{eax}”)。
通常选择单字母和双字母约束代码与GCC的约束代码相同
一些指令修改的寄存器可能保存有不同的值,所以我们使用覆盖列表来告诉编译器不要假设任何装载在这些寄存器的值是有效的
options
最后一部分,options是 Rust 特有的。格式是逗号分隔的基本字符串(也就是说,:“volatile”, “intel”, “alignstack”)。它被用来指定关于内联汇编的额外信息:
目前有效的选项有:
- volatile - 相当于 gcc/clang 中的
__asm__ __volatile__ (...)
- alignstack - 特定的指令需要栈按特定方式对齐(比如,SSE)并且指定这个告诉编译器插入通常的栈对齐代码
- intel - 使用 intel 语法而不是默认的 AT&T 语法
例如使用Intel语法编写内联汇编
asm!(
"mov eax, 2"
: "={eax}"(result)
:
:
: "intel"
);
更多例子
操作MSR寄存器
MSR寄存器的写入操作
pub unsafe fn wrmsr(msr: u32, data: u64) {
let low = data as u32; // 写入时需要将64位数据分解为2个32位数据
let high = (data >> 32) as u32;
asm!("wrmsr"
:
: "{ecx}"(msr),"{eax}"(low),"{edx}"(high)
: "memory"
: "volatile"
)
}
MSR寄存器的读取操作
pub fn rdmsr(msr: u32) -> u64 {
let (mut high, mut low) = (0_u32, 0_32); // 读取时需要将读到2个32位数据合并为64位数据
unsafe{
asm!("rdmsr"
: "={eax}"(low),"={edx}"(high)
: "{ecx}"(msr)
: "memory"
: "volatile"
);
}
((high as u64) << 32) | (low as u64)
}
操作CR0寄存器
写入CR0寄存器
pub unsafe fn write_cr0(value: u64) {
// $0表示传递的value值
asm!("mov $0, %cr0"
:
:"r"(value)
:"memory"
)
}
读取CR0寄存器
pub unsafe fn read_cr0() -> u64 {
let mut value: u64 = 0;
asm!("mov %cr0, $0"
:"=r"(value)
);
value
}
操作RFLAGS寄存器
读取RFLAGS寄存器
pub unsafe fn read_rflags() -> u64 {
let mut r: u64 = 0;
// 多个汇编语句以;分割
asm!("pushfq;
popq $0"
: "=r"(r)
:
: "memory"
);
r
}
写入RFLAGS寄存器
pub unsafe fn write_raw(val: u64) {
asm!("pushq $0;
popfq"
:
: "r"(val)
: "memory" "flags"
);
}
修改CS寄存器
CS寄存器表示当前执行的代码段,在重新加载GDT后需要重新设置CS段
pub unsafe fn set_cs(selector: u16) {
// 把新的选择子压到栈中,并且使用lretq(远返回指令)重新加载cs寄存器并在1:处继续
#[inline(always)]
unsafe fn inner(selector: u16) {
asm!(
"pushq $0;
leaq 1f(%rip), %rax;
pushq %rax;
lretq;
1:"
:
: "ri"(u64::from(selector)) // r表示目标通用寄存器类中的寄存器 i表示一个整数常量
: "rax" "memory" // 声明该内嵌汇编会修改rax寄存器
);
}
inner(selector);
}
在stable rust中嵌入汇编代码
因为asm!
宏暂时只能在nightly rust中使用,如果想在stable rust中使用怎么办呢?(在看x86_64 crate中看到了他的用法)
使用静态链接来嵌入汇编代码
我们可以通过静态链接的方式来完成,步骤如下
首先建立一个项目cargo new call_test
在src文件中创建asm mod
结构如下
src/
├── asm
│ ├── asm.s
│ └── mod.rs
├── lib.rs
└── main.rs
其中asm.s就是我们要编写的汇编代码,其内容如下
.text ; 我们编写的代码在.text节中
.code64 ; 使用的是64位汇编代码
.global nop_func ; 需要导出的函数名
.p2align 4 ; .p2align 4 意思为在16字节边界上对齐 具体定义可参考 https://sourceware.org/binutils/docs/as/P2align.html#P2align
nop_func:
nop ; 不做任何操作,空指令
retq ; 函数返回
随后在src/asm/mod.rs
中定义函数签名
#[link(name = "test_asm", kind = "static")] // 定义链接名称,使用的是静态链接方式
extern "C" {
#[cfg(link_name = "nop_func",target_env = "gnu")]
// link_name必须与asm.s文件中.global后导出名称一致,
// target_env = "gnu",target_env