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

从汇编代码调用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(())
}
补充说明
  1. 栈对齐的必要性:x86_64的很多底层指令(比如SSE系列指令)要求操作数在16字节对齐的内存地址上,标准库的内部实现大量依赖这些指令,一旦栈不对齐就会触发硬件异常。
  2. Valgrind的特殊性:Valgrind的VEX模拟层会对未对齐的内存访问做兼容处理,不会触发真实硬件的对齐检查,这就是为什么在Valgrind下能正常运行,但真实环境会崩溃。
  3. 轻量替代方案:如果不想建立完整栈帧,也可以直接调整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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 23:04:08