# 高级语言与机器级代码之间的对应

# x86 汇编语言指令基础

指令的作用:改变程序执行流、处理数据

指令格式:操作码 + 地址码

数据在哪:寄存器、主存、指令

  • mov 指令功能:将源操作数 s 复制到目的操作 d 所指的位置
    • mov 目的操作数 d,源操作数
    • mov eax,ebx:将寄存器 ebx 的值复制到寄存器 eax
    • mov eax,5:将立即数 5 复制到寄存器 eax
    • mov eax,dword ptr [af996h]:将内存地址 af996h 所指的 32bit 复制到寄存器 eax
    • mov byte ptr [af996h],5:将立即数 5 复制到内存地址 af996h 所指的一字节中

指明内存读写长度:

  • dword ptr—— 双字,32bit
  • word ptr—— 单字,16bit
  • byte ptr—— 字节,8bit
  • 若未指明主存读写长度,默认 32bit

x86 架构 CPU 有哪些寄存器(每个寄存器都是 32bit):

  • 通用寄存器:EAX,EBX,ECX,EDX(X = 未知)
  • 变址寄存器 (ESI,EDI,S=Source,D=Destination):变址寄存器可用于线性表、字符串的处理
  • 堆栈寄存器:用于实现函数调用
    • 堆栈基指针 (EBP)
    • 堆栈顶指针 (ESP)

# 常用 x86 汇编指令

# 算数运算指令

算术运算:加、减、乘、除、取负数、自增 ++、自减 --

  • 加:add d,s (计算 d+s,结果存入 d)
  • 减:sub d,s (计算 d-s,结果存入 d)
  • 乘:mul d,s (无符号数 d*s,乘积存入 d);imul d,s (有符号数 d*s,乘积存入 d)
  • 除:div d,s (无符号数除法 edx:eax/s,商存入 eax,余数存入 edx);idiv d,s (有符号数除法 edx:eax/s,商存入 eax,余数存入 edx)
  • 取负数:neg d (将 d 取负数,存入 d)
  • 自增 ++:inc d (将 d++,结果存入 d)
  • 自减 --:dec d (将 d--,结果存入 d)

# 逻辑运算指令

逻辑运算:与、或、非、异或、左移、右移

  • 与:and d,s (将 d,s 逐位相与,结果放到 d)
  • 或:or d,s (将 d,s 逐位相或,结果放到 d)
  • 非:not d (将 d 逐位取反,结果放到 d)
  • 异或:xor d,s (将 d,s 逐位异或,结果放到 d)
  • 左移:shl d,s (将 d 逻辑左移 s 位,结果放到 d (通常 s 为常量))
  • 右移:shr d,s (将 d 逻辑右移 s 位,结果放到 d (通常 s 为常量))

# 其他指令

用于实现分支结构、循环结构的指令:cmp,test,jmp,jxxx

用于实现函数调用的指令:push,pop,call,ret

用于实现数据转移的指令:mov

# AT&T 格式和 Intel 格式

AT&T 格式:Unix、Linux 的常用格式

Intel 格式:Windows 的常用格式

AT&T 格式Intel 格式
目的操作数 d、源操作数 sop s,d 注:源操作数在左,目的操作数在右op d,s 注:源操作数在右,目的操作数在左
寄存器的表示mov % ebx,% eax 注:寄存器名之前必须加 %mov eax,ebx 注:直接写寄存器名即可
立即数的表示mov $985,% eax 注:立即数前必须加 $mov eax,985 注:直接写数字即可
主存地址的表示mov % eax,(af996h) 注:用小括号mov [af996h],eax 注:用中括号
读写长度的表示movb,movw,movl,addb 注:指令后加 b,w,l 分别表示读写长度为 byte,word,dwordmove byte ptr,move word ptr,move dword ptr,add byte ptr 注:在主存地址前说明读写长度 byte,word,dword
主存地址偏移量的表示movl -8 (% ebx),% eax 注:偏移量 (基址) movl 4 (% ebx,% ecx,32),% eax 注:偏移量 (基址,变址,比例因子)mov eax,[ebx,-8] 注:[基址 + 偏移量] mov eax,[ebx+ecx*32+4] 注:[基址 + 变址 * 比例因子 + 偏移量]

# 选择语句机器级表示

