汇编
汇编
存储程序计算机
图灵机
冯诺依曼机
运算器、控制器、存储器、输入和输出设备
CPU由运算器(算术逻辑单元ALU),控制器和寄存器组成
寄存器程序计数器PC,在IA32(x86-32)中是EIP,指示要执行的下一条指令在存储器中的地址
x86-32汇编
- 通用寄存器
4个数据寄存器EAX,EBX,ECX,EDX
2个指针寄存器ESP,EBP
2个变址和指针寄存器ESI,EDI - 控制寄存器
1个指令指针寄存器EIP
1个标志寄存器EFlags - 段寄存器
6个段寄存器CS,ES,SS,DS,FS,GS
32位数据寄存器更具通用性,可以暂存数据,如ALU的运算结果,还可以作为指针寄存器
代码段寄存器CS,指令存储在代码段中,使用CS:EIP指明指令地址
堆栈段寄存器SS,每个进程都有内核态堆栈和用户态堆栈
寻址方式和汇编指令
汇编指令包括操作码和操作数
操作码,即汇编指令
movb, movw, movl, movq
操作数
- 立即数
即常数,如$8 - 寄存器数
如%eax - 存储器引用
寻址方式
- 寄存器寻址register
操作的是寄存器,不与内存交互,如movl %eax, %edx - 立即寻址immediate
$符号后跟一个数值,如movl $0x123, %edx - 直接寻址direct
直接用数值表示内存地址,如movl 0x123, %edx - 间接寻址indirect
寄存器加个小括号,表示寄存器中存储的是内存地址,如move (%ebx), %edx - 变址寻址displaced
在原地址上加一个立即数,如movl 4(%ebx), %edx
AT&T格式汇编,也是Linux内核使用的汇编格式
一般全是大写字母的为Intel汇编,全是小写字母的是AT&T汇编
pushl/popl
call/ret
pushl %eax
将EAX寄存器值压栈,实际上做了两个动作
subl $4, %esp
movl %eax, (%esp)
堆栈是向低地址增长的,所以用subl指令
popl %eax
从堆栈栈顶取一个存储单元放入EAX寄存器,实际上做了两个动作
movl (%esp), %eax
addl $4, %esp
call 0x12345
实际上做了两个动作,将EIP压栈,并将地址0x12345传入EIP寄存器,由硬件一次性完成
ret
将堆栈栈顶的一个存储单元放入EIP寄存器,该动作由硬件一次性完成
存储程序
函数调用堆栈
中断
ESP堆栈寄存器
EBP基址寄存器
x86体系结构,堆栈空间是从高地址向低地址增长的,即向上增长
堆栈操作push/pop
在函数传递返回值时,通过EAX寄存器来保存返回值,若有多个返回值,EAX返回内存地址
堆栈提供局部变量的空间,编译器会预留足够的栈空间以保存局部变量,但是早期编译器并没有这么做,而是要求局部变量声明在函数体前面
通过堆栈传递参数的方法是从右向左以此压栈,如形参的压栈顺序
C语言内嵌汇编
__asm__ __volatile__(
汇编语句模板:
输出部分:
输入部分:
破坏描述部分:
);
示例代码
#include<stdio.h>
int main(){
/* val1 + val2 = val3 */
unsigned int val1= 1;
unsigned int val2= 2;
unsigned int val3= 0;
printf("val1:%d, val2:%d, val3:%d\n", val1, val2, val3);
asm volatile(
"movl $0, %%eax\n\t" // clear %eax
"addl %1, %%eax\n\t" // %eax+= val1
"addl %2, %%eax\n\t" // %eax+= val2
"movl %%eax, %0\n\t" // val2= %eax
: "=m" (val3) // =m mean only write output memory variable
: "c" (val1), "d" (val2) // input c or d mean %ecx/%edx
);
printf("val1:%d + val2:%d = val3:%d\n", val1, val2, val3);
return 0;
}
__asm__是gcc关键字asm的宏定义,是内嵌汇编关键字
__volatile__是gcc关键字volatile的宏定义,告诉编译器不要优化代码
内嵌汇编的寄存器前有两个%,一个%加一个数字表示第二部分输出,第三部分输入或第四部分破坏描述
%1指的是val1,用c修饰,即存入ECX寄存器
%2指的是val2,用d修饰,即存入EDX寄存器
%0指的是val3,用=m修饰,即写入内存变量
内嵌汇编语法
内嵌汇编常用的修饰限定符
通用寄存器
| 限定符 | 说明 |
|---|---|
a |
EAX寄存器 |
b |
EBX寄存器 |
c |
ECX寄存器 |
d |
EDX寄存器 |
s |
ESI寄存器 |
D |
EDI寄存器 |
q |
将输入变量放入eax,ebx,ecx,edx中的一个 |
r |
将输入变量放入eax,ebx,ecx,esi,edi中的一个 |
A |
将eax和edx合成为一个64位寄存器 |
内存
| 限定符 | 说明 |
|---|---|
m |
内存变量 |
o |
操作数为内存变量,寻址方式是偏移量寻址 |
v |
操作数为内存变量,寻址方式不是偏移量寻址 |
. |
操作数为内存变量,寻址方式是自动增量 |
p |
操作数是一个合法的内存地址 |
操作数类型
| = | 操作数在指令中是只写的 |
| + | 操作数在指令中是读写的 |
寄存器或内存
| 限定符 | 说明 |
|---|---|
g |
将输入变量放入eax,ebx,ecx,edx中的一个或作为内存变量 |
x |
操作数可以是任何类型 |
立即数
| 限定符 | 说明 |
|---|---|
i |
0~31之间的立即数,用于32位移位指令 |
J |
0~63之间的立即数,用于64位移位指令 |
N |
0~255之间的立即数,用于out指令 |
l |
立即数 |
n |
立即数,有些系统不支持除字以外的立即数,这些系统应该使用n |
浮点数
| 限定符 | 说明 |
|---|---|
f |
浮点数 |
t |
第一个浮点寄存器 |
u |
第二个浮点寄存器 |
G |
标准的80387浮点常数 |
% |
该操作数可以和下一个操作数交换位置 |
示例
int main(){
int input, output, temp;
intput= 1;
__asm__ __volatile__(
"movl $0, %%eax;\n\t"
"movl %%eax, %1;\n\t"
"movl %2, %%eax;\n\t"
"movl %%eax, %0;\n\t"
: "=m" (output), "=m" (temp)
: "r" (input)
: "eax"
);
printf("%d, %d\n", temp, output);
return 0;
}
破坏描述部分"eax",表示EAX寄存器会被改动

浙公网安备 33010602011771号