ASM汇编语言基础
简介
计算机最底层的语言是机器语言;
机器语言由一系列机器指令组成,现代二进制计算机的机器指令都是一系列01串,对应硬件中的高低电平;
汇编语言是计算机机器语言的一种助记表示;
汇编语言的主体是汇编指令,每条汇编指令对应一条机器指令;
汇编语言可直接访问CPU内部的各个部件,CPU安装汇编语言生成的机器码按序执行;
汇编语言和具体的机器体系架构直接相关;
CPU架构
寄存器
寄存器按照其用途可分为以下4类:
- 数据寄存器
- 指针及变址寄存器
- 段寄存器
- 控制寄存器
类型 | 寄存器名称 | 8位 | 16位 | 32位 | 64位 | 说明 |
---|---|---|---|---|---|---|
通用数据寄存器 | 用来存放操作数,运算结果或者其他信息。 | |||||
累加寄存器 | AH|AL | AX | EAX | RAX | ||
基址寄存器 | BH|BL | BX | EBX | RBX | ||
计数寄存器 | CH|CL | CX | ECX | RCX | ||
数据寄存器 | DH|DL | DX | EDX | RDX | 在某些IO指令中,DX被用来存放端口地址; | |
指针及变址寄存器 | ||||||
基址寄存器 (BasePoint) | BP | EBP | RBP | 栈桢(Frame)底指针 | ||
栈顶指针寄存器 (StackPoint) | SP | ESP | RSP | 栈栈顶指针 | ||
源变址寄存器 (SourceIndex) | SI | ESI | RSI | DS:SI一般表示源地址 | ||
目的变址寄存器 (DestIndex) | DI | EDI | RDI | ES:DI一般表示目的地址 | ||
指令及状态寄存器 | ||||||
指令指针寄存器 (InstructPointer) | IP | EIP | RIP | CS:IP表示了当前执行指令在内存中的地址 | ||
状态寄存器 | FLAGS | EFLAGS | RFLAGS | 存放条件标志码、控制标志和系统标志等 | ||
段寄存器 | ||||||
代码段寄存器 (CodeSegment) | CS | ECS | 代码段起始地址 | |||
数据段寄存器 (DataSegment) | DS | EDS | 数据段起始地址 | |||
栈寄存器 (StackSegment) | SS | ESS | 栈 | |||
附加段寄存器 (ExtentSegment) | ES | EES | ||||
FS | EFS | |||||
GS | EGS | |||||
TSS寄存器 | TR | 指向当前任务的TSS段 | ||||
中断描述符表寄存器 | IDTR | 指向中断表述符表在内存中的起始位置 | ||||
全局描述符表寄存器 | GDTR | |||||
局部描述符表寄存器 | LDTR |
寄存器使用约定
x86 调用约定(不同编译器可能不遵守):
%rax
:用作返回值寄存器%rip
:指令指针寄存器,存放下条指令的地址
函数调用时:
如果参数数量 <= 6,就通过
%rdi
、%rsi
、%rdx
、%rcx
、%r8
、%r9
的寄存器来传递参数,分别对应函数的第 1 ~ 6 个参数。如果参数数量 > 6,需要通过栈来传递。传递时对齐 8 字节。
调用者保存寄存器和被调用者保存寄存器
调用者(Caller)保存:
%rdi, %rsi, %rdx, %rcx, %r8, %r9, %r10, %r11
被调用者(Callee)保存:
%rbx, %rbp, %rsp, %r12, %r13, %r14
栈桢
%rsp
和 %rbp
都可以被称为栈指针(stack pointer)。
%rsp
是栈顶指针,它指示栈顶的位置。%rbp
是栈基址指针,它指向当前栈底的位置,它是历史栈顶的快照,因此也充当各栈帧的分隔符。
通过 %rbp
,内存中串起一个栈帧链表。只要不断对其解引用就可以回溯到最前面的栈帧。
x86汇编语言格式
x86汇编语言是基于intel x86系列处理器所用的汇编语言标准;
常用的x86汇编语言格式主要分为2种:
intel x86汇编语言格式
- 由intel自己主导设计;
AT&T x86汇编语言格式
由发明unix的AT&T实验室设计;
unix, linux, gcc相关的工具链都是使用该汇编语言格式;
go语言的汇编是AT&T汇编的一个变种;
Intel 和 AT&T 汇编区别
intel和AT&T汇编的主要区别如下:
Intel Code | AT&T Code | 说明 |
---|---|---|
mov eax,1 | movl $1,%eax | 将eax设为1 |
mov ebx, 0ffh | movl $0xff,%ebx | 将ebx设置为0xff |
int 80h | int $0x80 | 调用0x80中断处理程序 |
mov ebx, eax | movl %eax, %ebx | 将eax中的值复制到ebx中 |
mov eax,[ecx] | movl (%ecx),%eax | 将eax中的值作为偏移, |
mov eax,[ebx+3] | movl 3(%ebx),%eax | |
mov eax,[ebx+20h] | movl 0x20(%ebx),%eax | |
add eax,[ebx+ecx*2h] | addl (%ebx,%ecx,0x2),%eax | |
lea eax,[ebx+ecx] | leal (%ebx,%ecx),%eax | |
sub eax,[ebx+ecx*4h-20h] | subl -0x20(%ebx,%ecx,0x4),%eax |
Intel | AT&T | 说明 | |
---|---|---|---|
寄存器命名 | eax | %eax | |
操作数顺序 | |||
立即数 | _value | $_value | |
mov ebx,0xd00d | movl $0xd00d,%ebx | 将0xd00d地址存入ebx寄存器 | |
操作数长度标识 | mov bx,ax | movw %ax,%bx | |
间接寻址方式 | immed32(basepointer, indexpointer,indexscale) | [basepointer + indexpointer*indexscale + immed32] |
汇编语言组成
汇编语言由一系列汇编指令和伪指令组成;
每条汇编指令对应一条机器码,可由机器直接执行生成的机器码;
伪指令用于汇编程序提供附加信息,用于控制汇编程序生成机器码,本身没有机器码对应,无法由机器执行;
指令()
指令由字母开头;
指令格式为:
<OP> []
;每条指令至少包含一个操作码;
每条指令对应一条机器码;
指令生成机器码后,可由硬件执行;
伪指令(Pseudo Instruction)
伪指令(Pseudo Instruction)是用于对汇编过程进行控制的指令;
伪指令只用于汇编过程中为汇编程序提供汇编信息,不可执行指令,没有机器代码。例如:哪些是指令、哪些是数据及数据的字长、程序的起始地址和结束地址等;
伪指令都由句号('.')开头,其余是字母,通常使用小写;
类别 | 伪指令 | 格式 | 说明 | 示例 |
---|---|---|---|---|
.code16 | .code16 | 生成16位的指令 | ||
.globl | .globl <label_name> | 将符号定义为全局可链接的, 使符号可跨文件访问 | .globl SYMBOL_NAME(idt) | |
.extern | .extern <label_name> | 定义外部符号 | ||
.text | ||||
数据定义 | ||||
.byte | .byte | 定义字节 | .byte 0x80,0xca | |
.word | .word | .word 0x800, | ||
.file | .file " | 开启一个逻辑文件 | .file ”stage1.s” | |
.fill | .fill | 填充 | .fill NR_CPUS*4,8,0 | |
符号定义 | ||||
.equ | ||||
伪指令分为:
数据定义伪指令
格式:
[<变量名>] <类型> <初值表>
符号定义伪指令
EQU(等值伪指令):
<符号名> EQU <表达式>
|
|
内存寻址
intel | AT&T | ||
---|---|---|---|
立即数寻址 | MOV EBX, 2 | movl $2, %ebx | 将32bit的立即数2存入寄存器ebx中 |
直接寻址 | MOV EAX, 8000h | movl 0x8000, %eax | 将内存地址0x8000上的32位数据放到eax中 |
间接寻址 | MOV EBX, [EAX] | movl (%eax), %ebx | |
基址寻址 | MOV EBX, [EAX-4] | movl -4(%eax), %ebx | |
变址寻址 | MOV ECX, [EAX+EBX+4] | movl 4(%eax,%ebx), %ecx |
AT&T汇编
AT&T伪指令
|
|
变量
- 寄存器前被冠以“%”
- 立即数前被冠以“$”
- 十六进制数前被冠以“0x”
|
|
应该放在.data
之后,使用“.类型 值
”的形式定义,基本类型有byte、short、long 3种,分别占用1/2/4个字节。
然后在之前加上标签。标签不是必须的,标签是当前数据的地址,在其它地方可以使用标签。还有其它定义形式:
|
|
第一行,类似指定一个数组。
第二行,10个字节的数据,全部为0。
第三行,一个字符串。当然,需要.globl声明这个符号,以及指定对齐、类型、大小等。
定义函数
和变量定义类似
|
|
操作数
有3类操作数,立即数、寄存器和存储器引用。下面是3种操作数的AT&T语法:
立即数,使用$加一个数字,如$1, $-20, $0x12等。
寄存器,使用%加上寄存器的名字,如%ebp, %ah等。
存储器引用,格式为Imm(Ea,Ei,s),存储器的值为Imm + Ea + Ei * s,s必须为1,2,4,8。可以省略Imm, Ei和s。如下合法的存储器引用:(%ebp), -1(%ebp), 7(%edx,%edx,4)。甚至可以直接是数字表示的内存地址,如0x100.
标签作操作数
如果标签var是变量,则var表示的变量的内存地址,$var表示的内存地址的立即数。
mov var, %eax,把var地址处内存的值拷贝到eax。
mov $var, %eax,把var值拷贝到寄存器eax。
mov var+1, %eax,把var+1地址处内存的值拷贝到eax。
mov $var+1, %eax,把var+1值拷贝到寄存器eax。
指令后缀
C声明 | Intel数据类型 | 后缀 | 大小 |
---|---|---|---|
char | 字节 | b | 1 |
short | 字 | w | 2 |
int | 双字 | l | 4 |
long | 四字 | q | 8 |
char* | 四字 | q | 8 |
float | 单精度 | s | 4 |
double | 双精度 | l | 8 |
重要的指令
call
负责将被调指令地址写入
rip
还要负责将返回地址压入栈中
push/pop
push
把数据压入栈。等价于:先减去栈指针的值,然后将压入数据的值写入栈顶。
|
|
pop
把数据弹出栈,是 push
的逆变换。等价于:把栈顶数据写入操作数,然后增加栈指针的值。
|
|
AT&T汇编示例
|
|
对应的AT&T汇编代码为:
|
|
编译
|
|
|
|
Intel汇编
通用数据传送指令
|
|
输入输出指令
|
|
目的地址传送指令
|
|
标志传送指令
|
|
EFLAGS寄存器
- FR(控制标志位):
- CF(进位标识位): 进行加减运算时, 如果最高二进制位产生进位或错位, CF则为1, 否则为0. 程序设计中, 常用条件转移指令JC, JNC指令据此标志位实现转移
- PF(奇偶标志位): 操作结果中二进制位1的个数为偶数是, PF为1, 某则为0
- AF(辅助进位标志位):运算时半字节产生进位或借位时, AF为1, 某则为0. 主要用于BCD码的调整
- ZF(零标志位): 运算结果为0时, ZF为1, 否则为0
- SF(符号标志位): 当运算结果的最高位为1时, SF为1, 否则为0. 最高位表示符号数的正和负
- TF(跟踪标志位): 用于调试程序时进入单步方式工作. TF=1时, 每条指令执行完后产生一个内部中断, 让用户检查指令运行后寄存器, 存储器和各标志位的内容. TF=0时, CPU工作正常, 不产生内部中断
- IF(中断允许标志位): IF=1同时中断屏蔽寄存器的相应位为0, 允许系统响应可屏蔽中断, 反之, 不接收外部发出的中断请求
- DF(方向位标志位): 用于控制串操作时地址指针位移方向. 当DF=1时, 指针向高地址方向移动
- OF(溢出标志位): 算术运算时结果超出系统所能表示的数的范围. 溢出时, OF=1
跳转指令
C语言程序的堆栈图
|
|
堆栈图如下(过程)
不同的编译器生成的堆栈图可能不同,要视情况而定
intel汇编示例
|
|
|
|