简单的栈操作与函数调用

汇编语言基础:编码风格、栈操作与函数调用

汇编语言是计算机底层的编程语言,直接与硬件交互。掌握汇编语言不仅有助于理解程序的运行机制,还能优化性能。本文将介绍汇编语言的编码风格、栈操作以及函数调用的实现,并通过示例代码详细解析。


汇编编码风格

汇编语言主要有以下几种编码风格:

1. Intel 风格

  • 操作数顺序目标操作数, 源操作数
  • 示例
    mov eax, ebx  ; 将 ebx 的值移动到 eax
    add eax, 5    ; 将 eax 的值加 5
    
  • 特点
    • 寄存器名称和指令助记符通常是小写。
    • 直观易读,适合初学者。

2. AT&T 风格

  • 操作数顺序源操作数, 目标操作数
  • 示例
    movl $5, %eax  ; 将立即数 5 移动到 eax
    addl %ebx, %eax ; 将 ebx 加到 eax
    
  • 特点
    • 寄存器名前加 %,立即数前加 $,内存地址用 () 表示。
    • 主要用于 Unix/Linux 系统。

3. NASM 风格

  • 类似于 Intel 风格,但语法更严格。
  • 示例
    mov eax, 5
    add eax, ebx
    
  • 特点
    • 常用于编写 Linux 内核模块或引导程序。

objdump 工具

objdump 是一个反汇编工具,用于将二进制文件(如可执行文件或目标文件)转换为汇编代码。常用选项:

  • -d:反汇编代码段。
  • -M intel:指定使用 Intel 风格的汇编语法。
  • -M att:指定使用 AT&T 风格的汇编语法。

示例

objdump -d -M intel test64 > output.asm
  • test64 文件反汇编为 Intel 风格的汇编代码,并保存到 output.asm 文件中。

示例代码解析

以下是一个完整的汇编代码示例,展示了函数调用、栈操作和平栈的过程:

0000000000001129 <func>:
    1129:       f3 0f 1e fa             endbr64          ; 用于Intel CET的指令,防止ROP攻击
    112d:       55                      push   rbp        ; 保存旧的基址指针
    112e:       48 89 e5                mov    rbp,rsp    ; 设置新的基址指针
    1131:       89 7d fc                mov    DWORD PTR [rbp-0x4],edi  ; 保存第一个参数
    1134:       89 75 f8                mov    DWORD PTR [rbp-0x8],esi  ; 保存第二个参数
    1137:       89 55 f4                mov    DWORD PTR [rbp-0xc],edx  ; 保存第三个参数
    113a:       89 4d f0                mov    DWORD PTR [rbp-0x10],ecx ; 保存第四个参数
    113d:       44 89 45 ec             mov    DWORD PTR [rbp-0x14],r8d ; 保存第五个参数
    1141:       44 89 4d e8             mov    DWORD PTR [rbp-0x18],r9d ; 保存第六个参数
    1145:       8b 55 fc                mov    edx,DWORD PTR [rbp-0x4]  ; 将第一个参数加载到edx
    1148:       8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]  ; 将第二个参数加载到eax
    114b:       01 c2                   add    edx,eax                  ; 将eax加到edx
    114d:       8b 45 f4                mov    eax,DWORD PTR [rbp-0xc]  ; 将第三个参数加载到eax
    1150:       01 c2                   add    edx,eax                  ; 将eax加到edx
    1152:       8b 45 f0                mov    eax,DWORD PTR [rbp-0x10] ; 将第四个参数加载到eax
    1155:       01 c2                   add    edx,eax                  ; 将eax加到edx
    1157:       8b 45 e8                mov    eax,DWORD PTR [rbp-0x18] ; 将第六个参数加载到eax
    115a:       01 c2                   add    edx,eax                  ; 将eax加到edx
    115c:       8b 45 10                mov    eax,DWORD PTR [rbp+0x10] ; 将第七个参数(栈上的参数)加载到eax
    115f:       01 c2                   add    edx,eax                  ; 将eax加到edx
    1161:       8b 45 18                mov    eax,DWORD PTR [rbp+0x18] ; 将第八个参数(栈上的参数)加载到eax
    1164:       01 d0                   add    eax,edx                  ; 将edx加到eax
    1166:       5d                      pop    rbp                      ; 恢复旧的基址指针
    1167:       c3                      ret                            ; 返回

0000000000001168 <main>:
    1168:       f3 0f 1e fa             endbr64          ; 用于Intel CET的指令,防止ROP攻击
    116c:       55                      push   rbp        ; 保存旧的基址指针
    116d:       48 89 e5                mov    rbp,rsp    ; 设置新的基址指针
    1170:       6a 08                   push   0x8        ; 将第八个参数(8)压入栈
    1172:       6a 07                   push   0x7        ; 将第七个参数(7)压入栈
    1174:       41 b9 06 00 00 00       mov    r9d,0x6    ; 将第六个参数(6)放入r9d
    117a:       41 b8 05 00 00 00       mov    r8d,0x5    ; 将第五个参数(5)放入r8d
    1180:       b9 04 00 00 00          mov    ecx,0x4    ; 将第四个参数(4)放入ecx
    1185:       ba 03 00 00 00          mov    edx,0x3    ; 将第三个参数(3)放入edx
    118a:       be 02 00 00 00          mov    esi,0x2    ; 将第二个参数(2)放入esi
    118f:       bf 01 00 00 00          mov    edi,0x1    ; 将第一个参数(1)放入edi
    1194:       e8 90 ff ff ff          call   1129 <func> ; 调用func函数
    1199:       48 83 c4 10             add    rsp,0x10    ; 清理栈上的参数
    119d:       b8 00 00 00 00          mov    eax,0x0     ; 将返回值设置为0
    11a2:       c9                      leave             ; 恢复栈帧
    11a3:       c3                      ret               ; 返回

栈操作与函数调用

1. 入栈操作

在函数调用时,参数和局部变量通常会被保存到栈中。以下代码将寄存器中的参数保存到栈中:

1131:       89 7d fc                mov    DWORD PTR [rbp-0x4],edi  ; 保存第一个参数
1134:       89 75 f8                mov    DWORD PTR [rbp-0x8],esi  ; 保存第二个参数
1137:       89 55 f4                mov    DWORD PTR [rbp-0xc],edx  ; 保存第三个参数
113a:       89 4d f0                mov    DWORD PTR [rbp-0x10],ecx ; 保存第四个参数
113d:       44 89 45 ec             mov    DWORD PTR [rbp-0x14],r8d ; 保存第五个参数
1141:       44 89 4d e8             mov    DWORD PTR [rbp-0x18],r9d ; 保存第六个参数

入栈的原因

  1. 保存寄存器状态

    • 函数调用时,寄存器可能被覆盖,因此需要将参数保存到栈中,以便后续使用。
    • 例如,DWORD PTR [rbp-0x4] 表示将 edi 的值保存到栈中,地址为 rbp-0x4
    • DWORD PTR 表示操作的数据大小为 4 字节(32 位),rbp-0x4 是栈中的一个偏移地址。
  2. 支持递归调用

    • 每次函数调用都会有自己的栈帧,保存参数和局部变量,避免数据冲突。
  3. 遵循调用约定

    • 在 x86-64 架构中,前 6 个参数通过寄存器传递(rdi, rsi, rdx, rcx, r8, r9),多余的参数通过栈传递。
    • 保存到栈中是为了统一处理,确保函数能够正确访问所有参数。

2. 平栈操作

平栈(Stack Alignment)是指在函数调用结束后,恢复栈指针 rsp 到调用前的状态,以保持栈的平衡。通常通过调整 rsp 的值来实现。

示例

1199:       48 83 c4 10             add    rsp,0x10
  • 这行代码将栈指针 rsp 增加 16 字节(0x10),用于清理栈上之前压入的两个 8 字节参数(push 0x8push 0x7)。
  • 平栈后,栈指针恢复到调用函数前的状态,保持栈的平衡。

平栈的原因

  1. 保持栈的完整性:函数调用时,参数和局部变量会占用栈空间。调用结束后,需要释放这些空间,否则会导致栈指针错误。
  2. 避免栈溢出:如果不平栈,栈空间会逐渐被耗尽,最终导致栈溢出。
  3. 遵循调用约定:在 x86-64 架构中,调用者负责清理栈上的参数。

堆栈图

以下是 main 函数调用 func 时的堆栈图:

+-------------------+
| 返回地址          | <-- rsp (调用 func 前)
+-------------------+
| 旧 rbp            | <-- rbp (main 函数的栈帧)
+-------------------+
| 局部变量/参数     |
+-------------------+
| 参数 8 (0x8)      | <-- rsp+0x10
+-------------------+
| 参数 7 (0x7)      | <-- rsp+0x8
+-------------------+
| 参数 6 (0x6)      | <-- r9d
+-------------------+
| 参数 5 (0x5)      | <-- r8d
+-------------------+
| 参数 4 (0x4)      | <-- ecx
+-------------------+
| 参数 3 (0x3)      | <-- edx
+-------------------+
| 参数 2 (0x2)      | <-- esi
+-------------------+
| 参数 1 (0x1)      | <-- edi
+-------------------+

总结

  1. 汇编编码风格:主要有 Intel、AT&T 和 NASM 风格,objdump 工具用于反汇编二进制文件。
  2. 入栈操作:保存参数和局部变量,避免寄存器被覆盖,并支持递归调用。
  3. 平栈操作:恢复栈指针,保持栈的完整性,避免栈溢出,并遵循调用约定。
posted @ 2025-03-14 22:46  MKY-门可意  阅读(47)  评论(0)    收藏  举报