从汇编代码调用Rust函数访问共享内存时触发General Protection Fault问题排查
问题根源:栈对齐违反x86_64 ABI要求
在x86_64的System V AMD64调用约定中,明确要求调用函数前栈指针(RSP)必须是16字节对齐的。你的JIT生成代码没有满足这个核心要求,导致调用foo时栈处于不对齐状态,进而触发一系列异常:
- 当
foo内部调用标准库函数(比如println!依赖的stdio底层调用)时,会因栈对齐不符合要求触发General Protection Fault; - 修改全局变量的场景在Valgrind下能运行,是因为Valgrind的内存模拟层自动忽略了硬件级的严格对齐检查,但真实环境下这种违反ABI的行为依然会导致未定义行为(比如崩溃)。
具体来说:
- 从
main调用JIT函数f()时,call指令会把8字节的返回地址压入栈,此时RSP的地址是8字节不对齐的(RSP % 16 = 8); - 你的JIT代码直接执行
call foo,调用前的RSP依然处于不对齐状态,完全违反了ABI规范。
解决方案:在JIT代码中保证栈对齐
你需要在JIT生成的代码里添加栈对齐逻辑,最稳妥的方式是建立标准栈帧,或者直接调整RSP位置来满足对齐要求。下面是修改后的可运行代码示例:
use anyhow::Result; use assembler::*; use assembler::mnemonic_parameter_types::{registers::*, immediates::*}; const CHUNK_LENGTH: usize = 4096; const LABEL_COUNT: usize = 64; static mut X: u32 = 0; #[no_mangle] unsafe extern "C" fn foo() { println!("hello world"); X += 1 } fn main() -> Result<()> { let mut memory_map = ExecutableAnonymousMemoryMap::new(CHUNK_LENGTH, true, true)?; let mut instr_stream = memory_map.instruction_stream(&InstructionStreamHints { number_of_labels: LABEL_COUNT, ..Default::default() }); let f = instr_stream.nullary_function_pointer::<i64>(); // 建立标准栈帧,强制保证16字节栈对齐 instr_stream.push_Register64Bit(Register64Bit::RBP); instr_stream.mov_Register64Bit_Register64Bit(Register64Bit::RBP, Register64Bit::RSP); // 调用foo函数 instr_stream.call_function(foo as unsafe extern "C" fn()); // 恢复栈帧 instr_stream.pop_Register64Bit(Register64Bit::RBP); instr_stream.mov_Register64Bit_Immediate64Bit(Register64Bit::RAX, Immediate64Bit(0x123456789abcdef0)); instr_stream.ret(); instr_stream.finish(); assert_eq!(unsafe { f() }, 0x123456789abcdef0); assert_eq!(unsafe { X }, 1); Ok(()) }
补充说明
- 栈对齐的必要性:x86_64的很多底层指令(比如SSE系列指令)要求操作数在16字节对齐的内存地址上,标准库的内部实现大量依赖这些指令,一旦栈不对齐就会触发硬件异常。
- Valgrind的特殊性:Valgrind的VEX模拟层会对未对齐的内存访问做兼容处理,不会触发真实硬件的对齐检查,这就是为什么在Valgrind下能正常运行,但真实环境会崩溃。
- 轻量替代方案:如果不想建立完整栈帧,也可以直接调整RSP来实现对齐:
这种方式更简洁,但需要准确计算需要调整的字节数,适合简单场景。// 调整RSP实现16字节对齐(调用前RSP是8字节不对齐,减8字节即可) instr_stream.sub_Register64Bit_Immediate64Bit(Register64Bit::RSP, Immediate64Bit(8)); instr_stream.call_function(foo as unsafe extern "C" fn()); instr_stream.add_Register64Bit_Immediate64Bit(Register64Bit::RSP, Immediate64Bit(8));
内容的提问来源于stack exchange,提问作者viluon
相关产品推荐
相关产品推荐

