复制`std :: thread :: spawn`会导致堆栈溢出
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了复制`std :: thread :: spawn`会导致堆栈溢出相关的知识,希望对你有一定的参考价值。
说明
我正在尝试以#[no_std]
板条箱在Windows计算机上生成线程,但是我在__chkstk函数中遇到问题。首先,我用std创建了一个箱子,并尝试在libstd中找到负责生成线程的位置。
Libstd代码(简称为仅显示相关部分)
// libstd/thread/mod.rs
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
Builder::new().spawn(f).expect("failed to spawn thread")
}
// libstd/thread/mod.rs
impl Builder {
pub fn spawn<F, T>(self, f: F) -> io::Result<JoinHandle<T>>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
unsafe { self.spawn_unchecked(f) }
}
pub unsafe fn spawn_unchecked<'a, F, T>(self, f: F) -> io::Result<JoinHandle<T>>
where
F: FnOnce() -> T,
F: Send + 'a,
T: Send + 'a,
{
// ...
imp::Thread::new(
mem::transmute::<Box<dyn FnOnce() + 'a>, Box<dyn FnOnce() + 'static>>(Box::new(
main,
)),
)
// ...
}
}
// libstd/sys/windows/thread.rs
impl Thread {
pub unsafe fn new(p: Box<dyn FnOnce()>) -> io::Result<Thread> {
extern "system" fn thread_start(main: *mut c_void) -> c::DWORD {
unsafe { start_thread(main as *mut u8); }
0
}
let p = box p;
let ret = c::CreateThread(
// ...
thread_start,
&*p as *const _ as *mut _,
// ...
);
if ret as usize == 0 {
Err(io::Error::last_os_error())
} else {
mem::forget(p);
Ok(Thread { handle: Handle::new(ret) })
};
}
}
// libstd/sys_common/thread.rs
pub unsafe fn start_thread(main: *mut u8) {
// ...
Box::from_raw(main as *mut Box<dyn FnOnce()>)()
}
// libstd/sys/windows/c.rs
extern "system" {
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread
pub fn CreateThread(
lpThreadAttributes: LPSECURITY_ATTRIBUTES,
dwStackSize: SIZE_T,
lpStartAddress: extern "system" fn(*mut c_void) -> DWORD,
lpParameter: LPVOID,
dwCreationFlags: DWORD,
lpThreadId: LPDWORD,
) -> HANDLE;
}
我复制libstd的尝试
我削减了此示例的代码,这是我尝试复制libstd的简短单线程方式:
#[repr(C)]
struct Handle(usize);
fn spawn_std_like<F, T>(f: F) -> Handle
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
try_spawn_std_like(f).expect("failed to spawn thread")
}
fn try_spawn_std_like<F, T>(f: F) -> Result<Handle, ()>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
extern "system" fn thread_start(main: *mut u8) -> u32 {
unsafe { Box::from_raw(main as *mut Box<dyn FnOnce()>)(); }
0
}
let p = Box::new(Box::new(f));
let handle = CreateThread(
thread_start,
&*p as *const _ as *mut _
);
if handle.0 != 0 {
core::mem::forget(p);
Ok(handle)
} else {
Err(())
}
}
// Minimal version of `kernel32.CreateThread`, with only the relevant parameters.
#[allow(non_snake_case)]
extern "system" fn CreateThread(
start_address: extern "system" fn(*mut u8) -> u32,
parameter: *mut u8
) -> Handle {
start_address(parameter);
// Emulate successful `CreateThread` call.
Handle(4)
}
用spawn_std_like(|| println!("std_like!"));
调用它会使进程崩溃,并在__chkstk
中发生堆栈溢出,因为它使用某些内存地址而不是闭包大小作为“计数器”来访问内存页面:Exception thrown at 0x00007FF7E41FE948 in example.exe: 0xC00000FD: Stack overflow (parameters: 0x0000000000000001, 0x000000EFEDC06000).
堆栈跟踪:
__chkstk()
第109行(d: agent_work 5 s src vctools crt vcstartup src misc amd64 chkstk.asm)- [
std::alloc::boxed::{{impl}}::call_once<(), FnOnce<()>>(core::ops::function::Box<FnOnce<()>> self)
第1015行(boxed.rs) - [
std::alloc::boxed::{{impl}}::call_once<(), alloc::boxed::Box<FnOnce<()>>>(core::ops::function::Box<FnOnce<()>> * self)
第1015行(boxed.rs) try_spawn_std_like::thread_start(unsigned char * main)
(main.rs)try_spawn_std_like::<closure-0, ()>(main::closure-0)
(main.rs)spawn_std_like<closure-0, ()>(main::closeure-0 f)
(main.rs)main()
(main.rs)
我尝试过的其他变体
// Explicitly typed out, `std` style.
fn spawn0<F, T>(f: F) -> Result<Handle, ()>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
extern "system" fn thread_start(f: *mut u8) -> u32 {
let f = f as *mut Box<dyn FnOnce()>;
let f: Box<Box<dyn FnOnce()>> = unsafe {
Box::from_raw(f)
};
f();
0
}
let p = Box::new(Box::new(f));
let handle = CreateThread(
thread_start,
&*p as *const _ as *mut _
);
if handle.0 != 0 {
core::mem::forget(p);
Ok(handle)
} else {
Err(())
}
}
// Explicitly typed out, with `into_raw`.
fn spawn1<F, T>(f: F) -> Result<Handle, ()>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
extern "system" fn thread_start(f: *mut u8) -> u32 {
let f = f as *mut Box<dyn FnOnce()>;
let f: Box<Box<dyn FnOnce()>> = unsafe {
Box::from_raw(f)
};
f();
0
}
let f: Box<Box<F>> = Box::new(Box::new(f));
let f: *mut Box<F> = Box::into_raw(f);
let handle = CreateThread(
thread_start,
f as *mut _
);
if handle.0 != 0 {
Ok(handle)
} else {
unsafe { Box::from_raw(f); }
Err(())
}
}
// Implicitly typed `spawn1` variant.
fn spawn2<F, T>(f: F) -> Result<Handle, ()>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
extern "system" fn thread_start(f: *mut u8) -> u32 {
unsafe {
Box::from_raw(
f as *mut Box<dyn FnOnce()>
)();
}
0
}
let f = Box::into_raw(Box::new(Box::new(f)));
let handle = CreateThread(
thread_start,
f as *mut _
);
if handle.0 != 0 {
Ok(handle)
} else {
unsafe { Box::from_raw(f); }
Err(())
}
}
// Generic `thread_start` routine.
fn spawn3<F, T>(f: F) -> Result<Handle, ()>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
extern "system" fn thread_start<F, T>(f: *mut Box<F>) -> Handle
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static
{
unsafe { Box::from_raw(f)(); }
Handle(1)
}
let f = Box::into_raw(Box::new(Box::new(f)));
let handle = thread_start(f);
if handle.0 != 0 {
Ok(handle)
} else {
unsafe { Box::from_raw(f); }
Err(())
}
}
// More explicit type in type-cast in `thread_start`. Does not compile.
/*
fn spawn4<F, T>(f: F) -> Result<Handle, ()>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
extern "system" fn thread_start(f: *mut u8) -> u32 {
unsafe {
Box::from_raw(
// f as *mut Box<dyn FnOnce() -> (dyn Send + 'static) + Send + 'static>
// f as *mut Box<dyn FnOnce() -> (dyn Sized + Send + 'static) + Send + 'static>
)();
}
0
}
let f = Box::into_raw(Box::new(Box::new(f)));
let handle = CreateThread(
thread_start,
f as *mut _
);
if handle.0 != 0 {
Ok(handle)
} else {
unsafe { Box::from_raw(f); }
Err(())
}
}
*/
// Like `spawn2`, but with `+ Send + 'static`.
fn spawn5<F, T>(f: F) -> Result<Handle, ()>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
// `kernel32.CreateThread` like start routine.
extern "system" fn thread_start(f: *mut u8) -> u32 {
unsafe {
Box::from_raw(
f as *mut Box<dyn FnOnce() + Send + 'static>
)();
}
0
}
let f = Box::into_raw(Box::new(Box::new(f)));
let handle = CreateThread(
thread_start,
f as *mut _
);
if handle.0 != 0 {
Ok(handle)
} else {
unsafe { Box::from_raw(f); }
Err(())
}
}
对于spawn3
以外的所有版本,闭包内部的实际代码已由编译器从二进制文件中删除,因此它永远无法工作。我通过在闭包中调用#[no_std]
在最小的user32.MessageBox
板条箱中尝试了此操作,它没有出现在二进制文件内的导入函数列表中。在我的__chkstk
实现中,它也崩溃了。调试时,我可以看到发送到rax
寄存器中的函数的参数(特殊调用约定)包含内存地址而不是闭包的大小,并且每次将循环中的参数减0x1000并触摸堆栈页面,直到堆栈溢出为止。
通用kernel32.CreateThread
spawn3
是唯一有效的变体。但是我不能将其用于实际的kernel32.CreateThread
,因为导入的C函数及其参数在Rust(error[E0044]: foreign items may not have type parameters
)中不能通用:
#[link(name = "kernel32", kind = "dylib")]
extern "system" {
fn CreateThread<
F: Send + 'static + FnOnce() -> T,
T: Send + 'static
>(
security_attributes: *const u8,
stack_size: usize,
start_address: extern "system" fn(*mut Box<F>) -> u32,
parameter: *mut Box<F>,
attributes: u32,
id: *mut u32
) -> usize;
}
我想应该有可能,但我做错了,因为它在libstd中有效。
在线
let p = Box::new(Box::new(f));
您正在创建Box<Box<F>>
。这里的两个方框是细指针,因为这里F
恰好是Sized
。
在线
unsafe { Box::from_raw(main as *mut Box<dyn FnOnce()>)(); }
您正在尝试将内部Box
解释为Box<dyn FnOnce()>
。 Box<dyn FnOnce()>
是粗略的指针:原始指针与一些辅助数据耦合–对于dyn Trait
类型,辅助数据是指向vtable的指针。
为了使代码正常工作,您需要实际创建一个Box<dyn FnOnce()>
。为此,您需要像下面这样强制转换内部Box
:
let p = Box::new(Box::new(f) as Box<dyn FnOnce()>);
现在,这还不够;强制转换无效,因为F
实现了FnOnce() -> T
,而不是FnOnce()
(这是FnOnce() -> ()
的简写)。将绑定的F: FnOnce() -> T
更改为F: FnOnce()
并删除T
和spawn_std_like
上现在多余的try_spawn_std_like
将解决此问题。另一种选择是将f
包装在返回()
的闭包中:
let p = Box::new(Box::new(|| { f(); }) as Box<dyn FnOnce()>);
以上是关于复制`std :: thread :: spawn`会导致堆栈溢出的主要内容,如果未能解决你的问题,请参考以下文章