前言
各方面都需要在c(gcc)中使用汇编,因此系统的学习下内联汇编
AT&T与Intel汇编
之前一直在用的是Intel汇编,其读起来比较方便。但gcc中使用的是AT&T汇编,二者都是对x86-64指令集的助记,只是编译器支持不同罢了。
主要不同点:
1.源操作数和目的操作数的方向
AT&T和Intel汇编语法源操作数和目的操作数的方向正好相反。AT&T中是左源右目的。
2.寄存器命名
在AT&T汇编中, 寄存器名前有%前缀。例如,如果要使用eax,得写作: %eax。
3.立即数
在AT&T语法中,立即数都有’$’前缀。引用的C语言静态变量也必须放上’$’前缀。
4.操作数大小
在AT&T语法中,操作符的最后一个字符决定着操作数访问内存的长度:b、w、l、q表示8、16、32、64bit
5.内存操作数
在Intel语法中,基址寄存器是放在方括号[]中的,但AT&T是放在小括弧()内的。
此外对于AT&T汇编,当一个常数被用作disp或者scale时,不需要’$’前缀。
示例对比:
Intel Code | AT&T Code |
---|---|
mov eax,1 | movl $1,%eax |
mov ebx,0ffh | movl $0xff,%ebx |
int 80h | int $0x80 |
mov ebx, eax | movl %eax, %ebx |
mov eax,[ecx] | movl (%ecx),%eax |
mov eax,[ebx+3] | movl 3(%ebx),%eax |
mov eax,[ebx+20h] | movl 0x20(%ebx),%eax |
add eax,[ebx+ecx*2h] | addl (%ebx,%ecx,0x2),%eax |
lea eax,[ebx+ecx] | leal (%ebx,%ecx),%eax |
sub eax,[ebx+ecx*4h-20h] | subl -0x20(%ebx,%ecx,0x4),%eax |
基本内联汇编
如果内联汇编有多条指令,则每行要加上双引号,并且该行要以\n\t结尾。这是因为GCC会将每行指令作为一个字符串传给as(GAS),使用换行和TAB可以将正确且格式良好的代码行传递给汇编器。
内联汇编表达式
汇编指令部分;出描述;入描述;损坏说明
示例:用汇编代码把a的值赋给b
说明:
- “b”是输出操作数,用%0来访问,”a”是输入操作数,用%1来访问。
- r约束,让GCC自己去选择一个寄存器去存储变量a与b。=是输出必须加的只读修饰。
- 操作数已经用了一个%作为前缀,寄存器只能用“%%”做前缀了
- 内联汇编代码中会改变寄存器eax的内容,如此一来GCC在调用内联汇编前就不会依赖保存在寄存器eax中的内容了。
汇编模板部分
- 每条指令放在一个双引号内,或者将所有的指令都放着一个双引号内。
- 每条指令都要包含一个分隔符。合法的分隔符是换行符(\n)或者分号。用换行符的时候通常后面放一个制表符\t。
- 访问C语言变量用%0,%1…%9。最多10条
操作数
格式:”约束”(c表达式)
“constraint” (C expression) //“=r”(result)
对于输出操作数一定要用 “=”或”+”修饰。 constraint主要用来指定操作数的寻址类型 (内存寻址或寄存器寻址),也用来指明使用哪个寄存器。
=表示纯粹输出、+表示输入输出都可、&只位于=+之后表示不可再为输入分配该内容。
多个操作数用,隔开
在汇编模板部分,我们按顺序用数字去引用操作数。%0-%9
%0表示引用那个内容,内容由后面的约束决定,同时执行前和执行后会进行表达式与指定内容的交互。
示例:
对于输入输出操作数中出现的寄存器不用写损坏说明
常用constraint约束:
r:操作数将被存储在通用寄存器中,gcc自己选择
q:从eax、ebx、ecx、edx中指派一个寄存器
a:rax,eax, ax, al
b:rbx,ebx, bx, bl
c:rcx,ecx, cx, cl
d:rdx,edx, dx, dl
S:rsi,esi, si
D:rdi,edi, di
m:使用寄存器传递数据往往是更改完数据再写回c变量的内存中,直接使用内存可以省去步骤。
匹配constraint:在某些情况下,一个变量可能被用来传递输入也用来保存输出。+指的是一个输出操作数即可输入又可输出。
m: 使用一个内存操作数,内存地址可以是机器支持的范围内
o: 使用一个内存操作数,但是要求内存地址范围在在同一段内
V: 内存操作数,但是不在同一个段内。
i: 使用一个立即整数操作数(值固定);也包含仅在编译时才能确定其值的符号常量。
F: 浮点类型的立即数
g: 除了通用寄存器以外的任何寄存器,内存和立即整数。
损坏说明
如果某个指令改变了某个寄存器的值,我们就必须在asm中第三个冒号后的Clobber List中标示出该寄存器。为的是通知GCC,让其不再假定之前存入这些寄存器中的值依然合法。输入输出寄存器不用放Clobber List中,因为GCC能知道asm将使用这些寄存器。其他使用到的寄存器,无论是显示还是隐式的使用,必须在clobbered list中标明。
如果指令中以无法预料的形式修改了内存值,需要在clobbered list中加上”memory”。从而使得GCC不去缓存在这些内存值。
示例:
防止优化
内联汇编一般与volatile一起使用。用于阻止编译器优化
示例
示例1:
俩数相加:
lock前缀的指令,使cpu多处理器下互斥的使用这个地址。当指令执行完毕,这个锁定动作也会消失。可以与:BT、ADD、OR、ADC、SBB、AND、SUB、XOR、NOT、NEG、INC、DEC等一起使用。
看下它的返汇编:
示例2:
示例3:
设置和清除寄存器中的某一位
示例4:
在Linux中,系统调用是用GCC内联汇编的形式实现的。