汇编语言----X86汇编指令
汇编语言----X86汇编指令1.汇编指令的构成2.X86架构CPU中包含的寄存器3.常见的x86汇编指令(1)算数运算(2)逻辑运算(3)其他4.AT&T格式5.选择语句(分支结构)6.循环语句(1)条件转移指令实现循环(2)loop指令实现循环7.函数调用的机器级指令如何访问栈帧中的数据函数调用栈在内存的位置:访问栈帧数据:push、pop指令:函数调用时切换栈帧:恢复esp与ebp的值:执行
目录
高级语言--->汇编语言---->机器语言
一条高级语言可能对应多条汇编语言,但是汇编语言指令与机器语言是一一对应的关系
机器语言与汇编语言合称为机器级代码
1.汇编指令的构成
汇编指令用来改变程序执行流以及处理数据,汇编指令的格式为
操作码+地址码
操作码:告诉机器如何处理
地址码:则告诉机器数据的位置(寄存器:在寄存器中给出"寄存器名",主存:在指令中给出"主存地址",指令:直接在指令中给出操作数,即立即寻址)
以mov指令为例:
其中的内存地址[af996h](h表示16进制)前面的符号:dword ptr,byte ptr,是用来指明内存的读写长度
2.X86架构CPU中包含的寄存器
EAX,EBX,ECX,EDX:通用寄存器
mov eax ebx #寄存器-->寄存器
mov eax dword ptr [af996h] #主存-->寄存器
mov eax,5 #立即数-->寄存器
若想只使用低16bit
mov ax bx #寄存器-->寄存器
mov ax dword ptr [af996h] #主存-->寄存器
mov ax,5 #立即数-->寄存器
也可以使用高低8bit
mov ah bl #寄存器-->寄存器
mov ah dword ptr [af996h] #主存-->寄存器
mov ah,5 #立即数-->寄存器
ESI,EDI:变址寄存器
变址寄存器可用于线性表,字符串的处理
EBP,ESP:堆栈基指针
堆栈寄存器用于实现函数调用
注:只有通用寄存器能使用低16bit或8bit,变址寄存器以及堆栈寄存器只能固定使用32bit
举例:
mov eax,dword ptr [ebx+8]
#将ebx所指的地址偏移8个单位,从这个地址当中复制低32bit到eax寄存器中
mov eax,dword ptr [af996-12h]
#将af996-12所指的贮存地址的低32bit复制到eax寄存器中
3.常见的x86汇编指令
机器识别汇编语言的原理:
CU控制单元会发送控制信号,例如告诉ALU进行算数运算或逻辑运算,ALU就会将输入的数(d,s)进行相应的运算
destination:目的地(d 目的操作数):目的操作数不能为常量
source:来源地 (s 源操作数):可以为一个常量
注:两个操作数不能同时来自于主存,可以同时来自于寄存器
(1)算数运算
对于除法中的被除数edx:eax表示存放64位的被除数,高32位存放在edx,低32位存放在eax中
(2)逻辑运算
(3)其他
用于实现分支结构、循环结构的指令:cmp、test、jmp、jxxx
用于实现函数调用的指令: push、pop、call、ret
用于实现数据转移的指令: mov
4.AT&T格式
AT&T格式与intel格式的区别
AT&T格式是Unix,Linux常用的格式,intel格式是windows常用格式
对于[ebx+ecx*32+4]的使用情景:
5.选择语句(分支结构)
在X86寄存器中,程序计数器PC通常被称为IP,执行一条指令时,PC自动+1,指向下一条即将执行的指令
无条件转移指令:
jmp <地址> #pc无条件转移到<地址>
jmp 128 #<地址>可以用常数给出
jmp eax #<地址>可以来自于寄存器
jmp [999] #<地址>可以来自于主存
若想跳转到某一条指令,就需要知道某条指令的地址,这样是很难的,可以用"标号"瞄准位置
注:这里的NEXT,名字是可以自己取的
无条件转移指令无法实现条件转移,可以使用jxxx
对于比较a,b两个数,需要使用cmp a,b
举个例子:
先else在if
先if后else
补充:cmp的底层原理
比较a,b的大小,本质是进行a-b减法运算
OF (Overflow Flag)溢出标志。溢出时为1,否则置0
SF(Sign Flag) 符号标志。结果为负时置1,否则置0
ZF(Zero Flag)零标志,运算结果为0时ZF位置1,否则置0
CF(Carry Flag)进位/借位标志,进位/借位时置1,否则置0
ALU每次运算的标志位都自动存入PSW程序状态字寄存器中(intel称为标志寄存器)
如下是8086CPU中的16位bit的PSW标志寄存器:
根据标志位的结果,进行判断是否满足jxxx,例如:
jne:若ZF=0,那么满足jne,进行跳转,如果ZF0,那么就不进行跳转
6.循环语句
(1)条件转移指令实现循环
循环语句由4个部分构成:
1.循环前的初始化:
2.是否直接跳过循环:
3.循环主体
4.是否继续循环
(2)loop指令实现循环
这里的loop Looptop等价于
dec ecx
cmp ecx,0
jne Looptop
所以使用loop指令实现的功能一定能用条件转移指令实现
补充:loopx指令--如loopnz,loopz
7.函数调用的机器级指令
函数调用指令:call<函数名>
函数返回指令:ret
例如:
对应的X86指令如下图所示:
call指令的作用:
①将IP寄存器(PC)的IP旧值压栈保存(保存在函数的栈帧顶部)
②设置IP新值,无条件转移到被调用函数的第一条指令
ret指令的作用:
从函数的栈帧顶部找到IP旧值,将其出栈并恢复IP寄存器
如何访问栈帧中的数据
函数调用栈在内存的位置:
在32位系统中,进程虚拟地址空间为4GB,栈低在上,栈顶在下
在常用寄存器中,我们可以看到两个关于栈的寄存器EBP,ESP
EBP与ESP代表的含义如下:EBP指向栈帧的底部,ESP指向栈帧的顶部
当add栈帧执行完后,会退到caller栈帧继续执行,esp与ebp指向的地址也会随之改变
访问栈帧数据:push、pop指令:
push、pop 指令实现入栈、出栈操作,x86 默认以4字节为单位。指令格式如下:
push x:先让esp减4,再将x压入
x可以为立即数,寄存器,主存地址
pop x:栈顶元素出栈写入x,再让esp加4
x可以为寄存器,主存地址
push和pop只能对esp进行访问,但是不能访问栈中其他值,可以使用mov:
•可以用mov 指令,结合 esp、ebp 指针访问栈帧数据
•可以用减法/加法指令,即 sub/add 修改栈顶指针esp 的值
函数调用时切换栈帧:
call指令的作用:
①将IP寄存器(PC)的IP旧值压栈保存(保存在函数的栈帧顶部):效果相当于push ip
②设置IP新值,无条件转移到被调用函数的第一条指令:效果相当于jmp add(add表示标号,程序的执行流会转移到标号位置)
流程如下:
1.push ip,先减4,再将IP旧址压入
2.jmp add,转到add的第一条指令
3.push ebp,先减4,再将ebp指向的值放到栈顶
这里的作用是将ebp指向的地址保存下来,也就是上一层函数的栈帧基址,当执行完当前函数时,可以根据这一地址返回上一层函数
4.mov ebp,esp:让ebp寄存器指向esp所指的地址
让ebp指向当前函数的基地址,也就是设置当前函数的栈帧基址
灰色部分表示原来的ebp和esp,总之完成两件事情,记录上一层函数的基地址,以及将ebp指向当前函数的基地址:
push ebp
mov ebp,esp
这里也可以直接用enter这个指令代替,即上面两条指令等价于:enter
我们可以看到,当前函数的栈帧中的栈底总是存储了上一层函数的基地址,这样执行完当前函数后,就可以根据栈底的地址返回上一层函数
恢复esp与ebp的值:
在执行完当前函数指令后
1.mov esp,ebp #让esp指向当前栈帧底部
2.pop ebp #将esp所指元素出栈,写入寄存器ebp,也就是让ebp重新指回上一函数的栈帧底部,同时esp+4
mov esp,ebp
pop ebp
等价于
leave
执行ret:
恢复esp,ebp的地址后,esp指向了IP的旧值,那么继续执行ret
ret的作用:从函数的栈帧顶部找到IP旧值,将其出栈并恢复IP寄存器,也就是让程序的执行流回到call指令的下一条指令:
总结如下:
栈帧内可能包含哪些内容
对于以下函数,栈帧存储的内容:
1.栈帧底部一定是上一层栈帧基地址(ebp旧值)
2.在调用下一层函数时,一定会使用到call指令,call指令会将IP寄存器的值压到栈顶保存,所以IP寄存器的值(返回地址)保存在栈帧顶部
3.将局部变量集中存储在栈帧底部区域,C语言中越靠前定义的局部变量越靠近栈顶
如何访问局部变量:
这里只需要将[ebp-4]=sum,[ebp-8]=temp2,[ebp-12]=temp3,[ebp-4]表示最后一个定义的局部变量
4.将调用参数集中存储在栈帧顶部区域,参数列表中越靠前的参数越靠近栈顶
如何访问调用参数:
这里只需要将[ebp+8],得到第一个参数,[ebp+12],得到第二个参数
5.栈帧中可能出现空闲未使用的区域
在gcc 编译器中,将每个栈大小设置为 16B 的整数倍(当前函数的栈除外),因此栈帧内可能出现空闲未使用的区域。
例如add栈帧可以为4B,8B,但是只要这个函数需要调用下一层的函数就必须凑齐16B的整数倍
总结:
如何传递返回值
多个参数可以通过函数调用栈传递,但是函数的返回值只有一个,所以通常将返回值保存到eax寄存器中,所以当返回到上一层函数时,只需要到eax中取结果即可
总结:
补充:调用其他函数前,如果有必要,可将某些寄存器 (如: eax、edx、ecx)的值入栈保存,防止中间结果被破坏。
本篇总结:
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)