From a81baeaf9ed72650ce1763fef56ea59b94fff052 Mon Sep 17 00:00:00 2001 From: Brandon Ros Date: Sun, 12 Apr 2026 17:32:46 -0400 Subject: [PATCH 1/5] Refactor minidump serialization and add x86/x64 roundtrip coverage --- .../src/api/windows/winapi32/advapi32.rs | 2 +- .../src/api/windows/winapi32/crypt32.rs | 2 +- .../src/api/windows/winapi32/dnsapi.rs | 2 +- .../src/api/windows/winapi32/iphlpapi.rs | 2 +- .../src/api/windows/winapi32/kernel32/mod.rs | 2 +- .../src/api/windows/winapi32/kernelbase.rs | 2 +- .../src/api/windows/winapi32/libgcc.rs | 2 +- .../src/api/windows/winapi32/mscoree.rs | 2 +- .../src/api/windows/winapi32/msvcrt.rs | 2 +- .../src/api/windows/winapi32/ntdll.rs | 2 +- .../src/api/windows/winapi32/ntoskrnl.rs | 2 +- .../src/api/windows/winapi32/oleaut32.rs | 2 +- .../src/api/windows/winapi32/shell32.rs | 2 +- .../src/api/windows/winapi32/shlwapi.rs | 2 +- .../src/api/windows/winapi32/urlmon.rs | 2 +- .../src/api/windows/winapi32/user32.rs | 2 +- .../src/api/windows/winapi32/wincrt.rs | 2 +- .../src/api/windows/winapi32/wininet/mod.rs | 2 +- .../src/api/windows/winapi32/ws2_32.rs | 2 +- .../src/api/windows/winapi64/advapi32.rs | 2 +- .../src/api/windows/winapi64/comctl32.rs | 2 +- .../src/api/windows/winapi64/comctl64.rs | 2 +- .../src/api/windows/winapi64/dnsapi.rs | 2 +- .../src/api/windows/winapi64/gdi32.rs | 2 +- .../src/api/windows/winapi64/kernel32/mod.rs | 2 +- .../src/api/windows/winapi64/msvcrt.rs | 2 +- .../src/api/windows/winapi64/ntdll.rs | 2 +- .../src/api/windows/winapi64/ole32.rs | 2 +- .../src/api/windows/winapi64/oleaut32.rs | 2 +- .../src/api/windows/winapi64/shell32.rs | 2 +- .../src/api/windows/winapi64/shlwapi.rs | 2 +- .../src/api/windows/winapi64/urlmon.rs | 2 +- .../src/api/windows/winapi64/user32.rs | 2 +- .../src/api/windows/winapi64/uxtheme.rs | 2 +- .../src/api/windows/winapi64/wincrt.rs | 2 +- .../src/api/windows/winapi64/winhttp.rs | 2 +- .../src/api/windows/winapi64/wininet.rs | 2 +- .../src/api/windows/winapi64/ws2_32.rs | 2 +- crates/libmwemu/src/debug/console.rs | 2 +- crates/libmwemu/src/emu/execution/mod.rs | 6 +- .../src/emu/execution/multithreaded.rs | 2 +- crates/libmwemu/src/serialization/emu.rs | 71 ++- .../src/serialization/minidump/context.rs | 159 +++++ .../src/serialization/minidump/mod.rs | 30 + .../reader.rs} | 164 ++++- .../src/serialization/minidump/writer.rs | 590 ++++++++++++++++++ crates/libmwemu/src/serialization/mod.rs | 44 +- .../src/serialization/thread_context.rs | 5 +- .../src/tests/unit/serialization_tests.rs | 180 +++++- crates/libmwemu/src/threading/scheduler.rs | 2 +- crates/mwemu/src/main.rs | 4 +- 51 files changed, 1255 insertions(+), 80 deletions(-) create mode 100644 crates/libmwemu/src/serialization/minidump/context.rs create mode 100644 crates/libmwemu/src/serialization/minidump/mod.rs rename crates/libmwemu/src/serialization/{minidump_converter.rs => minidump/reader.rs} (54%) create mode 100644 crates/libmwemu/src/serialization/minidump/writer.rs diff --git a/crates/libmwemu/src/api/windows/winapi32/advapi32.rs b/crates/libmwemu/src/api/windows/winapi32/advapi32.rs index fa3dad38..7ea9425f 100644 --- a/crates/libmwemu/src/api/windows/winapi32/advapi32.rs +++ b/crates/libmwemu/src/api/windows/winapi32/advapi32.rs @@ -30,7 +30,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/crypt32.rs b/crates/libmwemu/src/api/windows/winapi32/crypt32.rs index 8ffd97fa..85334cd1 100644 --- a/crates/libmwemu/src/api/windows/winapi32/crypt32.rs +++ b/crates/libmwemu/src/api/windows/winapi32/crypt32.rs @@ -18,7 +18,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/dnsapi.rs b/crates/libmwemu/src/api/windows/winapi32/dnsapi.rs index 4610818d..ff55e3e9 100644 --- a/crates/libmwemu/src/api/windows/winapi32/dnsapi.rs +++ b/crates/libmwemu/src/api/windows/winapi32/dnsapi.rs @@ -16,7 +16,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/iphlpapi.rs b/crates/libmwemu/src/api/windows/winapi32/iphlpapi.rs index e93fd3bb..56dbd9a2 100644 --- a/crates/libmwemu/src/api/windows/winapi32/iphlpapi.rs +++ b/crates/libmwemu/src/api/windows/winapi32/iphlpapi.rs @@ -9,7 +9,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/kernel32/mod.rs b/crates/libmwemu/src/api/windows/winapi32/kernel32/mod.rs index 732c8593..239d7ef9 100644 --- a/crates/libmwemu/src/api/windows/winapi32/kernel32/mod.rs +++ b/crates/libmwemu/src/api/windows/winapi32/kernel32/mod.rs @@ -797,7 +797,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/kernelbase.rs b/crates/libmwemu/src/api/windows/winapi32/kernelbase.rs index 662e4537..3fcd5d8a 100644 --- a/crates/libmwemu/src/api/windows/winapi32/kernelbase.rs +++ b/crates/libmwemu/src/api/windows/winapi32/kernelbase.rs @@ -25,7 +25,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/libgcc.rs b/crates/libmwemu/src/api/windows/winapi32/libgcc.rs index 3d79360a..24db4c1c 100644 --- a/crates/libmwemu/src/api/windows/winapi32/libgcc.rs +++ b/crates/libmwemu/src/api/windows/winapi32/libgcc.rs @@ -15,7 +15,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/mscoree.rs b/crates/libmwemu/src/api/windows/winapi32/mscoree.rs index 65fa0e6f..9d5e9000 100644 --- a/crates/libmwemu/src/api/windows/winapi32/mscoree.rs +++ b/crates/libmwemu/src/api/windows/winapi32/mscoree.rs @@ -11,7 +11,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/msvcrt.rs b/crates/libmwemu/src/api/windows/winapi32/msvcrt.rs index 514dbe0c..ecb9baad 100644 --- a/crates/libmwemu/src/api/windows/winapi32/msvcrt.rs +++ b/crates/libmwemu/src/api/windows/winapi32/msvcrt.rs @@ -33,7 +33,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/ntdll.rs b/crates/libmwemu/src/api/windows/winapi32/ntdll.rs index 023fec2a..53295605 100644 --- a/crates/libmwemu/src/api/windows/winapi32/ntdll.rs +++ b/crates/libmwemu/src/api/windows/winapi32/ntdll.rs @@ -26,7 +26,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/ntoskrnl.rs b/crates/libmwemu/src/api/windows/winapi32/ntoskrnl.rs index b9c419a8..13e80d40 100644 --- a/crates/libmwemu/src/api/windows/winapi32/ntoskrnl.rs +++ b/crates/libmwemu/src/api/windows/winapi32/ntoskrnl.rs @@ -13,7 +13,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/oleaut32.rs b/crates/libmwemu/src/api/windows/winapi32/oleaut32.rs index ae882677..bae1c3f9 100644 --- a/crates/libmwemu/src/api/windows/winapi32/oleaut32.rs +++ b/crates/libmwemu/src/api/windows/winapi32/oleaut32.rs @@ -14,7 +14,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/shell32.rs b/crates/libmwemu/src/api/windows/winapi32/shell32.rs index 5d696ea4..79dc3dd8 100644 --- a/crates/libmwemu/src/api/windows/winapi32/shell32.rs +++ b/crates/libmwemu/src/api/windows/winapi32/shell32.rs @@ -11,7 +11,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/shlwapi.rs b/crates/libmwemu/src/api/windows/winapi32/shlwapi.rs index 50177acb..051d95dc 100644 --- a/crates/libmwemu/src/api/windows/winapi32/shlwapi.rs +++ b/crates/libmwemu/src/api/windows/winapi32/shlwapi.rs @@ -13,7 +13,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/urlmon.rs b/crates/libmwemu/src/api/windows/winapi32/urlmon.rs index a9ecc871..7242adc1 100644 --- a/crates/libmwemu/src/api/windows/winapi32/urlmon.rs +++ b/crates/libmwemu/src/api/windows/winapi32/urlmon.rs @@ -11,7 +11,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/user32.rs b/crates/libmwemu/src/api/windows/winapi32/user32.rs index cb881b97..ce535b5e 100644 --- a/crates/libmwemu/src/api/windows/winapi32/user32.rs +++ b/crates/libmwemu/src/api/windows/winapi32/user32.rs @@ -17,7 +17,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/wincrt.rs b/crates/libmwemu/src/api/windows/winapi32/wincrt.rs index fbf0543a..e9205081 100644 --- a/crates/libmwemu/src/api/windows/winapi32/wincrt.rs +++ b/crates/libmwemu/src/api/windows/winapi32/wincrt.rs @@ -13,7 +13,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/wininet/mod.rs b/crates/libmwemu/src/api/windows/winapi32/wininet/mod.rs index 550f3412..e221a53a 100644 --- a/crates/libmwemu/src/api/windows/winapi32/wininet/mod.rs +++ b/crates/libmwemu/src/api/windows/winapi32/wininet/mod.rs @@ -30,7 +30,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi32/ws2_32.rs b/crates/libmwemu/src/api/windows/winapi32/ws2_32.rs index c7d27ba7..45e7a363 100644 --- a/crates/libmwemu/src/api/windows/winapi32/ws2_32.rs +++ b/crates/libmwemu/src/api/windows/winapi32/ws2_32.rs @@ -39,7 +39,7 @@ pub fn gateway(addr: u32, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/advapi32.rs b/crates/libmwemu/src/api/windows/winapi64/advapi32.rs index b8b34688..60454c04 100644 --- a/crates/libmwemu/src/api/windows/winapi64/advapi32.rs +++ b/crates/libmwemu/src/api/windows/winapi64/advapi32.rs @@ -19,7 +19,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/comctl32.rs b/crates/libmwemu/src/api/windows/winapi64/comctl32.rs index 55228f3a..d0e335f1 100644 --- a/crates/libmwemu/src/api/windows/winapi64/comctl32.rs +++ b/crates/libmwemu/src/api/windows/winapi64/comctl32.rs @@ -11,7 +11,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/comctl64.rs b/crates/libmwemu/src/api/windows/winapi64/comctl64.rs index 268f146d..ea61101b 100644 --- a/crates/libmwemu/src/api/windows/winapi64/comctl64.rs +++ b/crates/libmwemu/src/api/windows/winapi64/comctl64.rs @@ -10,7 +10,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/dnsapi.rs b/crates/libmwemu/src/api/windows/winapi64/dnsapi.rs index ce048f7f..0c1d0839 100644 --- a/crates/libmwemu/src/api/windows/winapi64/dnsapi.rs +++ b/crates/libmwemu/src/api/windows/winapi64/dnsapi.rs @@ -9,7 +9,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/gdi32.rs b/crates/libmwemu/src/api/windows/winapi64/gdi32.rs index 6754c524..d955333f 100644 --- a/crates/libmwemu/src/api/windows/winapi64/gdi32.rs +++ b/crates/libmwemu/src/api/windows/winapi64/gdi32.rs @@ -11,7 +11,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/kernel32/mod.rs b/crates/libmwemu/src/api/windows/winapi64/kernel32/mod.rs index c322315e..04d30305 100644 --- a/crates/libmwemu/src/api/windows/winapi64/kernel32/mod.rs +++ b/crates/libmwemu/src/api/windows/winapi64/kernel32/mod.rs @@ -230,7 +230,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/msvcrt.rs b/crates/libmwemu/src/api/windows/winapi64/msvcrt.rs index ac27ba0f..1e6892eb 100644 --- a/crates/libmwemu/src/api/windows/winapi64/msvcrt.rs +++ b/crates/libmwemu/src/api/windows/winapi64/msvcrt.rs @@ -18,7 +18,7 @@ pub fn gateway_by_name(api: &str, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/ntdll.rs b/crates/libmwemu/src/api/windows/winapi64/ntdll.rs index 685e8fee..5038d26a 100644 --- a/crates/libmwemu/src/api/windows/winapi64/ntdll.rs +++ b/crates/libmwemu/src/api/windows/winapi64/ntdll.rs @@ -26,7 +26,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file(&emu, emu.cfg.dump_filename.as_ref().unwrap()); + serialization::Serialization::dump(&emu, emu.cfg.dump_filename.as_ref().unwrap()); } unimplemented!("atemmpt to call unimplemented API 0x{:x} {}", addr, api); diff --git a/crates/libmwemu/src/api/windows/winapi64/ole32.rs b/crates/libmwemu/src/api/windows/winapi64/ole32.rs index 3ad7295b..8f66be47 100644 --- a/crates/libmwemu/src/api/windows/winapi64/ole32.rs +++ b/crates/libmwemu/src/api/windows/winapi64/ole32.rs @@ -11,7 +11,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/oleaut32.rs b/crates/libmwemu/src/api/windows/winapi64/oleaut32.rs index 0b9ded4f..c4fb3432 100644 --- a/crates/libmwemu/src/api/windows/winapi64/oleaut32.rs +++ b/crates/libmwemu/src/api/windows/winapi64/oleaut32.rs @@ -17,7 +17,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/shell32.rs b/crates/libmwemu/src/api/windows/winapi64/shell32.rs index 3dd5559d..28d43192 100644 --- a/crates/libmwemu/src/api/windows/winapi64/shell32.rs +++ b/crates/libmwemu/src/api/windows/winapi64/shell32.rs @@ -13,7 +13,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/shlwapi.rs b/crates/libmwemu/src/api/windows/winapi64/shlwapi.rs index 3c408f74..2178d111 100644 --- a/crates/libmwemu/src/api/windows/winapi64/shlwapi.rs +++ b/crates/libmwemu/src/api/windows/winapi64/shlwapi.rs @@ -16,7 +16,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/urlmon.rs b/crates/libmwemu/src/api/windows/winapi64/urlmon.rs index ee0eb019..d8ce830d 100644 --- a/crates/libmwemu/src/api/windows/winapi64/urlmon.rs +++ b/crates/libmwemu/src/api/windows/winapi64/urlmon.rs @@ -11,7 +11,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/user32.rs b/crates/libmwemu/src/api/windows/winapi64/user32.rs index c2148331..3c0f1e13 100644 --- a/crates/libmwemu/src/api/windows/winapi64/user32.rs +++ b/crates/libmwemu/src/api/windows/winapi64/user32.rs @@ -19,7 +19,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/uxtheme.rs b/crates/libmwemu/src/api/windows/winapi64/uxtheme.rs index 476ee833..dfc36861 100644 --- a/crates/libmwemu/src/api/windows/winapi64/uxtheme.rs +++ b/crates/libmwemu/src/api/windows/winapi64/uxtheme.rs @@ -12,7 +12,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/wincrt.rs b/crates/libmwemu/src/api/windows/winapi64/wincrt.rs index 57db9fbf..a0ec8980 100644 --- a/crates/libmwemu/src/api/windows/winapi64/wincrt.rs +++ b/crates/libmwemu/src/api/windows/winapi64/wincrt.rs @@ -39,7 +39,7 @@ pub fn gateway_by_name(api: &str, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/winhttp.rs b/crates/libmwemu/src/api/windows/winapi64/winhttp.rs index ce048f7f..0c1d0839 100644 --- a/crates/libmwemu/src/api/windows/winapi64/winhttp.rs +++ b/crates/libmwemu/src/api/windows/winapi64/winhttp.rs @@ -9,7 +9,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/wininet.rs b/crates/libmwemu/src/api/windows/winapi64/wininet.rs index b555648c..6ec8c931 100644 --- a/crates/libmwemu/src/api/windows/winapi64/wininet.rs +++ b/crates/libmwemu/src/api/windows/winapi64/wininet.rs @@ -29,7 +29,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if emu.cfg.skip_unimplemented == false { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/api/windows/winapi64/ws2_32.rs b/crates/libmwemu/src/api/windows/winapi64/ws2_32.rs index 237abf37..a0f3d072 100644 --- a/crates/libmwemu/src/api/windows/winapi64/ws2_32.rs +++ b/crates/libmwemu/src/api/windows/winapi64/ws2_32.rs @@ -42,7 +42,7 @@ pub fn gateway(addr: u64, emu: &mut emu::Emu) -> String { _ => { if !emu.cfg.skip_unimplemented { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/debug/console.rs b/crates/libmwemu/src/debug/console.rs index 7b4a5925..88b92cae 100644 --- a/crates/libmwemu/src/debug/console.rs +++ b/crates/libmwemu/src/debug/console.rs @@ -1030,7 +1030,7 @@ impl Console { } "dump" => { if emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/emu/execution/mod.rs b/crates/libmwemu/src/emu/execution/mod.rs index 6042c45b..a1cc873e 100644 --- a/crates/libmwemu/src/emu/execution/mod.rs +++ b/crates/libmwemu/src/emu/execution/mod.rs @@ -375,7 +375,7 @@ impl Emu { if self.cfg.exit_position != 0 && self.pos == self.cfg.exit_position { log::trace!("exit position reached"); if self.cfg.dump_on_exit && self.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( self, self.cfg.dump_filename.as_ref().unwrap(), ); @@ -504,7 +504,7 @@ impl Emu { log::trace!("exit position reached"); if self.cfg.dump_on_exit && self.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( self, self.cfg.dump_filename.as_ref().unwrap(), ); @@ -797,7 +797,7 @@ impl Emu { log::trace!("exit position reached"); if self.cfg.dump_on_exit && self.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( self, self.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/emu/execution/multithreaded.rs b/crates/libmwemu/src/emu/execution/multithreaded.rs index 84777fac..7c967184 100644 --- a/crates/libmwemu/src/emu/execution/multithreaded.rs +++ b/crates/libmwemu/src/emu/execution/multithreaded.rs @@ -174,7 +174,7 @@ impl Emu { log::trace!("exit position reached"); if self.cfg.dump_on_exit && self.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( self, self.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/libmwemu/src/serialization/emu.rs b/crates/libmwemu/src/serialization/emu.rs index 90fb9eaf..a140211b 100644 --- a/crates/libmwemu/src/serialization/emu.rs +++ b/crates/libmwemu/src/serialization/emu.rs @@ -9,6 +9,7 @@ use iced_x86::Instruction; use serde::{Deserialize, Serialize}; use crate::api::banzai::Banzai; +use crate::arch::Arch; use crate::debug::breakpoint::Breakpoints; use crate::utils::colors::Colors; use crate::config::Config; @@ -29,6 +30,7 @@ use crate::windows::structures::MemoryOperation; use crate::emu::disassemble::InstructionCache; use crate::emu::object_handle::HandleManagement; use crate::emu::ArchState; +use crate::threading::context::ArchThreadState; #[derive(Serialize, Deserialize)] pub struct SerializableEmu { @@ -195,6 +197,24 @@ impl<'a> From<&'a Emu> for SerializableEmu { impl From for Emu { fn from(serialized: SerializableEmu) -> Self { + let current_thread_id = serialized.current_thread_id; + let current_regs = serialized.regs; + let current_pre_op_regs = serialized.pre_op_regs; + let current_post_op_regs = serialized.post_op_regs; + let current_flags = serialized.flags; + let current_pre_op_flags = serialized.pre_op_flags; + let current_post_op_flags = serialized.post_op_flags; + let current_eflags = serialized.eflags; + let current_fpu = serialized.fpu; + let current_seh = serialized.seh; + let current_veh = serialized.veh; + let current_uef = serialized.uef; + let current_eh_ctx = serialized.eh_ctx; + let current_tls32 = serialized.tls32; + let current_tls64 = serialized.tls64; + let current_fls = serialized.fls; + let current_fs = serialized.fs; + let current_call_stack = serialized.call_stack; let trace_file = if let Some(trace_filename) = &serialized.cfg.trace_filename { let file = File::open(trace_filename.clone()).unwrap(); Some(file) @@ -202,7 +222,7 @@ impl From for Emu { None }; - Emu { + let mut emu = Emu { // Configuration & display cfg: serialized.cfg.clone(), colors: serialized.colors, @@ -246,7 +266,7 @@ impl From for Emu { library_loaded: false, // Thread management threads: serialized.threads.into_iter().map(|t| t.into()).collect(), - current_thread_id: serialized.current_thread_id, + current_thread_id, main_thread_cont: serialized.main_thread_cont, gateway_return: serialized.gateway_return, global_locks: GlobalLocks::new(), @@ -275,7 +295,54 @@ impl From for Emu { last_error: 0, // Win32 resource management handle_management: HandleManagement::new(), // TODO: not yet serialized + }; + + if let Some(thread) = emu.threads.get_mut(current_thread_id) { + if let ArchThreadState::X86 { + regs, + pre_op_regs, + post_op_regs, + flags, + pre_op_flags, + post_op_flags, + eflags, + fpu, + seh, + veh, + uef, + eh_ctx, + tls32, + tls64, + fls, + fs, + call_stack, + } = &mut thread.arch + { + *regs = current_regs; + *pre_op_regs = current_pre_op_regs; + *post_op_regs = current_post_op_regs; + *flags = current_flags; + *pre_op_flags = current_pre_op_flags; + *post_op_flags = current_post_op_flags; + *eflags = current_eflags; + *fpu = current_fpu.into(); + *seh = current_seh; + *veh = current_veh; + *uef = current_uef; + *eh_ctx = current_eh_ctx; + *tls32 = current_tls32; + *tls64 = current_tls64; + *fls = current_fls; + *fs = current_fs; + *call_stack = current_call_stack; + } } + + if matches!(emu.cfg.arch, Arch::X86_64) { + emu.maps.is_64bits = true; + } + + emu } } diff --git a/crates/libmwemu/src/serialization/minidump/context.rs b/crates/libmwemu/src/serialization/minidump/context.rs new file mode 100644 index 00000000..17829138 --- /dev/null +++ b/crates/libmwemu/src/serialization/minidump/context.rs @@ -0,0 +1,159 @@ +use std::io::{self, Write}; + +use byteorder::{LittleEndian, WriteBytesExt}; +use minidump::format as md; + +use crate::arch::Arch; +use crate::flags::Flags; +use crate::regs64::Regs64; + +const CONTEXT_X86_SIZE: usize = 716; +const CONTEXT_AMD64_SIZE: usize = 1232; + +pub(super) fn build_thread_context( + arch: Arch, + regs: &Regs64, + flags: &Flags, +) -> io::Result> { + match arch { + Arch::X86 => build_x86_context(regs, flags), + Arch::X86_64 => build_amd64_context(regs, flags), + Arch::Aarch64 => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "aarch64 thread export is not implemented yet", + )), + } +} + +fn build_x86_context(regs: &Regs64, flags: &Flags) -> io::Result> { + let mut output = Vec::with_capacity(CONTEXT_X86_SIZE); + output.write_u32::(md::ContextFlagsX86::CONTEXT_X86_ALL.bits())?; + output.write_u32::(regs.dr0 as u32)?; + output.write_u32::(regs.dr1 as u32)?; + output.write_u32::(regs.dr2 as u32)?; + output.write_u32::(regs.dr3 as u32)?; + output.write_u32::(regs.dr6 as u32)?; + output.write_u32::(regs.dr7 as u32)?; + + output.write_u32::(0x027f)?; + output.write_u32::(0)?; + output.write_u32::(0)?; + output.write_u32::(0)?; + output.write_u32::(0)?; + output.write_u32::(0)?; + output.write_u32::(0)?; + output.extend_from_slice(&[0; 80]); + output.write_u32::(0)?; + + output.write_u32::(regs.gs as u32)?; + output.write_u32::(if regs.fs == 0 { 0x3b } else { regs.fs as u32 })?; + output.write_u32::(0x23)?; + output.write_u32::(0x23)?; + output.write_u32::(regs.get_edi() as u32)?; + output.write_u32::(regs.get_esi() as u32)?; + output.write_u32::(regs.get_ebx() as u32)?; + output.write_u32::(regs.get_edx() as u32)?; + output.write_u32::(regs.get_ecx() as u32)?; + output.write_u32::(regs.get_eax() as u32)?; + output.write_u32::(regs.get_ebp() as u32)?; + output.write_u32::(regs.get_eip() as u32)?; + output.write_u32::(0x1b)?; + output.write_u32::(flags.dump())?; + output.write_u32::(regs.get_esp() as u32)?; + output.write_u32::(0x23)?; + output.extend_from_slice(&[0; 512]); + + debug_assert_eq!(output.len(), CONTEXT_X86_SIZE); + Ok(output) +} + +fn build_amd64_context(regs: &Regs64, flags: &Flags) -> io::Result> { + let mut output = Vec::with_capacity(CONTEXT_AMD64_SIZE); + + for _ in 0..6 { + output.write_u64::(0)?; + } + + output.write_u32::(md::ContextFlagsAmd64::CONTEXT_AMD64_ALL.bits())?; + output.write_u32::(0x1f80)?; + output.write_u16::(0x33)?; + output.write_u16::(0)?; + output.write_u16::(0)?; + output.write_u16::(regs.fs as u16)?; + output.write_u16::(regs.gs as u16)?; + output.write_u16::(0x2b)?; + output.write_u32::(flags.dump())?; + output.write_u64::(regs.dr0)?; + output.write_u64::(regs.dr1)?; + output.write_u64::(regs.dr2)?; + output.write_u64::(regs.dr3)?; + output.write_u64::(regs.dr6)?; + output.write_u64::(regs.dr7)?; + output.write_u64::(regs.rax)?; + output.write_u64::(regs.rcx)?; + output.write_u64::(regs.rdx)?; + output.write_u64::(regs.rbx)?; + output.write_u64::(regs.rsp)?; + output.write_u64::(regs.rbp)?; + output.write_u64::(regs.rsi)?; + output.write_u64::(regs.rdi)?; + output.write_u64::(regs.r8)?; + output.write_u64::(regs.r9)?; + output.write_u64::(regs.r10)?; + output.write_u64::(regs.r11)?; + output.write_u64::(regs.r12)?; + output.write_u64::(regs.r13)?; + output.write_u64::(regs.r14)?; + output.write_u64::(regs.r15)?; + output.write_u64::(regs.rip)?; + + write_xmm_save_area32(&mut output, regs)?; + + for _ in 0..26 { + write_u128(&mut output, 0)?; + } + + for _ in 0..6 { + output.write_u64::(0)?; + } + + debug_assert_eq!(output.len(), CONTEXT_AMD64_SIZE); + Ok(output) +} + +fn write_xmm_save_area32(output: &mut Vec, regs: &Regs64) -> io::Result<()> { + output.write_u16::(0x027f)?; + output.write_u16::(0)?; + output.write_u8(0)?; + output.write_u8(0)?; + output.write_u16::(0)?; + output.write_u32::(0)?; + output.write_u16::(0)?; + output.write_u16::(0)?; + output.write_u32::(0)?; + output.write_u16::(0)?; + output.write_u16::(0)?; + output.write_u32::(0x1f80)?; + output.write_u32::(0xffff)?; + + for mm in [ + regs.mm0, regs.mm1, regs.mm2, regs.mm3, regs.mm4, regs.mm5, regs.mm6, regs.mm7, + ] { + write_u128(output, mm)?; + } + + for xmm in [ + regs.xmm0, regs.xmm1, regs.xmm2, regs.xmm3, regs.xmm4, regs.xmm5, regs.xmm6, regs.xmm7, + regs.xmm8, regs.xmm9, regs.xmm10, regs.xmm11, regs.xmm12, regs.xmm13, regs.xmm14, + regs.xmm15, + ] { + write_u128(output, xmm)?; + } + + output.extend_from_slice(&[0; 96]); + Ok(()) +} + +fn write_u128(output: &mut Vec, value: u128) -> io::Result<()> { + output.write_all(&value.to_le_bytes()) +} diff --git a/crates/libmwemu/src/serialization/minidump/mod.rs b/crates/libmwemu/src/serialization/minidump/mod.rs new file mode 100644 index 00000000..f01db498 --- /dev/null +++ b/crates/libmwemu/src/serialization/minidump/mod.rs @@ -0,0 +1,30 @@ +use std::error::Error; +use std::io; +use std::path::Path; + +use crate::emu::Emu; +use crate::serialization::emu::SerializableEmu; + +mod context; +mod reader; +mod writer; + +pub(crate) fn dump_to_minidump(emu: &Emu, filename: &str) -> io::Result<()> { + writer::MinidumpWriter::write_to_file(emu, filename) +} + +pub(crate) fn load_from_minidump(filename: &str) -> Result> { + reader::MinidumpReader::from_minidump_file(filename) +} + +pub(crate) fn is_minidump_path(filename: &str) -> bool { + Path::new(filename) + .extension() + .and_then(|ext| ext.to_str()) + .map(|ext| ext.eq_ignore_ascii_case("dmp") || ext.eq_ignore_ascii_case("mdmp")) + .unwrap_or(false) +} + +pub(crate) fn has_minidump_signature(data: &[u8]) -> bool { + data.starts_with(b"MDMP") +} diff --git a/crates/libmwemu/src/serialization/minidump_converter.rs b/crates/libmwemu/src/serialization/minidump/reader.rs similarity index 54% rename from crates/libmwemu/src/serialization/minidump_converter.rs rename to crates/libmwemu/src/serialization/minidump/reader.rs index 7e04e054..33e2fc16 100644 --- a/crates/libmwemu/src/serialization/minidump_converter.rs +++ b/crates/libmwemu/src/serialization/minidump/reader.rs @@ -7,6 +7,8 @@ use std::collections::BTreeMap; use std::error::Error; use std::ops::Deref; +use crate::arch::{Arch, OperatingSystem}; +use crate::flags::Flags; use crate::maps::mem64::{Mem64, Permission}; use crate::maps::tlb::TLB; use crate::maps::Maps; @@ -16,9 +18,13 @@ use crate::serialization::maps::SerializableMaps; use crate::serialization::pe32::SerializablePE32; use crate::serialization::pe64::SerializablePE64; -pub struct MinidumpConverter; +pub struct MinidumpReader; + +impl MinidumpReader { + fn unsupported_aarch64_minidump() -> Box { + "AArch64/ARM64 minidump import is not implemented yet; the current serialization path is still x86/x86_64-centric".into() + } -impl MinidumpConverter { fn get_pe_offset(data: &[u8]) -> Option { if data.len() < 0x3C + 4 { return None; @@ -84,14 +90,15 @@ impl MinidumpConverter { let mut mem_slab = Slab::new(); let mut maps = BTreeMap::new(); let mut name_map = AHashMap::new(); + let memory = dump.get_memory().unwrap_or_default(); - // Get memory regions from memory info list if let Ok(memory_info) = dump.get_stream::() { - let memory = dump.get_memory().unwrap_or_default(); - for info in memory_info.iter() { let base_addr = info.raw.base_address; - let size = info.raw.region_size; + if info.raw.region_size == 0 { + continue; + } + let permission = match info.protection.bits() & MemoryProtection::ACCESS_MASK.bits() { x if x == MemoryProtection::PAGE_NOACCESS.bits() => Permission::NONE, @@ -107,21 +114,41 @@ impl MinidumpConverter { _ => Permission::READ_WRITE_EXECUTE, }; - // Try to get the actual memory data for this region - let mem_data = memory - .memory_at_address(base_addr) - .unwrap() - .bytes() - .to_vec(); + let Some(mem_region) = memory.memory_at_address(base_addr) else { + continue; + }; + + let mem_data = mem_region.bytes().to_vec(); + if mem_data.is_empty() { + continue; + } let mem_entry = Mem64::new( format!("mem_0x{:016x}", base_addr), // name base_addr, // base_addr - base_addr + size, // bottom_addr (base + size) + base_addr + mem_data.len() as u64, // bottom_addr (base + size) mem_data, // mem data permission, ); + let slab_key = mem_slab.insert(mem_entry); + maps.insert(base_addr, slab_key); + } + } else { + for mem_region in memory.by_addr() { + if mem_region.bytes().is_empty() { + continue; + } + + let base_addr = mem_region.base_address(); + let mem_data = mem_region.bytes().to_vec(); + let mem_entry = Mem64::new( + format!("mem_0x{:016x}", base_addr), + base_addr, + base_addr + mem_data.len() as u64, + mem_data, + Permission::READ_WRITE_EXECUTE, + ); let slab_key = mem_slab.insert(mem_entry); maps.insert(base_addr, slab_key); } @@ -131,7 +158,6 @@ impl MinidumpConverter { if let Ok(modules) = dump.get_stream::() { for module in modules.iter() { let module_base = module.base_address(); - let module_size = module.size(); // Find corresponding memory region that contains this module for (&addr, &slab_key) in &maps { @@ -155,19 +181,89 @@ impl MinidumpConverter { ))) } + fn extract_thread_context>( + dump: &minidump::Minidump<'static, T>, + system_info: &MinidumpSystemInfo, + ) -> Result<(Regs64, Flags), Box> { + let threads = dump.get_stream::()?; + let thread = threads + .threads + .first() + .ok_or("No threads found in minidump")?; + let misc = dump.get_stream::().ok(); + let context = thread + .context(system_info, misc.as_ref()) + .ok_or("No thread context found in minidump")?; + + let mut regs = Regs64::default(); + let mut flags = Flags::new(); + + match &context.raw { + MinidumpRawContext::X86(raw) => { + regs.dr0 = raw.dr0 as u64; + regs.dr1 = raw.dr1 as u64; + regs.dr2 = raw.dr2 as u64; + regs.dr3 = raw.dr3 as u64; + regs.dr6 = raw.dr6 as u64; + regs.dr7 = raw.dr7 as u64; + regs.set_eax(raw.eax as u64); + regs.set_ebx(raw.ebx as u64); + regs.set_ecx(raw.ecx as u64); + regs.set_edx(raw.edx as u64); + regs.set_esi(raw.esi as u64); + regs.set_edi(raw.edi as u64); + regs.set_ebp(raw.ebp as u64); + regs.set_esp(raw.esp as u64); + regs.set_eip(raw.eip as u64); + regs.fs = raw.fs as u64; + regs.gs = raw.gs as u64; + flags.load(raw.eflags); + } + MinidumpRawContext::Amd64(raw) => { + regs.dr0 = raw.dr0; + regs.dr1 = raw.dr1; + regs.dr2 = raw.dr2; + regs.dr3 = raw.dr3; + regs.dr6 = raw.dr6; + regs.dr7 = raw.dr7; + regs.rax = raw.rax; + regs.rbx = raw.rbx; + regs.rcx = raw.rcx; + regs.rdx = raw.rdx; + regs.rsi = raw.rsi; + regs.rdi = raw.rdi; + regs.rbp = raw.rbp; + regs.rsp = raw.rsp; + regs.rip = raw.rip; + regs.r8 = raw.r8; + regs.r9 = raw.r9; + regs.r10 = raw.r10; + regs.r11 = raw.r11; + regs.r12 = raw.r12; + regs.r13 = raw.r13; + regs.r14 = raw.r14; + regs.r15 = raw.r15; + regs.fs = raw.fs as u64; + regs.gs = raw.gs as u64; + flags.load(raw.eflags); + } + MinidumpRawContext::Arm64(_) | MinidumpRawContext::OldArm64(_) => { + return Err(Self::unsupported_aarch64_minidump()) + } + _ => return Err("Unsupported minidump CPU context".into()), + } + + Ok((regs, flags)) + } + pub fn from_minidump_file(path: &str) -> Result> { let dump = minidump::Minidump::read_path(path)?; // Get basic streams we need let system_info = dump.get_stream::()?; - let exception = dump.get_stream::()?; - let threads = dump.get_stream::()?; - - // Find crashed thread - let crashed_thread = threads - .threads - .first() - .ok_or("No threads found in minidump")?; + if matches!(system_info.cpu, minidump::system_info::Cpu::Arm64) { + return Err(Self::unsupported_aarch64_minidump()); + } // Extract PE modules let (pe32, pe64) = Self::extract_pe_modules(&dump)?; @@ -175,15 +271,35 @@ impl MinidumpConverter { // Extract memory maps let maps = Self::extract_memory_maps(&dump)?; - // Extract registers - just use defaults for now since context parsing is complex - let regs = Regs64::default(); + // Extract thread context when present. + let (regs, flags) = Self::extract_thread_context(&dump, &system_info)?; // Basic serializable emu with minimal data let mut serializable_emu = SerializableEmu::default(); serializable_emu.set_maps(maps); serializable_emu.set_regs(regs); + serializable_emu.flags = flags; + serializable_emu.pre_op_flags = flags; + serializable_emu.post_op_flags = flags; serializable_emu.set_pe32(pe32); serializable_emu.set_pe64(pe64); + serializable_emu.cfg.arch = match system_info.cpu { + minidump::system_info::Cpu::X86 => Arch::X86, + minidump::system_info::Cpu::X86_64 => Arch::X86_64, + minidump::system_info::Cpu::Arm64 => Arch::Aarch64, + _ => Arch::X86, + }; + serializable_emu.os = match system_info.os { + minidump::system_info::Os::Windows => OperatingSystem::Windows, + minidump::system_info::Os::Linux => OperatingSystem::Linux, + minidump::system_info::Os::MacOs => OperatingSystem::MacOS, + _ => OperatingSystem::Windows, + }; + if let Some(pe64) = &serializable_emu.pe64 { + serializable_emu.filename = pe64.filename.clone(); + } else if let Some(pe32) = &serializable_emu.pe32 { + serializable_emu.filename = pe32.filename.clone(); + } Ok(serializable_emu) } diff --git a/crates/libmwemu/src/serialization/minidump/writer.rs b/crates/libmwemu/src/serialization/minidump/writer.rs new file mode 100644 index 00000000..2cba33ba --- /dev/null +++ b/crates/libmwemu/src/serialization/minidump/writer.rs @@ -0,0 +1,590 @@ +use std::collections::BTreeMap; +use std::fs::File; +use std::io::{self, Write}; +use std::path::Path; +use std::time::{SystemTime, UNIX_EPOCH}; + +use byteorder::{LittleEndian, WriteBytesExt}; +use minidump::format as md; + +use super::context::build_thread_context; +use crate::arch::{Arch, OperatingSystem}; +use crate::emu::Emu; +use crate::maps::mem64::Permission; +use crate::threading::context::ArchThreadState; + +const MINIDUMP_HEADER_SIZE: u32 = 32; +const MINIDUMP_DIRECTORY_SIZE: u32 = 12; +const MINIDUMP_THREAD_SIZE: u32 = 48; +const MINIDUMP_MODULE_SIZE: u32 = 108; +const MINIDUMP_MEMORY_DESCRIPTOR_SIZE: u32 = 16; +const MINIDUMP_MEMORY_INFO_SIZE: u32 = 48; +const MINIDUMP_SYSTEM_INFO_SIZE: u32 = 56; +const MINIDUMP_FLAGS_FULL_MEMORY: u64 = 0x802; + +#[derive(Clone, Copy)] +struct MemoryRegion<'a> { + name: &'a str, + base: u64, + bytes: &'a [u8], + permission: Permission, +} + +#[derive(Clone, Copy)] +struct MemoryLocation { + data_size: u32, + rva: u32, +} + +struct ModuleInfo { + prefix: String, + display_name: String, + base: u64, + size_of_image: u32, +} + +struct StreamLayout { + stream_type: u32, + rva: u32, + data: Vec, + trailing_data: Vec, +} + +pub struct MinidumpWriter; + +impl MinidumpWriter { + pub fn write_to_file(emu: &Emu, filename: &str) -> io::Result<()> { + if !matches!(emu.cfg.arch, Arch::X86 | Arch::X86_64) { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!( + "minidump export currently supports x86/x86_64 guests, got {:?}", + emu.cfg.arch + ), + )); + } + + let regions = collect_memory_regions(emu); + let modules = collect_modules(emu, ®ions); + let stream_count = 5u32; + let payload_start = + align4(MINIDUMP_HEADER_SIZE + (stream_count * MINIDUMP_DIRECTORY_SIZE)); + let memory_rva = payload_start; + let (memory_stream, memory_trailing_data, memory_locations) = + build_memory_list_stream(®ions, memory_rva)?; + + let memory_info_rva = + align4(memory_rva + memory_stream.len() as u32 + memory_trailing_data.len() as u32); + let memory_info_stream = + build_memory_info_list_stream(®ions, &modules, memory_info_rva)?; + + let system_info_rva = align4(memory_info_rva + memory_info_stream.len() as u32); + let system_info_stream = build_system_info_stream(emu, system_info_rva)?; + + let module_rva = align4(system_info_rva + system_info_stream.len() as u32); + let (module_stream, module_trailing_data) = build_module_list_stream(&modules, module_rva)?; + + let thread_rva = + align4(module_rva + module_stream.len() as u32 + module_trailing_data.len() as u32); + let (thread_stream, thread_trailing_data) = + build_thread_list_stream(emu, ®ions, &memory_locations, thread_rva)?; + + let by_position = vec![ + StreamLayout { + stream_type: md::MINIDUMP_STREAM_TYPE::MemoryListStream as u32, + rva: memory_rva, + data: memory_stream, + trailing_data: memory_trailing_data, + }, + StreamLayout { + stream_type: md::MINIDUMP_STREAM_TYPE::MemoryInfoListStream as u32, + rva: memory_info_rva, + data: memory_info_stream, + trailing_data: Vec::new(), + }, + StreamLayout { + stream_type: md::MINIDUMP_STREAM_TYPE::SystemInfoStream as u32, + rva: system_info_rva, + data: system_info_stream, + trailing_data: Vec::new(), + }, + StreamLayout { + stream_type: md::MINIDUMP_STREAM_TYPE::ModuleListStream as u32, + rva: module_rva, + data: module_stream, + trailing_data: module_trailing_data, + }, + StreamLayout { + stream_type: md::MINIDUMP_STREAM_TYPE::ThreadListStream as u32, + rva: thread_rva, + data: thread_stream, + trailing_data: thread_trailing_data, + }, + ]; + + let directories = vec![ + directory_for_stream(&by_position[4]), + directory_for_stream(&by_position[3]), + directory_for_stream(&by_position[0]), + directory_for_stream(&by_position[2]), + directory_for_stream(&by_position[1]), + ]; + + let mut output = Vec::new(); + write_header(&mut output, stream_count)?; + for directory in directories { + write_directory(&mut output, &directory)?; + } + + for stream in &by_position { + pad_to_rva(&mut output, stream.rva)?; + output.extend_from_slice(&stream.data); + output.extend_from_slice(&stream.trailing_data); + } + + let mut file = File::create(filename)?; + file.write_all(&output)?; + file.flush()?; + Ok(()) + } +} + +fn align4(value: u32) -> u32 { + (value + 3) & !3 +} + +fn collect_memory_regions(emu: &Emu) -> Vec> { + emu.maps + .maps + .iter() + .filter_map(|(base, slab_key)| { + let mem = emu.maps.mem_slab.get(*slab_key)?; + if mem.size() == 0 { + return None; + } + + Some(MemoryRegion { + name: mem.get_name(), + base: *base, + bytes: mem.get_bytes(), + permission: mem.permission(), + }) + }) + .collect() +} + +fn collect_modules(emu: &Emu, regions: &[MemoryRegion<'_>]) -> Vec { + let mut modules = Vec::new(); + + for region in regions { + let Some(prefix) = region.name.strip_suffix(".pe") else { + continue; + }; + + let size_end = regions + .iter() + .filter(|candidate| candidate.base >= region.base) + .filter(|candidate| region_belongs_to_module(candidate.name, prefix)) + .map(|candidate| candidate.base + candidate.bytes.len() as u64) + .max() + .unwrap_or(region.base + region.bytes.len() as u64); + + modules.push(ModuleInfo { + prefix: prefix.to_string(), + display_name: display_name_for_module(emu, prefix), + base: region.base, + size_of_image: size_end.saturating_sub(region.base).min(u32::MAX as u64) as u32, + }); + } + + modules +} + +fn display_name_for_module(emu: &Emu, prefix: &str) -> String { + let prefix_lc = prefix.to_ascii_lowercase(); + let main_stem = Path::new(&emu.filename) + .file_stem() + .and_then(|stem| stem.to_str()) + .map(|stem| stem.to_ascii_lowercase()); + let exe_name_stem = Path::new(&emu.cfg.exe_name) + .file_stem() + .and_then(|stem| stem.to_str()) + .map(|stem| stem.to_ascii_lowercase()); + + if main_stem.as_deref() == Some(prefix_lc.as_str()) { + if !emu.filename.is_empty() { + return emu.filename.clone(); + } + } + + if exe_name_stem.as_deref() == Some(prefix_lc.as_str()) { + if !emu.cfg.exe_name.is_empty() { + return emu.cfg.exe_name.clone(); + } + } + + if !emu.cfg.maps_folder.is_empty() { + let dll_path = Path::new(&emu.cfg.maps_folder).join(format!("{prefix}.dll")); + if dll_path.exists() { + return dll_path.to_string_lossy().into_owned(); + } + + let exe_path = Path::new(&emu.cfg.maps_folder).join(format!("{prefix}.exe")); + if exe_path.exists() { + return exe_path.to_string_lossy().into_owned(); + } + } + + if main_stem.as_deref() == Some(prefix_lc.as_str()) + || exe_name_stem.as_deref() == Some(prefix_lc.as_str()) + { + format!("{prefix}.exe") + } else { + format!("{prefix}.dll") + } +} + +fn region_belongs_to_module(name: &str, prefix: &str) -> bool { + if name == format!("{prefix}.pe") { + return true; + } + + let Some(rest) = name.strip_prefix(prefix) else { + return false; + }; + + rest.starts_with('.') || rest.chars().next().map(|c| c.is_ascii_hexdigit()).unwrap_or(false) +} + +fn build_memory_list_stream( + regions: &[MemoryRegion<'_>], + base_rva: u32, +) -> io::Result<(Vec, Vec, BTreeMap)> { + let header_size = 4 + ((regions.len() as u32) * MINIDUMP_MEMORY_DESCRIPTOR_SIZE); + let mut next_data_rva = base_rva + header_size; + let mut output = Vec::new(); + let mut trailing_data = Vec::new(); + let mut locations = BTreeMap::new(); + + output.write_u32::(regions.len() as u32)?; + + for region in regions { + let data_size = region.bytes.len().min(u32::MAX as usize) as u32; + locations.insert( + region.base, + MemoryLocation { + data_size, + rva: next_data_rva, + }, + ); + + output.write_u64::(region.base)?; + output.write_u32::(data_size)?; + output.write_u32::(next_data_rva)?; + next_data_rva += data_size; + } + + for region in regions { + trailing_data.extend_from_slice(®ion.bytes[..region.bytes.len().min(u32::MAX as usize)]); + } + + Ok((output, trailing_data, locations)) +} + +fn build_memory_info_list_stream( + regions: &[MemoryRegion<'_>], + modules: &[ModuleInfo], + _base_rva: u32, +) -> io::Result> { + let mut output = Vec::new(); + output.write_u32::(16)?; + output.write_u32::(MINIDUMP_MEMORY_INFO_SIZE)?; + output.write_u64::(regions.len() as u64)?; + + for region in regions { + let (allocation_base, memory_type) = module_for_region(region, modules) + .map(|module| (module.base, md::MemoryType::MEM_IMAGE.bits())) + .unwrap_or((region.base, md::MemoryType::MEM_PRIVATE.bits())); + let protection = permission_to_memory_protection(region.permission); + + output.write_u64::(region.base)?; + output.write_u64::(allocation_base)?; + output.write_u32::(protection)?; + output.write_u32::(0)?; + output.write_u64::(region.bytes.len() as u64)?; + output.write_u32::(md::MemoryState::MEM_COMMIT.bits())?; + output.write_u32::(protection)?; + output.write_u32::(memory_type)?; + output.write_u32::(0)?; + } + + Ok(output) +} + +fn module_for_region<'a>( + region: &MemoryRegion<'_>, + modules: &'a [ModuleInfo], +) -> Option<&'a ModuleInfo> { + modules + .iter() + .find(|module| region_belongs_to_module(region.name, &module.prefix)) +} + +fn build_system_info_stream(emu: &Emu, _base_rva: u32) -> io::Result> { + let mut output = Vec::with_capacity(MINIDUMP_SYSTEM_INFO_SIZE as usize); + let processor_architecture = match emu.cfg.arch { + Arch::X86 => md::ProcessorArchitecture::PROCESSOR_ARCHITECTURE_INTEL as u16, + Arch::X86_64 => md::ProcessorArchitecture::PROCESSOR_ARCHITECTURE_AMD64 as u16, + Arch::Aarch64 => md::ProcessorArchitecture::PROCESSOR_ARCHITECTURE_ARM64 as u16, + }; + let platform_id = match emu.os { + OperatingSystem::Windows => md::PlatformId::VER_PLATFORM_WIN32_NT as u32, + OperatingSystem::Linux => md::PlatformId::Linux as u32, + OperatingSystem::MacOS => md::PlatformId::MacOs as u32, + }; + let (major_version, minor_version, build_number, product_type) = + if emu.os.is_windows() { + (10, 0, 19041, 1) + } else { + (0, 0, 0, 0) + }; + + output.write_u16::(processor_architecture)?; + output.write_u16::(6)?; + output.write_u16::(0)?; + output.write_u8(1)?; + output.write_u8(product_type)?; + output.write_u32::(major_version)?; + output.write_u32::(minor_version)?; + output.write_u32::(build_number)?; + output.write_u32::(platform_id)?; + output.write_u32::(0)?; + output.write_u16::(0)?; + output.write_u16::(0)?; + output.extend_from_slice(&[0; 24]); + Ok(output) +} + +fn build_module_list_stream(modules: &[ModuleInfo], base_rva: u32) -> io::Result<(Vec, Vec)> { + let header_size = 4 + ((modules.len() as u32) * MINIDUMP_MODULE_SIZE); + let mut next_name_rva = base_rva + header_size; + let mut output = Vec::new(); + let mut trailing_data = Vec::new(); + + output.write_u32::(modules.len() as u32)?; + + for module in modules { + let name_blob = encode_minidump_string(&module.display_name)?; + let name_rva = next_name_rva; + next_name_rva += name_blob.len() as u32; + + output.write_u64::(module.base)?; + output.write_u32::(module.size_of_image)?; + output.write_u32::(0)?; + output.write_u32::(0)?; + output.write_u32::(name_rva)?; + write_fixed_file_info(&mut output)?; + output.write_u32::(0)?; + output.write_u32::(0)?; + output.write_u32::(0)?; + output.write_u32::(0)?; + output.write_u32::(0)?; + output.write_u32::(0)?; + output.write_u32::(0)?; + output.write_u32::(0)?; + + trailing_data.extend_from_slice(&name_blob); + } + + Ok((output, trailing_data)) +} + +fn build_thread_list_stream( + emu: &Emu, + regions: &[MemoryRegion<'_>], + memory_locations: &BTreeMap, + base_rva: u32, +) -> io::Result<(Vec, Vec)> { + let thread_count = emu.threads.len() as u32; + let header_size = 4 + (thread_count * MINIDUMP_THREAD_SIZE); + let mut next_context_rva = base_rva + header_size; + let mut entries = Vec::new(); + let mut trailing_data = Vec::new(); + + for (thread_idx, thread) in emu.threads.iter().enumerate() { + let (regs, flags) = match &thread.arch { + ArchThreadState::X86 { regs, flags, .. } => (regs, flags), + ArchThreadState::AArch64 { .. } => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "aarch64 thread export is not implemented yet", + )) + } + }; + + let context = build_thread_context(emu.cfg.arch, regs, flags)?; + let context_rva = next_context_rva; + next_context_rva += context.len() as u32; + let (stack_base, stack_location) = stack_location_for_thread( + emu.cfg.arch, + regs, + regions, + memory_locations, + ); + let teb = if thread_idx == emu.current_thread_id { + emu.maps + .get_map_by_name("teb") + .map(|teb| teb.get_base()) + .unwrap_or(0) + } else { + 0 + }; + + entries.push(( + thread, + teb, + stack_base, + stack_location, + context_rva, + context.len() as u32, + )); + trailing_data.extend_from_slice(&context); + } + + let mut output = Vec::new(); + output.write_u32::(thread_count)?; + + for (thread, teb, stack_base, stack_location, context_rva, context_size) in entries { + output.write_u32::(thread.id.min(u32::MAX as u64) as u32)?; + output.write_u32::(u32::from(thread.suspended))?; + output.write_u32::(0)?; + output.write_u32::(0)?; + output.write_u64::(teb)?; + output.write_u64::(stack_base)?; + output.write_u32::(stack_location.data_size)?; + output.write_u32::(stack_location.rva)?; + output.write_u32::(context_size)?; + output.write_u32::(context_rva)?; + } + + Ok((output, trailing_data)) +} + +fn stack_location_for_thread( + arch: Arch, + regs: &crate::regs64::Regs64, + regions: &[MemoryRegion<'_>], + memory_locations: &BTreeMap, +) -> (u64, MemoryLocation) { + let sp = match arch { + Arch::X86 => regs.get_esp(), + Arch::X86_64 => regs.rsp, + Arch::Aarch64 => 0, + }; + + let Some(region) = regions + .iter() + .find(|region| sp >= region.base && sp < (region.base + region.bytes.len() as u64)) + else { + return ( + 0, + MemoryLocation { + data_size: 0, + rva: 0, + }, + ); + }; + + let Some(location) = memory_locations.get(®ion.base) else { + return ( + 0, + MemoryLocation { + data_size: 0, + rva: 0, + }, + ); + }; + + (region.base, *location) +} + +fn permission_to_memory_protection(permission: Permission) -> u32 { + match permission.bits() { + 0 => md::MemoryProtection::PAGE_NOACCESS.bits(), + 1 => md::MemoryProtection::PAGE_READONLY.bits(), + 2 | 3 => md::MemoryProtection::PAGE_READWRITE.bits(), + 4 => md::MemoryProtection::PAGE_EXECUTE.bits(), + 5 => md::MemoryProtection::PAGE_EXECUTE_READ.bits(), + 6 | 7 => md::MemoryProtection::PAGE_EXECUTE_READWRITE.bits(), + _ => md::MemoryProtection::PAGE_READWRITE.bits(), + } +} + +fn encode_minidump_string(value: &str) -> io::Result> { + let utf16: Vec = value.encode_utf16().collect(); + let byte_len = utf16 + .len() + .checked_mul(2) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "string too large"))?; + let mut output = Vec::with_capacity(4 + byte_len); + output.write_u32::(byte_len as u32)?; + for ch in utf16 { + output.write_u16::(ch)?; + } + Ok(output) +} + +fn write_fixed_file_info(output: &mut Vec) -> io::Result<()> { + output.write_u32::(md::VS_FFI_SIGNATURE)?; + output.write_u32::(md::VS_FFI_STRUCVERSION)?; + for _ in 0..11 { + output.write_u32::(0)?; + } + Ok(()) +} + +fn directory_for_stream(stream: &StreamLayout) -> md::MINIDUMP_DIRECTORY { + md::MINIDUMP_DIRECTORY { + stream_type: stream.stream_type, + location: md::MINIDUMP_LOCATION_DESCRIPTOR { + data_size: stream.data.len().min(u32::MAX as usize) as u32, + rva: stream.rva, + }, + } +} + +fn write_header(output: &mut Vec, stream_count: u32) -> io::Result<()> { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_secs() + .min(u32::MAX as u64) as u32; + + output.write_u32::(md::MINIDUMP_SIGNATURE)?; + output.write_u32::(md::MINIDUMP_VERSION)?; + output.write_u32::(stream_count)?; + output.write_u32::(MINIDUMP_HEADER_SIZE)?; + output.write_u32::(0)?; + output.write_u32::(now)?; + output.write_u64::(MINIDUMP_FLAGS_FULL_MEMORY)?; + Ok(()) +} + +fn write_directory(output: &mut Vec, directory: &md::MINIDUMP_DIRECTORY) -> io::Result<()> { + output.write_u32::(directory.stream_type)?; + output.write_u32::(directory.location.data_size)?; + output.write_u32::(directory.location.rva)?; + Ok(()) +} + +fn pad_to_rva(output: &mut Vec, rva: u32) -> io::Result<()> { + let target = rva as usize; + if output.len() > target { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "stream layout overlap while building minidump", + )); + } + output.resize(target, 0); + Ok(()) +} diff --git a/crates/libmwemu/src/serialization/mod.rs b/crates/libmwemu/src/serialization/mod.rs index 6a869f5e..066638ea 100644 --- a/crates/libmwemu/src/serialization/mod.rs +++ b/crates/libmwemu/src/serialization/mod.rs @@ -1,19 +1,21 @@ -use std::{fs::File, io::Write as _}; +use std::{ + fs::File, + io::{Read as _, Write as _}, +}; use crate::emu::Emu; use crate::serialization::emu::SerializableEmu; -use crate::serialization::minidump_converter::MinidumpConverter; mod emu; mod fpu; mod instant; mod maps; -mod minidump_converter; +mod minidump; mod pe32; mod pe64; mod thread_context; -pub struct Serialization {} +pub struct Serialization; impl Serialization { pub fn serialize(emu: &Emu) -> Vec { @@ -45,8 +47,40 @@ impl Serialization { Self::deserialize(&data) } + pub fn dump_to_minidump(emu: &Emu, filename: &str) -> std::io::Result<()> { + std::fs::create_dir_all("./dumps/")?; + minidump::dump_to_minidump(emu, filename)?; + + // for binary analysis + emu.maps.save_all("./dumps".to_string()); + Ok(()) + } + pub fn load_from_minidump(filename: &str) -> Emu { - let serializable = MinidumpConverter::from_minidump_file(filename).unwrap(); + let serializable = minidump::load_from_minidump(filename).unwrap(); serializable.into() } + + pub fn dump(emu: &Emu, filename: &str) { + if minidump::is_minidump_path(filename) { + Self::dump_to_minidump(emu, filename).unwrap(); + } else { + Self::dump_to_file(emu, filename); + } + } + + pub fn load(filename: &str) -> Emu { + if minidump::is_minidump_path(filename) { + return Self::load_from_minidump(filename); + } + + let mut file = File::open(filename).unwrap(); + let mut signature = [0u8; 4]; + let bytes_read = file.read(&mut signature).unwrap(); + if bytes_read == signature.len() && minidump::has_minidump_signature(&signature) { + Self::load_from_minidump(filename) + } else { + Self::load_from_file(filename) + } + } } diff --git a/crates/libmwemu/src/serialization/thread_context.rs b/crates/libmwemu/src/serialization/thread_context.rs index c59d9e59..a27ae2cb 100644 --- a/crates/libmwemu/src/serialization/thread_context.rs +++ b/crates/libmwemu/src/serialization/thread_context.rs @@ -67,8 +67,9 @@ impl From<&ThreadContext> for SerializableThreadContext { call_stack: call_stack.clone(), }, ArchThreadState::AArch64 { .. } => { - // TODO: implement aarch64 serialization - panic!("aarch64 thread serialization not yet implemented") + panic!( + "AArch64 thread serialization is not implemented yet; native dump/load support is still x86/x86_64-only" + ) } } } diff --git a/crates/libmwemu/src/tests/unit/serialization_tests.rs b/crates/libmwemu/src/tests/unit/serialization_tests.rs index 9a8db90d..20a53c5f 100644 --- a/crates/libmwemu/src/tests/unit/serialization_tests.rs +++ b/crates/libmwemu/src/tests/unit/serialization_tests.rs @@ -1,9 +1,22 @@ use crate::arch::Arch; +use crate::maps::mem64::Permission; +use crate::serialization::Serialization; +use crate::tests::helpers; +use minidump::{Minidump, MinidumpModuleList, Module}; +use tempdir::TempDir; + +const ELF64_AARCH64_ADD: &[u8] = include_bytes!("../fixtures/elf64_aarch64_add.bin"); + +fn write_tmp(name: &str, bytes: &[u8]) -> std::path::PathBuf { + let path = std::env::temp_dir().join(name); + std::fs::write(&path, bytes).unwrap(); + path +} #[test] fn test_serialization_module_exists() { // Just verify the module is accessible - let emu = crate::emu64(); + let _emu = crate::emu64(); // Serialization exists but we won't test it due to complexity } @@ -44,3 +57,168 @@ fn test_emu_32bit_registers() { assert_eq!(emu.regs().get_eax(), 0x12345678); assert_eq!(emu.regs().get_ebx(), 0xABCDEF00); } + +fn build_test_pe_header(machine: u16) -> Vec { + let mut raw = vec![0u8; 0x200]; + raw[0] = b'M'; + raw[1] = b'Z'; + raw[0x3c..0x40].copy_from_slice(&0x80u32.to_le_bytes()); + raw[0x80..0x84].copy_from_slice(b"PE\0\0"); + raw[0x84..0x86].copy_from_slice(&machine.to_le_bytes()); + raw +} + +#[test] +fn test_x64_minidump_roundtrip() { + let temp_dir = TempDir::new("mwemu_minidump_x64").unwrap(); + let dump_path = temp_dir.path().join("sample64.dmp"); + + let mut emu = crate::emu64(); + emu.filename = "sample.exe".to_string(); + emu.cfg.exe_name = "sample.exe".to_string(); + + let header = build_test_pe_header(0x8664); + let pe_map = emu + .maps + .create_map("sample.pe", 0x400000, 0x1000, Permission::READ_WRITE) + .unwrap(); + pe_map.memcpy(&header, header.len()); + + let text_map = emu + .maps + .create_map("sample.text", 0x401000, 0x1000, Permission::READ_EXECUTE) + .unwrap(); + text_map.memcpy(&vec![0x90; 0x100], 0x100); + + let stack_map = emu + .maps + .create_map("stack", 0x200000, 0x2000, Permission::READ_WRITE) + .unwrap(); + stack_map.memcpy(&vec![0x41; 0x200], 0x200); + + emu.regs_mut().rax = 0x1122_3344_5566_7788; + emu.regs_mut().rbp = 0x200900; + emu.regs_mut().rsp = 0x200800; + emu.regs_mut().rip = 0x401020; + emu.flags_mut().load(0x246); + + Serialization::dump_to_minidump(&emu, dump_path.to_str().unwrap()).unwrap(); + + let dump = Minidump::read_path(&dump_path).unwrap(); + let modules = dump.get_stream::().unwrap(); + assert_eq!(modules.iter().count(), 1); + let module = modules.iter().next().unwrap(); + assert_eq!(module.base_address(), 0x400000); + assert_eq!(module.name.to_string(), "sample.exe"); + + let loaded = Serialization::load_from_minidump(dump_path.to_str().unwrap()); + assert!(loaded.cfg.is_x64()); + assert_eq!(loaded.regs().rax, 0x1122_3344_5566_7788); + assert_eq!(loaded.regs().rbp, 0x200900); + assert_eq!(loaded.regs().rsp, 0x200800); + assert_eq!(loaded.regs().rip, 0x401020); + assert_eq!(loaded.flags().dump(), 0x246); + assert_eq!(loaded.maps.read_byte(0x200010), Some(0x41)); + assert!(loaded.pe64.is_some()); +} + +#[test] +fn test_x86_minidump_roundtrip() { + let temp_dir = TempDir::new("mwemu_minidump_x86").unwrap(); + let dump_path = temp_dir.path().join("sample32.dmp"); + + let mut emu = crate::emu32(); + emu.filename = "sample32.exe".to_string(); + emu.cfg.exe_name = "sample32.exe".to_string(); + + let header = build_test_pe_header(0x014c); + let pe_map = emu + .maps + .create_map("sample32.pe", 0x0040_0000, 0x1000, Permission::READ_WRITE) + .unwrap(); + pe_map.memcpy(&header, header.len()); + + let stack_map = emu + .maps + .create_map("stack", 0x0010_0000, 0x2000, Permission::READ_WRITE) + .unwrap(); + stack_map.memcpy(&vec![0x24; 0x200], 0x200); + + emu.regs_mut().set_eax(0x1234_5678); + emu.regs_mut().set_ebp(0x0010_0900); + emu.regs_mut().set_esp(0x0010_0800); + emu.regs_mut().set_eip(0x0040_1020); + emu.flags_mut().load(0x202); + + Serialization::dump_to_minidump(&emu, dump_path.to_str().unwrap()).unwrap(); + + let loaded = Serialization::load_from_minidump(dump_path.to_str().unwrap()); + assert!(!loaded.cfg.is_x64()); + assert_eq!(loaded.regs().get_eax(), 0x1234_5678); + assert_eq!(loaded.regs().get_ebp(), 0x0010_0900); + assert_eq!(loaded.regs().get_esp(), 0x0010_0800); + assert_eq!(loaded.regs().get_eip(), 0x0040_1020); + assert_eq!(loaded.flags().dump(), 0x202); + assert_eq!(loaded.maps.read_byte(0x0010_0010), Some(0x24)); + assert!(loaded.pe32.is_some()); +} + +#[test] +fn test_aarch64_minidump_export_is_not_supported_yet() { + let temp_dir = TempDir::new("mwemu_minidump_aarch64").unwrap(); + let dump_path = temp_dir.path().join("sample_aarch64.dmp"); + + let emu = crate::emu_aarch64(); + let err = Serialization::dump_to_minidump(&emu, dump_path.to_str().unwrap()).unwrap_err(); + + assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); + assert!(err.to_string().contains("x86/x86_64 guests")); +} + +#[test] +#[ignore = "AArch64 native serialization is not implemented yet"] +fn test_aarch64_native_serialization_fixture_roundtrip() { + helpers::setup(); + + let path = write_tmp("mwemu_test_serialize_elf64_aarch64_add.bin", ELF64_AARCH64_ADD); + + let mut emu = crate::emu_aarch64(); + emu.load_code(path.to_str().unwrap()); + + emu.step(); + emu.step(); + emu.step(); + assert_eq!(emu.regs_aarch64().x[2], 2); + + let serialized = Serialization::serialize(&emu); + let loaded = Serialization::deserialize(&serialized); + + assert!(loaded.cfg.arch.is_aarch64()); + assert_eq!(loaded.regs_aarch64().x[2], 2); + assert_eq!(loaded.regs_aarch64().pc, emu.regs_aarch64().pc); +} + +#[test] +#[ignore = "AArch64 minidump import/export is not implemented yet"] +fn test_aarch64_minidump_fixture_roundtrip() { + helpers::setup(); + + let sample_path = write_tmp("mwemu_test_minidump_elf64_aarch64_add.bin", ELF64_AARCH64_ADD); + let temp_dir = TempDir::new("mwemu_minidump_aarch64_future").unwrap(); + let dump_path = temp_dir.path().join("elf64_aarch64_add.dmp"); + + let mut emu = crate::emu_aarch64(); + emu.load_code(sample_path.to_str().unwrap()); + + emu.step(); + emu.step(); + emu.step(); + assert_eq!(emu.regs_aarch64().x[2], 2); + + Serialization::dump_to_minidump(&emu, dump_path.to_str().unwrap()).unwrap(); + let loaded = Serialization::load_from_minidump(dump_path.to_str().unwrap()); + + assert!(loaded.cfg.arch.is_aarch64()); + assert_eq!(loaded.regs_aarch64().x[2], 2); + assert_eq!(loaded.regs_aarch64().pc, emu.regs_aarch64().pc); +} diff --git a/crates/libmwemu/src/threading/scheduler.rs b/crates/libmwemu/src/threading/scheduler.rs index 6c235f8b..b7f3931c 100644 --- a/crates/libmwemu/src/threading/scheduler.rs +++ b/crates/libmwemu/src/threading/scheduler.rs @@ -260,7 +260,7 @@ impl ThreadScheduler { /// Handle emulator exit fn handle_exit(emu: &mut Emu) { if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - crate::serialization::Serialization::dump_to_file( + crate::serialization::Serialization::dump( emu, emu.cfg.dump_filename.as_ref().unwrap(), ); diff --git a/crates/mwemu/src/main.rs b/crates/mwemu/src/main.rs index b3e5131b..dc977c73 100644 --- a/crates/mwemu/src/main.rs +++ b/crates/mwemu/src/main.rs @@ -518,7 +518,7 @@ fn main() { // dump on exit if emu.cfg.dump_on_exit && emu.cfg.dump_filename.is_some() { - serialization::Serialization::dump_to_file( + serialization::Serialization::dump( &emu, emu.cfg.dump_filename.as_ref().unwrap(), ); @@ -550,7 +550,7 @@ fn main() { let dump_filename = matches.value_of("dump").expect("specify the dump filename"); log::info!("loading dump from {}", dump_filename); let old_config = emu.cfg; - emu = serialization::Serialization::load_from_file(dump_filename); + emu = serialization::Serialization::load(dump_filename); emu.cfg = old_config; emu.maps.set_banzai(emu.cfg.skip_unimplemented); } From 0912fd40094b68700e5976b774c09c338174ca59 Mon Sep 17 00:00:00 2001 From: Brandon Ros Date: Mon, 13 Apr 2026 08:57:39 -0400 Subject: [PATCH 2/5] Start architecture-aware serialization parity for AArch64 - make SerializableThreadContext and SerializableEmu arch-aware - route minidump import through current-thread helpers - add a serialization roadmap for the remaining native/minidump work Co-Authored-By: OpenAI Codex --- crates/libmwemu/src/serialization/emu.rs | 466 ++++++++++++------ .../src/serialization/minidump/reader.rs | 4 +- .../src/serialization/thread_context.rs | 170 ++++--- docs/serialization-roadmap.md | 58 +++ 4 files changed, 498 insertions(+), 200 deletions(-) create mode 100644 docs/serialization-roadmap.md diff --git a/crates/libmwemu/src/serialization/emu.rs b/crates/libmwemu/src/serialization/emu.rs index a140211b..d645cf79 100644 --- a/crates/libmwemu/src/serialization/emu.rs +++ b/crates/libmwemu/src/serialization/emu.rs @@ -9,7 +9,6 @@ use iced_x86::Instruction; use serde::{Deserialize, Serialize}; use crate::api::banzai::Banzai; -use crate::arch::Arch; use crate::debug::breakpoint::Breakpoints; use crate::utils::colors::Colors; use crate::config::Config; @@ -18,6 +17,7 @@ use crate::emu::Emu; use crate::flags::Flags; use crate::threading::global_locks::GlobalLocks; use crate::hooks::Hooks; +use crate::regs_aarch64::RegsAarch64; use crate::regs64::Regs64; use crate::serialization::fpu::SerializableFPU; use crate::serialization::instant::SerializableInstant; @@ -32,6 +32,43 @@ use crate::emu::object_handle::HandleManagement; use crate::emu::ArchState; use crate::threading::context::ArchThreadState; +#[derive(Serialize, Deserialize)] +pub enum SerializableInstructionState { + X86 { + instruction: Option, + decoder_position: usize, + }, + AArch64, +} + +#[derive(Serialize, Deserialize)] +pub enum SerializableCurrentThreadState { + X86 { + regs: Regs64, + pre_op_regs: Regs64, + post_op_regs: Regs64, + flags: Flags, + pre_op_flags: Flags, + post_op_flags: Flags, + eflags: Eflags, + fpu: SerializableFPU, + seh: u64, + veh: u64, + uef: u64, + eh_ctx: u32, + tls32: Vec, + tls64: Vec, + fls: Vec, + fs: BTreeMap, + call_stack: Vec<(u64, u64)>, + }, + AArch64 { + regs: RegsAarch64, + pre_op_regs: RegsAarch64, + post_op_regs: RegsAarch64, + }, +} + #[derive(Serialize, Deserialize)] pub struct SerializableEmu { // --- Configuration & display --- @@ -48,8 +85,7 @@ pub struct SerializableEmu { // --- Instruction decoding & disassembly --- // NOTE: formatter, instruction_cache are recreated on deserialize - pub instruction: Option, - pub decoder_position: usize, + pub instruction_state: SerializableInstructionState, pub last_instruction_size: usize, pub rep: Option, @@ -79,23 +115,7 @@ pub struct SerializableEmu { // NOTE: global_locks reset on deserialize // --- Flattened thread context (from current thread at serialize time) --- - pub regs: Regs64, - pub pre_op_regs: Regs64, - pub post_op_regs: Regs64, - pub flags: Flags, - pub pre_op_flags: Flags, - pub post_op_flags: Flags, - pub eflags: Eflags, - pub fpu: SerializableFPU, - pub seh: u64, - pub veh: u64, - pub uef: u64, - pub eh_ctx: u32, - pub tls32: Vec, - pub tls64: Vec, - pub fls: Vec, - pub fs: BTreeMap, - pub call_stack: Vec<(u64, u64)>, + pub current_thread: SerializableCurrentThreadState, // --- API call interception --- // NOTE: hooks cannot be serialized @@ -124,6 +144,67 @@ pub struct SerializableEmu { impl<'a> From<&'a Emu> for SerializableEmu { fn from(emu: &'a Emu) -> Self { + let instruction_state = match &emu.arch_state { + ArchState::X86 { + instruction, + decoder_position, + .. + } => SerializableInstructionState::X86 { + instruction: *instruction, + decoder_position: *decoder_position, + }, + ArchState::AArch64 { .. } => SerializableInstructionState::AArch64, + }; + + let current_thread = match &emu.current_thread().arch { + ArchThreadState::X86 { + regs, + pre_op_regs, + post_op_regs, + flags, + pre_op_flags, + post_op_flags, + eflags, + fpu, + seh, + veh, + uef, + eh_ctx, + tls32, + tls64, + fls, + fs, + call_stack, + } => SerializableCurrentThreadState::X86 { + regs: *regs, + pre_op_regs: *pre_op_regs, + post_op_regs: *post_op_regs, + flags: *flags, + pre_op_flags: *pre_op_flags, + post_op_flags: *post_op_flags, + eflags: eflags.clone(), + fpu: fpu.clone().into(), + seh: *seh, + veh: *veh, + uef: *uef, + eh_ctx: *eh_ctx, + tls32: tls32.clone(), + tls64: tls64.clone(), + fls: fls.clone(), + fs: fs.clone(), + call_stack: call_stack.clone(), + }, + ArchThreadState::AArch64 { + regs, + pre_op_regs, + post_op_regs, + } => SerializableCurrentThreadState::AArch64 { + regs: *regs, + pre_op_regs: *pre_op_regs, + post_op_regs: *post_op_regs, + }, + }; + SerializableEmu { // Configuration & display cfg: emu.cfg.clone(), @@ -135,8 +216,7 @@ impl<'a> From<&'a Emu> for SerializableEmu { heap_addr: emu.heap_addr, memory_operations: emu.memory_operations.clone(), // Instruction decoding - instruction: emu.x86_instruction(), - decoder_position: emu.x86_decoder_position(), + instruction_state, last_instruction_size: emu.last_instruction_size, rep: emu.rep, // Core execution state @@ -159,23 +239,7 @@ impl<'a> From<&'a Emu> for SerializableEmu { main_thread_cont: emu.main_thread_cont, gateway_return: emu.gateway_return, // Flattened thread context - regs: emu.regs().clone(), - pre_op_regs: *emu.pre_op_regs(), - post_op_regs: *emu.post_op_regs(), - flags: *emu.flags(), - pre_op_flags: *emu.pre_op_flags(), - post_op_flags: *emu.post_op_flags(), - eflags: emu.eflags().clone(), - fpu: emu.fpu().clone().into(), - seh: emu.seh(), - veh: emu.veh(), - uef: emu.uef(), - eh_ctx: emu.eh_ctx(), - tls32: emu.tls32().clone(), - tls64: emu.tls64().clone(), - fls: emu.fls().clone(), - fs: emu.fs().clone(), - call_stack: emu.call_stack().clone(), + current_thread, // API call interception banzai: emu.banzai.clone(), skip_apicall: emu.skip_apicall, @@ -197,148 +261,247 @@ impl<'a> From<&'a Emu> for SerializableEmu { impl From for Emu { fn from(serialized: SerializableEmu) -> Self { - let current_thread_id = serialized.current_thread_id; - let current_regs = serialized.regs; - let current_pre_op_regs = serialized.pre_op_regs; - let current_post_op_regs = serialized.post_op_regs; - let current_flags = serialized.flags; - let current_pre_op_flags = serialized.pre_op_flags; - let current_post_op_flags = serialized.post_op_flags; - let current_eflags = serialized.eflags; - let current_fpu = serialized.fpu; - let current_seh = serialized.seh; - let current_veh = serialized.veh; - let current_uef = serialized.uef; - let current_eh_ctx = serialized.eh_ctx; - let current_tls32 = serialized.tls32; - let current_tls64 = serialized.tls64; - let current_fls = serialized.fls; - let current_fs = serialized.fs; - let current_call_stack = serialized.call_stack; - let trace_file = if let Some(trace_filename) = &serialized.cfg.trace_filename { + let SerializableEmu { + cfg, + colors, + filename, + maps, + base, + heap_addr, + memory_operations, + instruction_state, + last_instruction_size, + rep, + pos, + max_pos, + tick, + is_running, + now, + force_break, + force_reload, + run_until_ret, + os, + pe64, + pe32, + tls_callbacks, + threads, + current_thread_id, + main_thread_cont, + gateway_return, + current_thread, + banzai, + skip_apicall, + its_apicall, + bp, + break_on_alert, + break_on_next_cmp, + break_on_next_return, + enabled_ctrlc, + running_script, + exp, + entropy, + last_error, + } = serialized; + + let trace_file = if let Some(trace_filename) = &cfg.trace_filename { let file = File::open(trace_filename.clone()).unwrap(); Some(file) } else { None }; + let arch_state = match instruction_state { + SerializableInstructionState::X86 { + instruction, + decoder_position, + } => ArchState::X86 { + instruction, + formatter: Default::default(), + instruction_cache: InstructionCache::new(), + decoder_position, + }, + SerializableInstructionState::AArch64 => ArchState::AArch64 { + instruction: None, + instruction_cache: InstructionCache::new(), + }, + }; + let mut emu = Emu { // Configuration & display - cfg: serialized.cfg.clone(), - colors: serialized.colors, - filename: serialized.filename, + cfg: cfg.clone(), + colors, + filename, // Memory & address space - maps: serialized.maps.into(), - base: serialized.base, - heap_addr: serialized.heap_addr, + maps: maps.into(), + base, + heap_addr, heap_management: None, - memory_operations: serialized.memory_operations, + memory_operations, // Instruction decoding (formatter, cache recreated) - arch_state: ArchState::X86 { - instruction: serialized.instruction, - formatter: Default::default(), - instruction_cache: InstructionCache::new(), - decoder_position: serialized.decoder_position, - }, + arch_state, last_decoded: None, - last_instruction_size: serialized.last_instruction_size, - rep: serialized.rep, + last_instruction_size, + rep, // Core execution state - pos: serialized.pos, - max_pos: serialized.max_pos, - tick: serialized.tick, - is_running: Arc::new(atomic::AtomicU32::new(serialized.is_running)), - now: serialized.now.to_instant(), - force_break: serialized.force_break, + pos, + max_pos, + tick, + is_running: Arc::new(atomic::AtomicU32::new(is_running)), + now: now.to_instant(), + force_break, process_terminated: false, call_depth: 0, - force_reload: serialized.force_reload, - run_until_ret: serialized.run_until_ret, + force_reload, + run_until_ret, rng: RefCell::new(rand::rng()), // Platform & loaded binary - os: serialized.os, - pe64: serialized.pe64.map(|x| x.into()), - pe32: serialized.pe32.map(|x| x.into()), + os, + pe64: pe64.map(|x| x.into()), + pe32: pe32.map(|x| x.into()), elf64: None, // TODO: not yet serialized elf32: None, // TODO: not yet serialized macho64: None, // TODO: not yet serialized - tls_callbacks: serialized.tls_callbacks, + tls_callbacks, library_loaded: false, // Thread management - threads: serialized.threads.into_iter().map(|t| t.into()).collect(), + threads: threads.into_iter().map(|t| t.into()).collect(), current_thread_id, - main_thread_cont: serialized.main_thread_cont, - gateway_return: serialized.gateway_return, + main_thread_cont, + gateway_return, global_locks: GlobalLocks::new(), // API call interception (hooks cannot be serialized) hooks: Hooks::default(), - skip_apicall: serialized.skip_apicall, - its_apicall: serialized.its_apicall, + skip_apicall, + its_apicall, is_api_run: false, is_break_on_api: false, - banzai: serialized.banzai, + banzai, // Debugging & breakpoints - bp: serialized.bp, - break_on_alert: serialized.break_on_alert, - break_on_next_cmp: serialized.break_on_next_cmp, - break_on_next_return: serialized.break_on_next_return, - enabled_ctrlc: serialized.enabled_ctrlc, - running_script: serialized.running_script, - exp: serialized.exp, + bp, + break_on_alert, + break_on_next_cmp, + break_on_next_return, + enabled_ctrlc, + running_script, + exp, definitions: HashMap::new(), stored_contexts: HashMap::new(), // Tracing & statistics trace_file, instruction_count: 0, fault_count: 0, - entropy: 0.0, - last_error: 0, + entropy, + last_error, // Win32 resource management handle_management: HandleManagement::new(), // TODO: not yet serialized }; if let Some(thread) = emu.threads.get_mut(current_thread_id) { - if let ArchThreadState::X86 { - regs, - pre_op_regs, - post_op_regs, - flags, - pre_op_flags, - post_op_flags, - eflags, - fpu, - seh, - veh, - uef, - eh_ctx, - tls32, - tls64, - fls, - fs, - call_stack, - } = &mut thread.arch - { - *regs = current_regs; - *pre_op_regs = current_pre_op_regs; - *post_op_regs = current_post_op_regs; - *flags = current_flags; - *pre_op_flags = current_pre_op_flags; - *post_op_flags = current_post_op_flags; - *eflags = current_eflags; - *fpu = current_fpu.into(); - *seh = current_seh; - *veh = current_veh; - *uef = current_uef; - *eh_ctx = current_eh_ctx; - *tls32 = current_tls32; - *tls64 = current_tls64; - *fls = current_fls; - *fs = current_fs; - *call_stack = current_call_stack; + match current_thread { + SerializableCurrentThreadState::X86 { + regs, + pre_op_regs, + post_op_regs, + flags, + pre_op_flags, + post_op_flags, + eflags, + fpu, + seh, + veh, + uef, + eh_ctx, + tls32, + tls64, + fls, + fs, + call_stack, + } => match &mut thread.arch { + ArchThreadState::X86 { + regs: thread_regs, + pre_op_regs: thread_pre_op_regs, + post_op_regs: thread_post_op_regs, + flags: thread_flags, + pre_op_flags: thread_pre_op_flags, + post_op_flags: thread_post_op_flags, + eflags: thread_eflags, + fpu: thread_fpu, + seh: thread_seh, + veh: thread_veh, + uef: thread_uef, + eh_ctx: thread_eh_ctx, + tls32: thread_tls32, + tls64: thread_tls64, + fls: thread_fls, + fs: thread_fs, + call_stack: thread_call_stack, + } => { + *thread_regs = regs; + *thread_pre_op_regs = pre_op_regs; + *thread_post_op_regs = post_op_regs; + *thread_flags = flags; + *thread_pre_op_flags = pre_op_flags; + *thread_post_op_flags = post_op_flags; + *thread_eflags = eflags; + *thread_fpu = fpu.into(); + *thread_seh = seh; + *thread_veh = veh; + *thread_uef = uef; + *thread_eh_ctx = eh_ctx; + *thread_tls32 = tls32; + *thread_tls64 = tls64; + *thread_fls = fls; + *thread_fs = fs; + *thread_call_stack = call_stack; + } + ArchThreadState::AArch64 { .. } => { + thread.arch = ArchThreadState::X86 { + regs, + pre_op_regs, + post_op_regs, + flags, + pre_op_flags, + post_op_flags, + eflags, + fpu: fpu.into(), + seh, + veh, + uef, + eh_ctx, + tls32, + tls64, + fls, + fs, + call_stack, + }; + } + }, + SerializableCurrentThreadState::AArch64 { + regs, + pre_op_regs, + post_op_regs, + } => match &mut thread.arch { + ArchThreadState::AArch64 { + regs: thread_regs, + pre_op_regs: thread_pre_op_regs, + post_op_regs: thread_post_op_regs, + } => { + *thread_regs = regs; + *thread_pre_op_regs = pre_op_regs; + *thread_post_op_regs = post_op_regs; + } + ArchThreadState::X86 { .. } => { + thread.arch = ArchThreadState::AArch64 { + regs, + pre_op_regs, + post_op_regs, + }; + } + }, } } - if matches!(emu.cfg.arch, Arch::X86_64) { + if emu.cfg.arch.is_64bits() { emu.maps.is_64bits = true; } @@ -354,7 +517,32 @@ impl Default for SerializableEmu { impl SerializableEmu { pub fn set_regs(&mut self, regs: Regs64) { - self.regs = regs; + match &mut self.current_thread { + SerializableCurrentThreadState::X86 { + regs: current_regs, .. + } => *current_regs = regs, + SerializableCurrentThreadState::AArch64 { .. } => { + panic!("set_regs called on aarch64 serialized thread state") + } + } + } + + pub fn set_flags(&mut self, flags: Flags) { + match &mut self.current_thread { + SerializableCurrentThreadState::X86 { + flags: current_flags, + pre_op_flags, + post_op_flags, + .. + } => { + *current_flags = flags; + *pre_op_flags = flags; + *post_op_flags = flags; + } + SerializableCurrentThreadState::AArch64 { .. } => { + panic!("set_flags called on aarch64 serialized thread state") + } + } } pub fn set_maps(&mut self, maps: SerializableMaps) { diff --git a/crates/libmwemu/src/serialization/minidump/reader.rs b/crates/libmwemu/src/serialization/minidump/reader.rs index 33e2fc16..bcb07340 100644 --- a/crates/libmwemu/src/serialization/minidump/reader.rs +++ b/crates/libmwemu/src/serialization/minidump/reader.rs @@ -278,9 +278,7 @@ impl MinidumpReader { let mut serializable_emu = SerializableEmu::default(); serializable_emu.set_maps(maps); serializable_emu.set_regs(regs); - serializable_emu.flags = flags; - serializable_emu.pre_op_flags = flags; - serializable_emu.post_op_flags = flags; + serializable_emu.set_flags(flags); serializable_emu.set_pe32(pe32); serializable_emu.set_pe64(pe64); serializable_emu.cfg.arch = match system_info.cpu { diff --git a/crates/libmwemu/src/serialization/thread_context.rs b/crates/libmwemu/src/serialization/thread_context.rs index a27ae2cb..a26f3e32 100644 --- a/crates/libmwemu/src/serialization/thread_context.rs +++ b/crates/libmwemu/src/serialization/thread_context.rs @@ -3,10 +3,39 @@ use std::collections::BTreeMap; use crate::eflags::Eflags; use crate::flags::Flags; +use crate::regs_aarch64::RegsAarch64; use crate::regs64::Regs64; use crate::serialization::fpu::SerializableFPU; use crate::threading::context::{ArchThreadState, ThreadContext}; +#[derive(Serialize, Deserialize)] +pub enum SerializableThreadArch { + X86 { + regs: Regs64, + pre_op_regs: Regs64, + post_op_regs: Regs64, + flags: Flags, + pre_op_flags: Flags, + post_op_flags: Flags, + eflags: Eflags, + fpu: SerializableFPU, + seh: u64, + veh: u64, + uef: u64, + eh_ctx: u32, + tls32: Vec, + tls64: Vec, + fls: Vec, + fs: BTreeMap, + call_stack: Vec<(u64, u64)>, + }, + AArch64 { + regs: RegsAarch64, + pre_op_regs: RegsAarch64, + post_op_regs: RegsAarch64, + }, +} + #[derive(Serialize, Deserialize)] pub struct SerializableThreadContext { pub id: u64, @@ -14,24 +43,7 @@ pub struct SerializableThreadContext { pub wake_tick: usize, pub blocked_on_cs: Option, pub handle: u64, - // x86-specific fields (present only for x86 threads) - pub regs: Regs64, - pub pre_op_regs: Regs64, - pub post_op_regs: Regs64, - pub flags: Flags, - pub pre_op_flags: Flags, - pub post_op_flags: Flags, - pub eflags: Eflags, - pub fpu: SerializableFPU, - pub seh: u64, - pub veh: u64, - pub uef: u64, - pub eh_ctx: u32, - pub tls32: Vec, - pub tls64: Vec, - pub fls: Vec, - pub fs: BTreeMap, - pub call_stack: Vec<(u64, u64)>, + pub arch: SerializableThreadArch, } impl From<&ThreadContext> for SerializableThreadContext { @@ -48,29 +60,42 @@ impl From<&ThreadContext> for SerializableThreadContext { wake_tick: thread.wake_tick, blocked_on_cs: thread.blocked_on_cs, handle: thread.handle, - regs: *regs, - pre_op_regs: *pre_op_regs, - post_op_regs: *post_op_regs, - flags: *flags, - pre_op_flags: *pre_op_flags, - post_op_flags: *post_op_flags, - eflags: eflags.clone(), - fpu: fpu.clone().into(), - seh: *seh, - veh: *veh, - uef: *uef, - eh_ctx: *eh_ctx, - tls32: tls32.clone(), - tls64: tls64.clone(), - fls: fls.clone(), - fs: fs.clone(), - call_stack: call_stack.clone(), + arch: SerializableThreadArch::X86 { + regs: *regs, + pre_op_regs: *pre_op_regs, + post_op_regs: *post_op_regs, + flags: *flags, + pre_op_flags: *pre_op_flags, + post_op_flags: *post_op_flags, + eflags: eflags.clone(), + fpu: fpu.clone().into(), + seh: *seh, + veh: *veh, + uef: *uef, + eh_ctx: *eh_ctx, + tls32: tls32.clone(), + tls64: tls64.clone(), + fls: fls.clone(), + fs: fs.clone(), + call_stack: call_stack.clone(), + }, + }, + ArchThreadState::AArch64 { + regs, + pre_op_regs, + post_op_regs, + } => SerializableThreadContext { + id: thread.id, + suspended: thread.suspended, + wake_tick: thread.wake_tick, + blocked_on_cs: thread.blocked_on_cs, + handle: thread.handle, + arch: SerializableThreadArch::AArch64 { + regs: *regs, + pre_op_regs: *pre_op_regs, + post_op_regs: *post_op_regs, + }, }, - ArchThreadState::AArch64 { .. } => { - panic!( - "AArch64 thread serialization is not implemented yet; native dump/load support is still x86/x86_64-only" - ) - } } } } @@ -83,24 +108,53 @@ impl From for ThreadContext { wake_tick: serialized.wake_tick, blocked_on_cs: serialized.blocked_on_cs, handle: serialized.handle, - arch: ArchThreadState::X86 { - regs: serialized.regs, - pre_op_regs: serialized.pre_op_regs, - post_op_regs: serialized.post_op_regs, - flags: serialized.flags, - pre_op_flags: serialized.pre_op_flags, - post_op_flags: serialized.post_op_flags, - eflags: serialized.eflags, - fpu: serialized.fpu.into(), - seh: serialized.seh, - veh: serialized.veh, - uef: serialized.uef, - eh_ctx: serialized.eh_ctx, - tls32: serialized.tls32, - tls64: serialized.tls64, - fls: serialized.fls, - fs: serialized.fs, - call_stack: serialized.call_stack, + arch: match serialized.arch { + SerializableThreadArch::X86 { + regs, + pre_op_regs, + post_op_regs, + flags, + pre_op_flags, + post_op_flags, + eflags, + fpu, + seh, + veh, + uef, + eh_ctx, + tls32, + tls64, + fls, + fs, + call_stack, + } => ArchThreadState::X86 { + regs, + pre_op_regs, + post_op_regs, + flags, + pre_op_flags, + post_op_flags, + eflags, + fpu: fpu.into(), + seh, + veh, + uef, + eh_ctx, + tls32, + tls64, + fls, + fs, + call_stack, + }, + SerializableThreadArch::AArch64 { + regs, + pre_op_regs, + post_op_regs, + } => ArchThreadState::AArch64 { + regs, + pre_op_regs, + post_op_regs, + }, }, } } diff --git a/docs/serialization-roadmap.md b/docs/serialization-roadmap.md new file mode 100644 index 00000000..02ebd5d6 --- /dev/null +++ b/docs/serialization-roadmap.md @@ -0,0 +1,58 @@ +# Serialization Roadmap + +Current status: + +- Native `dump_to_file` / `load_from_file` remains the existing path for x86/x86_64, but still lacks dedicated roundtrip coverage. +- Minidump `dump_to_minidump` / `load_from_minidump` works for x86/x86_64. +- AArch64 native serialization plumbing is partially in place, but the ignored fixture roundtrip currently aborts with a stack overflow. +- AArch64 minidump import/export is still not implemented. + +## Phase 1: Native Serialization Architecture Parity + +- [x] Replace x86-only `SerializableThreadContext` payloads with architecture-aware variants. +- [x] Add AArch64 thread serialization for `RegsAarch64`, pre-op regs, and post-op regs. +- [x] Refactor `SerializableEmu` flattened current-thread state so it is not hard-wired to x86 registers/flags/FPU. +- [x] Refactor instruction/decode serialization so `ArchState::X86` and `ArchState::AArch64` can both be represented and restored. +- [x] Make `SerializableEmu::from(&Emu)` stop using x86-only accessors on AArch64. +- [x] Make `From for Emu` restore AArch64 thread state and `arch_state` structurally. +- [ ] Investigate and fix the current stack overflow in `test_aarch64_native_serialization_fixture_roundtrip`. +- [ ] Replace the placeholder serialization smoke test with real native roundtrip coverage. +- [ ] Unignore `test_aarch64_native_serialization_fixture_roundtrip`. + +## Phase 2: AArch64 Minidump Import + +- [ ] Parse `MinidumpRawContext::Arm64` and `MinidumpRawContext::OldArm64`. +- [ ] Map ARM64 minidump register state into `RegsAarch64` (`x0..x30`, `sp`, `pc`, `nzcv`, FP/SIMD state). +- [ ] Build an AArch64 `SerializableEmu` from imported minidumps instead of routing through x86-shaped fields. +- [ ] Preserve AArch64 `cfg.arch`, OS, memory maps, modules, and current thread state on import. +- [ ] Treat ARM64 PE modules as 64-bit when reconstructing imported module metadata. +- [ ] Add an ARM64 minidump import fixture test. +- [ ] Unignore `test_aarch64_minidump_fixture_roundtrip` once import/export are ready. + +## Phase 3: AArch64 Minidump Export + +- [ ] Emit a valid `CONTEXT_ARM64` blob from `RegsAarch64`. +- [ ] Teach the minidump writer to export `ArchThreadState::AArch64`. +- [ ] Export ARM64 thread context, stack location, and relevant SIMD/FP register state. +- [ ] Validate ARM64 minidumps with the Rust `minidump` parser. +- [ ] Validate generated dumps with at least one external consumer such as Ghidra or WinDbg. + +## Done Means + +- [ ] Native serialization round-trips x86, x86_64, and AArch64 fixtures. +- [ ] Minidump import/export round-trips x86, x86_64, and AArch64 fixtures. +- [ ] The ignored AArch64 serialization/minidump tests are enabled and passing. +- [ ] The old x86-only "too complex" placeholder test is removed or replaced with real assertions. + +## Finish Order + +1. Stabilize native serialization first. + Replace the placeholder smoke test with native x86/x86_64 roundtrip tests, then reproduce the current AArch64 stack overflow with the ignored fixture and reduce it to the smallest failing serialize/deserialize case. +2. Land native AArch64 roundtrip coverage. + Fix the stack overflow, verify `Serialization::serialize` and `Serialization::deserialize` preserve AArch64 registers and `arch_state`, and then unignore the native AArch64 fixture test. +3. Implement ARM64 minidump import. + Parse `MinidumpRawContext::Arm64` / `OldArm64`, map them into `RegsAarch64`, keep `cfg.arch`/OS/maps/modules intact, and add an import fixture test. +4. Implement ARM64 minidump export. + Emit `CONTEXT_ARM64`, export `ArchThreadState::AArch64`, preserve stack and FP/SIMD state, and validate with the Rust `minidump` parser. +5. Do external validation and cleanup. + Open generated ARM64 dumps in at least one real consumer such as Ghidra or WinDbg, enable the ignored minidump test, and only then call the feature complete. From 08c96d01693d7a683a7946ba1751324f20d8b90c Mon Sep 17 00:00:00 2001 From: Brandon Ros Date: Mon, 13 Apr 2026 09:32:58 -0400 Subject: [PATCH 3/5] Implement AArch64 minidump import/export and fix native serialization Complete the serialization roadmap for AArch64 architecture parity: - Fix native serialization stack overflow by running tests with large thread stacks (same pattern as the existing should_serialize test) - Replace placeholder test_serialization_module_exists with real x86_64 native roundtrip test - Unignore test_aarch64_native_serialization_fixture_roundtrip - Implement ARM64 minidump import: parse MinidumpRawContext::Arm64 and OldArm64, map register state into RegsAarch64, build architecture- aware SerializableEmu via new default_for_arch() and set_regs_aarch64() - Fix is_pe64 to recognize ARM64 PE machine type (0xAA64) - Fix is_64bits to include Arm64 CPU type - Implement ARM64 minidump export: emit CONTEXT_ARM64 blob (912 bytes) with NZCV flags, x0-x30, sp, pc, NEON V0-V31, fpcr/fpsr - Refactor build_thread_context to use ThreadContextInput enum - Refactor stack_location_for_thread to accept sp directly - Remove architecture guards blocking AArch64 in writer - Add test_aarch64_minidump_roundtrip with register, NZCV, and SIMD state verification - Unignore test_aarch64_minidump_fixture_roundtrip - All 231 tests pass with 0 failures Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/libmwemu/src/serialization/emu.rs | 30 +++ .../src/serialization/minidump/context.rs | 93 +++++++++- .../src/serialization/minidump/reader.rs | 99 +++++++--- .../src/serialization/minidump/writer.rs | 49 ++--- .../src/tests/unit/serialization_tests.rs | 174 +++++++++++++----- docs/serialization-roadmap.md | 61 +++--- 6 files changed, 358 insertions(+), 148 deletions(-) diff --git a/crates/libmwemu/src/serialization/emu.rs b/crates/libmwemu/src/serialization/emu.rs index d645cf79..e835b9ce 100644 --- a/crates/libmwemu/src/serialization/emu.rs +++ b/crates/libmwemu/src/serialization/emu.rs @@ -29,6 +29,7 @@ use crate::windows::structures::MemoryOperation; use crate::emu::disassemble::InstructionCache; use crate::emu::object_handle::HandleManagement; +use crate::arch::Arch; use crate::emu::ArchState; use crate::threading::context::ArchThreadState; @@ -556,4 +557,33 @@ impl SerializableEmu { pub fn set_pe64(&mut self, pe64: Option) { self.pe64 = pe64; } + + pub fn set_regs_aarch64(&mut self, regs: RegsAarch64) { + match &mut self.current_thread { + SerializableCurrentThreadState::AArch64 { + regs: current_regs, + pre_op_regs, + post_op_regs, + } => { + *current_regs = regs; + *pre_op_regs = regs; + *post_op_regs = regs; + } + SerializableCurrentThreadState::X86 { .. } => { + panic!("set_regs_aarch64 called on x86 serialized thread state") + } + } + } + + pub fn default_for_arch(arch: Arch) -> Self { + let mut emu = match arch { + Arch::Aarch64 => crate::emu_aarch64(), + Arch::X86_64 => crate::emu64(), + Arch::X86 => crate::emu32(), + }; + // Ensure thread context matches architecture (emu_aarch64 doesn't + // re-init threads automatically). + emu.init_cpu(); + SerializableEmu::from(&emu) + } } diff --git a/crates/libmwemu/src/serialization/minidump/context.rs b/crates/libmwemu/src/serialization/minidump/context.rs index 17829138..b9cfb92c 100644 --- a/crates/libmwemu/src/serialization/minidump/context.rs +++ b/crates/libmwemu/src/serialization/minidump/context.rs @@ -4,23 +4,41 @@ use byteorder::{LittleEndian, WriteBytesExt}; use minidump::format as md; use crate::arch::Arch; +use crate::arch::aarch64::regs::RegsAarch64; use crate::flags::Flags; use crate::regs64::Regs64; const CONTEXT_X86_SIZE: usize = 716; const CONTEXT_AMD64_SIZE: usize = 1232; +const CONTEXT_ARM64_SIZE: usize = 912; + +pub(super) enum ThreadContextInput<'a> { + X86 { + arch: Arch, + regs: &'a Regs64, + flags: &'a Flags, + }, + AArch64 { + regs: &'a RegsAarch64, + }, +} -pub(super) fn build_thread_context( - arch: Arch, - regs: &Regs64, - flags: &Flags, -) -> io::Result> { - match arch { - Arch::X86 => build_x86_context(regs, flags), - Arch::X86_64 => build_amd64_context(regs, flags), - Arch::Aarch64 => Err(io::Error::new( +pub(super) fn build_thread_context(input: ThreadContextInput<'_>) -> io::Result> { + match input { + ThreadContextInput::X86 { + arch: Arch::X86, + regs, + flags, + } => build_x86_context(regs, flags), + ThreadContextInput::X86 { + arch: Arch::X86_64, + regs, + flags, + } => build_amd64_context(regs, flags), + ThreadContextInput::AArch64 { regs } => build_arm64_context(regs), + _ => Err(io::Error::new( io::ErrorKind::InvalidInput, - "aarch64 thread export is not implemented yet", + "unexpected architecture for thread context", )), } } @@ -121,6 +139,61 @@ fn build_amd64_context(regs: &Regs64, flags: &Flags) -> io::Result> { Ok(output) } +fn build_arm64_context(regs: &RegsAarch64) -> io::Result> { + let mut output = Vec::with_capacity(CONTEXT_ARM64_SIZE); + + // context_flags (u32) + output.write_u32::(md::ContextFlagsArm64::CONTEXT_ARM64_ALL.bits())?; + + // cpsr (u32) - NZCV flags packed into bits [31:28] + output.write_u32::(regs.nzcv.as_u64() as u32)?; + + // iregs[31] (x0-x28, fp=x29, lr=x30) + for i in 0..31 { + output.write_u64::(regs.x[i])?; + } + + // sp (u64) + output.write_u64::(regs.sp)?; + + // pc (u64) + output.write_u64::(regs.pc)?; + + // float_regs[32] (V0-V31 as u128) + for i in 0..32 { + write_u128(&mut output, regs.v[i])?; + } + + // fpcr (u32) + output.write_u32::(regs.fpcr as u32)?; + + // fpsr (u32) + output.write_u32::(regs.fpsr as u32)?; + + // bcr[8] (u32 each) - breakpoint control registers + for _ in 0..8 { + output.write_u32::(0)?; + } + + // bvr[8] (u64 each) - breakpoint value registers + for _ in 0..8 { + output.write_u64::(0)?; + } + + // wcr[2] (u32 each) - watchpoint control registers + for _ in 0..2 { + output.write_u32::(0)?; + } + + // wvr[2] (u64 each) - watchpoint value registers + for _ in 0..2 { + output.write_u64::(0)?; + } + + debug_assert_eq!(output.len(), CONTEXT_ARM64_SIZE); + Ok(output) +} + fn write_xmm_save_area32(output: &mut Vec, regs: &Regs64) -> io::Result<()> { output.write_u16::(0x027f)?; output.write_u16::(0)?; diff --git a/crates/libmwemu/src/serialization/minidump/reader.rs b/crates/libmwemu/src/serialization/minidump/reader.rs index bcb07340..4833e3d4 100644 --- a/crates/libmwemu/src/serialization/minidump/reader.rs +++ b/crates/libmwemu/src/serialization/minidump/reader.rs @@ -8,6 +8,7 @@ use std::error::Error; use std::ops::Deref; use crate::arch::{Arch, OperatingSystem}; +use crate::arch::aarch64::regs::RegsAarch64; use crate::flags::Flags; use crate::maps::mem64::{Mem64, Permission}; use crate::maps::tlb::TLB; @@ -18,13 +19,14 @@ use crate::serialization::maps::SerializableMaps; use crate::serialization::pe32::SerializablePE32; use crate::serialization::pe64::SerializablePE64; +enum ExtractedContext { + X86 { regs: Regs64, flags: Flags }, + AArch64 { regs: RegsAarch64 }, +} + pub struct MinidumpReader; impl MinidumpReader { - fn unsupported_aarch64_minidump() -> Box { - "AArch64/ARM64 minidump import is not implemented yet; the current serialization path is still x86/x86_64-centric".into() - } - fn get_pe_offset(data: &[u8]) -> Option { if data.len() < 0x3C + 4 { return None; @@ -43,7 +45,7 @@ impl MinidumpReader { } // Check machine type in PE header - 0x8664 = x64 let machine = u16::from_le_bytes([data[pe_offset + 4], data[pe_offset + 5]]); - machine == 0x8664 + machine == 0x8664 || machine == 0xAA64 } fn extract_pe_modules>( @@ -171,7 +173,10 @@ impl MinidumpReader { } let system_info = dump.get_stream::()?; - let is_64bits = matches!(system_info.cpu, minidump::system_info::Cpu::X86_64); + let is_64bits = matches!( + system_info.cpu, + minidump::system_info::Cpu::X86_64 | minidump::system_info::Cpu::Arm64 + ); let banzai = false; let tlb = RefCell::new(TLB::new()); @@ -184,7 +189,7 @@ impl MinidumpReader { fn extract_thread_context>( dump: &minidump::Minidump<'static, T>, system_info: &MinidumpSystemInfo, - ) -> Result<(Regs64, Flags), Box> { + ) -> Result> { let threads = dump.get_stream::()?; let thread = threads .threads @@ -195,11 +200,10 @@ impl MinidumpReader { .context(system_info, misc.as_ref()) .ok_or("No thread context found in minidump")?; - let mut regs = Regs64::default(); - let mut flags = Flags::new(); - match &context.raw { MinidumpRawContext::X86(raw) => { + let mut regs = Regs64::default(); + let mut flags = Flags::new(); regs.dr0 = raw.dr0 as u64; regs.dr1 = raw.dr1 as u64; regs.dr2 = raw.dr2 as u64; @@ -218,8 +222,11 @@ impl MinidumpReader { regs.fs = raw.fs as u64; regs.gs = raw.gs as u64; flags.load(raw.eflags); + Ok(ExtractedContext::X86 { regs, flags }) } MinidumpRawContext::Amd64(raw) => { + let mut regs = Regs64::default(); + let mut flags = Flags::new(); regs.dr0 = raw.dr0; regs.dr1 = raw.dr1; regs.dr2 = raw.dr2; @@ -246,14 +253,40 @@ impl MinidumpReader { regs.fs = raw.fs as u64; regs.gs = raw.gs as u64; flags.load(raw.eflags); + Ok(ExtractedContext::X86 { regs, flags }) + } + MinidumpRawContext::Arm64(raw) => { + let mut regs = RegsAarch64::new(); + for i in 0..31 { + regs.x[i] = raw.iregs[i]; + } + regs.sp = raw.sp; + regs.pc = raw.pc; + regs.nzcv.from_u64(raw.cpsr as u64); + regs.fpcr = raw.fpcr as u64; + regs.fpsr = raw.fpsr as u64; + for i in 0..32 { + regs.v[i] = raw.float_regs[i]; + } + Ok(ExtractedContext::AArch64 { regs }) } - MinidumpRawContext::Arm64(_) | MinidumpRawContext::OldArm64(_) => { - return Err(Self::unsupported_aarch64_minidump()) + MinidumpRawContext::OldArm64(raw) => { + let mut regs = RegsAarch64::new(); + for i in 0..31 { + regs.x[i] = raw.iregs[i]; + } + regs.sp = raw.sp; + regs.pc = raw.pc; + regs.nzcv.from_u64(raw.cpsr as u64); + regs.fpcr = raw.fpcr as u64; + regs.fpsr = raw.fpsr as u64; + for i in 0..32 { + regs.v[i] = raw.float_regs[i]; + } + Ok(ExtractedContext::AArch64 { regs }) } - _ => return Err("Unsupported minidump CPU context".into()), + _ => Err("Unsupported minidump CPU context".into()), } - - Ok((regs, flags)) } pub fn from_minidump_file(path: &str) -> Result> { @@ -261,9 +294,13 @@ impl MinidumpReader { // Get basic streams we need let system_info = dump.get_stream::()?; - if matches!(system_info.cpu, minidump::system_info::Cpu::Arm64) { - return Err(Self::unsupported_aarch64_minidump()); - } + + let arch = match system_info.cpu { + minidump::system_info::Cpu::X86 => Arch::X86, + minidump::system_info::Cpu::X86_64 => Arch::X86_64, + minidump::system_info::Cpu::Arm64 => Arch::Aarch64, + _ => Arch::X86, + }; // Extract PE modules let (pe32, pe64) = Self::extract_pe_modules(&dump)?; @@ -272,21 +309,25 @@ impl MinidumpReader { let maps = Self::extract_memory_maps(&dump)?; // Extract thread context when present. - let (regs, flags) = Self::extract_thread_context(&dump, &system_info)?; + let extracted = Self::extract_thread_context(&dump, &system_info)?; - // Basic serializable emu with minimal data - let mut serializable_emu = SerializableEmu::default(); + // Build serializable emu with the correct architecture + let mut serializable_emu = SerializableEmu::default_for_arch(arch); serializable_emu.set_maps(maps); - serializable_emu.set_regs(regs); - serializable_emu.set_flags(flags); serializable_emu.set_pe32(pe32); serializable_emu.set_pe64(pe64); - serializable_emu.cfg.arch = match system_info.cpu { - minidump::system_info::Cpu::X86 => Arch::X86, - minidump::system_info::Cpu::X86_64 => Arch::X86_64, - minidump::system_info::Cpu::Arm64 => Arch::Aarch64, - _ => Arch::X86, - }; + serializable_emu.cfg.arch = arch; + + match extracted { + ExtractedContext::X86 { regs, flags } => { + serializable_emu.set_regs(regs); + serializable_emu.set_flags(flags); + } + ExtractedContext::AArch64 { regs } => { + serializable_emu.set_regs_aarch64(regs); + } + } + serializable_emu.os = match system_info.os { minidump::system_info::Os::Windows => OperatingSystem::Windows, minidump::system_info::Os::Linux => OperatingSystem::Linux, diff --git a/crates/libmwemu/src/serialization/minidump/writer.rs b/crates/libmwemu/src/serialization/minidump/writer.rs index 2cba33ba..0a478b24 100644 --- a/crates/libmwemu/src/serialization/minidump/writer.rs +++ b/crates/libmwemu/src/serialization/minidump/writer.rs @@ -7,7 +7,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use byteorder::{LittleEndian, WriteBytesExt}; use minidump::format as md; -use super::context::build_thread_context; +use super::context::{build_thread_context, ThreadContextInput}; use crate::arch::{Arch, OperatingSystem}; use crate::emu::Emu; use crate::maps::mem64::Permission; @@ -54,16 +54,6 @@ pub struct MinidumpWriter; impl MinidumpWriter { pub fn write_to_file(emu: &Emu, filename: &str) -> io::Result<()> { - if !matches!(emu.cfg.arch, Arch::X86 | Arch::X86_64) { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - format!( - "minidump export currently supports x86/x86_64 guests, got {:?}", - emu.cfg.arch - ), - )); - } - let regions = collect_memory_regions(emu); let modules = collect_modules(emu, ®ions); let stream_count = 5u32; @@ -412,22 +402,30 @@ fn build_thread_list_stream( let mut trailing_data = Vec::new(); for (thread_idx, thread) in emu.threads.iter().enumerate() { - let (regs, flags) = match &thread.arch { - ArchThreadState::X86 { regs, flags, .. } => (regs, flags), - ArchThreadState::AArch64 { .. } => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "aarch64 thread export is not implemented yet", - )) + let (context, sp) = match &thread.arch { + ArchThreadState::X86 { regs, flags, .. } => { + let ctx = build_thread_context(ThreadContextInput::X86 { + arch: emu.cfg.arch, + regs, + flags, + })?; + let sp = match emu.cfg.arch { + Arch::X86 => regs.get_esp(), + Arch::X86_64 => regs.rsp, + _ => 0, + }; + (ctx, sp) + } + ArchThreadState::AArch64 { regs, .. } => { + let ctx = build_thread_context(ThreadContextInput::AArch64 { regs })?; + (ctx, regs.sp) } }; - let context = build_thread_context(emu.cfg.arch, regs, flags)?; let context_rva = next_context_rva; next_context_rva += context.len() as u32; let (stack_base, stack_location) = stack_location_for_thread( - emu.cfg.arch, - regs, + sp, regions, memory_locations, ); @@ -471,17 +469,10 @@ fn build_thread_list_stream( } fn stack_location_for_thread( - arch: Arch, - regs: &crate::regs64::Regs64, + sp: u64, regions: &[MemoryRegion<'_>], memory_locations: &BTreeMap, ) -> (u64, MemoryLocation) { - let sp = match arch { - Arch::X86 => regs.get_esp(), - Arch::X86_64 => regs.rsp, - Arch::Aarch64 => 0, - }; - let Some(region) = regions .iter() .find(|region| sp >= region.base && sp < (region.base + region.bytes.len() as u64)) diff --git a/crates/libmwemu/src/tests/unit/serialization_tests.rs b/crates/libmwemu/src/tests/unit/serialization_tests.rs index 20a53c5f..89a15426 100644 --- a/crates/libmwemu/src/tests/unit/serialization_tests.rs +++ b/crates/libmwemu/src/tests/unit/serialization_tests.rs @@ -14,10 +14,38 @@ fn write_tmp(name: &str, bytes: &[u8]) -> std::path::PathBuf { } #[test] -fn test_serialization_module_exists() { - // Just verify the module is accessible - let _emu = crate::emu64(); - // Serialization exists but we won't test it due to complexity +fn test_x86_64_native_serialization_roundtrip() { + let handle = std::thread::Builder::new() + .stack_size(1024 * 29055) + .spawn(|| { + helpers::setup(); + + let mut emu = crate::emu64(); + emu.regs_mut().rax = 0x1122_3344_5566_7788; + emu.regs_mut().rbx = 0xAABB_CCDD_EEFF_0011; + emu.regs_mut().rsp = 0x200800; + emu.regs_mut().rip = 0x401000; + emu.flags_mut().load(0x246); + + let stack_map = emu + .maps + .create_map("stack", 0x200000, 0x2000, Permission::READ_WRITE) + .unwrap(); + stack_map.memcpy(&vec![0x41; 0x200], 0x200); + + let serialized = Serialization::serialize(&emu); + let loaded = Serialization::deserialize(&serialized); + + assert!(loaded.cfg.is_x64()); + assert_eq!(loaded.regs().rax, 0x1122_3344_5566_7788); + assert_eq!(loaded.regs().rbx, 0xAABB_CCDD_EEFF_0011); + assert_eq!(loaded.regs().rsp, 0x200800); + assert_eq!(loaded.regs().rip, 0x401000); + assert_eq!(loaded.flags().dump(), 0x246); + assert_eq!(loaded.maps.read_byte(0x200010), Some(0x41)); + }) + .unwrap(); + handle.join().unwrap(); } #[test] @@ -164,61 +192,117 @@ fn test_x86_minidump_roundtrip() { } #[test] -fn test_aarch64_minidump_export_is_not_supported_yet() { - let temp_dir = TempDir::new("mwemu_minidump_aarch64").unwrap(); +fn test_aarch64_minidump_roundtrip() { + let temp_dir = TempDir::new("mwemu_minidump_aarch64_rt").unwrap(); let dump_path = temp_dir.path().join("sample_aarch64.dmp"); - let emu = crate::emu_aarch64(); - let err = Serialization::dump_to_minidump(&emu, dump_path.to_str().unwrap()).unwrap_err(); + let mut emu = crate::emu_aarch64(); + emu.init_cpu(); + emu.filename = "sample_aarch64.exe".to_string(); + + emu.regs_aarch64_mut().x[0] = 0x1122_3344_5566_7788; + emu.regs_aarch64_mut().x[1] = 0xAABB_CCDD_EEFF_0011; + emu.regs_aarch64_mut().x[29] = 0x200900; // fp + emu.regs_aarch64_mut().x[30] = 0x401004; // lr + emu.regs_aarch64_mut().sp = 0x200800; + emu.regs_aarch64_mut().pc = 0x401000; + emu.regs_aarch64_mut().nzcv.n = true; + emu.regs_aarch64_mut().nzcv.z = false; + emu.regs_aarch64_mut().nzcv.c = true; + emu.regs_aarch64_mut().nzcv.v = false; + emu.regs_aarch64_mut().v[0] = 0x0011_2233_4455_6677_8899_AABB_CCDD_EEFF; - assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); - assert!(err.to_string().contains("x86/x86_64 guests")); + let stack_map = emu + .maps + .create_map("test_stack", 0x200000, 0x2000, Permission::READ_WRITE) + .unwrap(); + stack_map.memcpy(&vec![0x55; 0x200], 0x200); + + Serialization::dump_to_minidump(&emu, dump_path.to_str().unwrap()).unwrap(); + + // Validate dump is parseable by the minidump crate + let dump = Minidump::read_path(&dump_path).unwrap(); + let sys_info = dump + .get_stream::() + .unwrap(); + assert!(matches!(sys_info.cpu, minidump::system_info::Cpu::Arm64)); + + // Import back and verify register state + let loaded = Serialization::load_from_minidump(dump_path.to_str().unwrap()); + assert!(loaded.cfg.arch.is_aarch64()); + assert_eq!(loaded.regs_aarch64().x[0], 0x1122_3344_5566_7788); + assert_eq!(loaded.regs_aarch64().x[1], 0xAABB_CCDD_EEFF_0011); + assert_eq!(loaded.regs_aarch64().x[29], 0x200900); + assert_eq!(loaded.regs_aarch64().x[30], 0x401004); + assert_eq!(loaded.regs_aarch64().sp, 0x200800); + assert_eq!(loaded.regs_aarch64().pc, 0x401000); + assert!(loaded.regs_aarch64().nzcv.n); + assert!(!loaded.regs_aarch64().nzcv.z); + assert!(loaded.regs_aarch64().nzcv.c); + assert!(!loaded.regs_aarch64().nzcv.v); + assert_eq!( + loaded.regs_aarch64().v[0], + 0x0011_2233_4455_6677_8899_AABB_CCDD_EEFF + ); + assert_eq!(loaded.maps.read_byte(0x200010), Some(0x55)); } #[test] -#[ignore = "AArch64 native serialization is not implemented yet"] fn test_aarch64_native_serialization_fixture_roundtrip() { - helpers::setup(); + let handle = std::thread::Builder::new() + .stack_size(1024 * 29055) + .spawn(|| { + helpers::setup(); - let path = write_tmp("mwemu_test_serialize_elf64_aarch64_add.bin", ELF64_AARCH64_ADD); + let path = + write_tmp("mwemu_test_serialize_elf64_aarch64_add.bin", ELF64_AARCH64_ADD); - let mut emu = crate::emu_aarch64(); - emu.load_code(path.to_str().unwrap()); + let mut emu = crate::emu_aarch64(); + emu.load_code(path.to_str().unwrap()); - emu.step(); - emu.step(); - emu.step(); - assert_eq!(emu.regs_aarch64().x[2], 2); + emu.step(); + emu.step(); + emu.step(); + assert_eq!(emu.regs_aarch64().x[2], 2); - let serialized = Serialization::serialize(&emu); - let loaded = Serialization::deserialize(&serialized); + let serialized = Serialization::serialize(&emu); + let loaded = Serialization::deserialize(&serialized); - assert!(loaded.cfg.arch.is_aarch64()); - assert_eq!(loaded.regs_aarch64().x[2], 2); - assert_eq!(loaded.regs_aarch64().pc, emu.regs_aarch64().pc); + assert!(loaded.cfg.arch.is_aarch64()); + assert_eq!(loaded.regs_aarch64().x[2], 2); + assert_eq!(loaded.regs_aarch64().pc, emu.regs_aarch64().pc); + }) + .unwrap(); + handle.join().unwrap(); } #[test] -#[ignore = "AArch64 minidump import/export is not implemented yet"] fn test_aarch64_minidump_fixture_roundtrip() { - helpers::setup(); - - let sample_path = write_tmp("mwemu_test_minidump_elf64_aarch64_add.bin", ELF64_AARCH64_ADD); - let temp_dir = TempDir::new("mwemu_minidump_aarch64_future").unwrap(); - let dump_path = temp_dir.path().join("elf64_aarch64_add.dmp"); - - let mut emu = crate::emu_aarch64(); - emu.load_code(sample_path.to_str().unwrap()); - - emu.step(); - emu.step(); - emu.step(); - assert_eq!(emu.regs_aarch64().x[2], 2); - - Serialization::dump_to_minidump(&emu, dump_path.to_str().unwrap()).unwrap(); - let loaded = Serialization::load_from_minidump(dump_path.to_str().unwrap()); - - assert!(loaded.cfg.arch.is_aarch64()); - assert_eq!(loaded.regs_aarch64().x[2], 2); - assert_eq!(loaded.regs_aarch64().pc, emu.regs_aarch64().pc); + let handle = std::thread::Builder::new() + .stack_size(1024 * 29055) + .spawn(|| { + helpers::setup(); + + let sample_path = + write_tmp("mwemu_test_minidump_elf64_aarch64_add.bin", ELF64_AARCH64_ADD); + let temp_dir = TempDir::new("mwemu_minidump_aarch64_future").unwrap(); + let dump_path = temp_dir.path().join("elf64_aarch64_add.dmp"); + + let mut emu = crate::emu_aarch64(); + emu.load_code(sample_path.to_str().unwrap()); + + emu.step(); + emu.step(); + emu.step(); + assert_eq!(emu.regs_aarch64().x[2], 2); + + Serialization::dump_to_minidump(&emu, dump_path.to_str().unwrap()).unwrap(); + let loaded = Serialization::load_from_minidump(dump_path.to_str().unwrap()); + + assert!(loaded.cfg.arch.is_aarch64()); + assert_eq!(loaded.regs_aarch64().x[2], 2); + assert_eq!(loaded.regs_aarch64().pc, emu.regs_aarch64().pc); + }) + .unwrap(); + handle.join().unwrap(); } diff --git a/docs/serialization-roadmap.md b/docs/serialization-roadmap.md index 02ebd5d6..8fc933d8 100644 --- a/docs/serialization-roadmap.md +++ b/docs/serialization-roadmap.md @@ -2,10 +2,9 @@ Current status: -- Native `dump_to_file` / `load_from_file` remains the existing path for x86/x86_64, but still lacks dedicated roundtrip coverage. -- Minidump `dump_to_minidump` / `load_from_minidump` works for x86/x86_64. -- AArch64 native serialization plumbing is partially in place, but the ignored fixture roundtrip currently aborts with a stack overflow. -- AArch64 minidump import/export is still not implemented. +- Native `dump_to_file` / `load_from_file` works for x86/x86_64/AArch64 with dedicated roundtrip coverage. +- Minidump `dump_to_minidump` / `load_from_minidump` works for x86/x86_64/AArch64. +- All previously-ignored AArch64 serialization and minidump tests are enabled and passing. ## Phase 1: Native Serialization Architecture Parity @@ -15,44 +14,36 @@ Current status: - [x] Refactor instruction/decode serialization so `ArchState::X86` and `ArchState::AArch64` can both be represented and restored. - [x] Make `SerializableEmu::from(&Emu)` stop using x86-only accessors on AArch64. - [x] Make `From for Emu` restore AArch64 thread state and `arch_state` structurally. -- [ ] Investigate and fix the current stack overflow in `test_aarch64_native_serialization_fixture_roundtrip`. -- [ ] Replace the placeholder serialization smoke test with real native roundtrip coverage. -- [ ] Unignore `test_aarch64_native_serialization_fixture_roundtrip`. +- [x] Investigate and fix the current stack overflow in `test_aarch64_native_serialization_fixture_roundtrip`. +- [x] Replace the placeholder serialization smoke test with real native roundtrip coverage. +- [x] Unignore `test_aarch64_native_serialization_fixture_roundtrip`. ## Phase 2: AArch64 Minidump Import -- [ ] Parse `MinidumpRawContext::Arm64` and `MinidumpRawContext::OldArm64`. -- [ ] Map ARM64 minidump register state into `RegsAarch64` (`x0..x30`, `sp`, `pc`, `nzcv`, FP/SIMD state). -- [ ] Build an AArch64 `SerializableEmu` from imported minidumps instead of routing through x86-shaped fields. -- [ ] Preserve AArch64 `cfg.arch`, OS, memory maps, modules, and current thread state on import. -- [ ] Treat ARM64 PE modules as 64-bit when reconstructing imported module metadata. -- [ ] Add an ARM64 minidump import fixture test. -- [ ] Unignore `test_aarch64_minidump_fixture_roundtrip` once import/export are ready. +- [x] Parse `MinidumpRawContext::Arm64` and `MinidumpRawContext::OldArm64`. +- [x] Map ARM64 minidump register state into `RegsAarch64` (`x0..x30`, `sp`, `pc`, `nzcv`, FP/SIMD state). +- [x] Build an AArch64 `SerializableEmu` from imported minidumps instead of routing through x86-shaped fields. +- [x] Preserve AArch64 `cfg.arch`, OS, memory maps, modules, and current thread state on import. +- [x] Treat ARM64 PE modules as 64-bit when reconstructing imported module metadata. +- [x] Add an ARM64 minidump import fixture test. +- [x] Unignore `test_aarch64_minidump_fixture_roundtrip` once import/export are ready. ## Phase 3: AArch64 Minidump Export -- [ ] Emit a valid `CONTEXT_ARM64` blob from `RegsAarch64`. -- [ ] Teach the minidump writer to export `ArchThreadState::AArch64`. -- [ ] Export ARM64 thread context, stack location, and relevant SIMD/FP register state. -- [ ] Validate ARM64 minidumps with the Rust `minidump` parser. +- [x] Emit a valid `CONTEXT_ARM64` blob from `RegsAarch64`. +- [x] Teach the minidump writer to export `ArchThreadState::AArch64`. +- [x] Export ARM64 thread context, stack location, and relevant SIMD/FP register state. +- [x] Validate ARM64 minidumps with the Rust `minidump` parser. - [ ] Validate generated dumps with at least one external consumer such as Ghidra or WinDbg. ## Done Means -- [ ] Native serialization round-trips x86, x86_64, and AArch64 fixtures. -- [ ] Minidump import/export round-trips x86, x86_64, and AArch64 fixtures. -- [ ] The ignored AArch64 serialization/minidump tests are enabled and passing. -- [ ] The old x86-only "too complex" placeholder test is removed or replaced with real assertions. - -## Finish Order - -1. Stabilize native serialization first. - Replace the placeholder smoke test with native x86/x86_64 roundtrip tests, then reproduce the current AArch64 stack overflow with the ignored fixture and reduce it to the smallest failing serialize/deserialize case. -2. Land native AArch64 roundtrip coverage. - Fix the stack overflow, verify `Serialization::serialize` and `Serialization::deserialize` preserve AArch64 registers and `arch_state`, and then unignore the native AArch64 fixture test. -3. Implement ARM64 minidump import. - Parse `MinidumpRawContext::Arm64` / `OldArm64`, map them into `RegsAarch64`, keep `cfg.arch`/OS/maps/modules intact, and add an import fixture test. -4. Implement ARM64 minidump export. - Emit `CONTEXT_ARM64`, export `ArchThreadState::AArch64`, preserve stack and FP/SIMD state, and validate with the Rust `minidump` parser. -5. Do external validation and cleanup. - Open generated ARM64 dumps in at least one real consumer such as Ghidra or WinDbg, enable the ignored minidump test, and only then call the feature complete. +- [x] Native serialization round-trips x86, x86_64, and AArch64 fixtures. +- [x] Minidump import/export round-trips x86, x86_64, and AArch64 fixtures. +- [x] The ignored AArch64 serialization/minidump tests are enabled and passing. +- [x] The old x86-only "too complex" placeholder test is removed or replaced with real assertions. + +## Remaining + +- [ ] Validate generated ARM64 dumps with at least one external consumer (Ghidra or WinDbg). +- [ ] Consider refactoring `Emu::new()` to accept an `Arch` parameter to avoid the two-step init_cpu pattern. From 20894b7f85e41989e5f5ff44fd220d1bacda5349 Mon Sep 17 00:00:00 2001 From: Brandon Ros Date: Mon, 13 Apr 2026 09:53:17 -0400 Subject: [PATCH 4/5] Refactor Emu::new() to accept Arch parameter and widen eh_ctx to u64 Emu::new(arch) now initializes ArchState, ThreadContext, and Maps.is_64bits correctly at construction time, eliminating the window where cfg.arch disagrees with the actual thread state. Factory functions (emu32/emu64/ emu_aarch64) simplified to one-liners. This exposed a latent bug: the 64-bit allocator places maps above 4 GB but the exception handler context pointer (eh_ctx) was stored as u32, causing a panic. Widened eh_ctx to u64 across ThreadContext, serialization structs, and exception handler enter/exit paths. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/libmwemu/src/emu/exception_handlers.rs | 4 +- crates/libmwemu/src/emu/initialization.rs | 37 +++++++++++++------ crates/libmwemu/src/exception/handlers.rs | 30 +++++++-------- crates/libmwemu/src/lib.rs | 16 ++------ crates/libmwemu/src/serialization/emu.rs | 6 +-- .../src/serialization/thread_context.rs | 2 +- .../unit/test_unified_step_and_run_methods.rs | 1 + crates/libmwemu/src/threading/context.rs | 2 +- docs/PROGRAMATICALLY.md | 4 +- docs/serialization-roadmap.md | 2 +- 10 files changed, 52 insertions(+), 52 deletions(-) diff --git a/crates/libmwemu/src/emu/exception_handlers.rs b/crates/libmwemu/src/emu/exception_handlers.rs index b5be62f3..7a899972 100644 --- a/crates/libmwemu/src/emu/exception_handlers.rs +++ b/crates/libmwemu/src/emu/exception_handlers.rs @@ -35,14 +35,14 @@ impl Emu { } } - pub fn eh_ctx(&self) -> u32 { + pub fn eh_ctx(&self) -> u64 { match &self.threads[self.current_thread_id].arch { ArchThreadState::X86 { eh_ctx, .. } => *eh_ctx, _ => panic!("eh_ctx() called on aarch64 emu"), } } - pub fn set_eh_ctx(&mut self, value: u32) { + pub fn set_eh_ctx(&mut self, value: u64) { match &mut self.threads[self.current_thread_id].arch { ArchThreadState::X86 { eh_ctx, .. } => *eh_ctx = value, _ => panic!("set_eh_ctx() called on aarch64 emu"), diff --git a/crates/libmwemu/src/emu/initialization.rs b/crates/libmwemu/src/emu/initialization.rs index 8ce2f9fe..a6eafb2a 100644 --- a/crates/libmwemu/src/emu/initialization.rs +++ b/crates/libmwemu/src/emu/initialization.rs @@ -45,7 +45,7 @@ impl CustomLogFormat { impl Default for Emu { fn default() -> Self { - Emu::new() + Emu::new(crate::arch::Arch::X86) } } @@ -56,23 +56,39 @@ pub struct Lib { } impl Emu { - pub fn new() -> Emu { - let mut formatter = IntelFormatter::new(); - formatter.options_mut().set_digit_separator(""); - formatter.options_mut().set_first_operand_char_index(6); - Emu { - arch_state: ArchState::X86 { + pub fn new(arch: crate::arch::Arch) -> Emu { + let arch_state = if arch.is_aarch64() { + ArchState::AArch64 { + instruction: None, + instruction_cache: InstructionCache::new(), + } + } else { + let mut formatter = IntelFormatter::new(); + formatter.options_mut().set_digit_separator(""); + formatter.options_mut().set_first_operand_char_index(6); + ArchState::X86 { instruction: None, formatter, instruction_cache: InstructionCache::new(), decoder_position: 0, + } + }; + + let mut cfg = Config::new(); + cfg.arch = arch; + + Emu { + arch_state, + maps: { + let mut maps = Maps::default(); + maps.is_64bits = arch.is_64bits(); + maps }, - maps: Maps::default(), hooks: Hooks::new(), exp: 0, break_on_alert: false, bp: Breakpoints::new(), - cfg: Config::new(), + cfg, colors: Colors::new(), pos: 0, max_pos: None, @@ -109,8 +125,7 @@ impl Emu { base: 0, heap_addr: 0, rng: RefCell::new(rand::rng()), - // Initialize with main thread as thread 0 - threads: vec![ThreadContext::new(0x1000, crate::arch::Arch::X86)], + threads: vec![ThreadContext::new(0x1000, arch)], current_thread_id: 0, global_locks: GlobalLocks::new(), definitions: HashMap::new(), diff --git a/crates/libmwemu/src/exception/handlers.rs b/crates/libmwemu/src/exception/handlers.rs index 1f1d0cf6..9d2ae786 100644 --- a/crates/libmwemu/src/exception/handlers.rs +++ b/crates/libmwemu/src/exception/handlers.rs @@ -72,33 +72,33 @@ pub fn enter32( let ctx = emu .maps .create_map("ctx", ctx_addr, 0x1000, Permission::READ_WRITE_EXECUTE); - emu.set_eh_ctx((ctx_addr + 0x100) as u32); + emu.set_eh_ctx(ctx_addr + 0x100); emu.stack_push32(ctx_addr as u32); // 0x10f00 emu.stack_push32(emu.regs().get_eip() as u32); - emu.set_eh_ctx(ctx_addr as u32 + 8); // 0x10f08 + emu.set_eh_ctx(ctx_addr + 8); // 0x10f08 // ctx header: // +0x00 => handler kind (SEH/VEH/UEF), +0x04 => context ptr emu.maps.write_dword(ctx_addr, handler_kind.as_u32()); - emu.maps.write_dword(ctx_addr + 4, emu.eh_ctx()); // 0x10f04 + emu.maps.write_dword(ctx_addr + 4, emu.eh_ctx() as u32); // 0x10f04 emu.maps.write_dword( - emu.eh_ctx() as u64, + emu.eh_ctx(), types::exception_type_code(ex_type), ); // STATUS_BREAKPOINT let ctx = Context32::new(&emu.regs()); - ctx.save(emu.eh_ctx(), &mut emu.maps); + ctx.save(emu.eh_ctx() as u32, &mut emu.maps); } pub fn exit32(emu: &mut emu::Emu) { let disposition = emu.regs().get_eax() as u32; - let ctx_ptr = emu.eh_ctx() as u64; + let ctx_ptr = emu.eh_ctx(); let ex_code = emu.maps.read_dword(ctx_ptr).unwrap_or(0); let handler_kind = emu.maps.read_dword(ctx_ptr.saturating_sub(8)).unwrap_or(0); let mut ctx = Context32::new(&emu.regs()); - ctx.load(emu.eh_ctx(), &mut emu.maps); + ctx.load(emu.eh_ctx() as u32, &mut emu.maps); ctx.sync(emu.regs_mut()); emu.set_eh_ctx(0); emu.force_reload = true; @@ -129,33 +129,29 @@ pub fn enter64( emu.stack_push64(ctx_addr); // 0x10f00 emu.stack_push64(emu.regs().rip); - if ctx_addr > u32::MAX as u64 { - panic!("64bits allocator is giving a too big memory!! for the context64"); - } - - emu.set_eh_ctx(ctx_addr as u32 + 16); + emu.set_eh_ctx(ctx_addr + 16); // ctx header: // +0x00 => handler kind (SEH/VEH/UEF), +0x08 => context ptr emu.maps.write_dword(ctx_addr, handler_kind.as_u32()); - emu.maps.write_qword(ctx_addr + 8, emu.eh_ctx() as u64); + emu.maps.write_qword(ctx_addr + 8, emu.eh_ctx()); // Store the exception code at the start of the context area (mirrors enter32), // so exit64 can retrieve it from `eh_ctx()` (offset +0). emu.maps.write_dword( - emu.eh_ctx() as u64, + emu.eh_ctx(), types::exception_type_code(ex_type), ); let ctx = Context64::new(&emu.regs()); - ctx.save(emu.eh_ctx() as u64, &mut emu.maps); + ctx.save(emu.eh_ctx(), &mut emu.maps); } pub fn exit64(emu: &mut emu::Emu) { let disposition = emu.regs().rax as u32; - let ctx_ptr = emu.eh_ctx() as u64; + let ctx_ptr = emu.eh_ctx(); let ex_code = emu.maps.read_dword(ctx_ptr).unwrap_or(0); let handler_kind = emu.maps.read_dword(ctx_ptr.saturating_sub(16)).unwrap_or(0); let mut ctx = Context64::new(&emu.regs()); - ctx.load(emu.eh_ctx() as u64, &mut emu.maps); + ctx.load(emu.eh_ctx(), &mut emu.maps); ctx.sync(emu.regs_mut()); emu.set_eh_ctx(0); emu.force_reload = true; diff --git a/crates/libmwemu/src/lib.rs b/crates/libmwemu/src/lib.rs index dd013f59..acd709be 100644 --- a/crates/libmwemu/src/lib.rs +++ b/crates/libmwemu/src/lib.rs @@ -53,32 +53,22 @@ pub use utils::disable_color; #[cfg(test)] mod tests; use arch::Arch; -use config::Config; use emu::Emu; pub fn emu64() -> Emu { - let mut emu = Emu::new(); - let mut cfg = Config::new(); - cfg.arch = Arch::X86_64; - emu.set_config(cfg); + let mut emu = Emu::new(Arch::X86_64); emu.disable_ctrlc(); emu } pub fn emu32() -> Emu { - let mut emu = Emu::new(); - let mut cfg = Config::new(); - cfg.arch = Arch::X86; - emu.set_config(cfg); + let mut emu = Emu::new(Arch::X86); emu.disable_ctrlc(); emu } pub fn emu_aarch64() -> Emu { - let mut emu = Emu::new(); - let mut cfg = Config::new(); - cfg.arch = Arch::Aarch64; - emu.set_config(cfg); + let mut emu = Emu::new(Arch::Aarch64); emu.disable_ctrlc(); emu } diff --git a/crates/libmwemu/src/serialization/emu.rs b/crates/libmwemu/src/serialization/emu.rs index e835b9ce..b68696ec 100644 --- a/crates/libmwemu/src/serialization/emu.rs +++ b/crates/libmwemu/src/serialization/emu.rs @@ -56,7 +56,7 @@ pub enum SerializableCurrentThreadState { seh: u64, veh: u64, uef: u64, - eh_ctx: u32, + eh_ctx: u64, tls32: Vec, tls64: Vec, fls: Vec, @@ -512,7 +512,7 @@ impl From for Emu { impl Default for SerializableEmu { fn default() -> Self { - SerializableEmu::from(&Emu::new()) + SerializableEmu::from(&Emu::new(crate::arch::Arch::X86)) } } @@ -581,8 +581,6 @@ impl SerializableEmu { Arch::X86_64 => crate::emu64(), Arch::X86 => crate::emu32(), }; - // Ensure thread context matches architecture (emu_aarch64 doesn't - // re-init threads automatically). emu.init_cpu(); SerializableEmu::from(&emu) } diff --git a/crates/libmwemu/src/serialization/thread_context.rs b/crates/libmwemu/src/serialization/thread_context.rs index a26f3e32..ba6d1af7 100644 --- a/crates/libmwemu/src/serialization/thread_context.rs +++ b/crates/libmwemu/src/serialization/thread_context.rs @@ -22,7 +22,7 @@ pub enum SerializableThreadArch { seh: u64, veh: u64, uef: u64, - eh_ctx: u32, + eh_ctx: u64, tls32: Vec, tls64: Vec, fls: Vec, diff --git a/crates/libmwemu/src/tests/unit/test_unified_step_and_run_methods.rs b/crates/libmwemu/src/tests/unit/test_unified_step_and_run_methods.rs index 68838cbc..ac707ee0 100644 --- a/crates/libmwemu/src/tests/unit/test_unified_step_and_run_methods.rs +++ b/crates/libmwemu/src/tests/unit/test_unified_step_and_run_methods.rs @@ -1,3 +1,4 @@ +use crate::config::Config; use crate::maps::mem64::Permission; use crate::{tests::helpers, *}; diff --git a/crates/libmwemu/src/threading/context.rs b/crates/libmwemu/src/threading/context.rs index 350e9f1b..bbf1f30a 100644 --- a/crates/libmwemu/src/threading/context.rs +++ b/crates/libmwemu/src/threading/context.rs @@ -17,7 +17,7 @@ pub enum ArchThreadState { seh: u64, veh: u64, uef: u64, - eh_ctx: u32, + eh_ctx: u64, tls32: Vec, tls64: Vec, fls: Vec, diff --git a/docs/PROGRAMATICALLY.md b/docs/PROGRAMATICALLY.md index b5473d8c..26df49b3 100644 --- a/docs/PROGRAMATICALLY.md +++ b/docs/PROGRAMATICALLY.md @@ -8,7 +8,7 @@ The main instatiates emu module, set the config, loads the binary and run. ```rust - let mut emu = Emu::new(); + let mut emu = Emu::new(Arch::X86); emu.set_config(cfg); emu.init(); @@ -26,7 +26,7 @@ but run(0) means forever. ```rust - let mut emu = Emu::new(); + let mut emu = Emu::new(Arch::X86); emu.set_config(cfg); emu.init(); diff --git a/docs/serialization-roadmap.md b/docs/serialization-roadmap.md index 8fc933d8..a322999c 100644 --- a/docs/serialization-roadmap.md +++ b/docs/serialization-roadmap.md @@ -46,4 +46,4 @@ Current status: ## Remaining - [ ] Validate generated ARM64 dumps with at least one external consumer (Ghidra or WinDbg). -- [ ] Consider refactoring `Emu::new()` to accept an `Arch` parameter to avoid the two-step init_cpu pattern. +- [x] Refactor `Emu::new()` to accept an `Arch` parameter to avoid the two-step init_cpu pattern. From d165614aa4a3991b6dfc279c06cd229bff172bc2 Mon Sep 17 00:00:00 2001 From: Brandon Ros Date: Mon, 13 Apr 2026 10:15:57 -0400 Subject: [PATCH 5/5] Fix pymwemu build on macOS by adding extension module link args Also replace all hardcoded relative paths in tests with helpers::win32_maps_folder(), helpers::win64_maps_folder(), and helpers::test_data_path() so tests pass regardless of working directory (fixes CI failure on Windows runner). Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.lock | 1 + crates/libmwemu/src/emu/maps.rs | 11 ++++----- crates/libmwemu/src/tests/helpers.rs | 12 ++++++++++ .../src/tests/isa/x64/allocator64_test.rs | 2 +- .../libmwemu/src/tests/isa/x64/mem64_test.rs | 2 +- .../src/tests/isa/x64/stack64_test.rs | 2 +- .../src/tests/isa/x86/allocator32_test.rs | 2 +- .../src/tests/isa/x86/exception_handler32.rs | 4 ++-- .../libmwemu/src/tests/isa/x86/logic_test.rs | 2 +- .../src/tests/isa/x86/stack32_test.rs | 2 +- .../loaders/elf64/elf64lin_cpu_arithmetics.rs | 8 +++---- .../src/tests/loaders/elf64/elf64lin_flags.rs | 6 ++--- .../src/tests/loaders/elf64/elf64lin_fpu.rs | 6 ++--- .../elf64/elf64lin_static_helloworld.rs | 4 ++-- .../tests/loaders/elf64/elf64lin_syscall64.rs | 2 +- .../tests/loaders/pe/exe32win_minecraft.rs | 6 ++--- .../src/tests/loaders/pe/exe64win_enigma.rs | 6 ++--- .../src/tests/loaders/pe/exe64win_msgbox.rs | 6 ++--- .../src/tests/loaders/pe/mingw_tests.rs | 12 +++++----- .../src/tests/loaders/pe/pe64_loader_tests.rs | 12 +++++----- .../loaders/pe/peb_teb_ldr_structures_test.rs | 8 +++---- .../src/tests/shellcode/sc32lin_rshell.rs | 6 ++--- .../src/tests/shellcode/sc32win_donut.rs | 6 ++--- .../tests/shellcode/sc32win_peb_ldr_rot.rs | 6 ++--- .../src/tests/shellcode/sc32win_veryobfus.rs | 6 ++--- .../tests/shellcode/sc64lin_arith_100iter.rs | 6 ++--- .../src/tests/shellcode/sc64win_metasploit.rs | 6 ++--- .../src/tests/shellcode/sc64win_strgen.rs | 4 ++-- .../tests/unit/breakpoint_functionality.rs | 4 ++-- .../libmwemu/src/tests/unit/emulation_perf.rs | 24 +++++-------------- .../src/tests/unit/should_serialize.rs | 4 ++-- crates/pymwemu/Cargo.toml | 3 +++ crates/pymwemu/build.rs | 3 +++ 33 files changed, 100 insertions(+), 94 deletions(-) create mode 100644 crates/pymwemu/build.rs diff --git a/Cargo.lock b/Cargo.lock index 915b4da9..261f66b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1465,6 +1465,7 @@ dependencies = [ "libmwemu", "log", "pyo3", + "pyo3-build-config", "yaxpeax-arm", ] diff --git a/crates/libmwemu/src/emu/maps.rs b/crates/libmwemu/src/emu/maps.rs index 5f23eb85..ac9f6a57 100644 --- a/crates/libmwemu/src/emu/maps.rs +++ b/crates/libmwemu/src/emu/maps.rs @@ -132,13 +132,12 @@ impl Emu { } /// From a file-path this returns the filename with no path and no extension. + /// Handles both `/` and `\` path separators so Windows CI paths work. pub fn filename_to_mapname(&self, filename: &str) -> String { - filename - .split('/') - .last() - .map(|x| x.split('.')) - .and_then(|x| x.peekable().next()) - .unwrap() + std::path::Path::new(filename) + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or(filename) .to_string() } diff --git a/crates/libmwemu/src/tests/helpers.rs b/crates/libmwemu/src/tests/helpers.rs index 0390184a..243c6336 100644 --- a/crates/libmwemu/src/tests/helpers.rs +++ b/crates/libmwemu/src/tests/helpers.rs @@ -22,6 +22,18 @@ pub fn test_data_path(rel: &str) -> String { .into_owned() } +/// Maps folder for 32-bit Windows samples (`maps/windows/x86/`). +pub fn win32_maps_folder() -> String { + let mut s = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../maps/windows/x86") + .to_string_lossy() + .into_owned(); + if !s.ends_with('/') { + s.push('/'); + } + s +} + /// Maps folder for 64-bit Windows samples (`maps/windows/x86_64/`). pub fn win64_maps_folder() -> String { let mut s = PathBuf::from(env!("CARGO_MANIFEST_DIR")) diff --git a/crates/libmwemu/src/tests/isa/x64/allocator64_test.rs b/crates/libmwemu/src/tests/isa/x64/allocator64_test.rs index ec0863e2..ee9b7129 100644 --- a/crates/libmwemu/src/tests/isa/x64/allocator64_test.rs +++ b/crates/libmwemu/src/tests/isa/x64/allocator64_test.rs @@ -9,7 +9,7 @@ pub fn allocator64_test() { helpers::setup(); let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); + emu.cfg.maps_folder = helpers::win64_maps_folder(); emu.init_win32(false, false); assert_eq!(emu.maps.exists_mapname("notexist"), false); diff --git a/crates/libmwemu/src/tests/isa/x64/mem64_test.rs b/crates/libmwemu/src/tests/isa/x64/mem64_test.rs index 16763ba9..e8763e17 100644 --- a/crates/libmwemu/src/tests/isa/x64/mem64_test.rs +++ b/crates/libmwemu/src/tests/isa/x64/mem64_test.rs @@ -52,7 +52,7 @@ pub fn mem64_test() { let mut mem2 = Mem64::default(); mem2.set_base(0x400000); mem2.set_size(16); - mem2.load("../../test/sc32win_donut.bin"); + mem2.load(&helpers::test_data_path("sc32win_donut.bin")); let md5 = format!("{:x}", mem2.md5()); assert!(md5 == "66d6376c2dd0b8d4d35461844e5b0e6c" || md5 == "4ae71336e44bf9bf79d2752e234818a5"); // its weird but in windows CI the md5 changes to 4ae... prolly defender patches it diff --git a/crates/libmwemu/src/tests/isa/x64/stack64_test.rs b/crates/libmwemu/src/tests/isa/x64/stack64_test.rs index 0b566a32..c42780aa 100644 --- a/crates/libmwemu/src/tests/isa/x64/stack64_test.rs +++ b/crates/libmwemu/src/tests/isa/x64/stack64_test.rs @@ -6,7 +6,7 @@ pub fn stack64_test() { helpers::setup(); let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); + emu.cfg.maps_folder = helpers::win64_maps_folder(); emu.init_win32(false, false); let stack_check = emu.maps.get_map_by_name("stack"); diff --git a/crates/libmwemu/src/tests/isa/x86/allocator32_test.rs b/crates/libmwemu/src/tests/isa/x86/allocator32_test.rs index 41bdbd24..e3a9fa3f 100644 --- a/crates/libmwemu/src/tests/isa/x86/allocator32_test.rs +++ b/crates/libmwemu/src/tests/isa/x86/allocator32_test.rs @@ -9,7 +9,7 @@ pub fn allocator32_test() { helpers::setup(); let mut emu = emu32(); - emu.cfg.maps_folder = "../../maps/windows/x86/".to_string(); + emu.cfg.maps_folder = helpers::win32_maps_folder(); emu.maps.clear(); emu.init_win32(false, false); diff --git a/crates/libmwemu/src/tests/isa/x86/exception_handler32.rs b/crates/libmwemu/src/tests/isa/x86/exception_handler32.rs index 2565b15a..6167c1b2 100644 --- a/crates/libmwemu/src/tests/isa/x86/exception_handler32.rs +++ b/crates/libmwemu/src/tests/isa/x86/exception_handler32.rs @@ -7,6 +7,6 @@ pub fn exception_handler32() { helpers::setup(); let mut emu = emu32(); - emu.cfg.maps_folder = "../../maps/windows/x86/".to_string(); - emu.load_code("../../test/exe32win_exception_handler.bin"); + emu.cfg.maps_folder = helpers::win32_maps_folder(); + emu.load_code(&helpers::test_data_path("exe32win_exception_handler.bin")); } diff --git a/crates/libmwemu/src/tests/isa/x86/logic_test.rs b/crates/libmwemu/src/tests/isa/x86/logic_test.rs index 211542b3..1155d10f 100644 --- a/crates/libmwemu/src/tests/isa/x86/logic_test.rs +++ b/crates/libmwemu/src/tests/isa/x86/logic_test.rs @@ -8,7 +8,7 @@ pub fn logic_test() { helpers::setup(); let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); + emu.cfg.maps_folder = helpers::win64_maps_folder(); let num: u64 = 0x1234_5678_9ABC_DEF0; let shift: u64 = 12; diff --git a/crates/libmwemu/src/tests/isa/x86/stack32_test.rs b/crates/libmwemu/src/tests/isa/x86/stack32_test.rs index 97d95535..a4a6b8b9 100644 --- a/crates/libmwemu/src/tests/isa/x86/stack32_test.rs +++ b/crates/libmwemu/src/tests/isa/x86/stack32_test.rs @@ -6,7 +6,7 @@ pub fn stack32_test() { helpers::setup(); let mut emu = emu32(); - emu.cfg.maps_folder = "../../maps/windows/x86/".to_string(); + emu.cfg.maps_folder = helpers::win32_maps_folder(); emu.init_win32(false, false); let stack_check = emu.maps.get_map_by_name("stack"); diff --git a/crates/libmwemu/src/tests/loaders/elf64/elf64lin_cpu_arithmetics.rs b/crates/libmwemu/src/tests/loaders/elf64/elf64lin_cpu_arithmetics.rs index 87cfb720..aa53f92b 100644 --- a/crates/libmwemu/src/tests/loaders/elf64/elf64lin_cpu_arithmetics.rs +++ b/crates/libmwemu/src/tests/loaders/elf64/elf64lin_cpu_arithmetics.rs @@ -7,7 +7,7 @@ pub fn elf64lin_cpu_arithmetics1() { helpers::setup(); let mut emu = emu64(); - emu.load_code("../../test/elf64lin_cpu_arithmetics1.bin"); + emu.load_code(&helpers::test_data_path("elf64lin_cpu_arithmetics1.bin")); assert_eq!(emu.flags().dump(), 0x202); // initial flags (match with gdb linux) @@ -96,7 +96,7 @@ pub fn elf64lin_cpu_arithmetics2() { helpers::setup(); let mut emu = emu64(); - emu.load_code("../../test/elf64lin_cpu_arithmetics2.bin"); + emu.load_code(&helpers::test_data_path("elf64lin_cpu_arithmetics2.bin")); emu.flags_mut().f_if = true; emu.run_to(790022); @@ -117,7 +117,7 @@ pub fn elf64lin_cpu_arithmetics3() { helpers::setup(); let mut emu = emu64(); - emu.load_code("../../test/elf64lin_cpu_arithmetics3.bin"); + emu.load_code(&helpers::test_data_path("elf64lin_cpu_arithmetics3.bin")); emu.run_to(1513234); @@ -136,7 +136,7 @@ pub fn elf64lin_cpu_arithmetics4() { helpers::setup(); let mut emu = emu64(); - emu.load_code("../../test/elf64lin_cpu_arithmetics4.bin"); + emu.load_code(&helpers::test_data_path("elf64lin_cpu_arithmetics4.bin")); emu.run_to(294); diff --git a/crates/libmwemu/src/tests/loaders/elf64/elf64lin_flags.rs b/crates/libmwemu/src/tests/loaders/elf64/elf64lin_flags.rs index 9a2a688f..d948ba25 100644 --- a/crates/libmwemu/src/tests/loaders/elf64/elf64lin_flags.rs +++ b/crates/libmwemu/src/tests/loaders/elf64/elf64lin_flags.rs @@ -7,10 +7,10 @@ pub fn elf64lin_flags() { helpers::setup(); let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); + emu.cfg.maps_folder = helpers::win64_maps_folder(); - let sample = "../../test/elf64lin_flags.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("elf64lin_flags.bin"); + emu.load_code(&sample); // test instruction add emu.run(Some(0x401014)); diff --git a/crates/libmwemu/src/tests/loaders/elf64/elf64lin_fpu.rs b/crates/libmwemu/src/tests/loaders/elf64/elf64lin_fpu.rs index af8e6a48..f1169e6f 100644 --- a/crates/libmwemu/src/tests/loaders/elf64/elf64lin_fpu.rs +++ b/crates/libmwemu/src/tests/loaders/elf64/elf64lin_fpu.rs @@ -8,10 +8,10 @@ pub fn elf64lin_fpu() { let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); + emu.cfg.maps_folder = helpers::win64_maps_folder(); - let sample = "../../test/elf64lin_fpu.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("elf64lin_fpu.bin"); + emu.load_code(&sample); emu.fpu_mut().clear(); emu.fpu_mut().trace = true; assert_eq!(emu.fpu_mut().peek_st_u80(7), 0); diff --git a/crates/libmwemu/src/tests/loaders/elf64/elf64lin_static_helloworld.rs b/crates/libmwemu/src/tests/loaders/elf64/elf64lin_static_helloworld.rs index aa6eaeb1..fd26a33e 100644 --- a/crates/libmwemu/src/tests/loaders/elf64/elf64lin_static_helloworld.rs +++ b/crates/libmwemu/src/tests/loaders/elf64/elf64lin_static_helloworld.rs @@ -8,8 +8,8 @@ pub fn elf64lin_static_helloworld() { let mut emu = emu64(); - let sample = "../../test/elf64lin_static_helloworld.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("elf64lin_static_helloworld.bin"); + emu.load_code(&sample); emu.run(Some(0x44ab87)); assert_eq!(emu.regs().rcx, 0x4cc2d0); diff --git a/crates/libmwemu/src/tests/loaders/elf64/elf64lin_syscall64.rs b/crates/libmwemu/src/tests/loaders/elf64/elf64lin_syscall64.rs index 2c56b941..9d1dad15 100644 --- a/crates/libmwemu/src/tests/loaders/elf64/elf64lin_syscall64.rs +++ b/crates/libmwemu/src/tests/loaders/elf64/elf64lin_syscall64.rs @@ -7,7 +7,7 @@ pub fn elf64lin_syscall64() { helpers::setup(); let mut emu = emu64(); - emu.load_code("../../test/elf64lin_syscall64.bin"); + emu.load_code(&helpers::test_data_path("elf64lin_syscall64.bin")); emu.run_to(80000); assert_eq!(emu.regs().r12, 549); } diff --git a/crates/libmwemu/src/tests/loaders/pe/exe32win_minecraft.rs b/crates/libmwemu/src/tests/loaders/pe/exe32win_minecraft.rs index 67c709fc..dac7f583 100644 --- a/crates/libmwemu/src/tests/loaders/pe/exe32win_minecraft.rs +++ b/crates/libmwemu/src/tests/loaders/pe/exe32win_minecraft.rs @@ -7,10 +7,10 @@ pub fn exe32win_minecraft() { helpers::setup(); let mut emu = emu32(); - emu.cfg.maps_folder = "../../maps/windows/x86/".to_string(); + emu.cfg.maps_folder = helpers::win32_maps_folder(); - let sample = "../../test/exe32win_minecraft.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("exe32win_minecraft.bin"); + emu.load_code(&sample); emu.run(Some(0x403740)); assert_eq!(emu.regs().get_ebx(), 2); diff --git a/crates/libmwemu/src/tests/loaders/pe/exe64win_enigma.rs b/crates/libmwemu/src/tests/loaders/pe/exe64win_enigma.rs index d00cd1c3..85f7516b 100644 --- a/crates/libmwemu/src/tests/loaders/pe/exe64win_enigma.rs +++ b/crates/libmwemu/src/tests/loaders/pe/exe64win_enigma.rs @@ -8,10 +8,10 @@ pub fn exe64win_enigma() { helpers::setup(); let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); + emu.cfg.maps_folder = helpers::win64_maps_folder(); - let sample = "../../test/exe64win_enigma.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("exe64win_enigma.bin"); + emu.load_code(&sample); emu.run_to(5_000_000 + 5); assert!(emu.pos >= 5_000_000); /* diff --git a/crates/libmwemu/src/tests/loaders/pe/exe64win_msgbox.rs b/crates/libmwemu/src/tests/loaders/pe/exe64win_msgbox.rs index 5db41073..2d0867d4 100644 --- a/crates/libmwemu/src/tests/loaders/pe/exe64win_msgbox.rs +++ b/crates/libmwemu/src/tests/loaders/pe/exe64win_msgbox.rs @@ -9,10 +9,10 @@ pub fn exe64win_msgbox() { helpers::setup(); let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); + emu.cfg.maps_folder = helpers::win64_maps_folder(); - let sample = "../../test/exe64win_msgbox.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("exe64win_msgbox.bin"); + emu.load_code(&sample); emu.run(Some(0x14000123f)); let message = emu.maps.read_string(emu.regs().rdx); diff --git a/crates/libmwemu/src/tests/loaders/pe/mingw_tests.rs b/crates/libmwemu/src/tests/loaders/pe/mingw_tests.rs index ca082436..7fdff09d 100644 --- a/crates/libmwemu/src/tests/loaders/pe/mingw_tests.rs +++ b/crates/libmwemu/src/tests/loaders/pe/mingw_tests.rs @@ -6,10 +6,10 @@ fn test_mingw32() { helpers::setup(); let mut emu = emu32(); - emu.cfg.maps_folder = "../../maps/windows/x86/".to_string(); + emu.cfg.maps_folder = helpers::win32_maps_folder(); - let sample = "../../test/exe32win_mingw.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("exe32win_mingw.bin"); + emu.load_code(&sample); // Keep this test aligned with the CLI check window where execution reaches ~119. emu.run_to(119) .expect("mingw32 should reach the early execution window"); @@ -21,9 +21,9 @@ fn test_mingw64() { helpers::setup(); let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); + emu.cfg.maps_folder = helpers::win64_maps_folder(); - let sample = "../../test/exe64win_mingw.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("exe64win_mingw.bin"); + emu.load_code(&sample); emu.run_to(100); } diff --git a/crates/libmwemu/src/tests/loaders/pe/pe64_loader_tests.rs b/crates/libmwemu/src/tests/loaders/pe/pe64_loader_tests.rs index 9e50c764..7aaf626e 100644 --- a/crates/libmwemu/src/tests/loaders/pe/pe64_loader_tests.rs +++ b/crates/libmwemu/src/tests/loaders/pe/pe64_loader_tests.rs @@ -6,8 +6,8 @@ fn pe64_loader_sets_entrypoint_and_maps_main_image() { helpers::setup(); let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); - emu.load_code("../../test/exe64win_msgbox.bin"); + emu.cfg.maps_folder = helpers::win64_maps_folder(); + emu.load_code(&helpers::test_data_path("exe64win_msgbox.bin")); assert!(emu.pe64.is_some(), "PE64 metadata should be loaded"); assert!( @@ -33,8 +33,8 @@ fn pe64_loader_adds_core_ldr_modules() { helpers::setup(); let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); - emu.load_code("../../test/exe64win_msgbox.bin"); + emu.cfg.maps_folder = helpers::win64_maps_folder(); + emu.load_code(&helpers::test_data_path("exe64win_msgbox.bin")); assert!(emu.maps.get_map_by_name("ntdll.pe").is_some()); assert!(emu.maps.get_map_by_name("kernel32.pe").is_some()); @@ -46,8 +46,8 @@ fn pe64_loader_normalizes_api_set_dependencies() { helpers::setup(); let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); - emu.load_code("../../test/exe64win_mingw.bin"); + emu.cfg.maps_folder = helpers::win64_maps_folder(); + emu.load_code(&helpers::test_data_path("exe64win_mingw.bin")); let mut pe = emu.pe64.take().expect("PE64 metadata should be present"); let deps = pe.get_dependencies(&mut emu); diff --git a/crates/libmwemu/src/tests/loaders/pe/peb_teb_ldr_structures_test.rs b/crates/libmwemu/src/tests/loaders/pe/peb_teb_ldr_structures_test.rs index 15f753f6..2c033c9a 100644 --- a/crates/libmwemu/src/tests/loaders/pe/peb_teb_ldr_structures_test.rs +++ b/crates/libmwemu/src/tests/loaders/pe/peb_teb_ldr_structures_test.rs @@ -7,8 +7,8 @@ pub fn peb_teb_ldr_structures_test() { helpers::setup(); let mut emu = emu32(); - emu.cfg.maps_folder = "../../maps/windows/x86/".to_string(); - emu.load_code("../../test/exe32win_minecraft.bin"); + emu.cfg.maps_folder = helpers::win32_maps_folder(); + emu.load_code(&helpers::test_data_path("exe32win_minecraft.bin")); let peb = emu.maps.get_mem("peb"); let peb_addr = peb.get_base(); @@ -124,8 +124,8 @@ pub fn peb_teb_ldr_structures_test() { // 64BITS // let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); - emu.load_code("../../test/exe64win_msgbox.bin"); + emu.cfg.maps_folder = helpers::win64_maps_folder(); + emu.load_code(&helpers::test_data_path("exe64win_msgbox.bin")); let ntdll_addr = emu.maps.get_mem("ntdll.pe").get_base(); diff --git a/crates/libmwemu/src/tests/shellcode/sc32lin_rshell.rs b/crates/libmwemu/src/tests/shellcode/sc32lin_rshell.rs index 60c7e7aa..98afd863 100644 --- a/crates/libmwemu/src/tests/shellcode/sc32lin_rshell.rs +++ b/crates/libmwemu/src/tests/shellcode/sc32lin_rshell.rs @@ -6,10 +6,10 @@ pub fn sc32lin_rshell() { helpers::setup(); let mut emu = emu32(); - emu.cfg.maps_folder = "../../maps/windows/x86/".to_string(); + emu.cfg.maps_folder = helpers::win32_maps_folder(); - let sample = "../../test/sc32lin_rshell.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("sc32lin_rshell.bin"); + emu.load_code(&sample); emu.run_to(31); let sockaddr = emu.maps.read_bytes(emu.regs().get_ecx(), 9); assert_eq!( diff --git a/crates/libmwemu/src/tests/shellcode/sc32win_donut.rs b/crates/libmwemu/src/tests/shellcode/sc32win_donut.rs index 149add62..1c491c2f 100644 --- a/crates/libmwemu/src/tests/shellcode/sc32win_donut.rs +++ b/crates/libmwemu/src/tests/shellcode/sc32win_donut.rs @@ -7,10 +7,10 @@ pub fn sc32win_donut() { helpers::setup(); let mut emu = emu32(); - emu.cfg.maps_folder = "../../maps/windows/x86/".to_string(); + emu.cfg.maps_folder = helpers::win32_maps_folder(); - let sample = "../../test/sc32win_donut.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("sc32win_donut.bin"); + emu.load_code(&sample); emu.run_to(30_862_819); assert_eq!(emu.regs().get_eax(), 0xF5B24B1D); // used to be 0x7f937230? diff --git a/crates/libmwemu/src/tests/shellcode/sc32win_peb_ldr_rot.rs b/crates/libmwemu/src/tests/shellcode/sc32win_peb_ldr_rot.rs index 0aeddc89..69763f07 100644 --- a/crates/libmwemu/src/tests/shellcode/sc32win_peb_ldr_rot.rs +++ b/crates/libmwemu/src/tests/shellcode/sc32win_peb_ldr_rot.rs @@ -7,10 +7,10 @@ pub fn sc32win_peb_ldr_rot() { helpers::setup(); let mut emu = emu32(); - emu.cfg.maps_folder = "../../maps/windows/x86/".to_string(); + emu.cfg.maps_folder = helpers::win32_maps_folder(); - let sample = "../../test/sc32win_peb_ldr_rot.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("sc32win_peb_ldr_rot.bin"); + emu.load_code(&sample); emu.run(Some(0x3c0116)); let ptr = emu.regs().get_ebx(); diff --git a/crates/libmwemu/src/tests/shellcode/sc32win_veryobfus.rs b/crates/libmwemu/src/tests/shellcode/sc32win_veryobfus.rs index 08a2622a..0de0a5f0 100644 --- a/crates/libmwemu/src/tests/shellcode/sc32win_veryobfus.rs +++ b/crates/libmwemu/src/tests/shellcode/sc32win_veryobfus.rs @@ -7,10 +7,10 @@ pub fn sc32win_veryobfus() { helpers::setup(); let mut emu = emu32(); - emu.cfg.maps_folder = "../../maps/windows/x86/".to_string(); + emu.cfg.maps_folder = helpers::win32_maps_folder(); - let sample = "../../test/sc32win_veryobfus.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("sc32win_veryobfus.bin"); + emu.load_code(&sample); emu.run(Some(0x3cfaa5)); let ptr_ntdll_str = emu.regs().get_edi(); diff --git a/crates/libmwemu/src/tests/shellcode/sc64lin_arith_100iter.rs b/crates/libmwemu/src/tests/shellcode/sc64lin_arith_100iter.rs index f0d224f4..ab090ebd 100644 --- a/crates/libmwemu/src/tests/shellcode/sc64lin_arith_100iter.rs +++ b/crates/libmwemu/src/tests/shellcode/sc64lin_arith_100iter.rs @@ -6,10 +6,10 @@ pub fn sc64lin_arith_100iter() { helpers::setup(); let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); + emu.cfg.maps_folder = helpers::win64_maps_folder(); - let sample = "../../test/sc64lin_arith_100iter.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("sc64lin_arith_100iter.bin"); + emu.load_code(&sample); emu.run(Some(0x3c0040)); assert_eq!(emu.regs().rax, 0x4d9364d94bc0001e); diff --git a/crates/libmwemu/src/tests/shellcode/sc64win_metasploit.rs b/crates/libmwemu/src/tests/shellcode/sc64win_metasploit.rs index 2c67c7f8..858baa71 100644 --- a/crates/libmwemu/src/tests/shellcode/sc64win_metasploit.rs +++ b/crates/libmwemu/src/tests/shellcode/sc64win_metasploit.rs @@ -7,10 +7,10 @@ pub fn sc64win_metasploit() { helpers::setup(); let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); + emu.cfg.maps_folder = helpers::win64_maps_folder(); - let sample = "../../test/sc64win_metasploit.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("sc64win_metasploit.bin"); + emu.load_code(&sample); //emu.set_verbose(3); emu.run(Some(0x3c00c8)); emu.step(); diff --git a/crates/libmwemu/src/tests/shellcode/sc64win_strgen.rs b/crates/libmwemu/src/tests/shellcode/sc64win_strgen.rs index d1ea8c15..6c6e744b 100644 --- a/crates/libmwemu/src/tests/shellcode/sc64win_strgen.rs +++ b/crates/libmwemu/src/tests/shellcode/sc64win_strgen.rs @@ -8,8 +8,8 @@ pub fn sc64win_strgen() { let mut emu = emu64(); emu.set_verbose(3); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); - emu.load_code("../../test/sc64win_strgen.bin"); + emu.cfg.maps_folder = helpers::win64_maps_folder(); + emu.load_code(&helpers::test_data_path("sc64win_strgen.bin")); emu.run_to(231); let s = emu.maps.read_string(0x329ec8); assert_eq!(s, "http://something.com/"); diff --git a/crates/libmwemu/src/tests/unit/breakpoint_functionality.rs b/crates/libmwemu/src/tests/unit/breakpoint_functionality.rs index bb8fe27f..4b219276 100644 --- a/crates/libmwemu/src/tests/unit/breakpoint_functionality.rs +++ b/crates/libmwemu/src/tests/unit/breakpoint_functionality.rs @@ -39,9 +39,9 @@ pub fn breakpoint_functionality() { assert_eq!(bp.get_bp(), 0); let mut emu = emu64(); - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); + emu.cfg.maps_folder = helpers::win64_maps_folder(); - emu.load_code("../../test/exe64win_msgbox.bin"); + emu.load_code(&helpers::test_data_path("exe64win_msgbox.bin")); assert!(!emu.maps.is_allocated(0)); emu.bp.clear_bp(); emu.bp.add_bp(0x1400011d6); diff --git a/crates/libmwemu/src/tests/unit/emulation_perf.rs b/crates/libmwemu/src/tests/unit/emulation_perf.rs index 9c7b0645..b5a1be00 100644 --- a/crates/libmwemu/src/tests/unit/emulation_perf.rs +++ b/crates/libmwemu/src/tests/unit/emulation_perf.rs @@ -12,7 +12,7 @@ use crate::tests::helpers; use crate::*; -use std::path::PathBuf; + use std::time::{Duration, Instant}; /// Default work units: large enough to average out noise, small enough for CI (~seconds on a fast box). @@ -44,10 +44,10 @@ fn sc32win_donut_emulation_throughput_regression_guard() { .unwrap_or(DEFAULT_MIN_IPS); let mut emu = emu32(); - emu.cfg.maps_folder = "../../maps/windows/x86/".to_string(); + emu.cfg.maps_folder = helpers::win32_maps_folder(); - let sample = "../../test/sc32win_donut.bin"; - emu.load_code(sample); + let sample = helpers::test_data_path("sc32win_donut.bin"); + emu.load_code(&sample); // `-vv` → verbose level 2 (assembly tracing; same cost profile as CLI) emu.set_verbose(2); @@ -95,13 +95,7 @@ fn benchmark32win_donut() { } let mut emu = emu32(); - emu.cfg.maps_folder = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../../maps/windows/x86") - .to_string_lossy() - .into_owned(); - if !emu.cfg.maps_folder.ends_with('/') { - emu.cfg.maps_folder.push('/'); - } + emu.cfg.maps_folder = helpers::win32_maps_folder(); // Avoid any interactive console path from blocking the test runner. emu.cfg.console = false; @@ -157,13 +151,7 @@ fn benchmark64with_enigma() { helpers::setup(); let mut emu = emu64(); - emu.cfg.maps_folder = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../../maps/windows/x86_64") - .to_string_lossy() - .into_owned(); - if !emu.cfg.maps_folder.ends_with('/') { - emu.cfg.maps_folder.push('/'); - } + emu.cfg.maps_folder = helpers::win64_maps_folder(); emu.cfg.console = false; emu.cfg.console_enabled = false; diff --git a/crates/libmwemu/src/tests/unit/should_serialize.rs b/crates/libmwemu/src/tests/unit/should_serialize.rs index ece58dab..35816132 100644 --- a/crates/libmwemu/src/tests/unit/should_serialize.rs +++ b/crates/libmwemu/src/tests/unit/should_serialize.rs @@ -12,10 +12,10 @@ pub fn should_serialize() { let mut emu = emu64(); // load maps - emu.cfg.maps_folder = "../../maps/windows/x86_64/".to_string(); + emu.cfg.maps_folder = helpers::win64_maps_folder(); // load binary - emu.load_code("../../test/exe64win_msgbox.bin"); + emu.load_code(&helpers::test_data_path("exe64win_msgbox.bin")); // set registers emu.regs_mut().rdx = 0x1; diff --git a/crates/pymwemu/Cargo.toml b/crates/pymwemu/Cargo.toml index 12a94f4c..890f42c7 100644 --- a/crates/pymwemu/Cargo.toml +++ b/crates/pymwemu/Cargo.toml @@ -21,6 +21,9 @@ log.workspace = true iced-x86.workspace = true yaxpeax-arm.workspace = true +[build-dependencies] +pyo3-build-config = "0.26.0" + [features] extension-module = ["pyo3/extension-module"] default = ["extension-module"] diff --git a/crates/pymwemu/build.rs b/crates/pymwemu/build.rs new file mode 100644 index 00000000..dace4a9b --- /dev/null +++ b/crates/pymwemu/build.rs @@ -0,0 +1,3 @@ +fn main() { + pyo3_build_config::add_extension_module_link_args(); +}