You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何GCC编译汇编程序崩溃,添加push %rbp/pop %rbp后恢复正常?

汇编程序入口改为main后崩溃的原因及push %rbp/pop %rbp的作用

环境信息

  • OS: Linux 4.15.0-202-generic Ubuntu SMP x86_64
  • GCC: gcc (Ubuntu 4.8.5-4ubuntu8) 4.8.5

问题背景

最初编写的以_start为入口的汇编程序:

.section .data
msg:
    .asciz "Hello world!\n"
.section .text
.globl _start
_start:
    /* call  printf() to print "Hello world!" */
    mov $msg, %rdi
    call printf
    /* call exit() function  */
    movq $0, %rdi
    call exit

通过as helloworld.s + ld ./a.out -o helloworld -lc -dynamic-linker /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2编译链接后,可正常输出Hello world!。

但将入口标签改为main,用gcc -o helloworld helloworld.s编译后运行出现Segmentation fault (core dumped),GDB调试显示动态链接解析阶段异常。对比C程序编译后的汇编代码添加push %rbp和pop %rbp指令后,程序恢复正常。


核心解析

1. _start与main的执行上下文差异

_start是Linux程序的原生入口点,由动态链接器直接调用,此时栈已经被初始化至符合x86_64 System V调用规范的16字节对齐状态,无需额外栈处理即可直接调用库函数。

而main是C标准约定的用户入口,它由C运行时启动代码(如crt0.o)调用。启动代码调用main时会将返回地址压入栈,这会让栈指针%rsp的对齐状态偏离16字节——而x86_64调用规范明确要求,调用函数前%rsp必须是16字节的整数倍。

2. push %rbp和pop %rbp的实际作用

这两条指令表面是维护栈帧基址,核心作用是修复栈的16字节对齐:

  • push %rbp:将当前%rbp的值压入栈,使%rsp向下移动8字节,刚好让栈指针回到16字节对齐的状态,满足printf等库函数的调用规范。
  • pop %rbp:在main返回前将栈顶值弹回%rbp,恢复栈的原始状态,确保C运行时能正确处理后续退出流程。

3. 崩溃的直接原因

当入口改为main却未添加这两条指令时,调用printf前栈未对齐,违反了x86_64调用规范。动态链接器在解析printf过程中需要访问栈上数据,不对齐的栈会导致非法内存访问,最终触发段错误。而直接用ld链接_start入口的程序时,动态链接器初始化的栈已经是对齐状态,因此不会出现问题。

内容的提问来源于stack exchange,提问作者zhouz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 15:00:53