背景
Linux内核代码、JVM源码中有很多关于内联汇编的操作,如果想阅读相关代码,比如JVM的Code Stub Generator,学习内联汇编是不可或缺的一环,在此进行总结一番。
汇编语法
语法特性 | intel | AT&T/UNIX |
---|---|---|
寄存器命名 | eax | %%eax(需要加’%%'前缀) |
操作数顺序 | op , | op , |
立即数格式 | 123, _C_static_variable | $123, $_C_static_variable(添加’$'前缀) |
操作数大小指定 | mov bx, ax | movw %ax,%bx(添加b(byte)、w(word)、l(longword)后缀) |
引用内存 | [base + index*scale + immed32] | immed32(base,index,scale) |
引用变量值 | _var(加’_'前缀) | $_var(加’$_'前缀) |
引用变量地址 | [_var] | _var |
引用寄存器地址 | [eax] | (%eax) |
引用var变量为基址的偏移 | [eax + _var] | _var(%eax) |
引用int数组arr的某个元素 | [eax * 4 + _arr] | _arr(, %eax, 4) |
内联语法
为什么需要内联?能获得可预知大小的代码
asm <关键字> (汇编代码
: 输出操作数
: 输入操作数
: Clobbers)
asm <关键字> (汇编代码
:
: 输入操作数
: Clobbers
: Goto C Labels)
- 关键字
输入操作数、输出操作数、跳转Label列表的操作数总数不能超过30个
volatile:阻止代码优化
inline:内联代码,将会对代码进行压缩
goto:告知编译器代码中将会对列表中的某个Label进行跳转
-
汇编代码
GAS汇编 -
输出操作数
用于接收汇编代码计算结果 -
输入操作数
输入变量值,被汇编代码引用计算 -
Clobbers
用于告知编译器汇编代码隐式的副作用 -
Goto C Label
C语言定义的Label列表
关键字
asm
或者__asm__
定义一段内联汇编代码。
如果使用ANSI C编写代码,必须使用__asm__
关键字
volatile
或者__volatile__
参考《汇编语言程序设计》p296
在一般的C或者C++程序中,编译器通常会尝试通过下面方式来对代码进行优化,以提高程序性能:
1、消除不使用的函数
2、在不同时使用的值之间共享寄存器
3、重新编排代码
volatile会抑制GCC的上述的代码优化。
如果我们修改了不在输入、输出操作数列表中的内存,我们必须添加在asm
后添加volatile
关键字。
assembler template
1、存在多行汇编指令时,使用\n\t
分隔,例如:
__asm__ ("movl %eax, %ebx\n\t"
"movl %eax, %edx")
输出操作数
<[可选名称]> 约束 (C表达式)
- 可选名称
为操作数指定一个名称,可以通过%[名称]
进行引用,如果不指定,则通过序号进行访问
__asm__ __volatile__ ("movw %%dx,%%ax\n\t"
"movw %4,%%dx\n\t"
"movl %%eax,%[gate_addr_1]\n\t"
"movl %%edx,%1"
:[gate_addr_1]"=m" (*((long *) (gate_addr))),
"=m" (*(1+(long *) (gate_addr))),
"=&a" (__d0),
"=&d" (__d1)
:"i" ((short) (0x8000+(14<<13)+(0<<8))),
"3" ((char *) (addr)),"2" (__KERNEL_CS << 16),
"d" (1)
);
其中定义了个gate_addr_1的输出操作数,在汇编中可以通过%[gate_addr_1]进行引用
- 约束
必须以’+‘或者’='作为前缀
‘+’:可读写,操作数会在输入操作数完成读取之前进行写入;不能被输入操作数绑定
__asm__ __volatile__ ("movw %%dx,%%ax\n\t"
"movw %4,%%dx\n\t"
"movl %%eax,%[gate_addr_1]\n\t"
"movl %%edx,%1"
:[gate_addr_1]"=m" (*((long *) (gate_addr))),
"=m" (*(1+(long *) (gate_addr))),
"+a" (__d0),
"+d" (__d1)
:"i" ((short) (0x8000+(14<<13)+(0<<8))),
"3" ((char *) (addr)),"2" (__KERNEL_CS << 16),
"d" (1)
);
输出操作数中,ax和dx寄存器使用’+‘进行约束,由于在输入操作数中又进行了操作数的绑定,这会报错。这是因为当使用’+'约束时,输入输出操作数是绑定在同一个C变量上的,当尝试进行绑定时,就发生了冲突
‘=’:只写、覆盖
- C表达式
C变量,必须是一个左操作数
1、’&‘约束表示寄存器不能与输入操作数重复;但可以进行操作数绑定
2、’+‘约束表示变量可能会被重写
3、’='约束表示可以进行读写,和输入操作数关联输出操作数语义类似
输入操作数
模版:
<[可选名称]> 约束 (C表达式)
-
可选名称
为操作数指定一个名称,可以通过%[名称]
进行引用,如果不指定,则通过序号进行访问 -
约束
不以’+’(表示变量会被重写)或者’=’(表示)开头 -
C表达式
右值表达式
1、不要修改只作为输入操作数的内容。一般通过将输入操作数绑定到输出操作数上
2、有且只有输入操作数可以在约束中使用数字,该数字必须是输出操作数的序号,以表示对该输出操作数的引用。(也可通过可选名称对输出操作数进行引用)
Clobbered Registers
示例:
"cc","r1","r2","cc","memory"
1、不能跟输入和输出操作数重复
2、新版GCC(10+)不支持栈寄存器,这是因为编译器要求栈指针在汇编指令执行前和执行后必须保持一致
3、"cc"表示条件寄存器的修改
4、"memory"告知编译器汇编代码会进行除了输入输出操作数之外的内存的读写,比如访问入参变量的值作为地址的内存
6、Clobber不会阻止通过指令进行的寄存器的预读,如有需要,必须通过处理器的屏障指令进行阻止。
跳转label
asm goto (汇编代码
:
: 输入操作数
: Clobbers
: Goto C Labels)
1、不能包含输出操作数
2、如果汇编代码发生修改,需要通过"memory" Clobber刷新内存
3、asm goto本身包含volatile的语义
4、引用标号:%l[<LABEL名称>]
__asm__ goto (
"cmp $1,$1\n\t"
"jz %l[LABEL]"
:
:
:
: LABEL
);
...
LABEL:
printf("jmp");
参考
- 《GCC Manual》链接:https://pan.baidu.com/s/1o6aDoRxAgN3eDA6DiLwOMw 密码: fa77
- http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html#ss6.1
- 《汇编语言程序设计》Richard Blum