Stop Staring at Hex, Start Reading the Story
Most people treat a memory dump like a bad fortune cookie. They crack it open, see a wall of hex, close their eyes, and hope the problem goes away. If you’ve ever fired up objdump or WinDbg and felt your eyes glaze over at the sight of register states and stack traces, you’re not alone. But that output isn’t noise. It’s a crime scene, and you’re the detective. The trick is knowing where to look, what to ignore, and how to piece together the fragments into something that actually tells you why your system just ate itself.

The Anatomy of a Crash
Before you can read a dump, you need to understand what you’re looking at. A memory dump is a raw snapshot of a process’s address space at the moment of failure. Operating systems write this data to disk because the process is dead and can’t defend itself. The dump includes the contents of CPU registers, the stack, the heap, and loaded modules.
The most common trap is treating all of this information as equally important. It isn’t. When you get a core dump on Linux or a minidump on Windows, the first few lines are usually the only ones that matter. They tell you the exception code, the faulting address, and the instruction pointer. Everything else is supporting evidence.
Exception Codes and Signal Numbers
On Windows, an exception code like 0xC0000005 is an access violation. On Linux, signal 11 (SIGSEGV) is the equivalent. These codes are your starting point. They tell you the class of the crime. An access violation means the code tried to read or write memory it shouldn’t have. A stack overflow means it ran out of stack space. Don’t skip this step. Looking at the register state before you know the exception code is like dusting for fingerprints before you know what room the murder happened in.
The Instruction Pointer: Your Prime Suspect
The instruction pointer (RIP on x64, EIP on x86) tells you exactly where the CPU was when the crash occurred. This is the single most important value in the entire dump. If you have your debug symbols loaded, this translates directly to a function name and line number. If you don’t, you’ll get a raw address, which is harder to read but not impossible to work with. You can still look up which module that address belongs to and narrow down the failure to a specific DLL or shared object.
Walking the Stack
The stack trace is the narrative of your crash. It tells you how the program got to the point of failure. If the instruction pointer is the scene of the accident, the stack trace is the path that led there. Read it from the bottom up. The bottom frames are the entry point—usually main() or a thread procedure. As you move up the stack, you see the chain of function calls that led to the crash.

Look for transitions between modules. If the top three frames are in ntdll.dll or libc.so, and the frame below that is in your code, the crash likely happened in a system call that your code invoked. The system code didn’t fail; your code probably passed it invalid parameters. If the entire stack is inside a third-party library, you’ve found your suspect. If it’s all your code, you have no one to blame but yourself.
Corrupted Stacks and Missing Frames
Sometimes the stack trace is garbage. You’ll see a few valid frames, then a wall of <unknown> or hex addresses that don’t resolve. This usually means stack corruption—something overwrote the saved base pointers on the stack. Buffer overflows are the classic cause. When this happens, you can’t rely on the stack trace alone. You need to inspect the stack memory directly. Look for patterns in the raw memory around the stack pointer. You might find a string, a vtable pointer, or a recognizable structure that hints at what overwrote the stack.
Registers Tell the Tale
The register state at the time of the crash is a snapshot of what the CPU was doing. For an access violation, look at the registers involved in the faulting instruction. If the crash was a read from address 0x0000000000000000, a quick glance at the registers will usually show a null pointer in one of the general-purpose registers like RAX or RCX. You can then trace that register backward through the stack to see where the null value came from.
On x64, the calling convention uses RCX, RDX, R8, and R9 for the first four arguments. If you’re looking at a crash in a function and you want to know what was passed to it, check those registers. The return address is on the stack, but the arguments are in registers for the first four parameters. This is a significant difference from x86, where everything was pushed onto the stack.
Heap and Module Context
Once you’ve exhausted the stack and registers, you can look at the heap and the loaded modules. Heap corruption is a nightmare to debug because the crash usually happens long after the corruption. The dump will show you the state of the heap at the time of the crash, but the code that caused the corruption is already gone. Tools like !heap in WinDbg or mtrace on Linux can help, but they require page heap or guard pages to be enabled before the crash.

The list of loaded modules is useful for versioning issues. If your crash is in graphics.dll, check the version. Maybe a recent update introduced a bug. If you see a module you don’t recognize, it could be injected code—antivirus, a hooking library, or something more malicious. Dumps from user machines often have weird modules loaded, and you need to account for them.
Practical Workflow
Here is a concrete workflow for tackling a new dump file. This is the process that actually gets results, not just stares at hex:
- Identify the exception. Look at the exception code or signal. Is it an access violation, a stack overflow, or something else?
- Find the instruction pointer. Resolve it to a module and function. This is ground zero.
- Walk the stack. Read it bottom-up. Find the transition from your code to the point of failure.
- Inspect registers. For an access violation, find the bad address and the register that held it.
- Check modules. Verify versions and look for unexpected loaded libraries.
- Examine heap only if necessary. This is a last resort. If you’re here, you’re in for a long night.
Following this order prevents you from getting lost. You always start with the most specific information (the exception and instruction pointer) and only broaden your search if the initial clues aren’t enough. For a detailed reference on Windows crash dump analysis using WinDbg, the Microsoft Debugging Tools documentation is a solid resource.
FAQ
What is the difference between a minidump and a full dump?
A minidump contains only the essential data: the thread stacks, the loaded module list, and the CPU registers. A full dump contains the entire address space of the process, including the heap. Minidumps are small and fast to generate, but they’re useless if you need to inspect heap memory. Full dumps can be hundreds of megabytes or larger, but they contain everything. For most crashes, a minidump with stack and register data is sufficient. For heap corruption, you need a full dump.
Do I always need debug symbols to read a dump?
No, but they make the process significantly easier. Without symbols, you’ll see raw memory addresses instead of function names. You can still figure out which module the crash is in and sometimes narrow it down to a specific function by looking at the module’s export table. But full debug symbols (PDB files on Windows, DWARF data on Linux) give you function names, parameter types, and line numbers. Always try to get symbols if you can.
Can I analyze a dump from a different operating system than the one it was generated on?
Cross-platform analysis is generally not supported for native dumps. A Windows minidump requires WinDbg or a compatible Windows debugger. A Linux core dump requires GDB or LLDB on a system with compatible libraries. You can sometimes analyze a Linux core dump on macOS if the architectures align, but you’ll need the original binaries and debug symbols from the target system. The safest approach is to analyze the dump on the same OS it was generated on.