本文是简单记录一下我入门学到的内容,防止后面忘了。
平台限制
当前版本"1.69", 只能支持,x86、x86-64、ARM、AArch64、RISC-V这几种cpu架构的汇编代码。
语法
这是参考手册中写的一个ABNF的定义:
|
|
看着比较繁琐,简而言之就是有如下格式:
|
|
作用域
asm!
在函数中使用,可以集成到编译器生成的函数汇编代码中。但是,这些汇编代码需要遵守严格的规则,去避免未定义的行为。编译器可能会将asm!
定义的汇编代码提取出为一个函数,然后以函数调用的方式使用汇编代码。
global_asm!
1:
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.
汇编代码部分
这里就是使用字符串常量去写汇编代码。简而言之,这里的就是println!
中的第一个参数的格式化字符串的用法类似。除了不支持“隐式命名参数2”
隐式命名参数的形式是:
|
|
原因很简单,asm!
需要指定寄存器的,像上面的例子那样的方式,变量a
使用的哪一个寄存器就是没有指定的。
下面是《rust by examples》中的例子(后续很多例子也来自它,不再赘述)
|
|
寄存器指定部分
这部分就是申请寄存器,并将一些表达式和寄存器进行绑定或映射。 编译器会根据申请的寄存器,进行一些寄存器的入栈保护等操作。
in(<reg>) <expr>
<reg>
可以使用"register class"3或者寄存器名去指定寄存器- 在汇编代码开始前,指定的寄存器会保存着
<expr>
的值 - 在汇编代码结束时,指定的寄存器要依旧保存着
<expr>
的值,(除非后续使用了lateout
申请这个寄存器)
out(<reg>) <expr>
<reg>
可以使用"register class"或者寄存器名去指定寄存器- 申请的寄存器在汇编代码开始时,其中的值是未定义的。
<expr>
要求是一个可以未初始化的place expression,在汇编代码结束时,寄存器的值会被写入表达式中。<expr>
可以是一个_
,这个意味着该寄存器的值将会在汇编代码结束的时候被丢弃
lateout(<reg>) <expr>
- 类似
out(<reg>) <expr>
, 但是lateout
可以重复指定in
指定的寄存器 - 应该在读取全部输入后,再向该寄存器写入。(不这么做好像rust也发现不了,不安全的代码需要注意安全啊)
- 类似
inout(<reg>) <expr>
<reg>
可以使用"register class"或者寄存器名去指定寄存器- 在汇编代码开始前,指定的寄存器会保存着
<expr>
的值 <expr>
要求是一个可变的(mut修饰)已经初始化的place expression,在汇编代码结束时,寄存器的值会被写入表达式中。 简而言是就是in
和out
的结合,“可变和已初始化”就是为了同时满足可以读取、可以写入的要求
inout(<reg>) <in expr> => <out expr>
- 类似前一个
inout
的用法,除了 - 寄存器初始值是
<in expr>
<out expr>
要求是一个可以未初始化的place expression<expr>
可以是一个_
,这个意味着该寄存器的值将会在汇编代码结束的时候被丢弃<in expr>
和<out expr
可能有不同的类型。 简而言之就是将in和out映射的表达式分开,减少了一些限制
- 类似前一个
inlateout(<reg>) <expr> / inlateout(<reg>) <in expr> => <out expr>
- 类似
inout
,除了可以复用in
申请的寄存器。(这个只会发生在in
和inlateout
之间的初始值相等时) inlateout
与inout
之间的区别,就类似lateout
和out
之间的区别。- 应该在读取全部输入后,再向该寄存器写入。
- 类似
sym <path>
- must refer to a fn or static.
- A mangled symbol name referring to the item is substituted into the asm template string.
- The substituted string does not include any modifiers (e.g. GOT, PLT, relocations, etc).
- is allowed to point to a #[thread_local] static, in which case the asm code can combine the symbol with relocations (e.g. @plt, @TPOFF) to read from thread-local data. 不是很理解,也没找到例子,就抄原文内容放在这里好了。
这里相关还有很多的内容,详细见参rust参考手册
clobber_abi部分
clobber 就是在汇编代码中可能会破坏某些寄存器或内存的值。
为了防止这些破坏影响外部代码,我们需要告诉编译器让其提前将这些资源保护起来。我们可以手动的使用out("a1") _
,inout("a1") 0 => _
等方式手动实现,也可以通过clobber_api
自动实现。
clobber_abi
就是rust预先定义了一些abi可能破坏的寄存器资源集合,可以简单的通过一个参数告诉编译器。而不需要一长串重复的操作。
clobber_abi
原理就是为指定集合中没有使用的寄存器,添加lateout("...") _
的隐式声明去告诉编译器进行寄存器状态的保护。
限制:Generic register class outputs are disallowed by the compiler when clobber_abi
is used: all outputs must specify an explicit register. Explicit register outputs have precedence over the implicit clobbers inserted by clobber_abi
: a clobber will only be inserted for a register if that register is not used as an output.
|
|
配置选项部分
略,详见 https://doc.rust-lang.org/reference/inline-assembly.html#options
asm!
需要遵守的规则
这里简单说一点:不能假设汇编代码只会在输出的binary文件中出现一次。编译器可能会有多个asm!
拷贝。例如含有asm!
块的函数在多个地方被内联。
影响:不能简单的使用lable
在汇编代码中,我们需要使用GNU assembler numeric local labels
|
|
规则内容太多,详细见: https://doc.rust-lang.org/reference/inline-assembly.html#rules-for-inline-assembly
总结
这篇文章讲的内容比较简陋和有些混乱吧。一方面想按照参考手册的格式和内容写,但是内容太多了,不想写了,就放了一堆连接;另一方面则是刚学,很多地方自己也是懵懵懂懂的。有待之后,再改改这篇文章吧。