汇编语言常用指令介绍与函数应用

一、汇编常见指令

1、mov 和 movl

将源操作数的内容复制到目标操作数, 两者必须有一个是寄存器

mov M/R, L/M/R

mov %rax(%rbx)       #(%rbx)表示把%rbx的值作为地址,取值。
D(R) 表示根据地址R+D取值

注意:寄存器的值是值或者地址

2、xchg

交换两个操作数的内容, 两者必须有一个是寄存器

xchg M/R, M/R

3、add 和 sub

add 为加法指令, sub 为减法指令. 两个操作数必须有一个是寄存器

add M/R, L/M/R
sub M/R, L/M/R

4、neg 切换操作数的正负号, 即求补码

neg M/R

5、mul 和 div

mul 为无符号乘法, div 为无符号除法 mul/div 的积存储在 edx : eax 中, 其中eax作被乘数, 只支持单个操作数

mul M/R

6、SHL 和 SHR

SHL 和 SHR 为逻辑左移 / 逻辑右移. 每移动 n 位二进制数,如果值为负数, 进行逻辑右移后其可能会出错.

shl M/R, L
shr M/R, L

7、SAL 和 SAR

SAL 和 SLR 为算数左移 / 算术右移. 每移动 n 位二进制数,负数进行算术右移不会像逻辑右移一样出现错误.

sal M/R, L
sar M/R, L

8、lea

加载有效地址, 将源操作数的地址加载到目标操作数中. 由于实际地址要在程序运行时才能知道, 因此涉及这些地址的操作应该用 lea 来完成. 该指令的目标操作数必须是寄存器

lea指令可以用来将一个内存地址直接赋给目的操作数

lea R, M

9、push和pop

PUSH 指令首先减少 ESP 的值(栈顶下降),再将源操作数复制到堆栈。操作数是 16 位的,则 ESP 减 2,操作数是 32 位的,则 ESP 减 4。 POP 指令首先把 ESP 指向的堆栈元素内容复制到一个 16 位或 32 位目的操作数中,再增加 ESP 的值。

PUSH reg/mem
pop reg/mem

10、cmp和test

cmp的功能相当于减法指令,只是对操作数之间运算比较,不保存结果。cmp指令执行后,将对标志寄存器产生影响。其他相关指令通过这些标志寄存器位来得知结果。
test将两个操作数进行逻辑与运算,并根据运算结果设置相关的标志位。

11、CS和IP

CS是代码段寄存器(也就是储存指令)、IP是指令指针寄存器。

8086机器中,任意时刻,CPU将CS:IP指向的内容当作指令执行。 意思是,任意时刻,设CS中的内容是M,IP中的内容为N,8086CPU将从内存M*16+N单元开始,读取一条指令并执行。

12、jmp和loop

JMP 指令无条件跳转到目标地址

JMP destination

13、LOOP 指令

正式称为按照 ECX 计数器循环,将程序块重复特定次数。ECX 自动成为计数器,每循环一次计数值减 1。 LOOP 指令的执行有两个步骤: 第一步,ECX 减 1。 第二步,将 ECX 与 0 比较。

mov ax,0
mov ecx,5
L1:
inc ax
loop L1

14、call和ret

call 常用来跳转到子函数 , ret用来返回到原函数
本质上,ret指令用栈中的数据,修改IP的内容,返回代码段的第一条指令。call指令将IP或者CS和IP压入栈中,实现跳转。

pushq %eip              # 当前下一条指令入栈,也就是返回地址。
jmpq addr

如上,call先把下一条指令地址入栈,然后跳到子程序执行下一条指令。

函数的标准开头为enter指令,相当于

pushq %rbp               # 保存旧的帧指针,相当于创建新的栈帧
movq  %rsp, %rbp     # 让 %rbp 指向新栈帧的起始位置
subq  $N, %rsp          # 在新栈帧中预留一些空位,供子程序使用,用 (%rsp+K) 或 (%rbp-K) 的形式引用空位

函数返回前,调用leaveq指令,相当于:

movq %rbp, %rsp
popq %rbp

ret指令,用栈中的数据,修改CS和IP的内容。该指令往往是返回到调用函数调用处的地址。
ret自动用栈中的返回地址修改CS和IP内容。 retq相当于:

pop IP
pop CS

因此,调用ret之前,需要保证栈中下一个元素是返回地址。

        计算机内部永远是顺序执行的,通知语句通过改变下一条指令,来改变程序执行的顺序。

assume cs:code
code segment
    main:   :
            :
            call sub1//调用子程序sub1
            :
            mov ax,4c00h
            int 21h
    sub1:   :
            :
            call sub2//调用子程序sub2
            :
            ret//子程序返回
    sub2:   ://子程序sub2开始
            :
            ret//子程序返回
code ends
end main

其他

1、lea是“load effective address”的缩写,简单的说,lea指令可以用来将一个内存地址直接赋给目的操作数,