无条件转移指令:

  • jmp <地址>(PC 无条件转移至 < 地址 >)

  • jmp 128(<地址> 可以用常数给出)

  • jmp eax(<地址> 可以来源于寄存器)

  • jmp [999](< 地址 > 可以来源于主存)

  • jmp NEXT(<地址> 可以用标号锚定)

可以用标号锚定位置,如 NEXT:(特征 —— 有冒号,名字可以自己取)

条件转移指令 ——jxxx

  • 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 则跳转)

条件转移指令一般要和 cmp 指令一起使用,cmp a,b(比较 a,b 两个数)

套路:

cmp eax,ebx #比较寄存器eax和ebx里的值
jg NEXT:    #若eax>ebx,则跳转到NEXT:

实例:

//C语言
if(a>b){
    c=a;
} else {
    c=b;
}

转为汇编语言:

mov eax,7     #假设变量a==7,存入eax
mov ebx,6     #假设变量b==6,存入ebx
cmp eax,ebx   #比较变量a和b
jg NEXT:      #若a>b,转移到NEXT:
mov ecx,eax   #假设用ecx存储变量c,令c=b
jmp END:      #无条件跳转到END:
NEXT:
mov ecx,eax   #假设用ecx存储变量c,令c=a
END:

扩展:cmp 指令底层原理

本质进行减法,生成 OF (溢出标志),SF (符号标志),ZF (零标志),CF (进位 / 错位标志),根据这些标志来判断哪边大,哪边小

# 循环语句的机器级表示

例子:

//C语言
int result=0;
for(int i=1;i<=100;i++){
    result+=i;
}//求1+2+3+...+100的和

转为汇编语言:

mov eax,0  #用eax保存result,初值为0
mov edx,1  #用edx保存i,初值为1
cmp edx,100  #比较i和100
jg L2      #若i>100,跳转到L2执行
L1:         #循环主题
add eax,edx #实现result+=i
inc edx    #inc自增指令,实现i++
cmp edx,100 #i和100
jle L1      #若i<=100,跳转到L1执行
L2:         #跳出循环主体

用转移类指令实现循环,需要 4 个部分组成:

  • 循环前的初始化
  • 是否直接跳过循环
  • 循环主体
  • 是否继续循环

用 loop 指令实现循环

for(int i=500;i>0;i--){
    //do something
} //循环500轮

转为汇编

mov ecx,500 #用ecx作为循环计数器
Looptop:    #循环的开始
...
做某些处理
...
loop LoopTop #ecx--,若ecx!=0,跳转到Looptop

理论上,用 loop 指令实现的功能一定能用条件转移指令实现

使用 loop 指令可能会使代码更清晰简洁

loopnz—— 当 ecx!=0 && ZF==0 时,继续循环

loopz—— 当 ecx!=0 && ZF==1 时,继续循环

# 函数调用机器级表示 (Call,ret 指令)

函数的栈帧:保存函数大括号定义的局部变量、保存函数调用相关的信息

当前正在执行的函数栈帧,位于栈顶

x86 汇编语言的函数调用

int add(int x,int y){
    return x+y;
}
int caller(){
    int temp1=125;
    int temp2=80;
    int sum=add(temp1,temp2);
    return sum
}

转为汇编语言

caller:
push ebp
mov ebp,esp
sub esp,24
mov [ebp-12],125
mov [ebp-8],80
mov eax,[ebp-8]
mov [esp+4],eax
mov eax,[ebp-12]
mov esp,eax
call add
mov [ebp-4],eax
mov eax,[ebp-4]
leave
ret

add:
push ebp
mov ebp,esp
mov eax,[ebp+12]
mov eax,[ebp+8]
add eax,edx
leave
ret

函数调用指令:call <函数名>

函数返回指令:ret

call 指令作用:

  • 将 IP 旧值压栈保存 (保存在函数的栈帧顶部)
  • 设置 IP 新值,无条件转移至被调用函数的第一条指令

ret 指令作用:从函数的栈帧顶部找到 IP 旧值,将其出栈并恢复 IP 寄存器

# 如何访问栈帧

函数调用栈

标记帧栈范围:EBP、ESP 寄存器

ebp:指向当前栈帧的 "底部",esp:指向当前栈帧的 "顶部"

注:在 x86 系统当中,默认以 4 字节为栈的操作单位

对栈帧内数据的访问,都是基于 ebp,esp 进行的

# 访问栈帧数据:push、pop 指令

