当从汇编代码访问的锈函数访问共享内存时,通用保护故障
我正在尝试在运行时组装和调用代码在用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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我对此代码有两个问题。
这是一种奇特的方式,即此
呼叫的参数
是相对于当前RIP
的32位值。通常,编译在一起的代码将完全远处,这已经足够了,但是有了生锈的功能和动态分配的匿名地图,因此不能保证它们会相距不到2GB。
例如,在我的系统中,
foo
在地址0x559A8039F5A0
时,匿名存储器位于0x40000000
。超过87657 GB!解决方案是按照文档指示和64位绝对跳跃进行操作,例如使用
rax
。另一个问题是,在X86_64 ABI中,必须将堆栈对准16个字节。但是,对 nullary 函数进行
调用
仅将8个字节推到堆栈中,并且被错误对准。为了解决此问题,功能需要以某种方式重新调整堆栈。如果该函数具有本地自动存储,则可以通过保留16 plus 8的多个字节多个。启动和相应的
pop
最后。工作代码将是:
There are two issues that I know of with this code.
One comes from this comment in the documentation of call_function:
That is a fancy way of saying that the argument of this
call
is a 32-bit value relative to the currentrip
.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 address0x559a8039f5a0
while the anonymous memory is at0x40000000
. 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 correspondingpop
at the end.The working code would be something like: