5.汇编语言
文章目录
- 5.==汇编语言==
- 5.1考试要求(408)
- x86汇编语言
- 5.2地址码
- x86架构CPU,有哪些寄存器?
- 总结
- 5.3操作码
- 5.3.1算术运算
- 5.3.2逻辑运算
- 5.3.3其他
- 5.4循环分支
- 5.4.1 jmp直接跳转指令
- 5.4.2 jxxx条件跳转指令
- 分支C→汇编
- 循环C→汇编
- 用loop指令实现循环
- 5.5函数调用
- 5.5.1函数调用栈在内存中的位置
- 5.5.2两种方式访问栈帧数据
- 5.5.3函数调用时,如何切换栈帧
- 5.5.4一个栈帧内可能包含哪些内容?
- 5.5.5如何传递参数和返回值
- 总结
- 5.6汇编格式
高级语言与机器级代码之间的对应:
汇编语言和机器语言都是机器级代码,是一一对应的。
5.1考试要求(408)
- 只需关注x86汇编语言;若考察其他汇编语言题目会详细注释。
- 题目给出某段简单程序的c语言、汇编语言、机器语言表示。能结合c语言看懂汇编语言的关键语句(看懂常见指令、选择结构、循环结构、函数调用)。
- 汇编语言、机器语言一一对应,要能结合汇编语言分析机器语言指令的格式、寻址方式。
不会考:将c语言人工翻译为汇编语言或机器语言。
x86汇编语言
mov为例子:
mov 要移动到的位置destination, 被移动的内容/位置source;
mov eax, ebx #寄存器→寄存器
mov eax, dword ptr [af996h] #主存→寄存器
mov eax, 5 #立即数→寄存器
#将ebx所指主存地址的32bit复制到eax寄存器中(寄存器间接寻址)
mov eax, dword ptr [ebx]
mov eax,[ebx] #若未指明主存读写长度,默认32 bit
#将eax的内容复制到af996h所指的地址
mov [af996h], eax #未指明长度默认32bit
#将eax的内容复制到ebx所指主存地址的32bit
mov dword ptr [ebx], eax
#将ebx所指的主存地址的8bit复制到eax
mov eax, byte ptr [ebx]
#将ebx+8所指主存地址的32bit 复制到eax寄存器中
mov eax, dword ptr [ebx+8]
#将af996-12所指主存地址的 32bit复制到eax寄存器中
mov eax, dword ptr [af996-12h]
5.2地址码
x86架构CPU,有哪些寄存器?
每个寄存器都是32bit,32bit = Extended = E。
都是E开头的,包含32bit数据。
-
通用寄存器 X
- EAX
- EBX
- ECX
- EDX
-
变址寄存器 I = index
变址寄存器可用于线性表、字符串的处理。
- ESI: source index(被移动的)
- EDI: destination index(要移动到的目的)
-
堆栈寄存器 P = pointer
用于函数调用。
- EBP: 堆栈基指针base pointer
- ESP: 堆栈顶指针stack pointer
- 通用寄存器还可以使用一半寄存器(低16位)
AX, BX, CX, DX:16bit
两个变址寄存器只能固定使用32bit;
两个堆栈寄存器只能固定使用32bit。
- 甚至还可以使用1/4=8bit:
AL, AH
BL, BH
总结
5.3操作码
操作码 地址码
操作码 d, s
#王道书中:
add <reg>/<mem>, <reg>/<mem>/<con>
#要注意,一般不建议同时访问两个主存:
add <mem>, <mem> # ×,访存太多是不可以的
- destination:目的地(d 目的操作数)
- source:来源地(s 源操作数)
目的操作数d不可以是常量,因为进行完操作之后还要把数据放到d的位置。
还有:
reg
:寄存器registermem
:内存memorycon
:常数constant
5.3.1算术运算
功能 | 英文 | 汇编指令 | 注释 |
---|---|---|---|
加 | add | add d, s | #计算d+s,结果存入d |
减 | subtract | sub d, s | #计算d-s,结果存入d |
乘 | multiply | mul d, s imul d, s | #无符号数d*s,乘积存入d #有符号数d*s,乘积存入d |
除 | divide | div s idiv s | #无符号数除法:edx:eax/s,商存入eax,余数存入edx #有符号数除法:edx:eax/s,商存入eax,余数存入edx |
取负数 | negative | neg d | #将d取负数,结果存入d |
自增 ++ | increase | inc d | #将d++,结果存入d |
自减 - - | decrease | dec d | #将d–,结果存入d |
【注意】除法(被除数/除数)用到了隐含寻址,s是除数,而被除数提前放到了edx和eax。
【注意!】add d, s在这里是(d)+(s)→(d),
但是有的是写add s, d就是(d)+(s)→(d)。
5.3.2逻辑运算
功能 | 英文 | 汇编指令 | 注释 |
---|---|---|---|
与 | and | and d, s | #将d、s逐位相与,结果放回d |
或 | or | or d, s | #将d、s 逐位相或,结果放回d |
非 | not | not d | #将d逐位取反,结果放回d |
异或 | exclusive or | xor d, s | #将d、s逐位异或,结果放回d |
左移 | shift left | shl d, s | #将d逻辑左移s位,结果放回d(通常s是常量) |
右移 | shift right | shr d, s | #将d逻辑右移s位,结果放回d(通常s是常量) |
5.3.3其他
- 用于实现分支结构、循环结构的指令:
cmp
:比较。test
jmp
:直接跳转。jxxx
:条件跳转。loop
:封装循环。
- 用于实现函数调用的指令:
push
:放入函数调用栈。pop
:从函数调用栈出栈。call
:函数调用。- ①将IP旧值压栈保存(保存在函数的栈帧顶部);
- ②设置IP新值,无条件转移至被调用函数的第一条指令。
ret
:函数返回。- 从函数的栈帧顶部找到IP旧值,将其出栈并恢复IP寄存器。
- 用于实现数据转移的指令:
mov
【注意】Intel x86处理器中,程序计数器PC ( Program Counter)通常被称为IP(Instruction Pointer)。
5.4循环分支
5.4.1 jmp直接跳转指令
jmp(jump)
jmp <address>
jmp 124
jmp eax
jmp [985]
#其中:
exa = 124
[985] = 124
这个地址可以是直接一个数字,也可以是寄存器或者主存
但是其实程序员其实是不知道指令在内存的位置,所以使用**NEXT:
标号**来锚定位置。
(标号,有冒号就是,NEXT是名字,可以自己改)。
例如:
mov eax, 1
mov ebx, 2
jmp BIAOHAO
add ebx, 2
BIAOHAO:
add ebx, exa
5.4.2 jxxx条件跳转指令
先**比较cmp
**两个数:
cmp本质上是进行a-b减法运算,并生成标志位OF、ZF、CF、SF,放入PSW程序状态字寄存器(Intel称其为“标志寄存器”)。
cmp d, s
然后紧跟跳转指令:
#jump when equal,若a==b则跳转
je <地址>
#jump when not equal,若a != b则跳转
jne <地址>
#jump when greater than,若a>b则跳转
jg <地址>
#jump when greater than or equal to,若a>=b则跳转
jge <地址>
#jump when less than,若a<b则跳转
jl <地址>
#jump when less than or equal to,若a<=b则跳转
jle <地址>
- e:等于equal
- n:不not
- g:大于greater
- l:小于less
例如:
cmp eax, ebx
je NEXT
分支C→汇编
那么就可以把c语言的代码翻译为汇编代码(机器级表示)
C:
int a=7;
int b=6;
if(a>b){
c=a;
}else{
c=b;
}
assembly:
mov eax,7 #假设变量a=7,存入eax
mov ebx,6 #假设变量b=6,存入ebx
cmp eax,ebx #比较变量a和b
jg NEXT #若a>b,转移到NEXT:
# 如果没有跳转,那么顺序执行,就是else
mov ecx ,ebx #假设用ecx存储变量c,令c=b
jmp END #无条件转移到END:
NEXT:
mov ecx ,eax #假设用ecx存储变量c,令c=a
END:
循环C→汇编
用条件转移指令实现循环,需要4个部分构成:
①循环前的初始化
②是否直接跳过循环?
③循环主体
④是否继续循环?
用loop指令实现循环
【注意】loop默认使用ECX作为循环计数器(只能是ECX)。
5.5函数调用
函数的栈帧( Stack Frame):保存函数大括号内定义的局部变量、保存函数调用相关的信息。
5.5.1函数调用栈在内存中的位置
地址码中的堆栈寄存器(P,pointer)用于函数调用。
- EBP: 堆栈基指针base pointer,指向栈的底部。
- ESP: 堆栈顶指针stack pointer ,指向栈的顶部(下面是“顶”,开口的)。
5.5.2两种方式访问栈帧数据
push
、pop
指令实现入栈、出栈操作,x86默认以4字节为单位。指令格式如:
5.5.3函数调用时,如何切换栈帧
在每一个函数前加上“例行处理”
push ebp #保存上一层函数的栈帧基址(ebp旧值)
mov ebp, esp #设置当前函数的栈帧基址(ebp新值)
#等价于:
enter #零地址指令,进入
在函数结束的时候,就移除函数栈,例行处理:
mov esp, ebp #让esp指向当前栈帧的底部
pop ebp #将esp所指元素出栈,写入寄存器ebp
#等价于:
leave
5.5.4一个栈帧内可能包含哪些内容?
一个函数栈:
0xFFFF FFFF
栈底
EBP
向上(栈底)是加
向下(栈顶)是减
ESP
栈顶
0x0000 0000
- 上一层栈帧基址:栈帧最底部一定是上一层栈帧基址(ebp旧值)。
- 返回地址:栈帧最顶部一定是返回地址(当前函数的栈帧除外)。
- 局部变量:保存在栈底,如果出现
[ebp-4]
(最后一个定义的变量)或ebp-8
这种,一般是局部变量。 - 调用参数:保存在栈顶,
[ebp+8]
(第一个调用参数),ebp+12
(第二个调用参数)…。
为什么是+8,因为+4保存了IP(PC)返回地址。这里的ebp不是上面的ebp,而是调用参数的ebp,属于上面函数的esp。 - 空闲:gcc编译器将每个栈帧大小设置为16B的整数倍(当前函数的栈帧除外),因此栈帧内可能出现空闲未使用的区域。
具体:
5.5.5如何传递参数和返回值
4.3_6_4_如何传递参数和返回值(函数调用的机器级表示)_哔哩哔哩_bilibili
相加之后,最终的结果存储在EAX,那么leave之后,caller函数只需要从EAX种就可以取到返回值。
总结
除了main函数,其他所有函数的汇编代码结构都一样!
5.6汇编格式
AT&T 格式:Unix、Linux的常用格式。
intel 格式:Windows的常用格式。(408常考,也是我们这里讲的)