push、pop 指令实现入栈、出栈操作,x86 默认以 4 字节为单位,指令格式如下:
Push x—— 先让 esp 减 4,再将 x 放入

Pop y—— 栈顶元素出栈写入 y,在让 esp 加 4

注:x 可以是立即数、寄存器、主存地址;y 可以是寄存器、主存地址

push eax  #将寄存器eax的值压栈
push 985  #将立即数985压栈
push [ebp+8] #将主存地址[ebp+8]里的数据压栈
pop eax   #栈顶元素出栈,写入寄存器eax
pop [ebp+8] #栈顶元素出栈,写入主存地址[ebp+8]
# 访问栈帧数据:mov 指令
sub esp,12     #栈顶指针-12
mov [esp+8],eax #将eax的值复制到主存[esp+8]
mov [esp+4],985 #将985复制到主存[esp+4]
mov eax,[ebp+8] #将主存[esp+8]的值复制到eax
mov [esp],eax   #将eax的值复制到主存[esp]
add esp,8       #栈顶指针+8
  • 可以用 mov 指令,结合 esp、ebp 指针访问栈帧数据
  • 可以用减法 / 加法指令,即 sub/add 修改栈顶指针 esp 的值

# 如何切换栈帧

call 指令作用:

  • 将 IP 旧值压栈保存 (效果相当于 (push IP))
  • 设置 IP 新值,无条件转移至被调用函数的第一条指令 (效果相当于 jmp add)
add:
push ebp      #保存上一层函数的栈帧基址(esp旧值)
mov ebp,esp   #设置当前函数的栈帧基址(ebp新值)
# 这两条指令等价于enter指令
mov esp,ebp   #让esp指向当前栈帧的底部
pop ebp       #将esp所指元素出栈,写入寄存器ebp
# 这两条指令等价于leave指令

ret 指令作用:

从函数的栈帧顶部找到 IP 旧值,将其出栈并恢复 IP 寄存器

函数调用机器级表示

# 如何传递参数与返回值

  • 栈帧底部一定是上一层栈帧基址 (ebp 旧址)

  • 栈帧顶部一定是返回地址 (当前函数的栈帧除外)

  • 通常将局部变量集中存储在栈帧底部区域,C 语言中越靠前定义的局部变量越靠近栈顶

  • 通常将调用参数集中存储在栈帧顶部区域,参数列表中越靠前的参数越靠近栈顶

  • gcc 编译器将每个栈帧大小设置为 16B 的整数倍 (当前函数的栈帧除外),因此栈帧内可能出现空闲未使用区域

自底向顶的栈帧包含内容:

栈帧包含内容

# CISC 和 RISC

CISC:Complex Instruction Set Computer(复杂指令集计算机)

  • 设计思路:一条指令完成一个复杂的基本功能

  • 代表架构:x86 架构,主要用于笔记本、台式机等

  • 80-20 规律:典型程序中 80% 的语句仅仅使用处理机中 20% 的指令

RISC:Reduced Instruction Set Computer(精简指令集计算机)

  • 设计思路:一条指令完成一个基本 "动作";多条指令组合完成一个复杂的基本功能
  • 代表:ARM 架构,主要用于手机、平板等

例子:

设计一套能实现整数、矩阵加 / 减 / 乘运算的指令集:

CISC 思路:除了提供整数的加减乘除指令之外,还提供矩阵的加法指令、矩阵的减法指令、矩阵的乘法指令

一条指令可以由一个专门的电路完成

有的复杂指令用纯硬件实现很困难 -> 采用存储程序的设计思想,由一个比较通用的电路配合存储部件完成一条指令

RISC 思路:只提供整数的加减乘指令

一条指令一个电路,电路设计相对简单,功耗更低。"并行","流水线"

CISCRISC
指令系统复杂,庞大短小,精简
指令数目一般大于 200 条一般小于 100 条
指令字长不固定定长
可访存指令不加限制只有 Load/Store 指令
各种指令执行时间相差较大绝大多数在一个周期内完成
各种指令使用频度相差很大都比较常用
通用寄存器数量很少
目标代码难以用优化编译生成高效的目标代码程序采用优化的编译程序,生成代码较为高效
控制方式绝大多数为微程序控制绝大多数为组合逻辑控制
指令流水线可以通过一定方式实现必须实现

乘法指令可以访存,一定是 CISC