当从汇编代码访问的锈函数访问共享内存时,通用保护故障

发布于 2025-02-02 19:58:43 字数 5705 浏览 2 评论 0原文

我正在尝试在运行时组装和调用代码在用Rust编写的口译项目中。我正在使用 gaysbler crate 为此。我想将extern“ c” fn包装在重要的运行时功能上,jit'ed代码可以直接调用。为了最大程度地减少生成的代码段中的复杂性,我想将解释器状态保留为全局变量。

但是,每当我尝试访问和/或修改任何全局状态时,该程序就会不断崩溃。一个简单的println!(“ Hello world”)stdio :: _ print()访问stdout时,似乎都会带有常规保护故障。一个具有静态mut变量的简单示例也将核心转储。有趣的是,虽然前者以整洁的堆栈跟踪在Valgrind下崩溃,但后一个示例毫不在线运行,实际上传递了暗示该程序按预期工作的断言。请注意,静态变量不需要变形,并且读取足以引起崩溃。

我对mmap详细信息汇编器板条箱在下面使用的详细信息,但我找不到任何暗示我的方法崩溃了。任何指导都将不胜感激。

我已经创建了一个 repl.it repl >。

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() {
    // printing here will lead to a coredump,
    // Valgrind will provide more insight (general protection fault)
  
    // println!("hello world");

    // modifying a global variable instead will also dump
    // core but will run without fail in Valgrind
    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>();
    instr_stream.call_function(foo as unsafe extern "C" fn());
    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(())
}

这是两种情况下的阀门输出。

全局变量:

==4186== Memcheck, a memory error detector
==4186== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==4186== Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info
==4186== Command: target/debug/jit-ffi-fault-mre
==4186== 
==4186== 
==4186== HEAP SUMMARY:
==4186==     in use at exit: 0 bytes in 0 blocks
==4186==   total heap usage: 14 allocs, 14 frees, 199,277 bytes allocated
==4186== 
==4186== All heap blocks were freed -- no leaks are possible
==4186== 
==4186== For lists of detected and suppressed errors, rerun with: -s
==4186== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

println!

==4341== Memcheck, a memory error detector
==4341== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==4341== Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info
==4341== Command: target/debug/jit-ffi-fault-mre
==4341== 
==4341== 
==4341== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==4341==  General Protection Fault
==4341==    at 0x13FB7A: std::io::stdio::_print (stdio.rs:1028)
==4341==    by 0x1174B0: foo (main.rs:15)
==4341==    by 0x4E58004: ???
==4341==    by 0x11A1BC: jit_ffi_fault_mre::main (main.rs:35)
==4341==    by 0x113FEA: core::ops::function::FnOnce::call_once (function.rs:248)
==4341==    by 0x11497D: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:122)
==4341==    by 0x114E70: std::rt::lang_start::{{closure}} (rt.rs:145)
==4341==    by 0x13CA95: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:280)
==4341==    by 0x13CA95: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:492)
==4341==    by 0x13CA95: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:456)
==4341==    by 0x13CA95: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:137)
==4341==    by 0x13CA95: {closure#2} (rt.rs:128)
==4341==    by 0x13CA95: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:492)
==4341==    by 0x13CA95: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:456)
==4341==    by 0x13CA95: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:137)
==4341==    by 0x13CA95: std::rt::lang_start_internal (rt.rs:128)
==4341==    by 0x114E3F: std::rt::lang_start (rt.rs:144)
==4341==    by 0x11A37B: main (in /home/runner/UnsightlyAwfulPhases/jit-ffi-fault-mre/target/debug/jit-ffi-fault-mre)
==4341== 
==4341== HEAP SUMMARY:
==4341==     in use at exit: 85 bytes in 3 blocks
==4341==   total heap usage: 14 allocs, 11 frees, 199,277 bytes allocated
==4341== 
==4341== LEAK SUMMARY:
==4341==    definitely lost: 0 bytes in 0 blocks
==4341==    indirectly lost: 0 bytes in 0 blocks
==4341==      possibly lost: 0 bytes in 0 blocks
==4341==    still reachable: 85 bytes in 3 blocks
==4341==         suppressed: 0 bytes in 0 blocks
==4341== Rerun with --leak-check=full to see details of leaked memory
==4341== 
==4341== For lists of detected and suppressed errors, rerun with: -s
==4341== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
/tmp/nix-shell-4318-0/rc: line 1:  4341 Segmentation fault      (core dumped) valgrind target/debug/jit-ffi-fault-mre

