• 计算机在执行汇编代码时,只会顺序执行
  • 通过call,jmp,ret这种指令来完成跳转
  • 汇编代码执行流不像高级语言程序一样流程明确
  • 汇编指令代码会经常跳转导致可读性差

例一:将下列代码转换成汇编指令:

  1. int i=0;
  2. while(i<100)
  3. i++;
  4. i=0;

翻译后的结果如下:

  1. mov rax 0;//int i=0;
  2. label1:
  3. inc rax; //a++
  4. cmp rax 100;
  5. jge label2://if a>=100,break;
  6. jmp label1:
  7. label2:
  8. mov rax 0;

jge 通过eflag寄存器中的标志位来判断
eflag标志通过之前的cmp来设置
例二:
int a,b;//存放在rax和rbx中
两种swap方式
第一种:

int c=a;
    a=b;
    b=c;  
相当于:
mov rax rax;int c=a;
mov rax rbx;a=b;
mov rbx rcx;b=c;

第二种:

a=a+b;
b=a-b;
a=a-b;
相当于:
add rax rbx; a=a+b
mov rcx rax;
sub rcx rbx;
mov rbx rcx; b=a-b
sub rax rbx; a=a-b

基础汇编指令:

POP:

  • POP指令的作用是弹栈,将栈顶的数据弹出到寄存器,然后栈顶指针向下移动一个单位
  • pop rax指令,作用相当于mov rax [rsp]; add rsp 8

PUSH:

  • PUSH指令的作用是压栈,将栈顶指针向上移动一个单位的距离,然后将一个寄存器的值存放在栈顶
  • push rax指令,作用效果相当于sub rsp 8;mov [rsp] rax

JMP:

  • 立即跳转,不涉及函数调用,用于循环,ifelse这种场合
  • jmp 1234h指令,作用相当于mov rip 1234h

CALL:

  • 函数调用,需要保存返回地址(返回地址保存在栈中)
  • call 1234h指令,效果就是:push rip;mov rip 1234h

LEAVE

  • mov rsp,rbp
  • pop rbp

RET:

  • 用于函数返回
  • 等效于POP RIP

    函数调用流程

    举个例子
    main调用func_b,func_b调用func_a
    代码如下
    void func_a()
    {
      //do sth
      return;
    }
    void func_b()
    {
      func_a();
      int c=1;
      return;
    }
    int main()
    {
      func_b();
      int a=2;
      return 0;
    }
    
    1.从main函数开始,分析栈帧变化
  1. 当运行到call func_b时main函数的栈帧
  2. rbp指向栈底,rsp指向栈顶
  3. 栈帧存放了一些main的局部变量
  4. main函数需要调用func_b,main只需要call func_b
  5. 即push rip;mov rip func_b

2.跳转到func_b后

  1. func_b直接执行主逻辑吗?并不,被调用函数还需要维护被调用函数自己的栈帧

被调函数维护自己栈帧的步骤

  1. push rbp;将调用函数的栈底指针保存
  2. mov rbp rsp;将栈底指针指向当前的栈顶
  3. sub rsp xxx;开辟被调用函数的栈帧,此时上一步的rbp就指向栈帧的底部

所谓栈帧的维护就是维护rbp和rsp两个指针
rsp永远指向栈顶
rbp用来定位局部变量
3.func_b要调用func_a,其调用流程和main函数调用func_b基本一致
不同处在于返回地址,rbp和rsp指向的地址,以及开辟的栈空间不同

调用完毕后,函数返回
leave指令
作用是维护栈帧,通常是出现在函数的结尾,与ret连用
其实际作用为:

  1. mov rsp rbp;
  2. pop rbp


将栈顶指针指向栈帧的底部,然后在栈中弹出新的栈底指针
在一个函数执行结束返回时,会执行leave;ret
等效于

  1. mov rsp rbp;
  2. pop rbp;
  3. pop eip

总结

  1. 调用函数

只需要将rip压栈,即push rip,然后rip赋值为被调用函数的起始地址,这一操作被隐性内置在call指令当中

  1. 被调用函数

push rbp
mov rbp rsp
sub rsp 0xxx
即保存调用函数的rbp指针,将自己的rbp指针指向栈顶,然后开辟栈空间给自己用,此时rbp就变成被调用函数的栈底

  1. 函数返回

leave;ret
等效于
mov rsp rbp
pop rbp
pop rip
即恢复栈帧,返回调用函数的返回地址