目录
高级语言与机器级代码之间的对应
- 指令的作用?1.改变程序执行流 2.处理数据
- 指令格式:
- 操作码:怎么处理?
- 地址码:数据在哪儿?
- 在寄存器里。在汇编指令中,给出寄存器名(通用寄存器:eax、ebx、ecx、edx;变址寄存器:esi、edi;堆栈寄存器:ebp、esp)
- 在主存里:在汇编指令中,给出读写长度、主存地址(dword ptr[地址] #32bit; word ptr[地址] #16bit;type ptr[地址] #8bit)
- 在指令里:在汇编指令中,直接给出常量,即“立即寻址”;可用十进制表示、也可用十六进制表示(通常用h结尾)
常见的算术运算指令
功能 | 英文 | 汇编指令 | 注释 |
---|---|---|---|
加 | 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 |
常见的逻辑运算指令
功能 | 英文 | 汇编指令 | 注释 |
---|---|---|---|
与 | 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是常量) |
AT&T格式 v.s. Intel格式
AT&T格式 | Intel格式 | |
---|---|---|
目的操作数d、源操作数s | op s,d 注:源操作数在左,目的操作数在右 | op d,s 注:源操作数在右,目的操作数在左 |
寄存器的表示 | mov %ebx,%eax 注:寄存器名之前必须加“%” | mov eax,ebx 注:直接写寄存器名即可 |
立即数表示 | mov $985,%eax 注:立即数之前必须加“$” | mov eax,985 注:直接写数字即可 |
主存地址表示 | mov %eax,(af996h) 注:用“()” | mov [af996h],eax 注:用“[]” |
读写长度表示 | movb $5,(af996h) movw $5,(af996h) movl $5,(af996h) addb $4,(af996h) 注:指令后加b、w、l分别表示读写长度为byte、word、dword | mov byte ptr[af996h],5 mov word ptr[af996h],5 mov dword ptr[af996h],5 add byte ptr[af996h],4 注:在主存地址前说明读写长度byte、word、dword |
主存地址偏移量表示 | movl -8(%ebx),%eax 注:偏移量(基址) movl 4(%ebx,%eax,32),%eax 注:偏移量(基址,变址,比例因子) | mov eax,[ebx - 8] 注:[基址+偏移量] mov eax,[ebx + eax*32 +4] 注:[基址+变址*比例因子+偏移量] |
选择语句的机器级表示
无条件转移指令——jmp
mov eax,7
mov ebx,6
jum NEXT
mov ecx,ebx
NEXT: #用“标号”锚定位置 特征——有冒号,名字可以自己取
mov ecx,eax
条件转移指令——jxxx
cmp a,b #比较a和b两个数
je<地址> #jump when equal,若a=b则跳转
jne<地址> #jump when not equal,若a!=b则跳转
jg<地址> #jump when greater than,若a>b则跳转
jge<地址> #jump when greater than or equal to,若a>=b则跳转
jl<地址> #jump when less than,若a<b则跳转
jle<地址> #jump when less than or equal to,若a<=b则跳转
示例:选择语句的机器级表示
if(a > b){
c = a;
}else{
c = b;
}
mov eax,7
mov ebx,6
cmp eax,ebx
jg NEXT
mov ecx,ebx
jum END
NEXT:
mov ecx,eax
END:
循环语句的机器级表示
用条件转移指令实现循环
int result = 0;
for(int i = 1;i <= 100;i++){
result += i;
}//求1+2+3+…+100
用条件转移指令实现循环,需要4个部分构成:
- 循环前的初始化
- 是否直接跳过循环?
- 循环主题
- 是否继续循环?
mov eax,0 #1.用eax保存result,初值为0
mov edx,1 #1.用edx保存i,初始值为1
cmp edx,100 #2.比较i和100
jg L2 #2.若i>100,转跳到L2执行
L1: #循环主体
add eax,edx #3.实现 result += i
inc edx #3.inc自增指令,实现i++
cmp edx,100 #4.i和100
jle L1 #4.若i <= 100,转跳到L1执行
L2: #跳出循环主体
用loop指令实现循环
for(int i = 500;i > 0;i--){
做某些处理;
}//循环500轮
mov ecx,500 #用ecx作为循环寄存器
Looptup: #循环的开始
...
做某些处理
...
loop Looptop #ecx--,若ecx!=0,跳转到Looptop
#等价于dec ecx
#cmp ecx,0
#jne Looptop
理论上,能用loop指令实现的功能一定能用条件转移指令实现。
使用loop指令可能会使代码更清晰简洁。
补充:loopx指令——loopnz,loopz
loopnz——当ecx≠0&&ZF==0时,继续循环
loopz——当ecx≠0&&ZF==1时,继续循环
函数调用机器级表示
call、ret指令
-
函数调用指令:call<函数名>
函数返回指令:ret
-
call指令的作用:
- 将IP旧值压栈保存(保存在函数的栈帧顶部)(效果相当于 push IP)
- 设置IP新值,无条件转移至被调用函数的第一条指令(效果相当于jmp add)
-
ret指令的作用:从函数的栈帧顶部找到IP旧值,将其出栈并恢复IP寄存器
如何访问栈帧?
栈帧(Stack Frame)保存函数大括号内定义的局部变量,保存函数调用相关信息。
访问栈帧数据:push、pop指令
push、pop指令实现入栈、出栈操作,x86默认以4字节为单位。指令格式如下:
push 🏇 //先让esp减4,再将🏇压入(🏇可以是立即数、寄存器、主存地址)
pop 🐘 //栈顶元素出栈写入🐘,再让esp加4(🐘可以是寄存器、主存地址)
访问栈帧数据:mov指令
sub esp,12 #栈顶指针-12
mov[esp+8],eax #将eax的值复制到主存[esp+8]
mov[esp+4],985 #将985复制到主存[esp+4]
mov eax,[ebp+8] #将主存[ebp+8]的值复制到eax
mov [esp],eax #将eax的值复制到主存[esp]
add esp,8 #栈顶指针+8
可以用mov指令,结合esp、ebp指针访问栈帧数据
可以用减法/加法指令,即sub/add修改栈顶指针esp的值
如何切换栈帧?
call指令的作用:
将IP旧值压栈保存(效果相当于 push IP)
设置IP新值,无条件转移至被调用函数的第一条指令(效果相当于jmp add)
push ebo #保存上一层函数的栈帧基址(ebp旧值)
mov ebp,esp #设置当前函数的栈帧基址(ebp新值)
在每个函数开头的“例行处理”。两条指令等价于enter
mov esp,ebp #让esp指向当前栈帧的底部
pop ebp #将esp所指元素出栈,写入寄存器ebp
在每个函数ret前的“例行处理”。两条指令等价于leave
ret指令的作用:从函数的栈帧顶部找到IP旧值,将其出栈并恢复IP寄存器
栈帧内包含哪些内容?
- gcc编译器将每个栈帧大小设置为16B的整数倍,因此战阵内可能出现空闲未使用的区域。
- 通常将局部变量集中存储在栈帧底部区域
- 通常将调用参数集中存储在栈帧顶部区域
- 栈帧最底部一定是上一层栈帧基址(ebp旧值)
- 栈帧最顶部一定是返回地址(当前函数的栈帧除外)
参数、返回值传递
- 在cal指令前,将调用参数写入栈帧顶部区域
- 在ret指令前,将函数返回值写入eax寄存器