考虑如下问题:
int subtract(int a, int b); // 函数声明
int sub = subtract(3, 2); // 调用函数
; 主调用者
; ----------
push 2 ; 参数 b
push 3 ; 参数 a
call subtract
; 被调用者
; ----------
push ebp ; 备份 ebp
mov ebp, esp ; 保存 esp, 用 ebp 来访问栈
mov eax, [ebp + 0x8]; a
add eax, [ebp + 0xc]; b
; 为了防止过程中 push 等其它操作使 esp 改变,可能导致最后无法正确 ret 返回
; 因此在此恢复 esp
mov esp, ebp ; 可有可无, 因为中间没有修改 esp 的操作
pop ebp ; 恢复 ebp
ret 8 ; 先返回,然后 esp + 8
stdcall 由被调用者清理栈空间。
int subtract(int a, int b); // 函数声明
int sub = subtract(3, 2); // 调用函数
; 主调用者
; ----------
push 2 ; 参数 b
push 3 ; 参数 a
call subtract
add esp, 8 ; 清理栈空间
; 被调用者
; ----------
push ebp ; 备份 ebp
mov ebp, esp ; 保存 esp, 用 ebp 来访问栈
mov eax, [ebp + 0x8]; a
add eax, [ebp + 0xc]; b
; 为了防止过程中 push 等其它操作使 esp 改变,可能导致最后无法正确 ret 返回
; 因此在此恢复 esp
mov esp, ebp ; 可有可无, 因为中间没有修改 esp 的操作
pop ebp ; 恢复 ebp
ret
cdecl 由调用者清理栈空间。
汇编的方式有:
AT&T 是汇编语言的一种语法风格、格式。并不是一种新的语言。
在指令的末尾有个后缀,表示操作数大小,
b:1byte
,w:2byte
,l:4byte
。例如:push => pushl
表示压入 4 字节。
固定寻址格式:base_address(offset_address, index, size)
对应表达式为:base_address + offset_address + index * size
注意:括号内不用的参数,需要用逗号代替。
base_address
:基地址offset_address
:偏移地址,必须是八大通用寄存器之一index
:索引值,必须是八大通用寄存器之一size
:因子,只能是 1、2、4、8(Intel 语法中也只能乘于这几个)直接寻址:base_address,例如:
movl $255, 0xc00008F0
寄存器间接寻址:(offset_address),只能用通用寄存器存储 offset_address,存储的内容为地址。
mov (%eax), %ebx
寄存器相对寻址:base_address(offset_address),也就是得到的内存为基地址+偏移地址
之和。
变址寻址: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 所指向的内存
固定格式:asm [volatile] ("assembly code")
asm
:asm
和 __asm__
一样,都是由 GCC 定义的宏:#define __asm__ asm
。
volatile
:是关键字,volatile
和 __volatile__
一样,都是由 GCC 定义的宏:#define __volatile volatile
。
在 GCC 中有个 -O
参数,使用该参数编译时,GCC 会按照自己的意图优化代码,可能会因此修改了自己写的代码。volatile
关键字的作用是告诉 GCC 不要修改我写的汇编代码。
assembly code
:编写汇编代码。
\
进行转义。;
。char* str = "hello, world\n";
int count = 0;
void main() {
asm("pusha; \
movl $4, %eax; \
movl $1, %ebx; \
movl str, %ecx; \
movl $12, %edx; \
int $0x80; \
mov %eax, count; \
popa");
}
基本内联汇编的局限性:在基本内联汇编中,若要引用 C 变量,只能将其定义为全局变量。若定义为局部,则链接时会找不到这两个符号。
格式:asm [volatile] ("assembly code" : output : input : clobber/modify)
output
:作为汇编代码的输出,这样 C 才可以访问汇编的数据。
格式:"操作数修饰符和约束名"(C 变量名)
input
:作为汇编代码的输入,这样汇编才可以访问 C 的数据。
格式:"[操作数修饰符] 约束名"(C 变量名)
clobber/modify
:保护可能遭到破坏的一些寄存器或内存资源。
a
:表示寄存器 eax/ax/alb
:表示寄存器 ebx/bx/blc
:表示寄存器 ecx/cx/cld
:表示寄存器 edx/dx/dlD
:表示寄存器 edi/diS
:表示寄存器 esi/siq
:表示任意这 4 个通用寄存器之一:eax/ebx/ecx/edxr
:表示任意这 6 个通用寄存器之一:eax/ebx/ecx/edx/esi/edig
:表示可以存放到任意地点(寄存器或内存)A
:把 eax 和 edx 组成 64 位整数f
:表示浮点寄存器t
:表示第 1 个浮点寄存器u
:表示第 2 个浮点寄存器// 例:相加
// 基本内联汇编
int in_a = 1, in_b = 2, out_sum;
void main() {
asm("pusha; \
movl in_a, %eax; \
movl in_b, %ebx; \
addl %ebx, %eax; \
movl %eax, out_sum; \
popa");
printf("sum is %d\n", out_sum);
}
// 扩展内联汇编
#include
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 is %d\n", out_sum);
}
// ---------------------
output: 操作数修饰符为“=”, 约束名为“a”, 变量名为“out_sum”
input: 约束名为“a”, 变量名为“in_a” // 另一个同理
m
:把 C 变量的指针作为内联汇编代码的操作数。o
:操作数为内存变量,但访问它是通过偏移量的形式访问,即包含 offset_address 的格式。#include
void 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 now is %d\n", in_b);
}
i
:表示操作数为整数立即数F
:表示操作数为浮点数立即数I
:表示操作数为 0~31 之间的立即数J
:表示操作数为 0~63 之间的立即数N
:表示操作数为 0~255 之间的立即数O
:表示操作数为 0~32 之间的立即数X
:表示操作数为任何类型立即数0~9
:表示可与 output 和 input 中第 n 个操作数用相同的寄存器或内存。此约束只能用在 input 部分。
序号占位符是对在 output 和 input 中的操作数,按照它们从左到右出现的次序从 0 开始编号,一直到 9,也就是说最多支持 10 个序号占位符。
操作数用在 assembly code 中,引导它的格式是 %0~9
。
asm("addl %%ebx, %%eax" : "=a"(out_sum) : "a"(in_a), "b"(in_b));
// 等价于
asm("addl %2, %1" : "=a"(out_sum) : "a"(in_a), "b"(in_b));
格式:[名称]"约束名"(C 变量)
#include
void main() {
int in_a = 18, in_b = 3, out = 0;
asm("divb %[divisor]; movb %%al, %[result]" : [result]"=m"(out) : "a"(in_a), [divisor]"m"(in_b));
printf("result is %d\n", out);
}
在 output 中:
=
:表示操作数是只写。+
:表示操作数是可读可写。&
:表示此 output 中的操作数要独占所约束(分配)的寄存器,只供 output 使用,任何 input 中所分配的寄存器都不能与此相同。在 input 中:
%
:该操作数可以和下一个输入操作数互换。机器模式用来在机器层面上指定数据的大小及格式。