Rust从入门到实战系列三百二十六:Explicit register operands

本文介绍了Rust中如何在x86架构上使用inlineassembly进行操作,如指定特定寄存器、处理clobberedregisters(被修改但后续不再使用的寄存器)以及处理像cpuid指令这样的例子。文章还展示了如何在asm块中利用临时寄存器和工作-aroundreservedregisters的方法。
摘要由CSDN通过智能技术生成

Some instructions require that the operands be in a specific register. Therefore, Rust inline assembly
provides some more specific constraint specifiers. While reg is generally available on any
architecture, explicit registers are highly architecture specific. E.g. for x86 the general purpose
registers eax , ebx , ecx , edx , ebp , esi , and edi among others can be addressed by their name.
use std::arch::asm;
let cmd = 0xd1;
unsafe {
asm!(“out 0x64, eax”, in(“eax”) cmd);
}
In this example we call the out instruction to output the content of the cmd variable to port 0x64 .
Since the out instruction only accepts eax (and its sub registers) as operand we had to use the
eax constraint specifier.
Note: unlike other operand types, explicit register operands cannot be used in the template
string: you can’t use {} and should write the register name directly instead. Also, they must
appear at the end of the operand list after all other operand types.
Consider this example which uses the x86 mul instruction:
use std::arch::asm;
fn mul(a: u64, b: u64) -> u128 {
let lo: u64;
let hi: u64;
unsafe {
asm!(
// The x86 mul instruction takes rax as an implicit input and writes
// the 128-bit result of the multiplication to rax:rdx.
“mul {}”,
in(reg) a,
inlateout(“rax”) b => lo,
lateout(“rdx”) hi
);
}
((hi as u128) << 64) + lo as u128
}
This uses the mul instruction to multiply two 64-bit inputs with a 128-bit result. The only explicit
operand is a register, that we fill from the variable a . The second operand is implicit, and must be
the rax register, which we fill from the variable b . The lower 64 bits of the result are stored in rax
from which we fill the variable lo . The higher 64 bits are stored in rdx from which we fill the
variable hi .
Clobbered registers
In many cases inline assembly will modify state that is not needed as an output. Usually this is either
because we have to use a scratch register in the assembly or because instructions modify state that
we don’t need to further examine. This state is generally referred to as being “clobbered”. We need
to tell the compiler about this since it may need to save and restore this state around the inline
assembly block.
use core::arch::asm;
fn main() {
// three entries of four bytes each
let mut name_buf = [0_u8; 12];
// String is stored as ascii in ebx, edx, ecx in order
// Because ebx is reserved, we get a scratch register and move from
// ebx into it in the asm. The asm needs to preserve the value of
// that register though, so it is pushed and popped around the main asm
// (in 64 bit mode for 64 bit processors, 32 bit processors would use ebx)
unsafe {
asm!(
“push rbx”,
“cpuid”,
“mov [{0}], ebx”,
“mov [{0} + 4], edx”,
“mov [{0} + 8], ecx”,
“pop rbx”,
// We use a pointer to an array for storing the values to simplify
// the Rust code at the cost of a couple more asm instructions
// This is more explicit with how the asm works however, as opposed
// to explicit register outputs such as out("ecx") val
// The pointer itself is only an input even though it’s written behind
in(reg) name_buf.as_mut_ptr(),
// select cpuid 0, also specify eax as clobbered
inout(“eax”) 0 => _,
// cpuid clobbers these registers too
out(“ecx”) _,
out(“edx”) _,
);
}
let name = core::str::from_utf8(&name_buf).unwrap();
println!(“CPU Manufacturer ID: {}”, name);
}
In the example above we use the cpuid instruction to read the CPU manufacturer ID. This
instruction writes to eax with the maximum supported cpuid argument and ebx , esx , and ecx
with the CPU manufacturer ID as ASCII bytes in that order.
Even though eax is never read we still need to tell the compiler that the register has been modified
so that the compiler can save any values that were in these registers before the asm. This is done by
declaring it as an output but with _ instead of a variable name, which indicates that the output
value is to be discarded.
This code also works around the limitation that ebx is a reserved register by LLVM. That means that
LLVM assumes that it has full control over the register and it must be restored to its original state
before exiting the asm block, so it cannot be used as an output. To work around this we save the
register via push , read from ebx inside the asm block into a temporary register allocated with
out(reg) and then restoring ebx to its original state via pop . The push and pop use the full 64-bit
rbx version of the register to ensure that the entire register is saved. On 32 bit targets the code
would instead use ebx in the push / pop .
This can also be used with a general register class (e.g. reg ) to obtain a scratch register for use
inside the asm code:
use std::arch::asm;
// Multiply x by 6 using shifts and adds
let mut x: u64 = 4;
unsafe {
asm!(
“mov {tmp}, {x}”,
“shl {tmp}, 1”,
“shl {x}, 2”,
“add {x}, {tmp}”,
x = inout(reg) x,
tmp = out(reg) _,
);
}
assert_eq!(x, 4 * 6);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值