Support GNU backtraces on 9x#2
Conversation
RtlCaptureContext is a Windows NT API and uses the stdcall calling convention. Failing to adhere to this can cause stack overflow and corruption later on in the program.
| #[cfg(all(target_arch = "x86", target_family = "rust9x"))] | ||
| #[unsafe(naked)] | ||
| unsafe extern "C" fn RtlCaptureContext(context: &mut CONTEXT) { | ||
| unsafe extern "stdcall" fn RtlCaptureContext(context: &mut CONTEXT) { |
| use alloc::vec::Vec; | ||
| use core::mem; | ||
| use core::mem::MaybeUninit; | ||
| use core::ptr; |
| let src: *const MODULEENTRY32 = narrow; | ||
| let dst: *mut MODULEENTRY32W = &mut out; | ||
| let len = mem::size_of::<MODULEENTRY32>(); | ||
| ptr::copy_nonoverlapping(src as *const u8, dst as *mut u8, len); |
There was a problem hiding this comment.
This is safe as long as the structs don't change. I'm pretty sure it's impossible to change the structs at this point as they're a stable ABI. But still, maybe it's better to write a function that copies the fields manually?
This also leaves garbage in szExePath and szModule, but that doesn't seem to be too big of a problem as we next overwrite it. Might be worth mentioning here?
| MultiByteToWideChar(CP_ACP, 0, | ||
| narrow.as_ptr(), narrow.len().try_into().unwrap(), | ||
| wide.as_mut_ptr(), wide.len().try_into().unwrap()); | ||
| } |
There was a problem hiding this comment.
It might be best to zero out the destination memory first.
Also it seems important to slap a NULL terminator on the end. Maybe len - 1 for the destination with zeroing might work well here?
| #[cfg(target_family = "rust9x")] | ||
| unsafe fn add_loaded_images(ret: &mut Vec<Library>) { | ||
| unsafe { | ||
| let module = GetModuleHandleA(c"KERNEL32.DLL".as_ptr().cast()); // TODO: may be 0 |
There was a problem hiding this comment.
If this is 0 there's probably bigger versions, but might be worth adding an assertion or check anyway to save debug time.
|
|
||
| let snapshot_func: extern "stdcall" fn(CREATE_TOOLHELP_SNAPSHOT_FLAGS, u32) -> HANDLE = mem::transmute(snapshot_proc); | ||
| let module32first_func: extern "stdcall" fn(HANDLE, *mut MODULEENTRY32) -> BOOL = mem::transmute(module32first_proc); | ||
| let module32next_func: extern "stdcall" fn(HANDLE, *mut MODULEENTRY32) -> BOOL = mem::transmute(module32next_proc); |
There was a problem hiding this comment.
Surely there's a less repetitive way to do this? And maybe only do it once?
| windows_link::link!("kernel32.dll" "system" fn GetProcAddress(hmodule : HMODULE, lpprocname : PCSTR) -> FARPROC); | ||
| windows_link::link!("kernel32.dll" "system" fn LoadLibraryA(lplibfilename : PCSTR) -> HMODULE); | ||
| windows_link::link!("kernel32.dll" "system" fn MapViewOfFile(hfilemappingobject : HANDLE, dwdesiredaccess : FILE_MAP, dwfileoffsethigh : u32, dwfileoffsetlow : u32, dwnumberofbytestomap : usize) -> MEMORY_MAPPED_VIEW_ADDRESS); | ||
| windows_link::link!("kernel32.dll" "system" fn Module32First(hsnapshot : HANDLE, lpme : *mut MODULEENTRY32) -> BOOL); |
There was a problem hiding this comment.
This and the other module definition aren't needed as we use dynamic loading.
| Windows.Win32.System.Diagnostics.Debug.SymSetOptions | ||
| Windows.Win32.System.Diagnostics.Debug.SymSetSearchPathW | ||
| Windows.Win32.System.Diagnostics.ToolHelp.CreateToolhelp32Snapshot | ||
| Windows.Win32.System.Diagnostics.ToolHelp.Module32First |
There was a problem hiding this comment.
Not needed with dynamic loading I don't think? Same with the Module32Next.
This is a draft do not merge.
Last night I managed to get GNU backtraces working on 9x and the program not error due to a missing Toolhelp on NT 4. Here's the code. It also includes my other PR because that's what I was testing it on. I'd like some feedback on whether this approach is worthwhile and if there's a better way to handle getting the procedures.