修改函数返回地址的代码为何在_int_malloc中触发段错误?
问题分析与解答
核心原因:手动跳转破坏了栈帧的正常结构
当你通过修改栈帧返回地址实现跳转时,跳过了正常函数调用的栈帧建立流程,导致目标函数malfunc的栈帧状态异常,进而触发_int_malloc中的段错误;添加pop rbp则是手动修复了栈帧的关键寄存器状态。
1. 正常函数调用的栈帧流程(x86-64)
以gcc默认的帧指针布局为例:
- 调用方执行
call malfunc时,会先将下一条指令的地址(返回地址)压入栈,然后跳转到malfunc入口 malfunc的开头会执行标准序言(prologue):push rbp ; 保存调用方的rbp值到栈 mov rbp, rsp ; 将rbp设置为当前栈顶,建立新的栈帧基址
此时栈结构为:[旧rbp] → [返回地址] → [malfunc局部变量/临时数据],rbp寄存器指向旧rbp的位置,保证后续内存访问的正确性。
2. 手动跳转导致的栈帧异常
你直接修改某函数的返回地址为malfunc的入口,当该函数执行ret指令时,会把修改后的地址加载到rip实现跳转,但这个过程没有执行call指令的压栈操作,也没有触发malfunc的标准序言:
malfunc的rbp寄存器仍然保留着前一个函数的栈帧基址,而非当前栈的正确位置- 栈顶
rsp的位置也和正常调用时不一致,相当于栈帧的"锚点"rbp没有正确初始化 _int_malloc内部会依赖正确的栈帧结构(比如通过rbp访问栈上的临时数据、进行栈相关的安全检查),栈帧混乱会导致非法内存访问,触发段错误
3. pop rbp为何能修复问题
添加pop rbp相当于手动完成了部分栈帧初始化工作:
- 当
ret跳转到malfunc时,栈顶的内容恰好是前一个函数栈帧中的旧rbp值(因为你修改的是该函数的返回地址,ret弹出返回地址后,rsp指向旧rbp的位置) - 执行
pop rbp会把这个旧rbp值加载到rbp寄存器,让rbp指向正确的栈帧基址,修复了栈帧的核心结构 - 此时后续的
malloc调用能正常依赖栈帧访问内存,因此不会触发段错误
4. Compiler Explorer与本地环境的差异
Compiler Explorer中运行正常,大概率是以下两种情况之一:
- Compiler Explorer的
malloc实现(或libc版本)对栈帧的依赖更宽松,即使栈帧异常也能侥幸执行 - 你在Compiler Explorer中测试时的栈布局巧合地让异常未暴露(比如修改返回地址的方式、栈上数据的分布刚好没有触发
_int_malloc的非法访问)
内容的提问来源于stack exchange,提问作者ttzytt
相关产品推荐
相关产品推荐

