Описание
Я пытаюсь порождать потоки на моей машине Windows в ящике #[no_std]
, но у меня возникают проблемы с функцией __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 (в штучной упаковке) .rs) std::alloc::boxed::{{impl}}::call_once<(), alloc::boxed::Box<FnOnce<()>>>(core::ops::function::Box<FnOnce<()>> * self)
Строка 1015 (в штучной упаковке) 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
(специальное соглашение о вызовах), содержит размер памяти вместо размера замыкания и уменьшает значение параметра в l oop на 0x1000 каждый раз и касается страницы стека, пока стек не переполнится.
Generi c kernel32.CreateThread
spawn3
- единственный вариант, который действительно работает. Но я не могу использовать это для реальных kernel32.CreateThread
, потому что импортированные C функции и их параметры не могут быть обобщенными 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.