From ed117fffc64b84e6b81a0e61817f68a29087c403 Mon Sep 17 00:00:00 2001 From: Buckminsterfullerene02 Date: Mon, 25 May 2026 16:26:37 +0100 Subject: [PATCH] filter out paths flag, suspend process when dumping --- jmap_dumper/Cargo.toml | 2 +- jmap_dumper/src/lib.rs | 23 +++++++++-- jmap_dumper/src/main.rs | 5 +++ jmap_dumper/src/mem.rs | 88 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 4 deletions(-) diff --git a/jmap_dumper/Cargo.toml b/jmap_dumper/Cargo.toml index faaa1f4..38c244c 100644 --- a/jmap_dumper/Cargo.toml +++ b/jmap_dumper/Cargo.toml @@ -43,7 +43,7 @@ jmap = { version = "0.1.1", path = "../jmap" } usmap = { version = "0.1.1", path = "../usmap" } [target.'cfg(target_os = "windows")'.dependencies] -windows = { version = "0.62.2", features = ["Win32_System_Diagnostics_ToolHelp", "Win32_System_Diagnostics_Debug"] } +windows = { version = "0.62.2", features = ["Win32_System_Diagnostics_ToolHelp", "Win32_System_Diagnostics_Debug", "Win32_System_Threading"] } [target.'cfg(target_os = "linux")'.dependencies] libc = "0.2" diff --git a/jmap_dumper/src/lib.rs b/jmap_dumper/src/lib.rs index e331041..d54b25d 100644 --- a/jmap_dumper/src/lib.rs +++ b/jmap_dumper/src/lib.rs @@ -329,6 +329,8 @@ pub struct DumpOptions { pub names: bool, /// Print struct layouts before dumping pub verbose: bool, + /// Filter out objects whose path contains any of these substrings + pub filter_out_paths: Vec, } pub fn dump( @@ -359,6 +361,8 @@ async fn dump_async( connect(mem, &image, overrides, struct_info, options.verbose).await? } }; + #[cfg(any(target_os = "windows", target_os = "linux"))] + let _suspend_guard = mem::ProcessSuspendGuard::suspend(pid)?; dump_inner(ctx, &source_name, options).await } Input::Dump(path) => { @@ -733,8 +737,18 @@ async fn dump_one( let path = obj.path().await?; + let filtered = options.filter_out_paths.iter().any(|f| path.contains(f.as_str())); + if options.verbose { - eprintln!("[{i}/{num}] {path}"); + if filtered { + eprintln!("[{i}/{num}] [filtered] {path}"); + } else { + eprintln!("[{i}/{num}] {path}"); + } + } + + if filtered { + return Ok(None); } Ok(read_object_type(obj, &path, options) @@ -810,11 +824,14 @@ pub async fn read_object_type( path: &str, options: &DumpOptions, ) -> Result> { - let class = obj.class_private().read().await?; - if !options.all && !path.starts_with("/Script/") { return Ok(None); } + if options.filter_out_paths.iter().any(|f| path.contains(f.as_str())) { + return Ok(None); + } + + let class = obj.class_private().read().await?; let object_flags = obj.object_flags().read().await?; let is_basic_object = object_flags.contains(EObjectFlags::RF_ArchetypeObject) || object_flags.contains(EObjectFlags::RF_ClassDefaultObject); diff --git a/jmap_dumper/src/main.rs b/jmap_dumper/src/main.rs index 873dd76..4d7944f 100644 --- a/jmap_dumper/src/main.rs +++ b/jmap_dumper/src/main.rs @@ -69,6 +69,10 @@ struct Cli { #[arg(long)] all: bool, + /// Filter out objects whose path contains the given substring (can be specified multiple times) + #[arg(long, value_name = "PATH_PREFIX")] + filter_out_paths: Vec, + /// Dump FName table #[arg(long)] names: bool, @@ -127,6 +131,7 @@ fn main() -> Result<()> { all: cli.all, names: cli.names, verbose: cli.verbose, + filter_out_paths: cli.filter_out_paths, }; let overrides = ConfigOverrides { diff --git a/jmap_dumper/src/mem.rs b/jmap_dumper/src/mem.rs index 92af051..7db68c3 100644 --- a/jmap_dumper/src/mem.rs +++ b/jmap_dumper/src/mem.rs @@ -583,3 +583,91 @@ impl Ptr>> { }) } } + +#[cfg(target_os = "windows")] +pub struct ProcessSuspendGuard { + thread_handles: Vec, +} + +#[cfg(target_os = "windows")] +impl ProcessSuspendGuard { + pub fn suspend(pid: i32) -> Result { + use windows::Win32::Foundation::{CloseHandle, HANDLE}; + use windows::Win32::System::Diagnostics::ToolHelp::{ + CreateToolhelp32Snapshot, Thread32First, Thread32Next, TH32CS_SNAPTHREAD, THREADENTRY32, + }; + use windows::Win32::System::Threading::{OpenThread, SuspendThread, THREAD_SUSPEND_RESUME}; + + let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)? }; + + let mut thread_handles: Vec = Vec::new(); + let mut entry = THREADENTRY32 { + dwSize: std::mem::size_of::() as u32, + ..Default::default() + }; + + unsafe { + if Thread32First(snapshot, &mut entry).is_ok() { + loop { + if entry.th32OwnerProcessID == pid as u32 { + if let Ok(handle) = + OpenThread(THREAD_SUSPEND_RESUME, false, entry.th32ThreadID) + { + SuspendThread(handle); + thread_handles.push(handle); + } + } + entry.dwSize = std::mem::size_of::() as u32; + if Thread32Next(snapshot, &mut entry).is_err() { + break; + } + } + } + let _ = CloseHandle(snapshot); + } + + eprintln!( + "Suspended {} thread(s) of pid {}", + thread_handles.len(), + pid + ); + Ok(Self { thread_handles }) + } +} + +#[cfg(target_os = "windows")] +impl Drop for ProcessSuspendGuard { + fn drop(&mut self) { + use windows::Win32::Foundation::CloseHandle; + use windows::Win32::System::Threading::ResumeThread; + for &handle in &self.thread_handles { + unsafe { + ResumeThread(handle); + let _ = CloseHandle(handle); + } + } + eprintln!("Resumed {} thread(s)", self.thread_handles.len()); + } +} + +#[cfg(target_os = "linux")] +pub struct ProcessSuspendGuard { + pid: i32, +} + +#[cfg(target_os = "linux")] +impl ProcessSuspendGuard { + pub fn suspend(pid: i32) -> Result { + unsafe { libc::kill(pid, libc::SIGSTOP) }; + eprintln!("Suspended pid {pid}"); + Ok(Self { pid }) + } +} + +#[cfg(target_os = "linux")] +impl Drop for ProcessSuspendGuard { + fn drop(&mut self) { + unsafe { libc::kill(self.pid, libc::SIGCONT) }; + eprintln!("Resumed pid {}", self.pid); + } +}