Skip to content

Support GNU backtraces on 9x#2

Draft
Jookia wants to merge 2 commits into
rust9x:rust9xfrom
Jookia:jookia_modulestub
Draft

Support GNU backtraces on 9x#2
Jookia wants to merge 2 commits into
rust9x:rust9xfrom
Jookia:jookia_modulestub

Conversation

@Jookia

@Jookia Jookia commented Jul 2, 2026

Copy link
Copy Markdown

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.

Jookia added 2 commits July 2, 2026 03:39
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.
Comment thread src/backtrace/win32.rs
#[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) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Ignore this!)

use alloc::vec::Vec;
use core::mem;
use core::mem::MaybeUninit;
use core::ptr;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a rust9x gate.

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);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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());
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surely there's a less repetitive way to do this? And maybe only do it once?

Comment thread src/windows_sys.rs
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);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and the other module definition aren't needed as we use dynamic loading.

Comment thread bindings.txt
Windows.Win32.System.Diagnostics.Debug.SymSetOptions
Windows.Win32.System.Diagnostics.Debug.SymSetSearchPathW
Windows.Win32.System.Diagnostics.ToolHelp.CreateToolhelp32Snapshot
Windows.Win32.System.Diagnostics.ToolHelp.Module32First

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed with dynamic loading I don't think? Same with the Module32Next.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant