Crash Dumps as Process Snapshots
A small dump preserves selected threads, modules, registers, and stacks for later inspection without a running process.
Cheatsheet for Lesson 14.3
A one-page summary of “Crash Dumps as Process Snapshots” you can keep beside your notes. Plain Markdown.
Lesson 14.3 — Crash Dumps as Process Snapshots: cheatsheet
- Remember: A dump preserves selected state at capture time; it needs logs or tracing to explain the earlier sequence of events.
- Also useful: Read stack frames as nested unfinished requests: the current work is on top, with the callers that led there underneath.
Key terms
dump_type: In the source, that choice is thedump_typevalue.
Review
- Q: What is a minidump? A: A selected snapshot of process state, such as threads, modules, and registers, captured at one moment
- Q: A dump from a crashed game shows the failing thread. A teammate asks what happened ten seconds earlier. What can the dump tell you? A: Little, because it records one instant; pair it with logs or an ETW trace when history matters
A dump is a saved debugging snapshot
Section titled “A dump is a saved debugging snapshot”The live tools in this chapter reported different parts of one process: modules, page regions, threads, and API results. The kernel lessons explained why those observations pass through controlled services. A dump brings selected process data together at one capture moment, so a later investigation can ask which module and thread were involved even after the process is gone.
A live debugger reads a running process. A dump file saves selected process state so it can be inspected later. A minidump may include:
- basic process and operating-system information;
- loaded and recently unloaded modules;
- thread records and selected thread context;
- stack memory needed to reconstruct calls;
- exception information when captured during a crash.
The word “mini” describes a selectable subset, not a harmless text log. Dumps can contain file paths, arguments, stack data, and other private values. Treat them like diagnostic data, not screenshots.
The dump type decides which optional streams are saved. That is why a missing heap object or exception record may mean “not captured,” not “never existed.”
1 One chosen instant, saved
Timeline
Timeearlier historycapture pointlaterSaved in the dump
saved from that instant
RecordsmodulesthreadsstacksSomething chooses one capture point; the dump keeps module, thread and stack records as they were then.
2 Earlier history is not in the dump
Timeline
Timeearlier history: not savedcapture pointlaterNeeds another source
Where history liveslogstracingHow the state came about needs logs or tracing, which a dump does not contain.
3 A missing stream may simply not have been captured
Saved in the dump
RecordsmodulesthreadsstacksNot captureda stream that was not requestedIf a dump lacks something, it may not have been asked for, which is not proof the thing did not exist.
The dump holds the state at its capture point. It does not hold how the program got there.
Why dumps help reverse engineering
Section titled “Why dumps help reverse engineering”A reproducible crash often disappears when you attach a debugger and change timing. A dump saves the process state close to the failure without a debugger attached, and from that file alone you can answer:
- Which thread failed?
- Which module contained the instruction pointer?
- What calls led to that point?
- Which game and DLL versions were loaded?
- Did the crash occur inside the course patch, the game, or a system DLL?
A dump does not record every earlier event. Combine it with logs or an ETW trace when the history matters.
Scope this lab to the current process
Section titled “Scope this lab to the current process”MiniDumpWriteDump can accept a process handle and PID. This course program deliberately uses GetCurrentProcess and GetCurrentProcessId, so it can dump only itself through this code path. It does not search for a game PID or request another process handle.
It also avoids MiniDumpWithFullMemory. A normal dump plus thread and unloaded-module information is enough to learn the workflow without copying every private page in the process.
In the source, that choice is the dump_type value. Each optional stream has its own bit, and | sets several bits at once:
dump_type in binary. MiniDumpWithThreadInfo is 0x1000 = 4,096 = 2^12, so it is bit 12. MiniDumpWithUnloadedModules is 0x20 = 32 = 2^5, bit 5. MiniDumpNormal is 0: it sets no bit and names the baseline dump. Together they make 0x1020 = 4,096 + 32 = 4,128. MiniDumpWithFullMemory would be 0x2, bit 1, and this lab leaves it clear.Complete lab source: self_minidump.rs
self_minidump.rs
Show the codeHide the code
#[cfg(windows)]
fn main() -> anyhow::Result<()> {
use std::{fs::OpenOptions, os::windows::io::AsRawHandle, path::PathBuf};
use anyhow::{Context, ensure};
use windows::Win32::{
Foundation::HANDLE,
System::{
Diagnostics::Debug::{
MiniDumpNormal, MiniDumpWithThreadInfo, MiniDumpWithUnloadedModules,
MiniDumpWriteDump,
},
Threading::{GetCurrentProcess, GetCurrentProcessId},
},
};
let output = std::env::args_os()
.nth(1)
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("gha-self.dmp"));
ensure!(
!output.exists(),
"refusing to overwrite {}",
output.display()
);
let file = OpenOptions::new()
.write(true)
.create_new(true)
.open(&output)
.with_context(|| format!("could not create {}", output.display()))?;
let file_handle = HANDLE(file.as_raw_handle());
// SAFETY: these calls take no pointers and return identifiers for this process.
let (process, process_id) = unsafe { (GetCurrentProcess(), GetCurrentProcessId()) };
let dump_type = MiniDumpNormal | MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules;
// SAFETY: both handles belong to this process, the file stays open during
// the call, and no optional exception/user/callback pointer is supplied.
unsafe {
MiniDumpWriteDump(
process,
process_id,
file_handle,
dump_type,
None,
None,
None,
)?;
}
drop(file);
println!("Wrote {}", output.display());
println!("The dump contains this lab process, not another process.");
println!("Open it with: windbgx -z {}", output.display());
Ok(())
}
#[cfg(not(windows))]
fn main() {
eprintln!("This self-dump lab must run on Windows.");
}#[cfg(windows)]fn main() -> anyhow::Result<()> { use std::{ fs::OpenOptions, os::windows::io::AsRawHandle, path::PathBuf, }; use anyhow::{Context, ensure}; use windows::Win32::{ Foundation::HANDLE, System::{ Diagnostics::Debug::{ MiniDumpNormal, MiniDumpWithThreadInfo, MiniDumpWithUnloadedModules, MiniDumpWriteDump, }, Threading::{GetCurrentProcess, GetCurrentProcessId}, }, };
let output = std::env::args_os() .nth(1) .map(PathBuf::from) .unwrap_or_else(|| PathBuf::from("gha-self.dmp")); ensure!( !output.exists(), "refusing to overwrite {}", output.display() ); let file = OpenOptions::new() .write(true) .create_new(true) .open(&output) .with_context(|| format!("could not create {}", output.display()))?; let file_handle = HANDLE(file.as_raw_handle());
// SAFETY: these calls take no pointers and return identifiers for this process. let (process, process_id) = unsafe { (GetCurrentProcess(), GetCurrentProcessId()) }; let dump_type = MiniDumpNormal | MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules; // SAFETY: both handles belong to this process, the file stays open during // the call, and no optional exception/user/callback pointer is supplied. unsafe { MiniDumpWriteDump( process, process_id, file_handle, dump_type, None, None, None, )?; } drop(file);
println!("Wrote {}", output.display()); println!("The dump contains this lab process, not another process."); println!("Open it with: windbgx -z {}", output.display()); Ok(())}
#[cfg(not(windows))]fn main() { eprintln!("This self-dump lab must run on Windows.");}Translate the raw file handle carefully
Section titled “Translate the raw file handle carefully”File owns a Windows file handle. AsRawHandle borrows the numeric handle so the Windows API can use it. The File must stay alive until MiniDumpWriteDump returns.
That is why drop(file) appears after the Windows call. Closing it first would leave file_handle as a stale number.
Build and capture
Section titled “Build and capture”The first command builds the small dumper in the Windows VM. The second gives
it an output path; create_new(true) makes a repeat run fail safely rather
than replace the first capture. Open the resulting file in the next step.
cd windows-labscargo build --release --target i686-pc-windows-msvc ` --bin self_minidump
.\target\i686-pc-windows-msvc\release\self_minidump.exe ` .\gha-self-first.dmpThe program should print the saved path. Its complete buildable source is self_minidump.rs.
Open the dump in WinDbg
Section titled “Open the dump in WinDbg”Install WinDbg from Microsoft, then open the file from the UI or run:
windbgx -z .\gha-self-first.dmpThese first commands answer different questions:
!analyze -v summarize an exception if the dump contains onelm list loaded modules and their address ranges~ list threads~* k show a stack trace for every threadr show registers for the selected threadThe self-dump is captured during a normal function call, so it does not contain a crash exception. That is useful: you can learn module, thread, register, and stack navigation without deliberately causing undefined behavior.
Read a stack from bottom to top
Section titled “Read a stack from bottom to top”A stack trace is a chain of function frames. The top frame is where the selected thread was when captured. Frames below it are callers that led there.
k listing for the thread that wrote the self-dump, newest call on top. Read from the bottom up, it replays the calls in the order they happened: Windows starts the thread, the C and Rust runtimes start up, main runs, and main calls MiniDumpWriteDump. A real listing splits each box into several frames.Names may be missing when matching symbols are unavailable. In that case, WinDbg may show a module plus an offset:
self_minidump+0x1234The offset is still useful when paired with the exact build hash and module base: it names the byte 0x1234 past the start of self_minidump.exe as loaded, and in one exact build that is one specific instruction.
Create a controlled crash exercise safely
Section titled “Create a controlled crash exercise safely”Do not corrupt a running game’s memory just to obtain a dump. Instead, make a tiny separate test program that crashes on purpose, enable Windows Error Reporting local dumps for that one executable in a disposable VM, and inspect the result.
Not every Rust failure is a crash that Windows Error Reporting can see. Returning an error from main prints it and exits with code 1. A panic under Rust’s default unwinding strategy is caught by the runtime, which prints the message and exits with code 101. Both are ordinary exits, so there is nothing to dump. A panic in a program built with panic = "abort" is different: it ends in a fail-fast exception, code 0xC0000409, which Windows Error Reporting does capture, and !analyze then reports that exception rather than an access violation.
Dump limitations
Section titled “Dump limitations”- A small dump may omit heap data needed to inspect a particular object.
- A dump records one instant, not the earlier event timeline.
- Unresolved symbols make stacks harder to name.
- Optimized code may inline functions or keep values only in registers.
- A module path and timestamp help, but a trusted hash is stronger build identity.
Start small. Capture more data only when a specific debugging question requires it, and keep diagnostic files with the exact build record and test logs they describe.
Chapter 12 uses that same build and module evidence to ask which DLL actually loaded, which API contract was resolved, and where a process-level view ends. It then follows requests into device drivers and distinguishes process virtual addresses from the physical-memory mappings underneath them.
References: MiniDumpWriteDump, Minidump files, and WinDbg overview.
A snapshot captures a calculation at one moment. This step-by-step trace needs JavaScript. Here is the code and where the run ends.
memory = 75; register = 80
choose the capture moment
if after store: memory = register
copy selected memory and register state
save this captured state
explain what this snapshot cannot establishResult: dump memory=75, register=80. A memory-only view would omit the temporary result; this snapshot alone cannot reconstruct the preceding event history.
Final values: moment = before; live_memory = 75; live_register = 80; saved_memory = 75; saved_register = 80; saved = true.
Read every step of the lesson’s example
Step 1, line 1. Start with the selected inputs. Follow the highlighted operation and the changing state below.
moment = before.
Step 2, line 1. The calculation’s temporary result has not yet been stored.
moment = before; live_memory = 75; live_register = 80.
Step 3, line 2. Capture at the selected boundary.
moment = before; live_memory = 75; live_register = 80.
Step 4, line 3. The store has not executed when capture begins.
moment = before; live_memory = 75; live_register = 80.
Step 5, line 4. Copy the selected state into the diagnostic file.
moment = before; live_memory = 75; live_register = 80; saved_memory = 75; saved_register = 80.
Step 6, line 5. The saved values remain available after this model process exits.
moment = before; live_memory = 75; live_register = 80; saved_memory = 75; saved_register = 80; saved = true.
Step 7, line 6. Result: dump memory=75, register=80. A memory-only view would omit the temporary result; this snapshot alone cannot reconstruct the preceding event history.
moment = before; live_memory = 75; live_register = 80; saved_memory = 75; saved_register = 80; saved = true.
Interpret a crash dump
What is a minidump?
Dump contents depend on capture flags. Threads, modules, contexts, and chosen memory ranges describe the captured instant, not future execution.
Finished Lesson 14.3?
Mark it done and the lesson list shows a beside it. A chapter whose lessons are all done is marked done too. Progress is saved in this browser only.
Plain Markdown, saved in this browser.
This book grew out of the original Game Hacking Academy by attilathedud and has been rewritten as a beginner-first course. Like the original repository, its source is published under the Apache License 2.0.