跳转至

函数调用

相关汇编

  • intel格式:optcode destination, source
; 将寄存器rsp的值存储到寄存器rbp中
mov rbp, rsp

; 将四个字节的4存储到地址为rbp-4的栈上
mov DWORD PTR [rbp-4], 4

; 将rsp的值减去16
sub rsp, 16
  • 传递数据的时候说明数据的大小:会在数据前面说明数据的大小

  • AT&T格式:optcode source, destination

movq %rsp, %rbp

movl $4, -4(%rbp)

subq $16, %rsp
  • 传递数据的时候说明数据的大小:后缀
  • 存储的地方:前缀(%q:寄存器;$立即数;()内存

常见汇编指令

相关寄存器

8086

  • 通用寄存器:均可用来存放地址和数据

  • 指针和变量寄存器:用来存放,某一段内地址偏移量,用来形成操作数地址,主要用来再堆栈操作或者变址操作中使用

  • 段寄存器:由于存储器空间是分段的,所以这些段寄存器则是每个段的首地址

  • 指令指针:IP用来存放将要执行的下一条指令在现在代码段的偏移量,将这个偏移量+段寄存器中存放的基地址,就找到了下一条指令的地址

  • 标志位寄存器:用来存放计算结果的特征,这些标志位常常被用作接下来程序运行的条件

  • 8086处理器内部有8个16位的通用寄存器,也就是CPU内部的数据单元,分别是:AX、BX、CX、DX、SP、BP、SI、DI

  • CS、DS、SS、ES这四个寄存器都是16位寄存器,用来存储进程的地址空间信息

  • CS是代码段寄存器(Code Segment Register),通过它可以找到代码在内存中的位置

  • DS是数据段寄存器(Data Segment Register),通过它可以找到数据在内存中的位置

  • SS是栈寄存器(Stack Register),栈是程序运行过程所需要的一种数据结构,主要用于记录函数调用的关系

  • ES是一个附加段寄存器(Extra Segment Register),当发现段寄存器不够用的时候,你可以考虑使用ES段寄存器。

CS和DS中都存放着一个段的起始地址代码段的偏移值存放在IP寄存器中,而数据段的偏移值放在通用寄存器中;由于8086架构中总线地址是20位的,而段寄存器和IP寄存器以及通用寄存器都是16位的,所以为了得到20位的地址,先将段寄存器中起始地址左移4位,然后再加上偏移量,就得到了20位的地址;也正是由于偏移量是16位的,所以每个段最大的大小是64K的

对于20位的地址总线来说,能访问到的内存大小最多也就只有2^20=1MB(如果计算得到某个要访问的地址是1MB+X,那么最后访问的是地址X,因为地址线只能发送低20位的

x86

x86-64

  • 多于6个的参数,依然还是通过入栈实现传递

  • 为了效率尽量使用少于6个参数的函数

  • 传递比较大的参数,尽量使用指针,因为寄存器只有64位

  • x86-64向下兼容:对于每个寄存器,我们还可以只使用它的一部分,并使用另一个新的名字

函数调用

  • Caller Function(调用者)
  • Callee Function(被调用者)

在子函数调用时,需要切换上下文使得当前调用栈进入到一个新的执行中:

  • 父函数将调用参数从后向前压栈:由函数调用者完成(上文中的Caller逻辑)
  • 将返回地址压栈保存:call指令完成
  • 跳转到子函数起始地址执行:call指令完成
  • 子函数将父函数栈帧起始地址(%rpb)压栈:由函数被调用者完成(上文中的Callee逻辑)
  • 将%rbp的值设置为当前%rsp的值,即将%rbp指向子函数栈帧的起始地址:由函数被调用者完成(上文中的Callee逻辑),完成函数上下文的切换

eg

void func() {}

void my_func() {
    func();
}
func():
        push    rbp
        mov     rbp, rsp
        nop
        pop     rbp
        ret
my_func():
        push    rbp
        mov     rbp, rsp
        call    func()
        nop
        pop     rbp
        ret
  • 开始的两句就是由编译器默认生成的切换上下文语句(函数my_func中也存在这个语句是因为它最终也会被其他函数s调用)

当my-func函数调用func函数时:

  • 首先,执行call指令,保存返回地址,并跳转至func函数起始地址(这里没有压栈调用参数是因为func入参为空)
  • 随后,在func函数中,使用push rbp和mov rbp,rsp保存上下文,随后开始执行func函数中的逻辑
  • 由于没有代码,且没有返回值,此次为nop指令
  • 最后,恢复上下文,并返回
  • 函数返回时,我们只需要得到函数的返回值(保存在%rax中),之后就需要将栈的结构恢复到函数调用之前的状态,并跳转到父函数的返回地址处继续执行即可
  • 首先执行pop rbp指令,直接将调用栈地址恢复至调用函数之前的状态
  • 通过ret指令跳转至返回地址处并执行

函数参数传递约定