I'm trying to assemble and call code at runtime in an interpreter project written in Rust. I'm using the assembler crate for this. I'd like to have extern "C" fn wrappers around important runtime functionality that JIT'ed code can call directly. To minimise complexity in the generated code segments, I'd like to keep the interpreter state as a global variable.

However, the program keeps crashing whenever I try to access and/or modify any global state. A simple println!("hello world") crashes with a general protection fault, seemingly when stdio::_print() accesses stdout. A simpler example with a static mut variable dumps core as well. Interestingly, while the former crashes under Valgrind with a neat stack trace, the latter example runs without fail, actually passing the assertions that hint that the program works as expected. Note that the static variable does not need to be mutable and reading it is enough to cause a crash.

I'm quite ignorant of the mmap details the assembler crate uses underneath, but I wasn't able to find any hint of why my approach crashes. Any guidance would be greatly appreciated.

I've created a Repl.it repl with an MRE.

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() {
    // printing here will lead to a coredump,
    // Valgrind will provide more insight (general protection fault)
  
    // println!("hello world");

    // modifying a global variable instead will also dump
    // core but will run without fail in Valgrind
    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>();
    instr_stream.call_function(foo as unsafe extern "C" fn());
    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(())
}

Here are the Valgrind outputs for both cases.

Global variable:

==4186== Memcheck, a memory error detector
==4186== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==4186== Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info
==4186== Command: target/debug/jit-ffi-fault-mre
==4186== 
==4186== 
==4186== HEAP SUMMARY:
==4186==     in use at exit: 0 bytes in 0 blocks
==4186==   total heap usage: 14 allocs, 14 frees, 199,277 bytes allocated
==4186== 
==4186== All heap blocks were freed -- no leaks are possible
==4186== 
==4186== For lists of detected and suppressed errors, rerun with: -s
==4186== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

println!:

