复制`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()并删除Tspawn_std_like上现在多余的try_spawn_std_like将解决此问题。另一种选择是将f包装在返回()的闭包中:

let p = Box::new(Box::new(|| { f(); }) as Box<dyn FnOnce()>);

以上是关于复制`std :: thread :: spawn`会导致堆栈溢出的主要内容,如果未能解决你的问题,请参考以下文章

使用成员函数启动线程

从 std::thread 获取返回码? [复制]

std::thread 通过引用传递调用复制构造函数

std::thread 按引用传递调用复制构造函数

使用 std::thread 或 CreateThread()? [复制]

带有类参数的 std::thread 初始化导致类对象被多次复制