gpt4 book ai didi

multithreading - 复制 `std::thread::spawn` 导致堆栈溢出

转载 作者:行者123 更新时间:2023-12-03 11:43:30 27 4
gpt4 key购买 nike

描述

我试图在我的 Windows 机器上生成线程 #[no_std] crate ,但我在 __chkstk 中遇到问题功能。首先,我使用 std 创建了一个 crate,并尝试在 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 中的函数register(特殊调用约定)包含一个内存地址而不是闭包的大小,它每次将循环中的参数递减 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在这里,因为 Box<T>总是 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() -> TF: FnOnce()并删除现在多余的 Tspawn_std_liketry_spawn_std_like将解决这个问题。另一种选择是包装 f在返回 () 的闭包中:
    let p = Box::new(Box::new(|| { f(); }) as Box<dyn FnOnce()>);

    关于multithreading - 复制 `std::thread::spawn` 导致堆栈溢出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59771881/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com