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/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/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/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/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/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/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 90fb9eaf..b68696ec 100644 --- a/crates/libmwemu/src/serialization/emu.rs +++ b/crates/libmwemu/src/serialization/emu.rs @@ -17,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; @@ -28,7 +29,46 @@ 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; + +#[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: u64, + 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 { @@ -46,8 +86,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, @@ -77,23 +116,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 @@ -122,6 +145,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(), @@ -133,8 +217,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 @@ -157,23 +240,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, @@ -195,99 +262,288 @@ impl<'a> From<&'a Emu> for SerializableEmu { impl From for Emu { fn from(serialized: SerializableEmu) -> Self { - 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 }; - Emu { + 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(), - current_thread_id: serialized.current_thread_id, - main_thread_cont: serialized.main_thread_cont, - gateway_return: serialized.gateway_return, + threads: threads.into_iter().map(|t| t.into()).collect(), + current_thread_id, + 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) { + 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 emu.cfg.arch.is_64bits() { + emu.maps.is_64bits = true; } + + emu } } impl Default for SerializableEmu { fn default() -> Self { - SerializableEmu::from(&Emu::new()) + SerializableEmu::from(&Emu::new(crate::arch::Arch::X86)) } } 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) { @@ -301,4 +557,31 @@ 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(), + }; + emu.init_cpu(); + SerializableEmu::from(&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..b9cfb92c --- /dev/null +++ b/crates/libmwemu/src/serialization/minidump/context.rs @@ -0,0 +1,232 @@ +use std::io::{self, Write}; + +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(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, + "unexpected architecture for thread context", + )), + } +} + +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 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)?; + 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/reader.rs b/crates/libmwemu/src/serialization/minidump/reader.rs new file mode 100644 index 00000000..4833e3d4 --- /dev/null +++ b/crates/libmwemu/src/serialization/minidump/reader.rs @@ -0,0 +1,345 @@ +use ahash::AHashMap; +use minidump::format::MemoryProtection; +use minidump::*; +use slab::Slab; +use std::cell::RefCell; +use std::collections::BTreeMap; +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; +use crate::maps::Maps; +use crate::regs64::Regs64; +use crate::serialization::emu::SerializableEmu; +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 get_pe_offset(data: &[u8]) -> Option { + if data.len() < 0x3C + 4 { + return None; + } + let offset = u32::from_le_bytes([data[0x3C], data[0x3D], data[0x3E], data[0x3F]]) as usize; + if offset < data.len() { + Some(offset) + } else { + None + } + } + + fn is_pe64(data: &[u8], pe_offset: usize) -> bool { + if pe_offset + 24 >= data.len() { + return false; + } + // 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 == 0xAA64 + } + + fn extract_pe_modules>( + dump: &minidump::Minidump<'static, T>, + ) -> Result<(Option, Option), Box> { + let mut pe32 = None; + let mut pe64 = None; + + if let Ok(modules) = dump.get_stream::() { + let memory = dump.get_memory().unwrap_or_default(); + + for module in modules.iter() { + // Try to read the module from memory + if let Some(mem_region) = memory.memory_at_address(module.base_address()) { + let raw_data = mem_region.bytes().to_vec(); + + // Basic PE detection - check for MZ header + if raw_data.len() > 0x40 && &raw_data[0..2] == b"MZ" { + // Read PE header to determine 32 vs 64 bit + if let Some(pe_offset) = Self::get_pe_offset(&raw_data) { + if Self::is_pe64(&raw_data, pe_offset) { + pe64 = Some(SerializablePE64 { + filename: module.name.to_string(), + raw: raw_data, + }); + } else { + pe32 = Some(SerializablePE32 { + filename: module.name.to_string(), + raw: raw_data, + }); + } + } + } + } + } + } + + Ok((pe32, pe64)) + } + + fn extract_memory_maps>( + dump: &minidump::Minidump<'static, T>, + ) -> Result> { + 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(); + + if let Ok(memory_info) = dump.get_stream::() { + for info in memory_info.iter() { + let base_addr = info.raw.base_address; + 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, + x if x == MemoryProtection::PAGE_READONLY.bits() => Permission::READ, + x if x == MemoryProtection::PAGE_READWRITE.bits() => Permission::READ_WRITE, + x if x == MemoryProtection::PAGE_EXECUTE.bits() => Permission::EXECUTE, + x if x == MemoryProtection::PAGE_EXECUTE_READ.bits() => { + Permission::READ_EXECUTE + } + x if x == MemoryProtection::PAGE_EXECUTE_READWRITE.bits() => { + Permission::READ_WRITE_EXECUTE + } + _ => Permission::READ_WRITE_EXECUTE, + }; + + 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 + 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); + } + } + + // Also add mapped modules to name_map + if let Ok(modules) = dump.get_stream::() { + for module in modules.iter() { + let module_base = module.base_address(); + + // Find corresponding memory region that contains this module + for (&addr, &slab_key) in &maps { + if addr <= module_base && module_base < addr + mem_slab[slab_key].size() as u64 + { + name_map.insert(module.name.to_string(), slab_key); + break; + } + } + } + } + + let system_info = dump.get_stream::()?; + 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()); + + Ok(SerializableMaps::new(Maps::new( + mem_slab, maps, name_map, is_64bits, banzai, tlb, + ))) + } + + fn extract_thread_context>( + dump: &minidump::Minidump<'static, T>, + system_info: &MinidumpSystemInfo, + ) -> Result> { + 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")?; + + 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; + 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); + 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; + 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); + 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::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 }) + } + _ => Err("Unsupported minidump CPU context".into()), + } + } + + 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 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)?; + + // Extract memory maps + let maps = Self::extract_memory_maps(&dump)?; + + // Extract thread context when present. + let extracted = Self::extract_thread_context(&dump, &system_info)?; + + // Build serializable emu with the correct architecture + let mut serializable_emu = SerializableEmu::default_for_arch(arch); + serializable_emu.set_maps(maps); + serializable_emu.set_pe32(pe32); + serializable_emu.set_pe64(pe64); + 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, + 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..0a478b24 --- /dev/null +++ b/crates/libmwemu/src/serialization/minidump/writer.rs @@ -0,0 +1,581 @@ +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, ThreadContextInput}; +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<()> { + 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 (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_rva = next_context_rva; + next_context_rva += context.len() as u32; + let (stack_base, stack_location) = stack_location_for_thread( + sp, + 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( + sp: u64, + regions: &[MemoryRegion<'_>], + memory_locations: &BTreeMap, +) -> (u64, MemoryLocation) { + 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/minidump_converter.rs b/crates/libmwemu/src/serialization/minidump_converter.rs deleted file mode 100644 index 7e04e054..00000000 --- a/crates/libmwemu/src/serialization/minidump_converter.rs +++ /dev/null @@ -1,190 +0,0 @@ -use ahash::AHashMap; -use minidump::format::MemoryProtection; -use minidump::*; -use slab::Slab; -use std::cell::RefCell; -use std::collections::BTreeMap; -use std::error::Error; -use std::ops::Deref; - -use crate::maps::mem64::{Mem64, Permission}; -use crate::maps::tlb::TLB; -use crate::maps::Maps; -use crate::regs64::Regs64; -use crate::serialization::emu::SerializableEmu; -use crate::serialization::maps::SerializableMaps; -use crate::serialization::pe32::SerializablePE32; -use crate::serialization::pe64::SerializablePE64; - -pub struct MinidumpConverter; - -impl MinidumpConverter { - fn get_pe_offset(data: &[u8]) -> Option { - if data.len() < 0x3C + 4 { - return None; - } - let offset = u32::from_le_bytes([data[0x3C], data[0x3D], data[0x3E], data[0x3F]]) as usize; - if offset < data.len() { - Some(offset) - } else { - None - } - } - - fn is_pe64(data: &[u8], pe_offset: usize) -> bool { - if pe_offset + 24 >= data.len() { - return false; - } - // Check machine type in PE header - 0x8664 = x64 - let machine = u16::from_le_bytes([data[pe_offset + 4], data[pe_offset + 5]]); - machine == 0x8664 - } - - fn extract_pe_modules>( - dump: &minidump::Minidump<'static, T>, - ) -> Result<(Option, Option), Box> { - let mut pe32 = None; - let mut pe64 = None; - - if let Ok(modules) = dump.get_stream::() { - let memory = dump.get_memory().unwrap_or_default(); - - for module in modules.iter() { - // Try to read the module from memory - if let Some(mem_region) = memory.memory_at_address(module.base_address()) { - let raw_data = mem_region.bytes().to_vec(); - - // Basic PE detection - check for MZ header - if raw_data.len() > 0x40 && &raw_data[0..2] == b"MZ" { - // Read PE header to determine 32 vs 64 bit - if let Some(pe_offset) = Self::get_pe_offset(&raw_data) { - if Self::is_pe64(&raw_data, pe_offset) { - pe64 = Some(SerializablePE64 { - filename: module.name.to_string(), - raw: raw_data, - }); - } else { - pe32 = Some(SerializablePE32 { - filename: module.name.to_string(), - raw: raw_data, - }); - } - } - } - } - } - } - - Ok((pe32, pe64)) - } - - fn extract_memory_maps>( - dump: &minidump::Minidump<'static, T>, - ) -> Result> { - let mut mem_slab = Slab::new(); - let mut maps = BTreeMap::new(); - let mut name_map = AHashMap::new(); - - // 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; - let permission = match info.protection.bits() & MemoryProtection::ACCESS_MASK.bits() - { - x if x == MemoryProtection::PAGE_NOACCESS.bits() => Permission::NONE, - x if x == MemoryProtection::PAGE_READONLY.bits() => Permission::READ, - x if x == MemoryProtection::PAGE_READWRITE.bits() => Permission::READ_WRITE, - x if x == MemoryProtection::PAGE_EXECUTE.bits() => Permission::EXECUTE, - x if x == MemoryProtection::PAGE_EXECUTE_READ.bits() => { - Permission::READ_EXECUTE - } - x if x == MemoryProtection::PAGE_EXECUTE_READWRITE.bits() => { - Permission::READ_WRITE_EXECUTE - } - _ => 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 mem_entry = Mem64::new( - format!("mem_0x{:016x}", base_addr), // name - base_addr, // base_addr - base_addr + size, // bottom_addr (base + size) - mem_data, // mem data - permission, - ); - - let slab_key = mem_slab.insert(mem_entry); - maps.insert(base_addr, slab_key); - } - } - - // Also add mapped modules to name_map - 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 { - if addr <= module_base && module_base < addr + mem_slab[slab_key].size() as u64 - { - name_map.insert(module.name.to_string(), slab_key); - break; - } - } - } - } - - let system_info = dump.get_stream::()?; - let is_64bits = matches!(system_info.cpu, minidump::system_info::Cpu::X86_64); - - let banzai = false; - let tlb = RefCell::new(TLB::new()); - - Ok(SerializableMaps::new(Maps::new( - mem_slab, maps, name_map, is_64bits, banzai, tlb, - ))) - } - - 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")?; - - // Extract PE modules - let (pe32, pe64) = Self::extract_pe_modules(&dump)?; - - // 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(); - - // Basic serializable emu with minimal data - let mut serializable_emu = SerializableEmu::default(); - serializable_emu.set_maps(maps); - serializable_emu.set_regs(regs); - serializable_emu.set_pe32(pe32); - serializable_emu.set_pe64(pe64); - - Ok(serializable_emu) - } -} 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..ba6d1af7 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: u64, + 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,28 +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 { .. } => { - // TODO: implement aarch64 serialization - panic!("aarch64 thread serialization not yet implemented") - } } } } @@ -82,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/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/serialization_tests.rs b/crates/libmwemu/src/tests/unit/serialization_tests.rs index 9a8db90d..89a15426 100644 --- a/crates/libmwemu/src/tests/unit/serialization_tests.rs +++ b/crates/libmwemu/src/tests/unit/serialization_tests.rs @@ -1,10 +1,51 @@ 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(); - // 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] @@ -44,3 +85,224 @@ 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_roundtrip() { + let temp_dir = TempDir::new("mwemu_minidump_aarch64_rt").unwrap(); + let dump_path = temp_dir.path().join("sample_aarch64.dmp"); + + 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; + + 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] +fn test_aarch64_native_serialization_fixture_roundtrip() { + 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 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); + }) + .unwrap(); + handle.join().unwrap(); +} + +#[test] +fn test_aarch64_minidump_fixture_roundtrip() { + 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/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/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/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); } 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(); +} 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 new file mode 100644 index 00000000..a322999c --- /dev/null +++ b/docs/serialization-roadmap.md @@ -0,0 +1,49 @@ +# Serialization Roadmap + +Current status: + +- 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 + +- [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. +- [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 + +- [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 + +- [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 + +- [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). +- [x] Refactor `Emu::new()` to accept an `Arch` parameter to avoid the two-step init_cpu pattern.