• IA-32 (Intel 32位) 寄存器
    Note: 32 位说明栈中指针偏移量以 32位(4字节)为一个基元。
    IA-32 Normally Used Register

    • EAX ~ EDX: 通用寄存器 (general purpose registers),
      • 通用寄存器是通用的,但是有一部分指令对寄存器有特殊要求,这时它们分别有如下用途:
        • EAX: 累加器 (“A” means Accumulator)
        • EBX: 数据基地址暂存器 (“B” means Base)
        • ECX: 循环计数器 (“C” means Counter)
        • EDX: 操作数或操作结果暂存器 (“D” means Data)
      • 为了方便存储较短的数据,通用寄存器都可以只使用低16位
    • ESIEDI: 这两个寄存器优势也被包括在通用寄存器内,但是不能拆分,一般用作字符串操作的源指针和目的指针。
    • ESPEBP: ESP 存储的是栈顶地址,即栈指针 (Stack Pointer),而 EBP 存储的是位于栈顶的栈帧的栈底地址,即基指针 (Base Pointer)
  • 控制权移交

    • 控制权是指 CPU 执行指令的顺序
    • 当一个函数调用另一个函数时,CPU需要暂停当前函数的执行,转而去执行被调用函数。这个过程称为控制权移交。
  • 栈帧(Stack Frame)

    • 栈帧是堆栈的逻辑片段,当调用函数时逻辑栈帧被压入堆栈,当函数返回时逻辑栈帧被从堆栈中弹出。
    • 栈帧的边界:EBP (Extended Base Pointer 扩展基指针) 指向当前栈帧底部(高地址),在当前栈帧内位置固定;ESP (Extended Stack Pointer 扩展栈指针) 指向当前栈帧顶部(低地址),当程序执行时 ESP 随着数据的入栈和出栈而移动。
  • 函数调用栈 (Call Stack)

    • 函数调用栈的典型内存布局图:
      Classic Call Stack Memory Allocation Picture
      这是 caller function 和 callee function 的栈帧布局,m(%ebp) 表示以 EBP 为基地址、偏移量为m字节的内存空间(中的内容)。
    • 函数序 (Prologue):被调函数设置 EBP,并保存其希望不变的寄存器值;被调函数为局部变量分配栈帧空间,并存储。
    • 函数跋 (Epilogue):释放为局部变量分配的栈空间;恢复函数序中希望保持不变的寄存器值,包括主调函数的 EBP;被调函数将控制权交还主调函数(使用ret指令),这一步可能也会将先前的参数从栈上清除。
  • 堆栈操作

    • push: ESP 减小 4 个字节;将寄存器数据压入堆栈,从高到底按字节讲数据存入 ESP-1、ESP-2、ESP-3 指向的地址单元

    • pop:从 ESP 指向的空间取回数据存入寄存器,ESP 增加 4 个字节。

    • call:将当前的指令指针(EIP)压入栈,以备返回时恢复执行下一条指令;然后将 EIP 指向被调函数的代码开始处,跳转到被调函数的入口处执行。

    • leave:恢复主调函数的栈帧。等价于:

      1
      2
      movl %ebp, %esp
      popl %ebp
    • ret:在执行leave之后,从栈顶弹出返回地址(由call指令压入栈中的)到 EIP,转到返回地址继续执行

  • 函数调用约定:

    • 函数参数的传递顺序和方式:是通过栈传递还是通过寄存器与栈的组合传递

    • 栈的维护方式:被调函数返回时压栈的参数是由被调函数清除还是由主调函数清除

    • 名称修饰 (Name-mangling) 策略:编译器在链接时为区分不同函数,对函数名作不同修饰

    • x86函数参数传递方法:
      x86 处理器 ABI 规范规定,所有传递给被调函数的参数都通过堆栈完成,压栈顺序为以函数参数从右到左的顺序。这种方式使得被调函数通过m(%ebp)读取参数时反而是随m增大而顺序读取,支持不定数量参数的函数。

      • 整型和指针参数的传递相同,因为都是32bit,直接从右向左压栈。
      • 浮点参数的传递:浮点参数的传递于整型的类似,只是浮点数据占用64bit
      • 结构体和联合体参数的传递:与整型的类似,但是注意数据对齐,占用字节数为 4 的倍数。
    • x86函数返回值传递方法:

      1. 若返回值不超过4字节,通常将其保存在EAX中,调用方通过EAX读取返回值。
      2. 若返回值大于4字节而小于8字节,则通过EAX+EDX联合返回,EAX保存低字节,EDX保存高字节。
      3. 若返回值为浮点类型,通过专用的协处理器浮点数寄存器的栈顶返回。
      4. 若返回值为结构体或联合体,传递返回值的方法根据编译器的不同,平台的不同,调用约定的不同,甚至编译参数的不同而不同,一种常见的方式是:
        • 在将被调函数的参数依次压栈之后再向栈中压入一个隐藏参数,它是用于保存返回结构体或联合体的一块栈空间的地址。
        • 若未定义用于接收返回值的变量,在栈上额外开辟一块空间作为接收返回值的临时变量
        • 被调函数将返回值拷贝到隐藏参数指向的内存空间,再将该地址存入EAX
    • x86_64的寄存器传参:
      前六个参数分别使用rdi, rsi, rdx, rcx, r8, r9传递,多余的参数均存储在栈上。

    • x86_64的函数返回值传递:
      使用rax传递第一个返回值。