例如:lea eax,[ebx+8]就是将ebx+8这个值直接赋给eax,而不是把ebx+8处的内存地址里的数据赋给eax。

而mov指令则相反,例如:mov eax,[ebx+8]则是把内存地址为ebx+8处的数据赋给eax。
2、栈的地址比堆高,栈是向下增长的,堆是向上增长的。而对栈来说,增长方向就是栈顶。
3、入栈顺序
      C语言函数参数采用自右向左的入栈顺序。 所谓定义,就是在栈或者堆上开辟空间,所谓赋值或者初始化,就是在空间上进行赋值。

栈向低地址方向延申, 因此sub减法常常用于分配内存, 而add则用于回收内存。

 寄存器约定

        函数调用需要传递参数时,第一个参数存到%edi里,第二个参数会存到%esi里,如果有返回值会存到%eax里

 x86-64系统中,用户级别的函数参数通过寄存器传递,%rdi,%rsi,%rdx,%rcx,%r8,%r9依次对应第1参数,第2参数.....

 

  1. 每个寄存器的用途并不是单一的。
  2. %rax 通常用于存储函数调用的返回结果,同时也用于乘法和除法指令中。在imul 指令中,两个64位的乘法最多会产生128位的结果,需要 %rax 与 %rdx 共同存储乘法结果,在div 指令中被除数是128 位的,同样需要%rax 与 %rdx 共同存储被除数。
  3. %rsp 是堆栈指针寄存器,通常会指向栈顶位置,堆栈的 pop 和push 操作就是通过改变 %rsp 的值即移动堆栈指针的位置来实现的。
  4. %rbp 是栈帧指针,用于标识当前栈帧的起始位置
  5. %rdi, %rsi, %rdx, %rcx,%r8, %r9 六个寄存器用于存储函数调用时的6个参数(如果有6个或6个以上参数的话)。
  6. 被标识为 “miscellaneous registers” 的寄存器,属于通用性更为广泛的寄存器,编译器或汇编程序可以根据需要存储任何数据。

函数调用过程

        子函数调用时,执行的操作有:父函数将调用参数从后向前压栈 -> 将返回地址压栈保存 -> 跳转到子函数起始地址执行 -> 子函数将父函数栈帧起始地址(%rbp) 压栈 -> 将 %rbp 的值设置为当前 %rsp 的值,即将 %rbp 指向子函数栈帧的起始地址。
        保存返回地址和跳转到子函数处执行由 call 一条指令完成,在call 指令执行完成时,已经进入了子程序中,因而将上一栈帧%rbp 压栈的操作,需要由子程序来完成。 注意,子栈帧指针指向的位置,包含的内容是父栈帧的地址。理解这句话。

函数返回过程

        函数返回时,我们只需要得到函数的返回值(保存在 %rax 中),之后就需要将栈的结构恢复到函数调用之前的状态,跳转到父函数的返回地址处继续执行。

movq %rbp, %rsp    # 使 %rsp 和 %rbp 指向同一位置,即子栈帧的起始处
popq %rbp                 # 将栈中保存的父栈帧的 %rbp 的值赋值给 %rbp,并且 %rsp 上移一个位置指向父栈帧的结尾处

注意:在pop %rbp时,%rsp会向上移动一个位置,这个位置内容恰好是返回地址。也就是说,执行完成 ret 后,%rsp 指向的是父栈帧的结尾处,父栈帧尾部存储的调用参数由编译器自动释放。

 

ESP,EBP

        每个任务(进程)有一个栈(可以这样理解一个程序中只有一个栈,每个函数对应一个栈中一段区域即帧),在这个进程中每个函数被调用时分别从这个栈占用一段区域,称为帧(frame)。ESP为栈指针,用于指向栈的栈顶,而EBP为帧指针,指向当前活动记录的顶部。栈指针与帧指针标识出了当前活动记录的位置。

        函数执行中%ebp一直保持不变,可以由此寻址获得函数参数。
保存一个int型数据,只要把%esp指针向下移动4个字节(因为栈增长方向是由高地址到低地址),再根据%esp把该数据移入.

        当前函数被调用开始,执作: 1. 将帧指针压入栈中:push ebp 2. 用ebp保存当前栈指针:mov ebp,esp。保证ebp的固定 3. 使得栈指针自减,自减得到的内存应当能够被用来存储被调用函数的本地状态:sub 0CCh,esp

        保存返回地址和保存上一栈帧的%rbp 都是为了函数返回时,恢复父函数的栈帧结构。返回值的作用是父函数执行位置,栈帧作用是传入参数。

   %esp寄存器指向当前整个栈的栈顶%ebp指向当前帧的帧底,可以认为%ebp只是存取某时刻的栈顶指针
        当前帧的帧底位置保存了上一级调用者的%ebp指针值(调用者函数帧底),而每个%ebp的前一个单元存放的就是当前函数的返回地址

  • 4
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术探索者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值