att汇编格式
调用约定
cdecl调用方式:
- 参数从右往左入栈
- 调用者保存eax,ecx,edx寄存器,其余寄存器由被调用者(函数)来保存
- 函数返回值保存在eax寄存器中
内联汇编
Intel和At&t的风格对比:
区别 | Intel | At&t | 说明 |
---|---|---|---|
寄存器 | 寄存器前有前缀% | ||
操作数顺序 | a<-b | a->b | 恰好相反 |
操作数字体指定大小 | +数据类型用来修饰:byte,word,dword,如:mov byte[0x1234],eax | 指令的最后一个字母表示操作树的大小,b,w,l,如:movl %eax,var | att语法,内存地址是第一位,数字默认是地址,所以操作数字就是操作内存,Intel语法数字就是数字,加上[]才是操作内存, |
立即数 | 6 | 有前缀$,$6 | |
远转跳 | jmp far segment:offset | ljmp $segment:$offset | |
远调用 | call far segment:offset | lcall $segment:$offset | |
远返回 | ret far n | lret $n |
att内存寻址通用格式:
segreg(段基址):base_address(offset_address,index,size)
对应的值是: base_address+offset_address+index*size
等价与Intel格式: segreg:[base+offset+index*size]
base_address表示基地址,可正负
offset_address表示偏移地址,index表示索引值(这2个必须是8个通用寄存器)
size:表示长度,值只能取: 1,2,4,8
att寻址方式:
直接寻址(只有base_address项),如:
movl $255,0xc00008f0
寄存器间接寻址(只有offset_address项,所以格式为(offset_address)): 如:
movl (%eax),%ebx
寄存器相对址(有offset_address和base_address项,格式为base_address(offset_address) ),如:
movb -4(%ebx),%al
,将地址(ebx-4)的1 byte字节复制到al寄存器中变址寻址(含有index,既然有index则有size,base_address和offset_address可有可无,):
无
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处
基本内联汇编
有
#define __asm__ asm
和#define __volatile__ volatile
,所以直接用asm和volatile就行
基本格式asm [volatile] ("assembly code");
- 指令用
""
括起来, - 指令之间用
;
或者\n
或\n\t
例子:
1 |
|
没什么需要注意的,此时基本内敛汇编引用C变量只能引用全局变量,在扩展中可以随意使用
x64下编译成32bit 可执行:gcc -m32 test.s
扩展内联汇编
问题:
- C和内联汇编对寄存器的使用冲突
- 汇编如何访问C栈上的变量
按照格式提供汇编要访问的变量给编译器看(gcc制订的规则)
asm [volatile] ("assembly code":output:input:modify)
- output:output指定汇编代码执行后如何将数据传递给C代码使用,格式:
"操作数修饰符 约束名"(c变量名)
- input:input,指定C中的变量如何传递给汇编代码使用.
[操作数修饰符] 约束名(C变量名)
- modify:通知编译器那些寄存器可能被破坏
寄存器约束
将input或output中的变量约束在某个寄存器中
1
2
3
4
5
6
7
8
9
10
11
12
13a: eax/ax/al
b: ebx/bx/bl
c: ecx/cx/cl
d: edx/dx/dl
D: edi/di
S: esi/si
q: 任意4个通用寄存器:eax/ebx/ecx/edx
r: 任意6个通用寄存器:eax/ebc/ecx/edx/esi/edi
g: q+内存
A: eax和edx组成的64bit整数
f: 浮点寄存器
t: 第一个浮点寄存器
u: 第2个浮点寄存器例(对比基本内联汇编):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23#include <stdio.h>
#define ps(str) printf("%s",str);
char *str="hello,world\n";
int count=0;
int in_a=1,in_b=2,out_sum;
int main()
{
asm("pusha;\
movl in_a,%eax;\
movl in_b,%ebx;\
addl %ebx,%eax;\
movl %eax,out_sum;\
popa");
printf("sum is %d",out_sum);
in_a=16;
in_b=22;
asm("addl %%ebx,%%eax":"=a"(out_sum):"a"(in_a),"b"(in_b));
printf("sum is %d",out_sum);
return 0;
}方便,并且由gcc完成将input赋值到寄存器中和寄存器赋值到output中
注意:
%
在扩展内联汇编中留给占位符用,寄存器使用2个%,既%%ebx
内存约束
gcc将input和output中的C变量的地址作为操作数,直接进行内存读写,
1
2m: 表示操作数可以使用任意一种内存形式
o: 操作数为内存变量,但是访问是通过偏移量的形式(包含offset_address的格式),例子:
1
2
3
4
5
6
7
8
9
10
11#include <stdio.h>
#define ps(str) printf("%s",str);
int main()
{
int in_a=1,in_b=2;
printf("in_b is %d\n",in_b);
asm("movb %b0,%1;"::"a"(in_a),"m"(in_b));
printf("in_b is %d\n",in_b);
return 0;
}立即数约束
gcc在传值的时候不通过内存和寄存器,直接将立即数传递给汇编代码,
1
2
3
4
5
6
7i: 表示操作数为整数立即数
F: 浮点立即数
I: 0-31之间的立即数
J: 0-63之间的立即数
N: 0-255之间的立即数
O: 0-32之间的立即数
X: 操作数为任何类型的立即数通用约束
- 序号占位符
名称占位符
[名称] "[操作类型]约束名" (C变量)
1
2
3
4
5
6
7
8
9
10
11
12#include <stdio.h>
int main()
{
int in_a=1;
int in_b=2;
int out;
asm("addl %%ebx,%%eax;"
"movl %%eax,%[result]":"+a"(in_a):"b"(in_b),[result]"m"(out));
// in_a和out都是结果
// +表示既作为输入又作为输出结果
printf("in_a is %d\nout is %d\n",in_a,out);
}
操作数类型修饰
output中:
=
:表示操作数只写,如:=a(var)
,等价与var=eax
+
:表示操作数是可读写的,寄存器或内存前被读入,然后被写入&
:表示output的操作数要独占被约束的寄存器, input中约束的寄存器不能与此相同
input:
%
:此操作数可以和下一个操作数互换
+
例子:
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!