内联汇编只能使用AT&T汇编,以前先学的是Intel汇编,更熟悉的也是这个,AT&T汇编也是忘的差不多了。。
Intel汇编与AT&T汇编的区别
区别 | intel | AT&T |
---|---|---|
寄存器 | 寄存器前无前缀 | 寄存器前有前缀 % |
操作数顺序 | 目的操作数在左,源操作数在右 | 目的操作数在右,源操作数在左 |
操作数指定大小 | 操作数前加修饰符,byte表示8位,word表示16位,dword表示32位 | 在指令后加字母表示,b表示8位,w表示16位,l表示32位 |
立即数 | 无前缀,直接用数字表示 | 有前缀$ |
远跳转 | jmp far segment:offset |
ljmp %segment: %offset |
远调用 | call far segment:offset |
lcall %segment: %offset |
远返回 | ret far n |
lret $n |
一些个人理解:
因为本人先学的Intel汇编,所以站在其角度上讲一下AT&T的不同理念。
- 数字在AT&T汇编的表示的更高优先级应该是内存地址,所以立即数退而求次
- 比起Intel的大量修饰符,AT&T使用的是在指令后增加简单的字符表示
基本内联汇编
基本内联汇编的格式如下:
asm [volatile] ("assembly code")
其中asm
是必加的,这个就不讨论。
volatile
和普通C一致,就是禁止编译器优化
再是汇编代码的注意事项
- 双引号是必须的
- 跨行需要加
\
- 指令之间用
;
,\n
等分割,统一一下还是用;
吧
还有需要注意的是,基本内联汇编只能使用C的全局变量,局部变量无法使用。
示例程序:
const char* str = "hello world";
int count = 0;
int main()
{
asm ("pusha; \
movl $4, %eax; \
movl $1, %ebx; \
movl str, %ecx; \
movl $12, %edx; \
int $0x80; \
mov %eax, count; \
popa");
return 0;
}
扩展内联汇编
扩展内联汇编增加的功能有
- 能使用局部变量
- 指定变量输入到寄存器,指定寄存器输出到变量
- 约束
- 占位符等
在扩展内联汇编中出现了占位符 %
,这与AT&T汇编中的寄存器表示有所冲突,所以在扩展内联汇编中只能退而求次,表示寄存器时只能使用%%
前缀
格式:
asm [volatile] ("assemble code": output : input : clobber/modify)
clobber/modify 用于告知编译器,该内联汇编修改了哪些寄存器或者内存,让编译器做好准备。
input 和 output是为了在汇编代码里使用C变量,在其前面都可以增加约束,其基本格式为
"各种约束"(变量名)
约束很多很繁杂,但也以此实现了其强大的功能。
以下简单介绍一下所有约束:
寄存器约束
寄存器约束表示让编译器使用那个寄存器,在input中表示,这个C变量存到哪个寄存器上,在output中表示将哪个寄存器的值存到这个C变量中。
约束如下:
- 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: 第一个浮点寄存器
- u: 第二个浮点寄存器
示例程序:
#include <stdio.h>
int main()
{
int a=1, b=2, sum;
asm("addl %%ebx, %%eax":"=a"(sum):"a"(a),"b"(b));
printf("%d\n",sum);
return 0;
}
内存约束
内存约束是指要求编译器将C的变量地址作为操作数直接给汇编代码,而不是用寄存器作为中转。
内存约束使用 m
即可,还有个o
,不太理解
立即数约束
要求编译器在传值的时候不通过内存和寄存器,而是直接作为立即数传给汇编代码
其约束有:
- i:整数立即数
- F:浮点数立即数
- I: 0~31
- J:0~63
- N:0~255
- O:0~32
- X:任意立即数
(感觉用处不大
占位符
%数字
表示,最多支持10个 0~9
表示的是output和input中依次出现的变量
asm("addl %2, %1":"=a"(sum): "a"(a),"b"(b))
%0 表示 sum,%1表示a,%2表示b
名称占位符
普通占位符数量是有限制的,为了突破其限制才有了名称占位符,(麻烦,还不如不要普通占位符
eg.
#include <stdio.h>
int main()
{
int a = 18, b = 3, out = 0;
asm("divb %[divisor];movb %%al, %[result]" \
:[result]"=m"(out) \
:"a"(a), [divisor]"m"(b));
printf("%d\n",out);
return 0;
}