AT&T汇编语法
GCC只支持AT&T汇编语法内嵌在C语言中。
Intel和AT&T汇编风格对比:
AT&T寻址
寄存器间接寻址:
- mov (%eax), %ebx ;表示将地址eax所指的内存复制4字节到ebx
寄存器相对寻址:
- movb -4(%eax), %al; 表示将地址(eax - 4)所指的内存复制一字节到al
变址寻址:
segreg(段基址):base_address(offset_address,index,size)
对应表达式为:
segreg(段基址):base_address + offset_address + index * size
格式中不存在的部分要用‘,’占位,共有4种组合:
-
无base_address,无offset_address
movl %eax, (,%esi,2) -- 表示将eax的值写入
esi*2
所指的内存 -
无base_address,有offset_address
movl %eax,(%ebx,%esi,2) -- 表示将eax的值写入
ebx + esi*2
所指的内存 -
有base_address,无offset_address
movl %eax, base_value(,%esi,2) -- 表示将eax的值写入base_value + esi*2`所指的内存
-
有base_address,有offset_address
movl %eax, base_value(%ebx,%esi,2) -- 表示将eax的值写入base_value + %ebx + esi*2`所指的内存
基本内联汇编
内联汇编基本格式:
asm [volatile] ("assembly code") volatile关键字可选,使得gcc在-O指定优化时,不改变汇编代码
assembly code规则:
- 指令必须在双引号内。
- 指令之间用分号、换行符
\n
或者换行符加制表符\n\t
分隔
示例:
char* str = "hello,world\n";
int count = 0;
void main(){
asm volatile ("\
pusha; \
movl $4, %eax; \
movl $1, %ebx; \
movl str, %ecx; \
movl $12, %edx; \
int $0x80; \
movl %eax, count; \
popa \
");
}
扩展内联汇编
当汇编代码嵌入到C代码中,如何找到可用的寄存器是个问题,程序员不知道哪些寄存器已经被分配。GCC提供了扩展内联汇编格式。
扩展内联汇编格式:
asm [volatile] ("assembly code" : output : input : clobber/modify)
- output : <output> 用来指定汇编代码的数据如何传递给C代码使用,output中每个操作数的格式为:
"操作数修饰符 + 约束名"(C变量名)
引号和圆括号不可少,操作数修饰符通常为‘=’。多个操作数之间用‘,’分隔
- input : <input> 用来指定C中数据如何传递给汇编使用,input中每个操作数的格式为:
”[操作数修饰符] + 约束名“(C变量名)
引号和圆括号不可少,操作数修饰符为可选项。多个操作数之间用‘,’分隔
clobber/modify : 汇编代码执行后会破坏一些内存或寄存器资源,通过此项通知编译器,哪些寄存器或内存需要提前保护起来。
-
约束名
寄存器约束
寄存器约束就是要求gcc使用哪个寄存器
a: eax/ax/al b: ebx/bx/bl c: ecx/cx/cl d: edx/dx/dl D: edi/di S: esi/si q: eax/ebx/ecx/edx中任意一个 r: eax/ebx/ecx/edx/esi/edi中任意一个 g: 表示存放到任意地点(寄存器和内存) A:把eax和edx组合成64位整数 f: 表示浮点寄存器 t: 表示第1个浮点寄存器 u: 表示第2个浮点寄存器
示例:
#include <stdio.h>
void main(){
int in_a = 1, in_b = 2, out_sum;
asm ("addl %%ebx, %%eax":"=a"(out_sum):"a"(in_a),"b"(in_b));
printf("sum = %d\n",out_sum);
}
ps: 扩展内联汇编中寄存器前缀是两个%
内存约束
m: 表示操作数可以使用任意一种内存形式
o: 操作数位内存变量
示例 :
#include <stdio.h>
void main(){
int in_a = 1, in_b = 2;
asm ("movb %b0, %1"::"a"(in_a),"m"(in_b));
printf("in_b = %d\n",in_b);
}
%1序号占位符,代表in_b的内存地址(指针)
%b0表示对寄存器eax的引用,b表示一个字节,所以%b0表示al寄存器
立即数约束
只放在input中
i: 整数立即数
F: 浮点数立即数
I: 0-31之间的立即数
J: 0-63之间的立即数
N: 0-255之间的立即数
O: 0-32之间的立即数
X: 任何类型的立即数
通用约束
0-9: 只用在input部分,表示与output和input中第n个操作数用相同的寄存器或内存
-
占位符
为方便对操作数的引用,扩展内联汇编提供了占位符来表示指定操作数。
序号占位符
序号占位符是对在output和input中的操作数,按照他们从左到右出现的次序从0开始编号,一直到9,最多支持10个序号占位符。引用它的格式是%0-9
若%0表示eax,则%h0 -- 表示ah,%b0表示al
名称占位符
可以对操作数进行显示命名,格式如下:
[名称]”约束名“(c变量)
采用%[名称]的形式引用操作数示例:
#include <stdio.h> void main(){ int in_a = 18, in_b = 3, out = 0; asm ("divb %[divisor]; movb %b1, %[result]":[result]"=m"(out):"a"(in_a),[divisor]"m"(in_b)); printf("out = %d\n",out); }
-
操作数修饰符
在output中:
=: 表示操作数只写 +: 表示操作数可读写 &: 表示此output中的操作数要独占约束(分配的)寄存器,只供output使用,任何input中分配的寄存器不能与此相同
input中:
%: 该操作数可以和下一个输入操作数交换
一般情况下input中的C变量是只读的,output中的C变量是只写的。
示例:
#include <stdio.h> void main(){ int in_a = 1, in_b = 2; asm ("addl %%ebx, %%eax":"+a"(in_a):"b"(in_b)); printf("sum = %d\n",in_a); }
参考
《操作系统真相还原》