asm 为 gcc 中的关键字,asm 表达式为在 C代码中嵌套汇编指令,该表达式只是单纯的替换出汇编代码,并不对汇编代码的含义进行解析。html
asm 表达式有两种形式,第二种 asm-qualifiers
包含了 goto
语句。
第一种形式为常见的用法,AssemblerTemplate 和 OutputOperands 必须存在, 其中 Clobbers 存在须要 InputOperands 也出现。express
asm asm-qualifiers ( AssemblerTemplate : OutputOperands [ : InputOperands [ : Clobbers ] ]) asm asm-qualifiers ( AssemblerTemplate : : InputOperands : Clobbers : GotoLabels)
Qualifiers 的类型编程
参数编辑器
汇编指令由一个字符串给出,多条汇编指令结合在一块儿使用的时候,中间以 \r\t
隔开,如优化
asm("inc %0\n\tinc %0" : "=r"(res) : "0"(res)); /APP # 11 "asm.c" 1 inc %rax inc %rax # 0 "" 2 /NO_APPs
须要转义的字符:%
, =
, {
, }
, |
this
故在ATT汇编中,对寄存器进行操做的须要双 %%, 如 inc %%rax
.spa
操做数之间用逗号分隔。 每一个操做数具备如下格式:指针
[ [asmSymbolicName] ] constraint (cvariablename)
%[name]
// res = num asm("movq %[num], %[res]" : [res] "=r"(res) : [num] "m"(num));
// res = num asm("movq %1, %0" : "=r"(res) : "m"(num));
第一个参数为增长可读性使用的,如今咱们有代码以下code
int64_t res; int64_t num = 1; asm("movq %[num], %[res]" : [res] "=r"(res) : [num] "m"(num)); asm("movq %1, %0" : "=r"(res) : "m"(num)); asm("movq %1, %0" : "=m"(res) : "m"(num)); asm("movq %1, %0" : "=r"(res) : "r"(num)); // 对应的汇编代码, 只保留asm表达式中的代码 # 13 "asm.c" 1 movq -16(%rbp), %rax // asm-1 # 0 "" 2 /NO_APP /APP # 15 "asm.c" 1 movq -16(%rbp), %rax // asm-2 # 0 "" 2 /NO_APP /APP # 17 "asm.c" 1 movq -16(%rbp), -8(%rbp) // asm-3 # 0 "" 2 /NO_APP /APP # 19 "asm.c" 1 movq %rax, %rax // asm-4 # 0 "" 2 /NO_APP
r
为经过寄存器寻址操做,m
经过内存寻址操做,因此看到当约束了 r
就对应寄存器的操做。输入操做数使C变量和表达式中的值可用于汇编代码。htm
[ [asmSymbolicName] ] constraint (cexpression)
constraint 约束不能使用 =
和 +
. 可使用 "0", 这代表在输出约束列表中(从零开始)的条目,指定的输入必须与输出约束位于同一位置。
```c
int64_t res = 3;
int64_t num = 1;
asm("addq %1, %0" : "=g"(res) : "0"(num));
cexpression 能够不为左值,做为汇编表达式的输入值便可
破坏列表,主要用于指示编译器生成的汇编指令。
从asm表达式中看到输出操做数中列出条目的更改编译器是能够肯定的,但内联汇编代码可能不只对输出进行了修改。 例如,计算可能须要其余寄存器,或者处理器可能会因为特定汇编程序指令而破坏寄存器的值。 为了将这些更改通知编译器,在Clobber列表中列出这些会产生反作用的条目。 破坏列表条目能够是寄存器名称,也能够是特殊的破坏列表项(在下面列出)。 每一个内容列表条目都是一个字符串常量,用双引号引发来并用逗号分隔。
寄存器
asm volatile("movc3 %0, %1, %2" : /* No outputs. */ : "r"(from), "r"(to), "g"(count) : "%rbx", "%rcx", "%rdx", "memory"); /APP # 25 "asm.c" 1 movc3 %rax, %r8, -72(%rbp) # 0 "" 2 /NO_APP
能够看到使用到了 rax 寄存器,而后修改程序在 Clobbers 增长 %rax, 结果以下
```c
asm volatile("movc3 %0, %1, %2"
: /* No outputs. */
: "r"(from), "r"(to), "g"(count)
: "%rax", "%rbx", "%rcx", "%rdx", "memory");
编译器为了破坏列表项的值受到破坏,当这些条目是寄存器时,不对其进行使用;为特殊参数时,从新刷新获得最新的值。
约束名 | 说明 |
---|---|
whitespace | 空白字符被忽略 |
m | 容许使用内存操做数,以及机器一般支持的任何类型的地址 |
o | 容许使用内存操做数,但前提是地址是可偏移的 |
V | 容许使用内存操做数,不可偏移的内存地址,与 "o'互斥 |
r | 容许在通用寄存器中使用的寄存器操做数,其中能够指定寄存器,如 a(%rax), b(%rbx) |
i | 容许使用当即整数操做数 |
n | 容许使用具备已知数值的当即整数操做数, ‘I’, ‘J’, ‘K’, … ‘P’ 更应该使用 n |
F | 容许使用浮点当即数 |
g | 容许使用任何寄存器,内存或当即数整数操做数,但非通用寄存器除外 |
X | 容许任何操做数, ‘0’, ‘1’, ‘2’, … ‘9’ |
p | 容许使用有效内存地址的操做数 |
标识符 | 说明 |
---|---|
= | 表示此操做数是由该指令写入的:先前的值将被丢弃并由新数据替换 |
+ | 表示该操做数由指令读取和写入 |
& | 表示(在特定替代方法中)此操做数是早期指令操做数,它是在使用输入操做数完成指令以前写入的,故输入操做数部分不能分配与输出操做数相同的寄存器 |
% | 表示该操做数与后续操做数的可交换指令 |
x86 的内存屏障指令。
```c
// 避免编译器的优化,声明此处内存可能发生破坏
#define barrier() asm volatile("" ::: "memory")
x86 下获取 current 的值
DECLARE_PER_CPU(struct task_struct *, current_task); #define this_cpu_read_stable(var) percpu_stable_op("mov", var) static __always_inline struct task_struct *get_current(void) { return this_cpu_read_stable(current_task); } #define percpu_stable_op(op, var) \ ({ \ typeof(var) pfo_ret__; \ switch (sizeof(var)) { \ case 8: \ asm(op "q "__percpu_arg(P1)",%0" \ : "=r" (pfo_ret__) \ : "p" (&(var))); \ break; \ } \ pfo_ret__; \ })
current_task 为一个 struct task_struct 类型的指针,追踪宏调用,在x86-64 下命中了 case 8: 的汇编代码, 展开的代码为
c asm("mov" "q ""%%""gs" ":" "%" "P1"",%0" : "=r" (pfo_ret__) : "p" (&(current_task))); // 变换一下为 asm("movq %%gs:%P1, %0" : "=r"(pfo_ret__) : "p"(&(current_task)));
这行代码的含义为将 约束输入部分必须为有效的地址(p约束), 将CPU id(经过段寄存器gs和偏移经过GDT获得,这里后文分析了)经过寄存器(r约束)赋值给 pfo_ret__.