==4341== Memcheck, a memory error detector
==4341== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==4341== Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info
==4341== Command: target/debug/jit-ffi-fault-mre
==4341== 
==4341== 
==4341== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==4341==  General Protection Fault
==4341==    at 0x13FB7A: std::io::stdio::_print (stdio.rs:1028)
==4341==    by 0x1174B0: foo (main.rs:15)
==4341==    by 0x4E58004: ???
==4341==    by 0x11A1BC: jit_ffi_fault_mre::main (main.rs:35)
==4341==    by 0x113FEA: core::ops::function::FnOnce::call_once (function.rs:248)
==4341==    by 0x11497D: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:122)
==4341==    by 0x114E70: std::rt::lang_start::{{closure}} (rt.rs:145)
==4341==    by 0x13CA95: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:280)
==4341==    by 0x13CA95: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:492)
==4341==    by 0x13CA95: try<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:456)
==4341==    by 0x13CA95: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:137)
==4341==    by 0x13CA95: {closure#2} (rt.rs:128)
==4341==    by 0x13CA95: do_call<std::rt::lang_start_internal::{closure_env#2}, isize> (panicking.rs:492)
==4341==    by 0x13CA95: try<isize, std::rt::lang_start_internal::{closure_env#2}> (panicking.rs:456)
==4341==    by 0x13CA95: catch_unwind<std::rt::lang_start_internal::{closure_env#2}, isize> (panic.rs:137)
==4341==    by 0x13CA95: std::rt::lang_start_internal (rt.rs:128)
==4341==    by 0x114E3F: std::rt::lang_start (rt.rs:144)
==4341==    by 0x11A37B: main (in /home/runner/UnsightlyAwfulPhases/jit-ffi-fault-mre/target/debug/jit-ffi-fault-mre)
==4341== 
==4341== HEAP SUMMARY:
==4341==     in use at exit: 85 bytes in 3 blocks
==4341==   total heap usage: 14 allocs, 11 frees, 199,277 bytes allocated
==4341== 
==4341== LEAK SUMMARY:
==4341==    definitely lost: 0 bytes in 0 blocks
==4341==    indirectly lost: 0 bytes in 0 blocks
==4341==      possibly lost: 0 bytes in 0 blocks
==4341==    still reachable: 85 bytes in 3 blocks
==4341==         suppressed: 0 bytes in 0 blocks
==4341== Rerun with --leak-check=full to see details of leaked memory
==4341== 
==4341== For lists of detected and suppressed errors, rerun with: -s
==4341== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
/tmp/nix-shell-4318-0/rc: line 1:  4341 Segmentation fault      (core dumped) valgrind target/debug/jit-ffi-fault-mre

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

时光清浅 2025-02-09 19:58:43

我对此代码有两个问题。

32位位移符号以64位模式扩展到64位。

警告:发射代码的位置可能是,如果它距离公共库功能呼叫(例如printf)超过2GB;在这种情况下,可以间接使用绝对地址,例如call_register64bit或call_any64bitmemory。

这是一种奇特的方式,即此呼叫的参数是相对于当前RIP的32位值。

通常,编译在一起的代码将完全远处,这已经足够了,但是有了生锈的功能和动态分配的匿名地图,因此不能保证它们会相距不到2GB。

例如,在我的系统中,foo在地址0x559A8039F5A0时,匿名存储器位于0x40000000。超过87657 GB!

解决方案是按照文档指示和64位绝对跳跃进行操作,例如使用rax

另一个问题是,在X86_64 ABI中,必须将堆栈对准16个字节。但是,对 nullary 函数进行调用仅将8个字节推到堆栈中,并且被错误对准。

为了解决此问题,功能需要以某种方式重新调整堆栈。如果该函数具有本地自动存储,则可以通过保留16 plus 8的多个字节多个。启动和相应的pop最后。

工作代码将是:

// push %rax
instr_stream.push_Register64Bit_r64(Register64Bit::RAX);
// movabs foo, %rax
instr_stream.mov_Register64Bit_Immediate64Bit(
    Register64Bit::RAX,
    Immediate64Bit(foo as i64)
);
// call *%rax
instr_stream.call_Register64Bit(Register64Bit::RAX);
// movabs 0x123456789abcdef0, %rax
instr_stream.mov_Register64Bit_Immediate64Bit(Register64Bit::RAX, Immediate64Bit(0x123456789abcdef0));
// pop %ecx
instr_stream.pop_Register64Bit_r64(Register64Bit::RCX);
// ret
instr_stream.ret();
instr_stream.finish();

There are two issues that I know of with this code.

One comes from this comment in the documentation of call_function:

32-bit displacement sign extended to 64-bits in 64-bit mode.

WARNING: The location of emitted code may be such that if it is more than 2Gb away from common library function calls (eg printf); it may be preferrable to use an absolute address indirectly in this case, eg call_Register64Bit or call_Any64BitMemory.

That is a fancy way of saying that the argument of this call is a 32-bit value relative to the current rip.

Usually, code compiled together will be all quite near, and that is more than enough but with a Rust compiled function and a dynamically allocated anonymous map there is no guarantee that they will be less than 2GB apart.

For example, in my system, foo is at address 0x559a8039f5a0 while the anonymous memory is at 0x40000000. That is more than 87657 GB away!

The solution is to do as the documentation instructs and to a 64-bit absolute jump, for example using rax.

The other problem is that in x86_64 ABI the stack must be aligned to 16 bytes. But doing a call to a nullary function only pushes 8 bytes to the stack and it gets misaligned.

To fix this, functions need to somehow realign the stack. If the function has local automatic storage, it is done by reserving a number of bytes multiple of 16 plus 8. Functions that do not use automatic storage, such as yours, usually just do a random push at the start and a corresponding pop at the end.

The working code would be something like:

// push %rax
instr_stream.push_Register64Bit_r64(Register64Bit::RAX);
// movabs foo, %rax
instr_stream.mov_Register64Bit_Immediate64Bit(
    Register64Bit::RAX,
    Immediate64Bit(foo as i64)
);
// call *%rax
instr_stream.call_Register64Bit(Register64Bit::RAX);
// movabs 0x123456789abcdef0, %rax
instr_stream.mov_Register64Bit_Immediate64Bit(Register64Bit::RAX, Immediate64Bit(0x123456789abcdef0));
// pop %ecx
instr_stream.pop_Register64Bit_r64(Register64Bit::RCX);
// ret
instr_stream.ret();
instr_stream.finish();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文