内联汇编是AT&T语法,对比Intel汇编语法学习
先说基本区别:
1、AT&T 源操作数在左边
2、寄存器前要加%
3、立即数前要有$
4、操作指令要指定操作数据大小
b: byte, w: word, l: long
movb, movw, movl
ljmp, lcall
5、访问内存不用 [] ,用 ()
寻址:
- 直接寻址
// Intel
segreg(段基址): [base_address + offset_address + index * size]
// AT&T
segreg(段基址): base_address + offset_address + index * size
- 寄存器寻址
// Intel
mov ebx,[eax]
// AT&T
mov (%eax), %ebx
- 寄存器相对寻址
// Intel
mov al, [ebx-4]
// AT&T
movb -4(%ebx), %al
- 变址寻址
// Intel
mov [esi*2], eax
mov [ebx+esi*2], eax
mov [base_value+esi*2], eax
mov [base_value+ebx+esi*2], eax
// AT&T
// 无 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不要修改我写的汇编代码,原样保留
assembly code: 自己写的汇编代码,汇编指令之前用 “;” 分隔开
提醒 一 下,即使是指令分布在多个双引号中, gcc 最终也要把它们合并到一起来处理,合并之后,指令间必须要有分隔符。所以,当指令在多个双引号中时,除最后一个双引号外,其余双引号中的代码最后一定要有分隔符 “;”
// example
asm ("movl $9, %eax;""pushl %eax")
asm ("pusha; \
movl $4, %eax ; \
movl $1, %ebx; \
movl str, %ecx;\
movl $12, %edx ; \
int $0x80; \
mov %eax,count;\
pop a \
")
扩展内联汇编
解决的问题:
- 防止和C代码中使用的寄存器冲突
- 汇编语言访问C代码中变量
扩展后的内联汇编格式:
asm [volatile] ("assembly code" : output : input : clobber/modify)
多了 output 、 input 和clobber/modify 三项。其中的每 一 部分都可以省略,甚至包括 assembly code。省略的部分要保留冒号分隔符来占位,如果省略的是后面的 一 个或多个连续的部分,分隔符也不用保留,比如省略了clobber/modify,不需 要保留 input 后面的冒号。
input:给汇编代码提供要处理的数据
ouput:用来给指定汇编代码的数据如何输出给 C 代码使用
input 格式
“[操作数修饰符]约束名”(C 变量名)
output 格式
“[操作数修饰符] 约束名”(C 变量名)
clobber/modify :汇编代码执行后会破坏 一 些内存或寄存器资源,通过此项通知编译器,可能造成寄存器或内存数据的破坏,这样 gcc 就知道哪些寄存器或内存需要提前保护起来
约束
- 寄存器约束
a :表示寄存器 eax/ax/al
b :表示寄存器 ebx/bx/bl
c :表示寄存器 eex/ex/cl
d :表示寄存器 edx/dx/dl
D :表示寄存器 edi/di
S :表示寄存器 esi/si
q :表示任意这 4 个通用寄存器之-: eax/ebx/ecx/edx
r :表示任意这 6 个通用寄存器之一: eax/ebx/ecx/edx/esi/edi
g :表示可以存放到任意地点(寄存器和内存)。相当于除了同 q 一样外,还可以让 gcc 安排在内存中
A :把 eax 和 edx 组合成 64 位整数
f :表示浮点寄存器
t :表示第 1 个浮点寄存器
u :表示第 2 个浮点寄存器
扩展内联汇编中寄存器前缀是两个%,单个%用来占位
void main() {
int in_a = 1, in b = 2, out_sum;
/*
功能:ebx加到eax
输出out_sum约束到eax中,"=" 表示只写
输入in_a, in_b 分别约束到eax和ebx中
*/
asm ("addl %%ebx, %%eax":"=a"(out_sum):"a"(in_a), "b"(in_b));
printf ("sum is %d\n", out _ sum);
}
- 内存约束
m :表示操作数可以使用任意一种内存形式。
o :操作数为内存变量,但访问它是通过偏移量的形式访问,即包含 offset_address的格式。
void main() {
int in_a = 1, in_b = 2;
printf ("in_b is %d\n", in_b);
/*
in_b 约束到内存空间
*/
asm ("movb %b0, %1;" ::"a"(in_a), "m"(in_b));
printf ("in_b now is %d\n", in_b);
}
内存约束使用时注意,要求汇编指令是可以操作内存的,例如 movl 就不允许内存到内存的复制
- 立即数约束
立即数只能在 input 中
i :表示操作数为整数立即数
F :表示操作数为浮点数立即数
I :表示操作数为 0 ~ 31 之间的立即数
J :表示操作数为 0 ~ 63 之间的立即数
N :表示操作数为 0 ~ 255 之间的立即数
O :表示操作数为 0 ~ 32 之间的立即数
X :表示操作数为任何类型立即数
- 通用约束
0 ~ 9 :此约束只用在 input 部分,但表示可与 output 和 input 中第 n 个操作数用相同的寄存器或内存
- 序号占位符
asm("addl %2, %1" :"=a"(out_sum) : "a"(in_a), "b"(in _ b));
从左到右,最先出现的的是 “=a”(out_sum) ,%0 对应的就是eax
其次 %1 对应的是 “a”(in_a) 中的 eax,%2 对应的是 “b”(in _ b) 中的 ebx
对于 %0~9,还可以加 ‘b’, ‘h’ 来精细控制是16位数据中的低八位还是高八位,如%b0,%h0。
b,h属于机器模式的内容,相关的还有 w,k 等。
机器模式命名:数据大小+数据类型+mod
列举整型相关的机器模式
- 名称占位符
[名称]"约束名"(C 变量)
asm ("divb %[divisor];movb %%al, %[result]" \
:[result]"=m"(out) \
:"a "(in_a), [divisor]"m"(in_b) \
);
“m”(in_b) 这个内存约束就有个名字 divisor ,以后在汇编指令中可以直接使用该名字操作这块内存数据
- 操作数类型修饰符
= :表示操作数是只写,相当于为 output 括号中的 C 变量赋值,如= a(c_var),此修饰符相当于 c_var=eax 。
+:表示操作数是可读写的,告诉 gee 所约束的寄存器或内存先被读入,再被写入。
&:表示此 output 中的操作数要独占所约束(分配)的寄存器,只供 output 使用,任何 input 中所分配的寄存器不能与此相同。注意,当表达式中有多个修饰符时,&要与约束名挨着,不能分隔。
% :该操作数可以和下一个输入操作数互换。
补充知识:
注意,常见的字符串操作指令有movs[bwd]、ins[bwd] 和 outs[bwd]、lods[bwd]和stos[bwd]。
但是,esi和edi并不是被以上三组指令同时使用.
1、只有movs[bwd]才同时使用esi和edi,它是把esi所指向的地址处的数据复制到edi所指向的内存地址处。
2、ins[bwd]是从端口读入数据到内存的目的地址,故只涉及到edi。
3、outs[bwd]是把内存中的源数据写入端口,故只涉及到esi。lods[bwd]是 把内存中的源数据加载到寄存器al、ax或eax,故只涉及到esi。
4、stos[bwd]是将寄存器al、ax 或eax中的值写入内存中的目的地址,故只涉及到edi。
以上字符串指令每执行一次,所涉及到的源变址寄存器esi或目的变址寄存器edi都要根据操作数大小有所增减,至于是增加,还是减少,要取决于标志寄存器中的方向位DF,若DF为0,esi 和edi都自增,地址值越来越大,否则DF为1,esi和edi都自减,地址值越来越小。
这些字符串操作指令在读写数据时,esi 和edi作为它们的输入操作数,执行完成后,根据DF位的情况自增或自减,这时又作为输出。
内联汇编的第三部分:
clobber/modify:告诉编译器我们改了那些寄存器和内存,编译器能够合理安排。
如果在 output 和 input 中通过寄存器约束指定了寄存器,gcc必然会知道这些寄存器会被修改,所以,需要在 clobber/modify 中通知的寄存器肯定不是在 output 和 input 中出现过的。
这个很简单,只要在 clobber/modify 部分明确写出来就行了,记得要用双引号把寄存器名称引起来,多个寄存器之间用逗号’,’分隔开。
asm ("movl %%eax, %0 ;movl %%eax, %%ebx ":"=m"(ret_value ):: "bx")
// eflag寄存器用 cc 来声明
// 修改了内存用 memory 来声明
// volatile表示不把内存中的数据缓存在寄存器(不是asm后面的volatile)