Repository: luser/rust-minidump
Pull request #445 (2022-02-10 10:55:51)

Bump arbitrary from 1.0.3 to 1.1.0
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some time.

Note: if you make any changes to this PR yourself, they will take precedence over the rebase.

---

[//]: # (dependabot-end)

Bumps [arbitrary](https://github.com/rust-fuzz/arbitrary) from 1.0.3 to 1.1.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/rust-fuzz/arbitrary/blob/master/CHANGELOG.md">arbitrary's changelog</a>.</em></p>
<blockquote>
<h2>1.1.0</h2>
<p>Released 2022-02-09.</p>
<h3>Added</h3>
<ul>
<li>
<p>Added the <code>Unstructured::ratio</code> method to generate a boolean that is <code>true</code> at
the given rate.</p>
</li>
<li>
<p>Added the <code>Unstructured::arbitrary_loop</code> method to call a function an
arbitrary number of times.</p>
</li>
</ul>
<hr />
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a href="https://github.com/rust-fuzz/arbitrary/commits">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=arbitrary&package-manager=cargo&previous-version=1.0.3&new-version=1.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 35119d4..800aea1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -19,9 +19,9 @@ dependencies = [
 
 [[package]]
 name = "arbitrary"
-version = "1.0.3"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "510c76ecefdceada737ea728f4f9a84bd2e1ef29f1ba555e560940fe279954de"
+checksum = "c38b6b6b79f671c25e1a3e785b7b82d7562ffc9cd3efdc98627e5668a2472490"
 dependencies = [
  "derive_arbitrary",
 ]
@@ -632,9 +632,9 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
 
 [[package]]
 name = "memmap2"
-version = "0.5.2"
+version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe3179b85e1fd8b14447cbebadb75e45a1002f541b925f0bfec366d56a81c56d"
+checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f"
 dependencies = [
  "libc",
 ]
@@ -710,6 +710,7 @@ dependencies = [
  "insta",
  "log",
  "minidump",
+ "minidump-common",
  "minidump-processor",
  "openssl",
  "simplelog",
diff --git a/README.md b/README.md
index c4ffbe4..304f768 100644
--- a/README.md
+++ b/README.md
@@ -220,19 +220,9 @@ Provides a simple interface for mocking minidumps for unit tests.
 A CLI frontend for `minidump-processor`, providing both machine-readable and human-readable
 digests of a minidump with backtraces and symbolication.
 
-See the [README](minidump-stackwalk/README.md) for details.
-
-
-
+Also includes the functionality of the old minidump_dump tool (see the --dump flag).
 
-## [minidump-dump](minidump/src/bin/minidump_dump.rs)
-
-(currently private to this project, may be removed in favour of just making it
-another possible output type of minidump-stackwalk.)
-
-A CLI dumper of the minidump's raw contents.
-
-Although minidump-stackwalk is generally much better and more useful, minidump-dump can help debug strange minidumps, as it more explicitly exposes raw values and the stream directory's layout. This is *mostly* only really useful for developing rust-minidump itself.
+See the [README](minidump-stackwalk/README.md) for details.
 
 
 
diff --git a/RELEASES.md b/RELEASES.md
index a648fe5..640ab8e 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,19 +1,112 @@
-# Pending Release (TBD)
+# Pending Release (TBD -- 0.10.0?)
 
 Commit: TBD
 
-Performance and quality of implementation work.
+This release is a mix of substantial quality improvements, one major breaking change (making some things async), and several smaller changes to APIs. It's a bit of a big release because some major experimentation was going on and we didn't want to release something that we might immediately revert.
+
+
+
+## CURRENT BLOCKERS FOR THIS RELEASE
+
+
+### How unloaded modules are handled in JSON output 
+
+A change to how we handle unloaded modules was introduced in #348, reimplementing a hack in tools that predate rust-minidump. But #367 wants to introduce a more complete and prinicipled design. Cutting the release before resolving #367 would be a forward-compat hazard.
+
+
+
+### Do we really want to be async?
+
+The async branch (#329) was landed to just force us to try it out and see what it's like, but we may still conclude it's a bad idea and revert it. Best not to cut a release until it has had some time to bake.
+
+
+
+### debugid/codeid churn
+
+Many changes have been made to how we handle codeids/debugids, and some are still in the process of review, it would be good to let those patches all land and bake before cutting a release and "comitting" to those changes.
+
+
+### audit error types
+
+In #356, error strings now include the "type" of the error, but this is done slopily with Debug, so if the error types have payloads those may get dumped into the error message, which is probably undesirable. We should probably be more principled before cutting a release.
+
+
+
+
+
+## **Major Breaking Change: Symbolication Is Now `async`(!)**
+
+Making rust-minidump async is in some sense pointless, because it's a single-threaded design that is architected to scale by deploying multiple *processes*. The primary bottleneck on minidump processing is loading and parsing *symbol files*, which rust-minidump already maintains a system-global temporary cache for. This cache is designed specifically for the multi-process workflow.
+
+The motivation for introducing `async` is more of an interoperation concern. For instance, compiling to wasm generally requires I/O to be converted to `async`. Users of `minidump-stackwalk` should be unaffected.
+
+Because symbolication is core functionality for `minidump-processor`, this infects its entire API and means anyone using it will need to run in an async executor. If this proves to be too unpleasant to our users, we may look into making this async-ness configurable with a feature flag (but that would be a lot of work and have a very nasty maintenance burden, so that option isn't to be taken lightly).
+
+---
+
+If you are building an application, making it work with `async` may be as simple as adding the following to your Cargo.toml:
+
+```
+tokio =  { version = "*", features = ["full"] }
+```
+
+changing `main` to the following:
+
+```
+#[tokio::main]
+async fn main() {
+    ...
+```
+
+and adding `.await` to the end of your `process_minidump` call.
+
+---
+
+If you are building a library, the upgrade story is more complicated: you can either expose the `async`-ness in your own APIs, or try to hide it with APIs like `block_on`. Alternatively, you can depend on the `minidump-stackwalk` binary which behaves the same as it did before.
+
+
+
+
+## Major Performance Improvements!
 
 For minidump-stackwalk workloads that make use of large (200MB+) symbol files (e.g. Firefox), 
 peak memory usage has been reduced by about 50%, and runtime decreased by up to 10%!
 Memory usage wins are consistent whether loading from network or disk. Runtime numbers
 depend heavily on how much of a bottleneck symbol file I/O is.
 
-And as always, more docs and tests! :D
 
 
 
-Changes:
+
+## Major Reliability Improvements! (Fuzzing!)
+
+Thanks to @5225225, rust-minidump has had a ton of fuzzing infrastructure added. The fuzzers found many subtle bugs in the code *and* @5225225 fixed most of them too! Thank you so much!! 😭
+
+To the best of our knowledge, none of the bugs found were security issues more serious than: 
+
+* denial of service through long loops.
+* denial of service through large allocations. 
+* denial of service through crashes (tripping safe assertions).
+* heuristic analyses producing worse results.
+* validation steps accidentally discarding valid values.
+
+Thanks to their work, rust-minidump is significantly more robust to "absurd" inputs that could result from either a malicious attacker or random memory corruption (which a crashreporting tool is of course obligated to deal with).
+
+The primary strategy for taming "denial of service" inputs is to realize that although *in principle* a minidump can specify enormous amounts of work to do or enormous amounts of memory to allocate, a *well-formed* input will be linearly bounded by the size of the minidump itself. **This allows the user of rust-minidump to limit resource usage by setting file-size limits on the inputs they will accept.**
+
+For instance, if a minidump reports "I have a list of 10 billion threads" but the minidump itself is only 2MB, we can reject this list length based on our knowledge of how large an entry is in that list (either by rejecting the stream entirely, or by truncating to the maximum possible value for the minidump's size).
+
+Similarly, our stackwalkers have strict "forward progress of the stack pointer" requirements. Although *in principle* the "language" of stackwalking can tell us to go backwards or loop infinitely in place, we terminate stackwalking whenever this happens. CFI (call frame information) evaluation is similarly bounded by supporting no control flow mechanisms, guaranteeing linear forward progress.
+
+Hardening rust-minidump in this manner is an ongoing project.
+
+(Note however that debuginfo is orders of magnitude larger than a minidump (~2MB vs ~200MB), **so it's still quite easy to DOS a rust-minidump instance by just having a stackwalk traverse through a ton of different modules**, necessitating an enormous amount of debuginfo to be downloaded, loaded into memory, and parsed -- if a symbol server is made available to rust-minidump. There is no obvious solution to this at the moment.)
+
+
+
+
+
+Detailed Changes:
 
 
 # minidump
@@ -26,6 +119,7 @@ Changes:
 * **BREAKING CHANGE**: `UnloadedModules::by_addr` now does not deduplicate or discard overlaps (as is correct)
     * This still needs some reworking, so precise order of duplicates is unspecfied.
 
+
 ## Crash Address Fix
 
 get_crash_address will now mask out the high bits of the return value on 32-bit platforms.
@@ -33,11 +127,46 @@ We were seeing some minidumps where the pointer (which is always stored in a 64-
 was incorrectly sign-extended. Now you will always get a value that only ever has the low 32 bits set.
 
 
+## LinuxMaps Now Properly Implements Address Queries
+
+The initial implementation had a reversed comparison, so valid memory ranges were discarded, and invalid ranges accepted (tripping an assertion later in the code). This has been fixed and better tests to cover this were added.
+
+This didn't affect minidump-processor/minidump-stackwalk because we don't currently have analyses based on MemoryInfo or LinuxMaps (which tell you things like "what memory was executable?").
+
+
+## Better Codeid and Debugid handling
+
+Codeids/Debugids are the "primary keys" for looking up binaries and symbols for different modules. There is a fair amount of complexity in handling this because over the years microsoft has introduced new versions of the format, and breakpad has hacked in variants for other platforms.
+
+We now use the debugid crate and strongly type these ids to better handle the various corner cases.
+As a result, we should more reliably handle the various types of id for each platform, especially for MacOS.
+
+
+## Input Validation Hardening
+
+As discussed in the top-level notes, allocations are generally bounded by the size of the minidump now, so a rogue list length shouldn't cause you to instantly OOM and die. Allocations and runtime should generally be linearly bounded by the size of the minidump. (But minidump-processor can be made to do a lot more work than this when given a symbol server.)
 
 
 
 # minidump-stackwalk/minidump-processor
 
+## minidump_dump is now minidump-stackwalk --dump
+
+The old minidump_dump binary that was hidden away in the `minidump` crate has
+had its functionality merged into minidump-stackwalk with a new output format,
+--dump. This simplifies many workflows, because more detailed investigation isn't
+"oh so there's this other tool you need...".
+
+This provides a more "raw" dump of the minidump's contents, which is useful for
+debugging unexpected or strange results. This is a fairly separate
+path that doesn't really use minidump-processor at all, so many flags of
+minidump-stackwalk are useless in this mode.
+
+Technically a **BREAKING CHANGE** because `cargo install minidump` would actually
+install minidump_dump but this was more of an accident than an intentional
+feature of the crate.
+
+
 ## Unloaded Modules
 
 * `process_state::StackFrame` now includes an `unloaded_modules` field that contains all
@@ -53,18 +182,39 @@ was incorrectly sign-extended. Now you will always get a value that only ever ha
     get things done for now.
 
 
+## Better Stackwalking
+
+* We now handle noreturn callees properly in the stack scanning heuristics
+    * When you call a noreturn function you are not obligated to have any code following the CALL instruction, so the implicitly pushed return pointer may be "one-past-the-end" of the function's code. Subtracting 1 from the return pointer is sufficient to handle this.
+* We no longer \~infinite loop when the stackpointer doesn't change on ARM64
+    * Same issue we had on ARM but we forgot to port the fix to the ARM64 backend
+* Many fixed overflows that were \~benign but would assert and crash debug builds
+
+
+## More information in ProcessState
+
+* **BREAKING CHANGE**:`SystemInfo::os_version` has been split into two fields.
+    * `os_version`: "5.1.2600"
+    * `os_build`: "Service Pack 2"
+    * system_info.os_ver in the JSON schema still contains the combined value.
+    * The combined value can be obtained with `SystemInfo::format_os_version`
+
+* Threads (CallStacks) now include their original thread_id
+
+
 ## Better Errors
 
-When minidump-stackwalk encounters a fatal error (basically only happens for empty
-or Effectively Empty minidumps), it will include an error type before the message.
+* More detailed formatting of OS-specific errors 
+    * macOS EXC_GUARD
+    * macOS KERN_FAILURE
+    * Better handling of Linux si_code values
 
-This can be useful for aggregating and correlating failures, and also gives
-you a starting point if you want to check the docs/code for that error type.
+* Errors now use `thiserror` instead of `failure`, making the interop with std better.
 
-A few error types may still include payloads (via debug formatting), this should
-probably be fixed...
+* Fatal error messages are now prefixed with the "type" of error.
+    * This can be useful for aggregating and correlating failures, and also gives you a starting point if you want to check the docs/code for that error type.
 
-Examples:
+Examples of new fatal errors:
 
 ```text
 [ERROR] HeaderMismatch - Error reading dump: Header mismatch
@@ -100,6 +250,22 @@ runtime. I/O time should be about the same, but CPU time should be reduced.
 
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
 
 
diff --git a/minidump-common/src/format.rs b/minidump-common/src/format.rs
index 06485ea..c10a382 100644
--- a/minidump-common/src/format.rs
+++ b/minidump-common/src/format.rs
@@ -476,7 +476,7 @@ impl<'a> scroll::ctx::TryFromCtx<'a, Endian> for CV_INFO_PDB70 {
 /// assert_eq!("0000000A000B000C0102030405060708", format!("{:#}", guid));
 /// ```
 ///
-/// [msdn]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa373931(v=vs.85).aspx
+/// [msdn]: https://docs.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Pread, SizeWith)]
 pub struct GUID {
     pub data1: u32,
diff --git a/minidump-processor/Cargo.toml b/minidump-processor/Cargo.toml
index 42efaeb..d3a9c61 100644
--- a/minidump-processor/Cargo.toml
+++ b/minidump-processor/Cargo.toml
@@ -25,7 +25,7 @@ breakpad-symbols = { version = "0.9.6", path = "../breakpad-symbols", optional =
 clap = "2.34"
 debugid = "0.7"
 log = "0.4"
-memmap2 = "0.5.2"
+memmap2 = "0.5.3"
 minidump = { version = "0.9.6", path = "../minidump" }
 scroll = "0.10.2"
 serde = { version = "1.0", features = ["derive"] }
diff --git a/minidump-processor/fuzz/fuzz_targets/process.rs b/minidump-processor/fuzz/fuzz_targets/process.rs
index 8beda8b..88dcaaa 100644
--- a/minidump-processor/fuzz/fuzz_targets/process.rs
+++ b/minidump-processor/fuzz/fuzz_targets/process.rs
@@ -23,8 +23,12 @@ fuzz_target!(|data: (&[u8], &[u8])| {
 
         let provider = minidump_processor::Symbolizer::new(supplier);
 
-        let _: Result<_, _> = minidump_processor_fuzz::fuzzing_block_on(
+        let val: Result<_, _> = minidump_processor_fuzz::fuzzing_block_on(
             minidump_processor::process_minidump(&dump, &provider),
         );
+
+        if let Ok(v) = val {
+            let _: Result<(), _> = v.print_json(&mut std::io::sink(), true);
+        }
     }
 });
diff --git a/minidump-processor/src/process_state.rs b/minidump-processor/src/process_state.rs
index 76b63bc..2ed83d8 100644
--- a/minidump-processor/src/process_state.rs
+++ b/minidump-processor/src/process_state.rs
@@ -803,26 +803,30 @@ Unknown streams encountered:
             // of a normal "threads" entry, while the original schema strips
             // many of the fields here. We don't to keep things more uniform.
 
-            let registers = json_registers(&self.threads[requesting_thread].frames[0].context);
-
-            // Yuck, spidering through json...
-            let mut thread =
-                output.get_mut("threads").unwrap().as_array().unwrap()[requesting_thread].clone();
-            let thread_obj = thread.as_object_mut().unwrap();
-            let frames = thread_obj
-                .get_mut("frames")
-                .unwrap()
-                .as_array_mut()
-                .unwrap();
-            let frame = frames[0].as_object_mut().unwrap();
-
-            frame.insert(String::from("registers"), registers);
-            thread_obj.insert(String::from("threads_index"), json!(requesting_thread));
-
-            output
-                .as_object_mut()
-                .unwrap()
-                .insert(String::from("crashing_thread"), thread);
+            // We can't do any of this work if we don't have at least one frame.
+            if let Some(f) = self.threads[requesting_thread].frames.get(0) {
+                let registers = json_registers(&f.context);
+
+                // Yuck, spidering through json...
+                let mut thread = output.get_mut("threads").unwrap().as_array().unwrap()
+                    [requesting_thread]
+                    .clone();
+                let thread_obj = thread.as_object_mut().unwrap();
+                let frames = thread_obj
+                    .get_mut("frames")
+                    .unwrap()
+                    .as_array_mut()
+                    .unwrap();
+                let frame = frames[0].as_object_mut().unwrap();
+
+                frame.insert(String::from("registers"), registers);
+                thread_obj.insert(String::from("threads_index"), json!(requesting_thread));
+
+                output
+                    .as_object_mut()
+                    .unwrap()
+                    .insert(String::from("crashing_thread"), thread);
+            }
         }
 
         if pretty {
diff --git a/minidump-processor/src/stackwalker/arm.rs b/minidump-processor/src/stackwalker/arm.rs
index 707517e..3f70f36 100644
--- a/minidump-processor/src/stackwalker/arm.rs
+++ b/minidump-processor/src/stackwalker/arm.rs
@@ -24,7 +24,7 @@ const FRAME_POINTER: &str = Registers::FramePointer.name();
 const STACK_POINTER: &str = Registers::StackPointer.name();
 const PROGRAM_COUNTER: &str = Registers::ProgramCounter.name();
 const LINK_REGISTER: &str = Registers::LinkRegister.name();
-const CALLEE_SAVED_REGS: &[&str] = &["r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11"];
+const CALLEE_SAVED_REGS: &[&str] = &["r4", "r5", "r6", "r7", "r8", "r9", "r10", "fp"];
 
 async fn get_caller_by_cfi<P>(
     ctx: &ArmContext,
diff --git a/minidump-processor/src/stackwalker/arm64.rs b/minidump-processor/src/stackwalker/arm64.rs
index a536c70..1e40eb4 100644
--- a/minidump-processor/src/stackwalker/arm64.rs
+++ b/minidump-processor/src/stackwalker/arm64.rs
@@ -25,7 +25,7 @@ const STACK_POINTER: &str = Registers::StackPointer.name();
 const LINK_REGISTER: &str = Registers::LinkRegister.name();
 const PROGRAM_COUNTER: &str = Registers::ProgramCounter.name();
 const CALLEE_SAVED_REGS: &[&str] = &[
-    "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29",
+    "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "fp",
 ];
 
 async fn get_caller_by_cfi<P>(
diff --git a/minidump-processor/src/stackwalker/arm64_old.rs b/minidump-processor/src/stackwalker/arm64_old.rs
index 7fdecc2..1f34f99 100644
--- a/minidump-processor/src/stackwalker/arm64_old.rs
+++ b/minidump-processor/src/stackwalker/arm64_old.rs
@@ -25,7 +25,7 @@ const STACK_POINTER: &str = Registers::StackPointer.name();
 const LINK_REGISTER: &str = Registers::LinkRegister.name();
 const PROGRAM_COUNTER: &str = Registers::ProgramCounter.name();
 const CALLEE_SAVED_REGS: &[&str] = &[
-    "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29",
+    "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "fp",
 ];
 
 async fn get_caller_by_cfi<P>(
diff --git a/minidump-stackwalk/Cargo.toml b/minidump-stackwalk/Cargo.toml
index 9b2a4cd..6d03aff 100644
--- a/minidump-stackwalk/Cargo.toml
+++ b/minidump-stackwalk/Cargo.toml
@@ -18,6 +18,7 @@ travis-ci = { repository = "luser/rust-minidump" }
 clap = { version = "2.34", features=["wrap_help"] }
 log = "0.4"
 minidump = { version = "0.9.6", path = "../minidump" }
+minidump-common = { version = "0.9.6", path = "../minidump-common" }
 minidump-processor = { version = "0.9.6", path = "../minidump-processor" }
 simplelog = "0.11.2"
 tokio =  { version = "1.12.0", features = ["full"] }
diff --git a/minidump-stackwalk/README.md b/minidump-stackwalk/README.md
index dd093e0..8c8e86a 100644
--- a/minidump-stackwalk/README.md
+++ b/minidump-stackwalk/README.md
@@ -23,19 +23,27 @@ Full documentation of the CLI can be found in the "minidump-stackwalk CLI manual
 
 # Output Formats
 
+Quick Reference:
+
+* `--human` (default) - human friendly output, modified by `--brief`
+* `--json` - machine friendly output, modified by `--pretty`
+* `--cyborg=some/file/for/machine/output.json` - both human and machine!
+* `--dump` - "raw" output of the minidump's contents (for debugging)
+
 minidump-stackwalk defaults to outputting human-readable reports because this is a nicer default for casual use, but the machine-readable output is considered the "main" output format.
 
-If you pass the --json flag you will get machine-readable (JSON) output. As of version 0.9.6 this format *should* be stable, [and has a fully documented schema](https://github.com/luser/rust-minidump/blob/master/minidump-processor/json-schema.md)! (--pretty will make this output easier for a human to read.)
+If you pass **the --json flag** you will get machine-readable (JSON) output. As of version 0.9.6 this format *should* be stable, [and has a fully documented schema](https://github.com/luser/rust-minidump/blob/master/minidump-processor/json-schema.md)! (--pretty will make this output easier for a human to read.)
 
-If you pass the --human flag, minidump-stackwalk will output a report in a more human-friendly format with no particular structure. (--brief will make this output less verbose.)
+If you pass **the --human flag**, minidump-stackwalk will output a report in a more human-friendly format with no particular structure. (--brief will make this output less verbose.)
 
 By default, output is written to stdout, but `--output-file=some/file/name.txt` allows you to specify a file to write the output to instead. We will create and completely overwrite the specified file. If there is a fatal error, we will try to avoid writing anything to the output,
 which may result in `--output-file` not being created/cleared at all. 
 
 Similarly, errors and warnings are written to stderr by default, which can be configured with `--log-file=...`. `--verbose=...` can be used to set the log level (defaults to "error").
 
-If you wish to get both --human and --json output in one execution (saving lots of duplicated work), you can use `--cyborg=some/file/for/machine/output.json`. When --cyborg output is enabled, human output will still be the "primary" output that goes to stdout and can still be configured with `--output-file`.
+If pass **the --cyborg flag** you will get both --human and --json output in one execution (saving lots of duplicated work), specifically you must pass `--cyborg=some/file/for/machine/output.json`. When cyborg mode is enabled, human output will still be the "primary" output that goes to stdout and can still be configured with `--output-file`.
 
+Finally, **the --dump flag** will get you "raw" output of the minidump, for debugging its contents. The precise meaning of this is purposefully vague; the output will contain whatever we find useful to include for debugging. Most other flags will be fairly irrelevant in this mode, because `minidump_processor` will not be invoked (we only use the `minidump` crate for basic parsing of each stream). This is equivalent to the old minidump_dump tool.
 
 
 
@@ -193,6 +201,13 @@ Emit a human-readable report (the default).
 The human-readable report does not have a specified format, and may not have as many details as the JSON
 format. It is intended for quickly inspecting a crash or debugging rust-minidump itself.
 
+### `--dump`
+Dump the 'raw' contents of the minidump.
+
+This is an implementation of the functionality of the old minidump_dump tool. It minimally parses and
+interprets the minidump in an attempt to produce a fairly 'raw' dump of the minidump's contents. This is
+most useful for debugging minidump-stackwalk itself, or a misbehaving minidump generator.
+
 ### `--pretty`
 Pretty-print --json output.
 
@@ -211,9 +226,9 @@ Prints version information
 ### `--cyborg <cyborg>`
 Combine --human and --json
 
-Because this creates two output streams, you must specify a path to write the --json
-output to. The --human output will be the 'primary' output and default to stdout, which
-can be configured with --output-file as normal.
+Because this creates two output streams, you must specify a path to write the --json output to. The --human
+output will be the 'primary' output and default to stdout, which can be configured with --output-file as
+normal.
 
 ### `--output-file <output-file>`
 Where to write the output to (if unspecified, stdout is used)
diff --git a/minidump-stackwalk/src/main.rs b/minidump-stackwalk/src/main.rs
index 82918fe..2113463 100644
--- a/minidump-stackwalk/src/main.rs
+++ b/minidump-stackwalk/src/main.rs
@@ -53,9 +53,19 @@ a crash or debugging rust-minidump itself.\n\n\n")
                 .takes_value(true)
                 .long_help("Combine --human and --json
 
-Because this creates two output streams, you must specify a path to write the --json
-output to. The --human output will be the 'primary' output and default to stdout, which
+Because this creates two output streams, you must specify a path to write the --json \
+output to. The --human output will be the 'primary' output and default to stdout, which \
 can be configured with --output-file as normal.\n\n\n")
+        )
+        .arg(
+            Arg::with_name("dump")
+                .long("dump")
+                .long_help("Dump the 'raw' contents of the minidump.
+
+This is an implementation of the functionality of the old minidump_dump tool. \
+It minimally parses and interprets the minidump in an attempt to produce a \
+fairly 'raw' dump of the minidump's contents. This is most useful for debugging \
+minidump-stackwalk itself, or a misbehaving minidump generator.\n\n\n")
         )
         .arg(
             Arg::with_name("help-markdown")
@@ -64,7 +74,7 @@ can be configured with --output-file as normal.\n\n\n")
                 .hidden(true)
         )
         .group(ArgGroup::with_name("output-format")
-            .args(&["json", "human", "cyborg"])
+            .args(&["json", "human", "cyborg", "dump", "help-markdown"])
         )
         .arg(
             Arg::with_name("output-file")
@@ -341,12 +351,16 @@ async fn main() {
 
     let minidump_path = matches.value_of_os("minidump").map(Path::new).unwrap();
 
-    // Determine the kind of output we're producing -- json, human, or cyborg (both).
+    // Determine the kind of output we're producing -- dump, json, human, or cyborg (both).
     // Although we have a --human argument it's mostly just there to make the documentation
     // more clear. human output is enabled by default, and --json disables it.
-    // Mutual exclusion is enforced by an ArgGroup.
+    // Mutual exclusion is enforced by an ArgGroup, but it doesn't understand that "human"
+    // is the implicit default, so we have to do some munging here.
+    let raw_dump = matches.is_present("dump");
     let mut json = matches.is_present("json");
-    let mut human = !json;
+    // Human is just enabled if nothing else is
+    let mut human = !json && !raw_dump;
+    // Cyborg is just "desugarred" to --json --human
     let cyborg = matches.value_of_os("cyborg").map(Path::new);
 
     if cyborg.is_some() {
@@ -373,6 +387,23 @@ async fn main() {
 
     match Minidump::read_path(minidump_path) {
         Ok(dump) => {
+            let mut stdout;
+            let mut output_f;
+            let cyborg_output_f = cyborg.map(|path| File::create(path).unwrap());
+
+            let mut output: &mut dyn Write = if let Some(output_path) = output_file {
+                output_f = File::create(output_path).unwrap();
+                &mut output_f
+            } else {
+                stdout = std::io::stdout();
+                &mut stdout
+            };
+
+            // minidump_dump mode
+            if raw_dump {
+                return print_minidump_dump(&dump, &mut output).unwrap();
+            }
+
             let mut provider = MultiSymbolProvider::new();
 
             if !symbols_urls.is_empty() {
@@ -392,18 +423,6 @@ async fn main() {
             match minidump_processor::process_minidump_with_options(&dump, &provider, options).await
             {
                 Ok(state) => {
-                    let mut stdout;
-                    let mut output_f;
-                    let cyborg_output_f = cyborg.map(|path| File::create(path).unwrap());
-
-                    let mut output: &mut dyn Write = if let Some(output_path) = output_file {
-                        output_f = File::create(output_path).unwrap();
-                        &mut output_f
-                    } else {
-                        stdout = std::io::stdout();
-                        &mut stdout
-                    };
-
                     // Print the human output if requested (always uses the "real" output).
                     if human {
                         if brief {
@@ -493,3 +512,91 @@ fn print_help_markdown() {
         println!("{}", line);
     }
 }
+
+fn print_minidump_dump<'a, T, W>(dump: &Minidump<'a, T>, output: &mut W) -> std::io::Result<()>
+where
+    T: Deref<Target = [u8]> + 'a,
+    W: Write,
+{
+    dump.print(output)?;
+
+    // Other streams depend on these, so load them upfront.
+    let system_info = dump.get_stream::<MinidumpSystemInfo>().ok();
+    let memory_list = dump.get_stream::<MinidumpMemoryList<'_>>().ok();
+    let misc_info = dump.get_stream::<MinidumpMiscInfo>().ok();
+
+    if let Ok(thread_list) = dump.get_stream::<MinidumpThreadList<'_>>() {
+        thread_list.print(
+            output,
+            memory_list.as_ref(),
+            system_info.as_ref(),
+            misc_info.as_ref(),
+        )?;
+    }
+    if let Ok(module_list) = dump.get_stream::<MinidumpModuleList>() {
+        module_list.print(output)?;
+    }
+    if let Ok(module_list) = dump.get_stream::<MinidumpUnloadedModuleList>() {
+        module_list.print(output)?;
+    }
+    if let Some(memory_list) = memory_list {
+        memory_list.print(output)?;
+    }
+    if let Ok(memory_info_list) = dump.get_stream::<MinidumpMemoryInfoList<'_>>() {
+        memory_info_list.print(output)?;
+    }
+    if let Ok(exception) = dump.get_stream::<MinidumpException>() {
+        exception.print(output, system_info.as_ref(), misc_info.as_ref())?;
+    }
+    if let Ok(assertion) = dump.get_stream::<MinidumpAssertion>() {
+        assertion.print(output)?;
+    }
+    if let Some(system_info) = system_info {
+        system_info.print(output)?;
+    }
+    if let Some(misc_info) = misc_info {
+        misc_info.print(output)?;
+    }
+    if let Ok(breakpad_info) = dump.get_stream::<MinidumpBreakpadInfo>() {
+        breakpad_info.print(output)?;
+    }
+    if let Ok(thread_names) = dump.get_stream::<MinidumpThreadNames>() {
+        thread_names.print(output)?;
+    }
+    match dump.get_stream::<MinidumpCrashpadInfo>() {
+        Ok(crashpad_info) => crashpad_info.print(output)?,
+        Err(Error::StreamNotFound) => (),
+        Err(_) => write!(output, "MinidumpCrashpadInfo cannot print invalid data")?,
+    }
+
+    // Handle Linux streams that are just a dump of some system "file".
+    macro_rules! streams {
+        ( $( $x:ident ),* ) => {
+            &[$( ( minidump_common::format::MINIDUMP_STREAM_TYPE::$x, stringify!($x) ) ),*]
+        };
+    }
+    fn print_raw_stream<T: Write>(name: &str, contents: &[u8], out: &mut T) -> std::io::Result<()> {
+        writeln!(out, "Stream {}:", name)?;
+        let s = contents
+            .split(|&v| v == 0)
+            .map(String::from_utf8_lossy)
+            .collect::<Vec<_>>()
+            .join("\\0\n");
+        write!(out, "{}\n\n", s)
+    }
+
+    for &(stream, name) in streams!(
+        LinuxCmdLine,
+        LinuxEnviron,
+        LinuxLsbRelease,
+        LinuxProcStatus,
+        LinuxCpuInfo,
+        LinuxMaps
+    ) {
+        if let Ok(contents) = dump.get_raw_stream(stream) {
+            print_raw_stream(name, contents, output)?;
+        }
+    }
+
+    Ok(())
+}
diff --git a/minidump/Cargo.toml b/minidump/Cargo.toml
index 1cb23f9..f8e2ca0 100644
--- a/minidump/Cargo.toml
+++ b/minidump/Cargo.toml
@@ -16,7 +16,7 @@ arbitrary = { version = "1", optional = true, features = ["derive"] }
 debugid = "0.7"
 encoding = "0.2"
 log = "0.4.1"
-memmap2 = "0.5.2"
+memmap2 = "0.5.3"
 minidump-common = { version = "0.9.6", path = "../minidump-common" }
 num-traits = "0.2"
 range-map = "0.1.5"
diff --git a/minidump/src/bin/minidump_dump.rs b/minidump/src/bin/minidump_dump.rs
deleted file mode 100644
index f713c96..0000000
--- a/minidump/src/bin/minidump_dump.rs
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2015 Ted Mielczarek. See the COPYRIGHT
-// file at the top-level directory of this distribution.
-
-use std::env;
-use std::io::{self, Write};
-use std::path::Path;
-use std::str;
-
-use minidump::*;
-
-const USAGE: &str = "Usage: minidump_dump <minidump>";
-
-macro_rules! streams {
-    ( $( $x:ident ),* ) => {
-        &[$( ( minidump_common::format::MINIDUMP_STREAM_TYPE::$x, stringify!($x) ) ),*]
-    };
-}
-
-fn print_raw_stream<T: Write>(name: &str, contents: &[u8], out: &mut T) -> io::Result<()> {
-    writeln!(out, "Stream {}:", name)?;
-    let s = contents
-        .split(|&v| v == 0)
-        .map(String::from_utf8_lossy)
-        .collect::<Vec<_>>()
-        .join("\\0\n");
-    write!(out, "{}\n\n", s)
-}
-
-fn print_minidump_dump(path: &Path) {
-    match Minidump::read_path(path) {
-        Ok(dump) => {
-            let stdout = &mut std::io::stdout();
-            dump.print(stdout).unwrap();
-
-            // Other streams depend on these, so load them upfront.
-            let system_info = dump.get_stream::<MinidumpSystemInfo>().ok();
-            let memory_list = dump.get_stream::<MinidumpMemoryList<'_>>().ok();
-            let misc_info = dump.get_stream::<MinidumpMiscInfo>().ok();
-
-            if let Ok(thread_list) = dump.get_stream::<MinidumpThreadList<'_>>() {
-                thread_list
-                    .print(
-                        stdout,
-                        memory_list.as_ref(),
-                        system_info.as_ref(),
-                        misc_info.as_ref(),
-                    )
-                    .unwrap();
-            }
-            if let Ok(module_list) = dump.get_stream::<MinidumpModuleList>() {
-                module_list.print(stdout).unwrap();
-            }
-            if let Ok(module_list) = dump.get_stream::<MinidumpUnloadedModuleList>() {
-                module_list.print(stdout).unwrap();
-            }
-            if let Some(memory_list) = memory_list {
-                memory_list.print(stdout).unwrap();
-            }
-            if let Ok(memory_info_list) = dump.get_stream::<MinidumpMemoryInfoList<'_>>() {
-                memory_info_list.print(stdout).unwrap();
-            }
-            if let Ok(exception) = dump.get_stream::<MinidumpException>() {
-                exception
-                    .print(stdout, system_info.as_ref(), misc_info.as_ref())
-                    .unwrap();
-            }
-            if let Ok(assertion) = dump.get_stream::<MinidumpAssertion>() {
-                assertion.print(stdout).unwrap();
-            }
-            if let Some(system_info) = system_info {
-                system_info.print(stdout).unwrap();
-            }
-            if let Some(misc_info) = misc_info {
-                misc_info.print(stdout).unwrap();
-            }
-            if let Ok(breakpad_info) = dump.get_stream::<MinidumpBreakpadInfo>() {
-                breakpad_info.print(stdout).unwrap();
-            }
-            if let Ok(thread_names) = dump.get_stream::<MinidumpThreadNames>() {
-                thread_names.print(stdout).unwrap();
-            }
-            match dump.get_stream::<MinidumpCrashpadInfo>() {
-                Ok(crashpad_info) => crashpad_info.print(stdout).unwrap(),
-                Err(Error::StreamNotFound) => (),
-                Err(_) => write!(stdout, "MinidumpCrashpadInfo cannot print invalid data").unwrap(),
-            }
-            for &(stream, name) in streams!(
-                LinuxCmdLine,
-                LinuxEnviron,
-                LinuxLsbRelease,
-                LinuxProcStatus,
-                LinuxCpuInfo,
-                LinuxMaps
-            ) {
-                if let Ok(contents) = dump.get_raw_stream(stream) {
-                    print_raw_stream(name, contents, stdout).unwrap();
-                }
-            }
-        }
-        Err(err) => {
-            let mut stderr = std::io::stderr();
-            writeln!(&mut stderr, "Error reading dump: {}", err).unwrap();
-        }
-    }
-}
-
-#[cfg_attr(test, allow(dead_code))]
-fn main() {
-    if let Some(dump_arg) = env::args().nth(1) {
-        let path = Path::new(&dump_arg);
-        print_minidump_dump(path);
-    } else {
-        let mut stderr = std::io::stderr();
-        writeln!(&mut stderr, "{}", USAGE).unwrap();
-    }
-}
diff --git a/minidump/src/context.rs b/minidump/src/context.rs
index ca114c2..4d155c0 100644
--- a/minidump/src/context.rs
+++ b/minidump/src/context.rs
@@ -37,6 +37,9 @@ pub trait CpuContext {
     /// The word size of general-purpose registers in the context.
     type Register: fmt::LowerHex;
 
+    /// General purpose registers in this context type.
+    const REGISTERS: &'static [&'static str];
+
     /// Gets whether the given register is valid
     ///
     /// This is exposed so that the context can map aliases. For instance
@@ -48,6 +51,7 @@ pub trait CpuContext {
             self.memoize_register(reg).is_some()
         }
     }
+
     /// Get a register value if it is valid.
     ///
     /// Get the value of the register named `reg` from this CPU context
@@ -70,7 +74,11 @@ pub trait CpuContext {
     fn set_register(&mut self, reg: &str, val: Self::Register) -> Option<()>;
 
     /// Gets a static version of the given register name, if possible.
-    fn memoize_register(&self, reg: &str) -> Option<&'static str>;
+    ///
+    /// Returns the default name of the register for register name aliases.
+    fn memoize_register(&self, reg: &str) -> Option<&'static str> {
+        default_memoize_register(Self::REGISTERS, reg)
+    }
 
     /// Return a String containing the value of `reg` formatted to its natural width.
     fn format_register(&self, reg: &str) -> String {
@@ -81,15 +89,80 @@ pub trait CpuContext {
         )
     }
 
+    /// An iterator over all registers in this context.
+    ///
+    /// This iterator yields registers and values regardless of whether the register is valid. To
+    /// get valid values, use [`valid_registers`](Self::valid_registers), instead.
+    fn registers(&self) -> CpuRegisters<'_, Self> {
+        self.valid_registers(&MinidumpContextValidity::All)
+    }
+
+    /// An iterator over valid registers in this context.
+    ///
+    /// This iterator yields valid registers and their values.
+    fn valid_registers<'a>(&'a self, valid: &'a MinidumpContextValidity) -> CpuRegisters<'a, Self> {
+        let regs = match valid {
+            MinidumpContextValidity::All => CpuRegistersInner::Slice(Self::REGISTERS.iter()),
+            MinidumpContextValidity::Some(valid) => CpuRegistersInner::Set(valid.iter()),
+        };
+
+        CpuRegisters {
+            regs,
+            context: self,
+        }
+    }
+
     /// Gets the name of the stack pointer register (for use with get_register/set_register).
     fn stack_pointer_register_name(&self) -> &'static str;
+
     /// Gets the name of the instruction pointer register (for use with get_register/set_register).
     fn instruction_pointer_register_name(&self) -> &'static str;
 }
 
+/// Default implementation for `CpuContext::memoize_register`.
+fn default_memoize_register(registers: &[&'static str], reg: &str) -> Option<&'static str> {
+    let idx = registers.iter().position(|val| *val == reg)?;
+    Some(registers[idx])
+}
+
+#[derive(Debug, Clone)]
+enum CpuRegistersInner<'a> {
+    Slice(std::slice::Iter<'a, &'static str>),
+    Set(std::collections::hash_set::Iter<'a, &'static str>),
+}
+
+/// An iterator over registers and values in a [`CpuContext`].
+///
+/// Returned by [`CpuContext::registers`] and [`CpuContext::valid_registers`].
+#[derive(Clone, Debug)]
+pub struct CpuRegisters<'a, T: ?Sized> {
+    regs: CpuRegistersInner<'a>,
+    context: &'a T,
+}
+
+impl<'a, T> Iterator for CpuRegisters<'a, T>
+where
+    T: CpuContext,
+{
+    type Item = (&'static str, T::Register);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let reg = match &mut self.regs {
+            CpuRegistersInner::Slice(iter) => iter.next(),
+            CpuRegistersInner::Set(iter) => iter.next(),
+        }?;
+
+        Some((reg, self.context.get_register_always(reg)))
+    }
+}
+
 impl CpuContext for md::CONTEXT_X86 {
     type Register = u32;
 
+    const REGISTERS: &'static [&'static str] = &[
+        "eip", "esp", "ebp", "ebx", "esi", "edi", "eax", "ecx", "edx", "efl",
+    ];
+
     fn get_register_always(&self, reg: &str) -> u32 {
         match reg {
             "eip" => self.eip,
@@ -102,7 +175,7 @@ impl CpuContext for md::CONTEXT_X86 {
             "ecx" => self.ecx,
             "edx" => self.edx,
             "efl" => self.eflags,
-            _ => unreachable!("Invalid x86 register!"),
+            _ => unreachable!("Invalid x86 register! {}", reg),
         }
     }
 
@@ -123,11 +196,6 @@ impl CpuContext for md::CONTEXT_X86 {
         Some(())
     }
 
-    fn memoize_register(&self, reg: &str) -> Option<&'static str> {
-        let idx = X86_REGS.iter().position(|val| *val == reg)?;
-        Some(X86_REGS[idx])
-    }
-
     fn stack_pointer_register_name(&self) -> &'static str {
         "esp"
     }
@@ -140,6 +208,11 @@ impl CpuContext for md::CONTEXT_X86 {
 impl CpuContext for md::CONTEXT_AMD64 {
     type Register = u64;
 
+    const REGISTERS: &'static [&'static str] = &[
+        "rax", "rdx", "rcx", "rbx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11", "r12",
+        "r13", "r14", "r15", "rip",
+    ];
+
     fn get_register_always(&self, reg: &str) -> u64 {
         match reg {
             "rax" => self.rax,
@@ -159,7 +232,7 @@ impl CpuContext for md::CONTEXT_AMD64 {
             "r14" => self.r14,
             "r15" => self.r15,
             "rip" => self.rip,
-            _ => unreachable!("Invalid x86-64 register!"),
+            _ => unreachable!("Invalid x86-64 register! {}", reg),
         }
     }
 
@@ -187,11 +260,6 @@ impl CpuContext for md::CONTEXT_AMD64 {
         Some(())
     }
 
-    fn memoize_register(&self, reg: &str) -> Option<&'static str> {
-        let idx = X86_64_REGS.iter().position(|val| *val == reg)?;
-        Some(X86_64_REGS[idx])
-    }
-
     fn stack_pointer_register_name(&self) -> &'static str {
         "rsp"
     }
@@ -204,6 +272,21 @@ impl CpuContext for md::CONTEXT_AMD64 {
 impl CpuContext for md::CONTEXT_ARM {
     type Register = u32;
 
+    const REGISTERS: &'static [&'static str] = &[
+        "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r12", "fp", "sp", "lr",
+        "pc",
+    ];
+
+    fn memoize_register(&self, reg: &str) -> Option<&'static str> {
+        match reg {
+            "r11" => Some("fp"),
+            "r13" => Some("sp"),
+            "r14" => Some("lr"),
+            "r15" => Some("pc"),
+            _ => default_memoize_register(Self::REGISTERS, reg),
+        }
+    }
+
     fn register_is_valid(&self, reg: &str, valid: &MinidumpContextValidity) -> bool {
         if let MinidumpContextValidity::Some(ref which) = valid {
             match reg {
@@ -271,11 +354,6 @@ impl CpuContext for md::CONTEXT_ARM {
         Some(())
     }
 
-    fn memoize_register(&self, reg: &str) -> Option<&'static str> {
-        let idx = ARM_REGS.iter().position(|val| *val == reg)?;
-        Some(ARM_REGS[idx])
-    }
-
     fn stack_pointer_register_name(&self) -> &'static str {
         "sp"
     }
@@ -288,6 +366,20 @@ impl CpuContext for md::CONTEXT_ARM {
 impl CpuContext for md::CONTEXT_ARM64_OLD {
     type Register = u64;
 
+    const REGISTERS: &'static [&'static str] = &[
+        "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13",
+        "x14", "x15", "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26",
+        "x27", "x28", "fp", "lr", "sp", "pc",
+    ];
+
+    fn memoize_register(&self, reg: &str) -> Option<&'static str> {
+        match reg {
+            "x29" => Some("fp"),
+            "x30" => Some("lr"),
+            _ => default_memoize_register(Self::REGISTERS, reg),
+        }
+    }
+
     fn register_is_valid(&self, reg: &str, valid: &MinidumpContextValidity) -> bool {
         if let MinidumpContextValidity::Some(ref which) = valid {
             match reg {
@@ -383,11 +475,6 @@ impl CpuContext for md::CONTEXT_ARM64_OLD {
         Some(())
     }
 
-    fn memoize_register(&self, reg: &str) -> Option<&'static str> {
-        let idx = ARM64_REGS.iter().position(|val| *val == reg)?;
-        Some(ARM64_REGS[idx])
-    }
-
     fn stack_pointer_register_name(&self) -> &'static str {
         "sp"
     }
@@ -400,6 +487,20 @@ impl CpuContext for md::CONTEXT_ARM64_OLD {
 impl CpuContext for md::CONTEXT_ARM64 {
     type Register = u64;
 
+    const REGISTERS: &'static [&'static str] = &[
+        "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13",
+        "x14", "x15", "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26",
+        "x27", "x28", "fp", "lr", "sp", "pc",
+    ];
+
+    fn memoize_register(&self, reg: &str) -> Option<&'static str> {
+        match reg {
+            "x29" => Some("fp"),
+            "x30" => Some("lr"),
+            _ => default_memoize_register(Self::REGISTERS, reg),
+        }
+    }
+
     fn register_is_valid(&self, reg: &str, valid: &MinidumpContextValidity) -> bool {
         if let MinidumpContextValidity::Some(ref which) = valid {
             match reg {
@@ -495,9 +596,279 @@ impl CpuContext for md::CONTEXT_ARM64 {
         Some(())
     }
 
-    fn memoize_register(&self, reg: &str) -> Option<&'static str> {
-        let idx = ARM64_REGS.iter().position(|val| *val == reg)?;
-        Some(ARM64_REGS[idx])
+    fn stack_pointer_register_name(&self) -> &'static str {
+        "sp"
+    }
+
+    fn instruction_pointer_register_name(&self) -> &'static str {
+        "pc"
+    }
+}
+
+impl CpuContext for md::CONTEXT_PPC {
+    type Register = u32;
+
+    const REGISTERS: &'static [&'static str] = &[
+        "srr0", "srr1", "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11",
+        "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", "r24",
+        "r25", "r26", "r27", "r28", "r29", "r30", "r31", "cr", "xer", "lr", "ctr", "mq", "vrsave",
+    ];
+
+    fn get_register_always(&self, reg: &str) -> Self::Register {
+        match reg {
+            "srr0" => self.srr0,
+            "srr1" => self.srr1,
+            "r0" => self.gpr[0],
+            "r1" => self.gpr[1],
+            "r2" => self.gpr[2],
+            "r3" => self.gpr[3],
+            "r4" => self.gpr[4],
+            "r5" => self.gpr[5],
+            "r6" => self.gpr[6],
+            "r7" => self.gpr[7],
+            "r8" => self.gpr[8],
+            "r9" => self.gpr[9],
+            "r10" => self.gpr[10],
+            "r11" => self.gpr[11],
+            "r12" => self.gpr[12],
+            "r13" => self.gpr[13],
+            "r14" => self.gpr[14],
+            "r15" => self.gpr[15],
+            "r16" => self.gpr[16],
+            "r17" => self.gpr[17],
+            "r18" => self.gpr[18],
+            "r19" => self.gpr[19],
+            "r20" => self.gpr[20],
+            "r21" => self.gpr[21],
+            "r22" => self.gpr[22],
+            "r23" => self.gpr[23],
+            "r24" => self.gpr[24],
+            "r25" => self.gpr[25],
+            "r26" => self.gpr[26],
+            "r27" => self.gpr[27],
+            "r28" => self.gpr[28],
+            "r29" => self.gpr[29],
+            "r30" => self.gpr[30],
+            "r31" => self.gpr[31],
+            "cr" => self.cr,
+            "xer" => self.xer,
+            "lr" => self.lr,
+            "ctr" => self.ctr,
+            "mq" => self.mq,
+            "vrsave" => self.vrsave,
+            _ => unreachable!("Invalid ppc register! {}", reg),
+        }
+    }
+
+    fn set_register(&mut self, reg: &str, val: Self::Register) -> Option<()> {
+        match reg {
+            "srr0" => self.srr0 = val,
+            "srr1" => self.srr1 = val,
+            "r0" => self.gpr[0] = val,
+            "r1" => self.gpr[1] = val,
+            "r2" => self.gpr[2] = val,
+            "r3" => self.gpr[3] = val,
+            "r4" => self.gpr[4] = val,
+            "r5" => self.gpr[5] = val,
+            "r6" => self.gpr[6] = val,
+            "r7" => self.gpr[7] = val,
+            "r8" => self.gpr[8] = val,
+            "r9" => self.gpr[9] = val,
+            "r10" => self.gpr[10] = val,
+            "r11" => self.gpr[11] = val,
+            "r12" => self.gpr[12] = val,
+            "r13" => self.gpr[13] = val,
+            "r14" => self.gpr[14] = val,
+            "r15" => self.gpr[15] = val,
+            "r16" => self.gpr[16] = val,
+            "r17" => self.gpr[17] = val,
+            "r18" => self.gpr[18] = val,
+            "r19" => self.gpr[19] = val,
+            "r20" => self.gpr[20] = val,
+            "r21" => self.gpr[21] = val,
+            "r22" => self.gpr[22] = val,
+            "r23" => self.gpr[23] = val,
+            "r24" => self.gpr[24] = val,
+            "r25" => self.gpr[25] = val,
+            "r26" => self.gpr[26] = val,
+            "r27" => self.gpr[27] = val,
+            "r28" => self.gpr[28] = val,
+            "r29" => self.gpr[29] = val,
+            "r30" => self.gpr[30] = val,
+            "r31" => self.gpr[31] = val,
+            "cr" => self.cr = val,
+            "xer" => self.xer = val,
+            "lr" => self.lr = val,
+            "ctr" => self.ctr = val,
+            "mq" => self.mq = val,
+            "vrsave" => self.vrsave = val,
+            _ => return None,
+        }
+        Some(())
+    }
+
+    fn stack_pointer_register_name(&self) -> &'static str {
+        "r1"
+    }
+
+    fn instruction_pointer_register_name(&self) -> &'static str {
+        "srr0"
+    }
+}
+
+impl CpuContext for md::CONTEXT_PPC64 {
+    type Register = u64;
+
+    const REGISTERS: &'static [&'static str] = &[
+        "srr0", "srr1", "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11",
+        "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", "r24",
+        "r25", "r26", "r27", "r28", "r29", "r30", "r31", "cr", "xer", "lr", "ctr", "vrsave",
+    ];
+
+    fn get_register_always(&self, reg: &str) -> Self::Register {
+        match reg {
+            "srr0" => self.srr0,
+            "srr1" => self.srr1,
+            "r0" => self.gpr[0],
+            "r1" => self.gpr[1],
+            "r2" => self.gpr[2],
+            "r3" => self.gpr[3],
+            "r4" => self.gpr[4],
+            "r5" => self.gpr[5],
+            "r6" => self.gpr[6],
+            "r7" => self.gpr[7],
+            "r8" => self.gpr[8],
+            "r9" => self.gpr[9],
+            "r10" => self.gpr[10],
+            "r11" => self.gpr[11],
+            "r12" => self.gpr[12],
+            "r13" => self.gpr[13],
+            "r14" => self.gpr[14],
+            "r15" => self.gpr[15],
+            "r16" => self.gpr[16],
+            "r17" => self.gpr[17],
+            "r18" => self.gpr[18],
+            "r19" => self.gpr[19],
+            "r20" => self.gpr[20],
+            "r21" => self.gpr[21],
+            "r22" => self.gpr[22],
+            "r23" => self.gpr[23],
+            "r24" => self.gpr[24],
+            "r25" => self.gpr[25],
+            "r26" => self.gpr[26],
+            "r27" => self.gpr[27],
+            "r28" => self.gpr[28],
+            "r29" => self.gpr[29],
+            "r30" => self.gpr[30],
+            "r31" => self.gpr[31],
+            "cr" => self.cr,
+            "xer" => self.xer,
+            "lr" => self.lr,
+            "ctr" => self.ctr,
+            "vrsave" => self.vrsave,
+            _ => unreachable!("Invalid ppc64 register! {}", reg),
+        }
+    }
+
+    fn set_register(&mut self, reg: &str, val: Self::Register) -> Option<()> {
+        match reg {
+            "srr0" => self.srr0 = val,
+            "srr1" => self.srr1 = val,
+            "r0" => self.gpr[0] = val,
+            "r1" => self.gpr[1] = val,
+            "r2" => self.gpr[2] = val,
+            "r3" => self.gpr[3] = val,
+            "r4" => self.gpr[4] = val,
+            "r5" => self.gpr[5] = val,
+            "r6" => self.gpr[6] = val,
+            "r7" => self.gpr[7] = val,
+            "r8" => self.gpr[8] = val,
+            "r9" => self.gpr[9] = val,
+            "r10" => self.gpr[10] = val,
+            "r11" => self.gpr[11] = val,
+            "r12" => self.gpr[12] = val,
+            "r13" => self.gpr[13] = val,
+            "r14" => self.gpr[14] = val,
+            "r15" => self.gpr[15] = val,
+            "r16" => self.gpr[16] = val,
+            "r17" => self.gpr[17] = val,
+            "r18" => self.gpr[18] = val,
+            "r19" => self.gpr[19] = val,
+            "r20" => self.gpr[20] = val,
+            "r21" => self.gpr[21] = val,
+            "r22" => self.gpr[22] = val,
+            "r23" => self.gpr[23] = val,
+            "r24" => self.gpr[24] = val,
+            "r25" => self.gpr[25] = val,
+            "r26" => self.gpr[26] = val,
+            "r27" => self.gpr[27] = val,
+            "r28" => self.gpr[28] = val,
+            "r29" => self.gpr[29] = val,
+            "r30" => self.gpr[30] = val,
+            "r31" => self.gpr[31] = val,
+            "cr" => self.cr = val,
+            "xer" => self.xer = val,
+            "lr" => self.lr = val,
+            "ctr" => self.ctr = val,
+            "vrsave" => self.vrsave = val,
+            _ => return None,
+        }
+        Some(())
+    }
+
+    fn stack_pointer_register_name(&self) -> &'static str {
+        "r1"
+    }
+
+    fn instruction_pointer_register_name(&self) -> &'static str {
+        "srr0"
+    }
+}
+
+impl CpuContext for md::CONTEXT_MIPS {
+    type Register = u64;
+
+    const REGISTERS: &'static [&'static str] = &[
+        "gp", "sp", "fp", "ra", "pc", "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7",
+    ];
+
+    fn get_register_always(&self, reg: &str) -> Self::Register {
+        match reg {
+            "gp" => self.iregs[md::MipsRegisterNumbers::GlobalPointer as usize],
+            "sp" => self.iregs[md::MipsRegisterNumbers::StackPointer as usize],
+            "fp" => self.iregs[md::MipsRegisterNumbers::FramePointer as usize],
+            "ra" => self.iregs[md::MipsRegisterNumbers::ReturnAddress as usize],
+            "pc" => self.epc,
+            "s0" => self.iregs[md::MipsRegisterNumbers::S0 as usize],
+            "s1" => self.iregs[md::MipsRegisterNumbers::S1 as usize],
+            "s2" => self.iregs[md::MipsRegisterNumbers::S2 as usize],
+            "s3" => self.iregs[md::MipsRegisterNumbers::S3 as usize],
+            "s4" => self.iregs[md::MipsRegisterNumbers::S4 as usize],
+            "s5" => self.iregs[md::MipsRegisterNumbers::S5 as usize],
+            "s6" => self.iregs[md::MipsRegisterNumbers::S6 as usize],
+            "s7" => self.iregs[md::MipsRegisterNumbers::S7 as usize],
+            _ => unreachable!("Invalid mips register! {}", reg),
+        }
+    }
+
+    fn set_register(&mut self, reg: &str, val: Self::Register) -> Option<()> {
+        match reg {
+            "gp" => self.iregs[md::MipsRegisterNumbers::GlobalPointer as usize] = val,
+            "sp" => self.iregs[md::MipsRegisterNumbers::StackPointer as usize] = val,
+            "fp" => self.iregs[md::MipsRegisterNumbers::FramePointer as usize] = val,
+            "ra" => self.iregs[md::MipsRegisterNumbers::ReturnAddress as usize] = val,
+            "pc" => self.epc = val,
+            "s0" => self.iregs[md::MipsRegisterNumbers::S0 as usize] = val,
+            "s1" => self.iregs[md::MipsRegisterNumbers::S1 as usize] = val,
+            "s2" => self.iregs[md::MipsRegisterNumbers::S2 as usize] = val,
+            "s3" => self.iregs[md::MipsRegisterNumbers::S3 as usize] = val,
+            "s4" => self.iregs[md::MipsRegisterNumbers::S4 as usize] = val,
+            "s5" => self.iregs[md::MipsRegisterNumbers::S5 as usize] = val,
+            "s6" => self.iregs[md::MipsRegisterNumbers::S6 as usize] = val,
+            "s7" => self.iregs[md::MipsRegisterNumbers::S7 as usize] = val,
+            _ => return None,
+        }
+        Some(())
     }
 
     fn stack_pointer_register_name(&self) -> &'static str {
@@ -509,6 +880,114 @@ impl CpuContext for md::CONTEXT_ARM64 {
     }
 }
 
+impl CpuContext for md::CONTEXT_SPARC {
+    type Register = u64;
+
+    const REGISTERS: &'static [&'static str] = &[
+        "g_r0", "g_r1", "g_r2", "g_r3", "g_r4", "g_r5", "g_r6", "g_r7", "g_r8", "g_r9", "g_r10",
+        "g_r11", "g_r12", "g_r13", "g_r14", "g_r15", "g_r16", "g_r17", "g_r18", "g_r19", "g_r20",
+        "g_r21", "g_r22", "g_r23", "g_r24", "g_r25", "g_r26", "g_r27", "g_r28", "g_r29", "g_r30",
+        "g_r31", "ccr", "pc", "npc", "y", "asi", "fprs",
+    ];
+
+    fn get_register_always(&self, reg: &str) -> Self::Register {
+        match reg {
+            "g_r0" | "g0" => self.g_r[0],
+            "g_r1" | "g1" => self.g_r[1],
+            "g_r2" | "g2" => self.g_r[2],
+            "g_r3" | "g3" => self.g_r[3],
+            "g_r4" | "g4" => self.g_r[4],
+            "g_r5" | "g5" => self.g_r[5],
+            "g_r6" | "g6" => self.g_r[6],
+            "g_r7" | "g7" => self.g_r[7],
+            "g_r8" | "o0" => self.g_r[8],
+            "g_r9" | "o1" => self.g_r[9],
+            "g_r10" | "o2" => self.g_r[10],
+            "g_r11" | "o3" => self.g_r[11],
+            "g_r12" | "o4" => self.g_r[12],
+            "g_r13" | "o5" => self.g_r[13],
+            "g_r14" | "o6" => self.g_r[14],
+            "g_r15" | "o7" => self.g_r[15],
+            "g_r16" | "l0" => self.g_r[16],
+            "g_r17" | "l1" => self.g_r[17],
+            "g_r18" | "l2" => self.g_r[18],
+            "g_r19" | "l3" => self.g_r[19],
+            "g_r20" | "l4" => self.g_r[20],
+            "g_r21" | "l5" => self.g_r[21],
+            "g_r22" | "l6" => self.g_r[22],
+            "g_r23" | "l7" => self.g_r[23],
+            "g_r24" | "i0" => self.g_r[24],
+            "g_r25" | "i1" => self.g_r[25],
+            "g_r26" | "i2" => self.g_r[26],
+            "g_r27" | "i3" => self.g_r[27],
+            "g_r28" | "i4" => self.g_r[28],
+            "g_r29" | "i5" => self.g_r[29],
+            "g_r30" | "i6" => self.g_r[30],
+            "g_r31" | "i7" => self.g_r[31],
+            "ccr" => self.ccr,
+            "pc" => self.pc,
+            "npc" => self.npc,
+            "y" => self.y,
+            "asi" => self.asi,
+            "fprs" => self.fprs,
+            _ => unreachable!("Invalid sparc register! {}", reg),
+        }
+    }
+
+    fn set_register(&mut self, reg: &str, val: Self::Register) -> Option<()> {
+        match reg {
+            "g_r0" | "g0" => self.g_r[0] = val,
+            "g_r1" | "g1" => self.g_r[1] = val,
+            "g_r2" | "g2" => self.g_r[2] = val,
+            "g_r3" | "g3" => self.g_r[3] = val,
+            "g_r4" | "g4" => self.g_r[4] = val,
+            "g_r5" | "g5" => self.g_r[5] = val,
+            "g_r6" | "g6" => self.g_r[6] = val,
+            "g_r7" | "g7" => self.g_r[7] = val,
+            "g_r8" | "o0" => self.g_r[8] = val,
+            "g_r9" | "o1" => self.g_r[9] = val,
+            "g_r10" | "o2" => self.g_r[10] = val,
+            "g_r11" | "o3" => self.g_r[11] = val,
+            "g_r12" | "o4" => self.g_r[12] = val,
+            "g_r13" | "o5" => self.g_r[13] = val,
+            "g_r14" | "o6" => self.g_r[14] = val,
+            "g_r15" | "o7" => self.g_r[15] = val,
+            "g_r16" | "l0" => self.g_r[16] = val,
+            "g_r17" | "l1" => self.g_r[17] = val,
+            "g_r18" | "l2" => self.g_r[18] = val,
+            "g_r19" | "l3" => self.g_r[19] = val,
+            "g_r20" | "l4" => self.g_r[20] = val,
+            "g_r21" | "l5" => self.g_r[21] = val,
+            "g_r22" | "l6" => self.g_r[22] = val,
+            "g_r23" | "l7" => self.g_r[23] = val,
+            "g_r24" | "i0" => self.g_r[24] = val,
+            "g_r25" | "i1" => self.g_r[25] = val,
+            "g_r26" | "i2" => self.g_r[26] = val,
+            "g_r27" | "i3" => self.g_r[27] = val,
+            "g_r28" | "i4" => self.g_r[28] = val,
+            "g_r29" | "i5" => self.g_r[29] = val,
+            "g_r30" | "i6" => self.g_r[30] = val,
+            "g_r31" | "i7" => self.g_r[31] = val,
+            "ccr" => self.ccr = val,
+            "pc" => self.pc = val,
+            "npc" => self.npc = val,
+            "y" => self.y = val,
+            "asi" => self.asi = val,
+            "fprs" => self.fprs = val,
+            _ => return None,
+        }
+        Some(())
+    }
+
+    fn stack_pointer_register_name(&self) -> &'static str {
+        "g_r14" // alias out register o6
+    }
+
+    fn instruction_pointer_register_name(&self) -> &'static str {
+        "pc"
+    }
+}
+
 /// Information about which registers are valid in a `MinidumpContext`.
 #[derive(Clone, Debug, PartialEq)]
 pub enum MinidumpContextValidity {
@@ -547,29 +1026,6 @@ pub enum ContextError {
     UnknownCpuContext,
 }
 
-/// General-purpose registers for x86.
-static X86_REGS: [&str; 10] = [
-    "eip", "esp", "ebp", "ebx", "esi", "edi", "eax", "ecx", "edx", "efl",
-];
-
-/// General-purpose registers for x86-64.
-static X86_64_REGS: [&str; 17] = [
-    "rax", "rdx", "rcx", "rbx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11", "r12", "r13",
-    "r14", "r15", "rip",
-];
-
-/// General-purpose registers for ARM (32-bit).
-static ARM_REGS: [&str; 20] = [
-    "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r13", "r14",
-    "r15", "pc", "lr", "fp", "sp",
-];
-
-/// General-purpose registers for aarch64.
-static ARM64_REGS: [&str; 35] = [
-    "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14",
-    "x15", "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27",
-    "x28", "x29", "x30", "pc", "lr", "fp", "sp",
-];
 //======================================================
 // Implementations
 
@@ -765,34 +1221,91 @@ impl MinidumpContext {
         }
     }
 
+    pub fn get_register_always(&self, reg: &str) -> u64 {
+        match self.raw {
+            MinidumpRawContext::Amd64(ref ctx) => ctx.get_register_always(reg),
+            MinidumpRawContext::Arm(ref ctx) => ctx.get_register_always(reg).into(),
+            MinidumpRawContext::Arm64(ref ctx) => ctx.get_register_always(reg),
+            MinidumpRawContext::OldArm64(ref ctx) => ctx.get_register_always(reg),
+            MinidumpRawContext::Ppc(ref ctx) => ctx.get_register_always(reg).into(),
+            MinidumpRawContext::Ppc64(ref ctx) => ctx.get_register_always(reg),
+            MinidumpRawContext::Sparc(ref ctx) => ctx.get_register_always(reg),
+            MinidumpRawContext::X86(ref ctx) => ctx.get_register_always(reg).into(),
+            MinidumpRawContext::Mips(ref ctx) => ctx.get_register_always(reg),
+        }
+    }
+
+    pub fn get_register(&self, reg: &str) -> Option<u64> {
+        let valid = match &self.raw {
+            MinidumpRawContext::X86(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::Ppc(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::Ppc64(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::Amd64(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::Sparc(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::Arm(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::Arm64(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::OldArm64(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::Mips(ctx) => ctx.register_is_valid(reg, &self.valid),
+        };
+
+        if valid {
+            Some(self.get_register_always(reg))
+        } else {
+            None
+        }
+    }
+
     pub fn format_register(&self, reg: &str) -> String {
         match self.raw {
             MinidumpRawContext::Amd64(ref ctx) => ctx.format_register(reg),
             MinidumpRawContext::Arm(ref ctx) => ctx.format_register(reg),
             MinidumpRawContext::Arm64(ref ctx) => ctx.format_register(reg),
             MinidumpRawContext::OldArm64(ref ctx) => ctx.format_register(reg),
-            MinidumpRawContext::Ppc(_) => unimplemented!(),
-            MinidumpRawContext::Ppc64(_) => unimplemented!(),
-            MinidumpRawContext::Sparc(_) => unimplemented!(),
+            MinidumpRawContext::Ppc(ref ctx) => ctx.format_register(reg),
+            MinidumpRawContext::Ppc64(ref ctx) => ctx.format_register(reg),
+            MinidumpRawContext::Sparc(ref ctx) => ctx.format_register(reg),
             MinidumpRawContext::X86(ref ctx) => ctx.format_register(reg),
-            MinidumpRawContext::Mips(_) => unimplemented!(),
+            MinidumpRawContext::Mips(ref ctx) => ctx.format_register(reg),
         }
     }
 
     pub fn general_purpose_registers(&self) -> &'static [&'static str] {
         match self.raw {
-            MinidumpRawContext::Amd64(_) => &X86_64_REGS[..],
-            MinidumpRawContext::Arm(_) => &ARM_REGS[..],
-            MinidumpRawContext::Arm64(_) => &ARM64_REGS[..],
-            MinidumpRawContext::OldArm64(_) => &ARM64_REGS[..],
-            MinidumpRawContext::Ppc(_) => unimplemented!(),
-            MinidumpRawContext::Ppc64(_) => unimplemented!(),
-            MinidumpRawContext::Sparc(_) => unimplemented!(),
-            MinidumpRawContext::X86(_) => &X86_REGS[..],
-            MinidumpRawContext::Mips(_) => unimplemented!(),
+            MinidumpRawContext::Amd64(_) => md::CONTEXT_AMD64::REGISTERS,
+            MinidumpRawContext::Arm(_) => md::CONTEXT_ARM::REGISTERS,
+            MinidumpRawContext::Arm64(_) => md::CONTEXT_ARM64::REGISTERS,
+            MinidumpRawContext::OldArm64(_) => md::CONTEXT_ARM64::REGISTERS,
+            MinidumpRawContext::Ppc(_) => md::CONTEXT_PPC::REGISTERS,
+            MinidumpRawContext::Ppc64(_) => md::CONTEXT_PPC64::REGISTERS,
+            MinidumpRawContext::Sparc(_) => md::CONTEXT_SPARC::REGISTERS,
+            MinidumpRawContext::X86(_) => md::CONTEXT_X86::REGISTERS,
+            MinidumpRawContext::Mips(_) => md::CONTEXT_MIPS::REGISTERS,
         }
     }
 
+    pub fn registers(&self) -> impl Iterator<Item = (&'static str, u64)> + '_ {
+        self.general_purpose_registers()
+            .iter()
+            .map(move |&reg| (reg, self.get_register_always(reg)))
+    }
+
+    pub fn valid_registers(&self) -> impl Iterator<Item = (&'static str, u64)> + '_ {
+        // This is suboptimal in theory, as we could iterate over self.valid just like the original
+        // and faster `CpuRegisters` iterator does. However, this complicates code here, and the
+        // minimal gain in performance hasn't been worth the added complexity.
+        self.registers().filter(move |(reg, _)| match &self.raw {
+            MinidumpRawContext::X86(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::Ppc(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::Ppc64(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::Amd64(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::Sparc(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::Arm(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::Arm64(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::OldArm64(ctx) => ctx.register_is_valid(reg, &self.valid),
+            MinidumpRawContext::Mips(ctx) => ctx.register_is_valid(reg, &self.valid),
+        })
+    }
+
     /// Write a human-readable description of this `MinidumpContext` to `f`.
     ///
     /// This is very verbose, it is the format used by `minidump_dump`.
@@ -1052,3 +1565,34 @@ impl MinidumpContext {
         Ok(())
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    /// Smoke test for the default implementation of `memoize_register`.
+    fn test_memoize_amd64() {
+        let context = md::CONTEXT_AMD64::default();
+        assert_eq!(context.memoize_register("rip"), Some("rip"));
+        assert_eq!(context.memoize_register("foo"), None);
+    }
+
+    #[test]
+    /// Test ARM register aliases by example of `fp`.
+    fn test_memoize_arm_alias() {
+        let context = md::CONTEXT_ARM::default();
+        assert_eq!(context.memoize_register("r11"), Some("fp"));
+        assert_eq!(context.memoize_register("fp"), Some("fp"));
+        assert_eq!(context.memoize_register("foo"), None);
+    }
+
+    #[test]
+    /// Test ARM register aliases by example of `fp`.
+    fn test_memoize_arm64_alias() {
+        let context = md::CONTEXT_ARM64::default();
+        assert_eq!(context.memoize_register("x29"), Some("fp"));
+        assert_eq!(context.memoize_register("fp"), Some("fp"));
+        assert_eq!(context.memoize_register("foo"), None);
+    }
+}
diff --git a/minidump/src/minidump.rs b/minidump/src/minidump.rs
index 7dcc468..1105704 100644
--- a/minidump/src/minidump.rs
+++ b/minidump/src/minidump.rs
@@ -909,9 +909,22 @@ impl Module for MinidumpModule {
     fn code_file(&self) -> Cow<'_, str> {
         Cow::Borrowed(&self.name)
     }
+
     fn code_identifier(&self) -> CodeId {
         match self.codeview_info {
             Some(CodeView::Elf(ref raw)) => CodeId::from_binary(&raw.build_id),
+            Some(CodeView::Pdb70(ref raw)) if raw.age == 0 => {
+                // This is a macOS minidump. It looks like the age is typically
+                // >0 for Windows minidumps, and Breakpad sets it to 0 on macOS.
+                // We're relying on this observation in order to distinguish
+                // between these two platforms since macOS uses PDB70 instead of
+                // its own dedicated format.
+                // See the following linked comment in a Windows PDB writer:
+                // https://github.com/microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/dbi/pdb.cpp#L602-L606
+                // TODO: Figure out a way to get access to `MinidumpSystemInfo`
+                // here instead of relying on `age`.
+                CodeId::new(format!("{:#}", raw.signature))
+            }
             _ => {
                 // TODO: Breakpad stubs this out on non-Windows.
                 CodeId::new(format!(
@@ -933,6 +946,10 @@ impl Module for MinidumpModule {
     fn debug_identifier(&self) -> Option<DebugId> {
         match self.codeview_info {
             Some(CodeView::Pdb70(ref raw)) => {
+                // For macOS, this should be its code ID with the age (0)
+                // appended to the end of it. This makes it identical to debug
+                // IDs for Windows, and is why it doesn't have a special case
+                // here.
                 let uuid = Uuid::from_fields(
                     raw.signature.data1,
                     raw.signature.data2,
@@ -5857,6 +5874,53 @@ c70206ca83eb2852-de0206ca83eb2852  -w-s  10bac9000 fd:05 1196511 /usr/lib64/libt
         );
     }
 
+    #[test]
+    fn test_macos_ids() {
+        let name = DumpString::new("macos module", Endian::Little);
+        let cv_record = Section::with_endian(Endian::Little)
+            // signature
+            .D32(md::CvSignature::Pdb70 as u32)
+            // signature, a GUID
+            .D32(0xaabbccdd)
+            .D16(0xeeff)
+            .D16(0x0011)
+            .append_bytes(b"\x22\x33\x44\x55\x66\x77\x88\x99")
+            // age, breakpad writes 0
+            .D32(0)
+            // pdb_file_name
+            .append_bytes(b"helpivecrashed.dylib\0");
+        let module = SynthModule::new(
+            Endian::Little,
+            0x100000000,
+            0x4000,
+            &name,
+            0xb1054d2a,
+            0x34571371,
+            Some(&STOCK_VERSION_INFO),
+        )
+        .cv_record(&cv_record);
+        let dump = SynthMinidump::with_endian(Endian::Little)
+            .add_module(module)
+            .add(name)
+            .add(cv_record);
+        let dump = read_synth_dump(dump).unwrap();
+        let module_list = dump.get_stream::<MinidumpModuleList>().unwrap();
+        let modules = module_list.iter().collect::<Vec<_>>();
+        assert_eq!(modules.len(), 1);
+        // should be the uuid stored in cv record
+        assert_eq!(
+            modules[0].code_identifier(),
+            CodeId::new("AABBCCDDEEFF00112233445566778899".to_owned())
+        );
+        // should match code identifier, but with the age appended to it
+        assert_eq!(
+            modules[0].debug_identifier().unwrap(),
+            DebugId::from_breakpad("AABBCCDDEEFF001122334455667788990").unwrap()
+        );
+        assert_eq!(modules[0].code_file(), "macos module");
+        assert_eq!(modules[0].debug_file().unwrap(), "helpivecrashed.dylib");
+    }
+
     #[test]
     fn test_thread_list_x86() {
         let context = synth_minidump::x86_context(Endian::Little, 0xabcd1234, 0x1010);


Test patch:
diff --git a/minidump-processor/src/stackwalker/arm_unittest.rs b/minidump-processor/src/stackwalker/arm_unittest.rs
index c6f12b9..78c3b50 100644
--- a/minidump-processor/src/stackwalker/arm_unittest.rs
+++ b/minidump-processor/src/stackwalker/arm_unittest.rs
@@ -427,7 +427,7 @@ async fn test_frame_pointer_infinite_equality() {
     // Never get to frame 2, alas!
 }
 
-const CALLEE_SAVE_REGS: &[&str] = &["pc", "sp", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11"];
+const CALLEE_SAVE_REGS: &[&str] = &["pc", "sp", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "fp"];
 
 fn init_cfi_state() -> (TestFixture, Section, CONTEXT_ARM, MinidumpContextValidity) {
     let mut f = TestFixture::new();
diff --git a/minidump-processor/tests/test_processor.rs b/minidump-processor/tests/test_processor.rs
index 10dd9dd..f4a60ee 100644
--- a/minidump-processor/tests/test_processor.rs
+++ b/minidump-processor/tests/test_processor.rs
@@ -240,3 +240,31 @@ async fn test_linux_proc_status() {
     let dump = minimal_minidump().set_linux_proc_status(input);
     let _state = read_synth_dump(dump).await;
 }
+
+#[tokio::test]
+async fn test_no_frames() {
+    let context = synth_minidump::x86_context(Endian::Little, 0, 0);
+
+    let stack = Memory::with_section(Section::with_endian(Endian::Little), 0);
+
+    let thread = Thread::new(Endian::Little, 0x1234, &stack, &context);
+    let system_info = SystemInfo::new(Endian::Little);
+
+    let mut ex = Exception::new(Endian::Little);
+    ex.thread_id = 0x1234;
+
+    let dump = SynthMinidump::with_endian(Endian::Little)
+        .add_thread(thread)
+        .add_exception(ex)
+        .add_system_info(system_info)
+        .add(context)
+        .add_memory(stack);
+
+    let mut state = read_synth_dump(dump).await;
+
+    // I'm not sure if this is really a valid move in a test.
+    // But I can't figure out *how* to get the frames to be clear in a valid dump.
+    state.threads[0].frames.clear();
+
+    state.print_json(&mut std::io::sink(), true).unwrap();
+}
diff --git a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__dump.snap b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__dump.snap
new file mode 100644
index 0000000..109bb3c
--- /dev/null
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__dump.snap
@@ -0,0 +1,714 @@
+---
+source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
+assertion_line: 55
+expression: stdout
+
+---
+MDRawHeader
+  signature            = 0x504d444d
+  version              = 0x5128a793
+  stream_count         = 9
+  stream_directory_rva = 0x20
+  checksum             = 0x0
+  time_date_stamp      = 0x45d35f73 2007-02-14T19:13:55Z
+  flags                = 0x0
+
+mDirectory[0]
+MDRawDirectory
+  stream_type        = 0x3 (ThreadListStream)
+  location.data_size = 100
+  location.rva       = 0x184
+
+mDirectory[1]
+MDRawDirectory
+  stream_type        = 0x4 (ModuleListStream)
+  location.data_size = 1408
+  location.rva       = 0x1e8
+
+mDirectory[2]
+MDRawDirectory
+  stream_type        = 0x5 (MemoryListStream)
+  location.data_size = 52
+  location.rva       = 0x1505
+
+mDirectory[3]
+MDRawDirectory
+  stream_type        = 0x6 (ExceptionStream)
+  location.data_size = 168
+  location.rva       = 0xdc
+
+mDirectory[4]
+MDRawDirectory
+  stream_type        = 0x7 (SystemInfoStream)
+  location.data_size = 56
+  location.rva       = 0x8c
+
+mDirectory[5]
+MDRawDirectory
+  stream_type        = 0xf (MiscInfoStream)
+  location.data_size = 24
+  location.rva       = 0xc4
+
+mDirectory[6]
+MDRawDirectory
+  stream_type        = 0x47670001 (BreakpadInfoStream)
+  location.data_size = 12
+  location.rva       = 0x14f9
+
+mDirectory[8]
+MDRawDirectory
+  stream_type        = 0x0 (UnusedStream)
+  location.data_size = 0
+  location.rva       = 0x0
+
+Streams:
+  stream type 0x0 (UnusedStream) at index 8
+  stream type 0x3 (ThreadListStream) at index 0
+  stream type 0x4 (ModuleListStream) at index 1
+  stream type 0x5 (MemoryListStream) at index 2
+  stream type 0x6 (ExceptionStream) at index 3
+  stream type 0x7 (SystemInfoStream) at index 4
+  stream type 0xf (MiscInfoStream) at index 5
+  stream type 0x47670001 (BreakpadInfoStream) at index 6
+
+MinidumpThreadList
+  thread_count = 2
+
+thread[0]
+MINIDUMP_THREAD
+  thread_id                   = 0xbf4
+  suspend_count               = 0
+  priority_class              = 0x0
+  priority                    = 0x0
+  teb                         = 0x7ffdf000
+  stack.start_of_memory_range = 0x12f31c
+  stack.memory.data_size      = 0xce4
+  stack.memory.rva            = 0x1639
+  thread_context.data_size    = 0x2cc
+  thread_context.rva          = 0xd94
+
+CONTEXT_X86
+  context_flags                = 0x1003f
+  dr0                          = 0x0
+  dr1                          = 0x0
+  dr2                          = 0x0
+  dr3                          = 0x0
+  dr6                          = 0x0
+  dr7                          = 0x0
+  float_save.control_word      = 0xffff027f
+  float_save.status_word       = 0xffff0000
+  float_save.tag_word          = 0xffffffff
+  float_save.error_offset      = 0x0
+  float_save.error_selector    = 0x220000
+  float_save.data_offset       = 0x0
+  float_save.data_selector     = 0xffff0000
+  float_save.register_area[80] = 0x0000000018b72200000118b72200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+  float_save.cr0_npx_state     = 0x0
+  gs                           = 0x0
+  fs                           = 0x3b
+  es                           = 0x23
+  ds                           = 0x23
+  edi                          = 0x0
+  esi                          = 0x7b8
+  ebx                          = 0x7c883780
+  edx                          = 0x7c97c0d8
+  ecx                          = 0x7c80b46e
+  eax                          = 0x400000
+  ebp                          = 0x12f384
+  eip                          = 0x7c90eb94
+  cs                           = 0x1b
+  eflags                       = 0x246
+  esp                          = 0x12f320
+  ss                           = 0x23
+  extended_registers[512]      = 0x7f0200000000220000000000000000000000000000000000801f0000ffff00000000000018b72200000100000000000018b72200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004509917c4e09917c38b622002400020024b42200020000009041917c0070fd7f0510907cccb22200000000009cb3220018ee907c7009917cc0e4977c6f3e917c623e917c08020000dcb62200b4b622001e000000000000000000000000000000000000002eb42200000000000f000000020000001e00200000fcfd7f2f63796764726976652f632f444f43554d457e312f4d4d454e544f7e312f4c4f43414c537e312f54656d7000000000000000000130b422000000004300000000000000001efcfd7f4509917c4e09917c5ad9000008b32200b4b62200
+
+Stack
+0x00000000c0e9907ccb25807cb8070000000000000000000034ff1200b0fe12008037887c140000000100000000000000000000001000000027e0907c2e39917c0050fd7f00f0fd7f000000000400000034f312006947c788d4f31200a89a837cf825807c0000000098f312003225807cb8070000ffffffff00000000e4f31200ff1d4000b8070000ffffffffa8fa12008037887c0e1c4000a8fa120000000000ff792a0f64f91200b01b400000004000b0fe12000040020070fa1200084042000000000080fa120080fa12004e30867ca8fa12000000000000000000000000000018000002100000e3ef907c0000000079d900000000000048f41200000014003207917c0500000078071400000014000000000020f412003207917ca05f140018ee907cfa00000074f61200000000009615917ceb06917cf00298000100000000000000384f14009615917ceb06917c7801140008000000404f14000000a659985f1400080000000000000018ee907c90fea700400698003815917c184f1400eb06917c00000000800000000000a65988f69f0090f51200a569917cf8f41200d95c878880f5120043ef907c480485000500000090f5120088fea700a8212400000000000000000080f512002469917cf8fbfd7fa821240008000000f500000000000000384d850078019800a8fa120004000000a05f140096d4917c00000000b0d4917c0000000008069800184f140000000000104f140000000000184f140000004000000000010040020063003a005c0074006500730074005f006100700070002e006500780065000000000000002f00000028000000184f1400780185007801140028000000000000000000140084f3120010000000d8f5120018ee907cf006917cffffffffeb06917ce619917c88e6a7003003000001030000ff1b917c0000980080e6a70080069800400698000000980080e6a70000000000000000000000000080e6a7000818000088e6a700d759927c78019800081800000210000000009800f8f31200280a0000dcf6120018ee907cf006917cffffffffeb06917c0859927c00009800080000005859927c00000000000001000000a659000000005a6202000010000068fe030001000000dffe03000000010000000100fffffe7f0100000001c0c27700008500000000002dc0c27700000000684aaf590000a659241a917c80c0977c0000000000000000cd5c927c0050fd7f0000a65900000100080000004550fd7f0000000000000000000000000050fd7fcd5c927c05000000d4f61200f45c927c80e4977c05000000010000000050fd7f18f712007009917cc0e4977c172e817cff2d817c000000000000a6590000a6590210000000f0fd7f05000000e8f612000806980064f81200a89a837c002e817cffffffffff2d817cc8242400dd8ea75901000000000000004cf712003608a9590000a65901000000000000000100000060f71200e407a9596cf7120000004000000000010040020063003a005c0074006500730074005f006100700070002e0065007800650000006d05917c905f140000000000440d020000000000604f14000c0000007801140000000000a04e1400d84d8700400687004509917c0800000000000000000000004000000078011400a8038700404f14000510907c000000000000000078011400980387000800000008000000c0e4977cd04d8700f835887c7801140010000000a0e7ae591600000030fd1200d39b917c44000000620000000000a65950e9ae59d8eaae59a80387000100000014040000000000000100000002000000f800a659c003870001000000780114009508917c0000a659091b917c020000000900000040000000a2fd12009cfd1200404f1400a2fd1200d04d8700640187000000000000000000d04d870010000000d84d8700384f1400a803870010000000c00300000000870074fb1200404f14006cfe120018ee907cf006917cffffffffeb06917ca09d400000008700000000000400000000000000ffffff3fc04d8700ccfd12009c4d400004000000fa19917cb84d870064018700c04d8700063440000400000018000000c04d870079d90000c0038700fa31400000000000c04d8700c04d87000000000001000000b0fe120082294000c04d87000000000000000000c04d870048fe12008cfe120000000000e224400040fe12008cfe1200c04d8700d84d8700b0fe12008600817c54fa1200d8f9120000000000160018005479420079d90000000000000757917c00000200a4f91200a4f91200a4f91200020000000200000000000000c4f912000000000079d9000014fb12004cfa120014fb1200005a917c00fa1200a0fb120001000000655a917ca405817c74c1977ce705817c00000000f4fd120098fb120000000000a0fb12000000000090fb12000000800070fa120000000000000000000000000016001800547942000000000001000000000000000000000000000000000000003308917ca89a837c0000807c0000807ce800807c2cfa12001fe2907c11fa877cffffffffe06f817c000000006cfa12001c0000000f000000e06f817c8fc60000f0f312000060817cc8fa1200a89a837c7039867cfffffffff0ff1200da36847ca8fa1200099b837cb0fa120000000000b0fa12000000000000000000000000009cfb1200b8fb1200d4fa1200bf37907c9cfb1200e0ff1200b8fb120070fb1200b0ff1200d837907ce0ff120084fb12008b37907c9cfb1200e0ff1200b8fb120070fb1200a89a837c010000009cfb1200e0ff12006078937c9cfb1200e0ff1200b8fb120070fb1200a89a837c280a00009cfb12000200000018ee907c9032917cffffffff8832917c3364917c68fb1200000087003207917c02000000dc31917c1232917c8132917c8832917c1e000000c01e2400080200003807917c54fb12003207917cc4fb120018ee907c9032917c0000130000d01200beb4800088fe1200faea907c00000000b8fb12009cfb1200b8fb1200050000c000000000000000009e4240000200000001000000450000003f0001000000000000000000000000000000000000000000000000007f02ffff0000ffffffffffff0000000000002200000000000000ffff0000000018b72200000118b7220000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003b0000002300000023000000280a000002000000c1ab807c58bc420094fe12004500000088fe12009e4240001b0000004602010084fe1200230000007f0200000000220000000000000000000000000000000000801f0000ffff00000000000018b72200000100000000000018b72200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004509917c4e09917c38b622002400020024b42200020000009041917c0070fd7f0510907cccb22200000000009cb3220018ee907c7009917cc0e4977c6f3e917c623e917c08020000dcb62200b4b622001e000000000000000000000000000000000000002eb42200000000000f000000020000001e00200000fcfd7f2f63796764726976652f632f444f43554d457e312f4d4d454e544f7e312f4c4f43414c537e312f54656d7000000000000000000130b422000000004300000000000000001efcfd7f4509917c4e09917c5ad9000008b32200b4b622004500000070ff120000424000b8278700dc31917c00000000004c870000000020040000000000000007000000000000004042400000000000000000002e000000000000000cff12007b434100010000000700000084434100004d87002e39917cffffffff24000000240000002700000000000000584d870004000000b1944000244c87002a0000002f000000c0fe1200004d8700584d87000000a659b0b9a859015d400015aa400000000000b4070000784e14000000000001000000f40b00000000000000000000bc070000b8070000f40b0000a8fa120000000000009c4000599c400094b240004f752a0fc0ff1200ec534000010000003039870050398700ff752a0f00002400a02024000050fd7f050000c00100000005000000000000000000240084ff1200acfa1200e0ff1200d06f4000a70b7a0f00000000f0ff1200d76f817c00002400a02024000050fd7f050000c0c8ff1200a8fa1200ffffffffa89a837ce06f817c0000000000000000000000004354400000000000
+
+thread[1]
+MINIDUMP_THREAD
+  thread_id                   = 0x11c0
+  suspend_count               = 0
+  priority_class              = 0x0
+  priority                    = 0x0
+  teb                         = 0x7ffde000
+  stack.start_of_memory_range = 0x97f6e8
+  stack.memory.data_size      = 0x918
+  stack.memory.rva            = 0x231d
+  thread_context.data_size    = 0x2cc
+  thread_context.rva          = 0x1060
+
+CONTEXT_X86
+  context_flags                = 0x1003f
+  dr0                          = 0x0
+  dr1                          = 0x0
+  dr2                          = 0x0
+  dr3                          = 0x0
+  dr6                          = 0x0
+  dr7                          = 0x0
+  float_save.control_word      = 0xffff027f
+  float_save.status_word       = 0xffff0000
+  float_save.tag_word          = 0xffffffff
+  float_save.error_offset      = 0x0
+  float_save.error_selector    = 0x870000
+  float_save.data_offset       = 0x0
+  float_save.data_selector     = 0xffff0000
+  float_save.register_area[80] = 0x84fb120000001400320778071400000014000000f4fe1200a0fd120018eeb0fd12003815917c961534ff120034ff12000000e7712a0f2a0000005400ccfb120068514000584d540000002a000000f4fe
+  float_save.cr0_npx_state     = 0x0
+  gs                           = 0x0
+  fs                           = 0x3b
+  es                           = 0x23
+  ds                           = 0x23
+  edi                          = 0x145b00
+  esi                          = 0x145aa8
+  ebx                          = 0x145ad0
+  edx                          = 0x7c90eb94
+  ecx                          = 0x7
+  eax                          = 0xa80000
+  ebp                          = 0x97f6fc
+  eip                          = 0x7c90eb94
+  cs                           = 0x1b
+  eflags                       = 0x246
+  esp                          = 0x97f6ec
+  ss                           = 0x23
+  extended_registers[512]      = 0x7f0200000000870000000000000000000000000000000000801f0000ccfb120084fb1200000014003207917c050000007807140000001400000000005cfb1200f4fe1200a0fd120018ee907c2d020000b0fd12003815917c9615917ceb06917c34ff120034ff12000000000060000000e7712a0f2a0000005400000000000000ccfb120068514000584d870034fc1200540000002a000000f4fe1200f8fe12002c2f4000584d87005e00000034fc12005400000000000000b0fe1200f4fe1200c0fe12005f21400034fc12002a0000003b762a0f91214000303132330000870038393a3b3c3d3e3f4041424300000000070000003bd11e2340061400b858101e5e03e0652e005c00320033003100650064003100780114002d0066003300380034002d0000000000390034002d0062003800350038002d0031003000984e1400350065003000330065003000360035002e0064006d0070000000907c08000000ffffffff8832917cbeb4807c780114001d00f40b784e14000401000044fd120050fd1200c01e240078011400bdb9807ca04e14007c80c2770000000008fd120078011400ecfc1200f0fc1200e6b9807cffffffff7c80c27708fd12001c00000024fd1200e92a867c7c80c277b45a887c8037887c2d0200000080c2770000c17780000000005003000010000020000000780114005cff12001648847c091b917c
+
+Stack
+0x8000108020fa97009fd7907c0000000048f7970005000f0040061400000000004cf7970037b9807c00000000000000003103917c780114000000000061dc907cf1b8807c00000000ffffffff70f79700000000000000000054f797003082140001000000000000000200000000000000000000007cf7970020b9807c0e00000004000000006000000000a80078011400000000000882140092d5907c8b9b807c9c070000d0f89700780114009c07000038821400807f140020dea85910fa9700780114009807000088fb9700e07f14009c0700000000000078011400000000000882140000000000000000000000000078011400ba0300000000000000000000000000000000000000000000000000007801140000000000000000000000000000000000c0030000000000000000000000000000088214005c0057000600000078011400000000005c00730079007300740065006d0033000082140068011400000000000000000000821400d47f1400807f140070f8970061eea859e000a8000000a8001c4e000084f89700bdeea859e000a8000000a8001c4e0000a4f897005fefa8590000a8000000000006000000c4f89700e000a80060fe9700c8f897005abfa8590000a80000000000060000001c000000d47f1400807f1400380000006ce9907c88b9807cffffffff0000a80000000000807f140030fa97007fc3a859a0c4a859b0fb970060fe9700684f1400504500004c010400ca9610410000000000000000e0000e210b01070a00400000003a000000000000f1100000001000000000bf760000000000100000000200000500010005000100040000000000000000b00000000400009ba2000000001400285214000000000034fa97007801140034fa9700910e917c080614006d05917cc84d85005c4e8500684f140000000000b04800002852140078011400e00300003052140000000000000000000000000078011400c403000030821400380000000000000000000000000000000000000000000000000000003882140048050000780200003800000000100000ec000000b8470000400000000000000000000000000000000000140000000000807f140000000000000000000000000000000101a900000060fe9700dcff9700dcff97000050fd7f78fa970054fa9700ad9d917c8cfa9700c2066f7f0e000000000000001c01000078fa9700c84d850068fa970085ae807c78fc970024fc970083dba85978fc97008cfa9700800000008edba85914010000050000000100000096020000b8fc97003815917c9615917ceb06917c60fe970060fe9700c4fd9700d8fa9700000014003207917c21000000b80c14000000140030521400b0fa9700fffffffff4fc970018ee907c3807917cffffffff3207917cab06917ceb06917ccc4f140060fe9700684f1400e0004000e4fa9700a863917c74fb970018ee907c3808917cffffffff3308917c5b2c817c872c817c00000000f4fb9700000000000000000054fd970018ee907c4006140064fd97003815917c00e0fd7feb06917c684f140038821400780114000050fd7facfb970000000000000004000000000090fe97000000000018fb970050531400508b1400a89a837cffe9907cf60d817ca807000000000000ffe9907cf60d817c08000000000000000000000000000000585314003cfc97003882140000000000160e817cc4fd970060fe970058531400208f1400588b1400460f917c50531400208f1400780114003082140000000000b8fc970078011400b8fc9700910e917c080614006d05917c3882140060fe97000000000000000000960200003082140078011400ffe9907c38821400a807000000000000780114005853140058fc9700505314001000000000000000160e817c784e8500c4fd9700388214000000000024010000f00c00001000000044fc970000000000c7e2907ce721807cffffffffe8f69700388214001809000098fc9700acfc9700d2e2a859ffffffffe8f697003882140018090000c4fc97003882140060fe9700c4fd9700ccfc97004ee3a859ffffffffe8f69700000000003882140018090000e0fc9700f4fc970093b2a859ffffffffe8f69700000000003882140018090000c4fd970060fe9700a85a140078fd9700a5b3a85960fe9700c4fd9700684f1400e8f697000000000018090000000000000200000080fd9700c4fd970060fe970000000000f40b000000000000000000000000000000f0fd7f000000001cf3120000000000e40c000039160000cc020000940d00000000000000000000000000000000000002000000ac4f14006010000098fd97005eb7a8593916000000000000684f1400784e85000000000084ff9700d4fe97007bb9a85960fe9700c4fd9700684f14003849140084ff9700010000000000000008ff9700f40b0000090000000000000020000000200000006c0000008c000000380000001e000000c4000000dc000000a80000008401000064000000b801000030000000e801000080050000e8010000680700000000000068070000680700000000000005150000340000002915000039150000fc1600001d23000068070000600300008a070000c80a0000310a00002c130000f91400000c000000352c000000000000ffffffff5c0f0000c84d8500784e8500884e85000000000000000000000000004c010000fc39a6590000000002000000050000000100000000008500280a000001000001cc0200009c0000000b00000050000000040000000010000000000000000000000001000000000000000000000000000034ff970078baa859384914005c0f0000c84d850001000000884e8500684f140008ff970064ff970000000000a8070000b0fe1200f40b00009cfb120000000000b8fb12000000000000000000ae20140000000000884e8500784e8500c84d8500a8fa120011204000ffffffff5c0f0000a80700000000000078ff970064ff970000000000adbf807c2025807cecff97000100000084ff970003000000c0110000f40b0000f40b0000a8fa120000000000010067470c0000006cff9700bc070000ffffffff000000006c1a4000f40b000000fa12000000000000004000a09d4000b0fe120083b6807cb0fe120000004000a09d4000b0fe120000e0fd7f00069c86c0ff9700d8863f86ffffffffa89a837c90b6807c000000000000000000000000301a4000b0fe120000000000
+
+MinidumpModuleList
+  module_count = 13
+
+module[0]
+MINIDUMP_MODULE
+  base_of_image                   = 0x400000
+  size_of_image                   = 0x2d000
+  checksum                        = 0x0
+  time_date_stamp                 = 0x45d35f6c 2007-02-14T19:13:48Z
+  module_name_rva                 = 0x78a
+  version_info.signature          = 0x0
+  version_info.struct_version     = 0x0
+  version_info.file_version       = 0x0:0x0
+  version_info.product_version    = 0x0:0x0
+  version_info.file_flags_mask    = 0x0
+  version_info.file_flags         = 0x0
+  version_info.file_os            = 0x0
+  version_info.file_type          = 0x0
+  version_info.file_subtype       = 0x0
+  version_info.file_date          = 0x0:0x0
+  cv_record.data_size             = 40
+  cv_record.rva                   = 0x132c
+  misc_record.data_size           = 0
+  misc_record.rva                 = 0x0
+  (code_file)                     = "c:\test_app.exe"
+  (code_identifier)               = "45d35f6c2d000"
+  (cv_record).cv_signature        = 0x53445352
+  (cv_record).signature           = 5a9832e5-2872-41c1-838e-d98914e9b7ff
+  (cv_record).age                 = 1
+  (cv_record).pdb_file_name       = "c:\test_app.pdb"
+  (misc_record)                   = (null)
+  (debug_file)                    = "c:\test_app.pdb"
+  (debug_identifier)              = "5a9832e5-2872-41c1-838e-d98914e9b7ff-1"
+  (version)                       = ""
+
+module[1]
+MINIDUMP_MODULE
+  base_of_image                   = 0x7c900000
+  size_of_image                   = 0xb0000
+  checksum                        = 0xaf2f7
+  time_date_stamp                 = 0x411096b4 2004-08-04T07:56:36Z
+  module_name_rva                 = 0x7ae
+  version_info.signature          = 0xfeef04bd
+  version_info.struct_version     = 0x10000
+  version_info.file_version       = 0x50001:0xa280884
+  version_info.product_version    = 0x50001:0xa280884
+  version_info.file_flags_mask    = 0x3f
+  version_info.file_flags         = 0x0
+  version_info.file_os            = 0x40004
+  version_info.file_type          = 0x2
+  version_info.file_subtype       = 0x0
+  version_info.file_date          = 0x0:0x0
+  cv_record.data_size             = 34
+  cv_record.rva                   = 0x1354
+  misc_record.data_size           = 0
+  misc_record.rva                 = 0x0
+  (code_file)                     = "C:\WINDOWS\system32\ntdll.dll"
+  (code_identifier)               = "411096b4b0000"
+  (cv_record).cv_signature        = 0x53445352
+  (cv_record).signature           = 36515fb5-d043-45e4-91f6-72fa2e2878c0
+  (cv_record).age                 = 2
+  (cv_record).pdb_file_name       = "ntdll.pdb"
+  (misc_record)                   = (null)
+  (debug_file)                    = "ntdll.pdb"
+  (debug_identifier)              = "36515fb5-d043-45e4-91f6-72fa2e2878c0-2"
+  (version)                       = "5.1.2600.2180"
+
+module[2]
+MINIDUMP_MODULE
+  base_of_image                   = 0x7c800000
+  size_of_image                   = 0xf4000
+  checksum                        = 0xf724d
+  time_date_stamp                 = 0x44ab9a84 2006-07-05T10:55:00Z
+  module_name_rva                 = 0x7ee
+  version_info.signature          = 0xfeef04bd
+  version_info.struct_version     = 0x10000
+  version_info.file_version       = 0x50001:0xa280b81
+  version_info.product_version    = 0x50001:0xa280b81
+  version_info.file_flags_mask    = 0x3f
+  version_info.file_flags         = 0x0
+  version_info.file_os            = 0x40004
+  version_info.file_type          = 0x2
+  version_info.file_subtype       = 0x0
+  version_info.file_date          = 0x0:0x0
+  cv_record.data_size             = 37
+  cv_record.rva                   = 0x1376
+  misc_record.data_size           = 0
+  misc_record.rva                 = 0x0
+  (code_file)                     = "C:\WINDOWS\system32\kernel32.dll"
+  (code_identifier)               = "44ab9a84f4000"
+  (cv_record).cv_signature        = 0x53445352
+  (cv_record).signature           = bce8785c-57b4-4245-a669-896b6a19b954
+  (cv_record).age                 = 2
+  (cv_record).pdb_file_name       = "kernel32.pdb"
+  (misc_record)                   = (null)
+  (debug_file)                    = "kernel32.pdb"
+  (debug_identifier)              = "bce8785c-57b4-4245-a669-896b6a19b954-2"
+  (version)                       = "5.1.2600.2945"
+
+module[3]
+MINIDUMP_MODULE
+  base_of_image                   = 0x774e0000
+  size_of_image                   = 0x13d000
+  checksum                        = 0x13dc6b
+  time_date_stamp                 = 0x42e5be93 2005-07-26T04:39:47Z
+  module_name_rva                 = 0x834
+  version_info.signature          = 0xfeef04bd
+  version_info.struct_version     = 0x10000
+  version_info.file_version       = 0x50001:0xa280aa6
+  version_info.product_version    = 0x50001:0xa280aa6
+  version_info.file_flags_mask    = 0x3f
+  version_info.file_flags         = 0x0
+  version_info.file_os            = 0x40004
+  version_info.file_type          = 0x2
+  version_info.file_subtype       = 0x0
+  version_info.file_date          = 0x0:0x0
+  cv_record.data_size             = 34
+  cv_record.rva                   = 0x139b
+  misc_record.data_size           = 0
+  misc_record.rva                 = 0x0
+  (code_file)                     = "C:\WINDOWS\system32\ole32.dll"
+  (code_identifier)               = "42e5be9313d000"
+  (cv_record).cv_signature        = 0x53445352
+  (cv_record).signature           = 683b65b2-46f4-4187-96d2-ee6d4c55eb11
+  (cv_record).age                 = 2
+  (cv_record).pdb_file_name       = "ole32.pdb"
+  (misc_record)                   = (null)
+  (debug_file)                    = "ole32.pdb"
+  (debug_identifier)              = "683b65b2-46f4-4187-96d2-ee6d4c55eb11-2"
+  (version)                       = "5.1.2600.2726"
+
+module[4]
+MINIDUMP_MODULE
+  base_of_image                   = 0x77dd0000
+  size_of_image                   = 0x9b000
+  checksum                        = 0xa0de4
+  time_date_stamp                 = 0x411096a7 2004-08-04T07:56:23Z
+  module_name_rva                 = 0x874
+  version_info.signature          = 0xfeef04bd
+  version_info.struct_version     = 0x10000
+  version_info.file_version       = 0x50001:0xa280884
+  version_info.product_version    = 0x50001:0xa280884
+  version_info.file_flags_mask    = 0x3f
+  version_info.file_flags         = 0x0
+  version_info.file_os            = 0x40004
+  version_info.file_type          = 0x2
+  version_info.file_subtype       = 0x0
+  version_info.file_date          = 0x0:0x0
+  cv_record.data_size             = 37
+  cv_record.rva                   = 0x13bd
+  misc_record.data_size           = 0
+  misc_record.rva                 = 0x0
+  (code_file)                     = "C:\WINDOWS\system32\advapi32.dll"
+  (code_identifier)               = "411096a79b000"
+  (cv_record).cv_signature        = 0x53445352
+  (cv_record).signature           = 455d6c5f-184d-45bb-b5c5-f30f82975114
+  (cv_record).age                 = 2
+  (cv_record).pdb_file_name       = "advapi32.pdb"
+  (misc_record)                   = (null)
+  (debug_file)                    = "advapi32.pdb"
+  (debug_identifier)              = "455d6c5f-184d-45bb-b5c5-f30f82975114-2"
+  (version)                       = "5.1.2600.2180"
+
+module[5]
+MINIDUMP_MODULE
+  base_of_image                   = 0x77e70000
+  size_of_image                   = 0x91000
+  checksum                        = 0x9c482
+  time_date_stamp                 = 0x411096ae 2004-08-04T07:56:30Z
+  module_name_rva                 = 0x8ba
+  version_info.signature          = 0xfeef04bd
+  version_info.struct_version     = 0x10000
+  version_info.file_version       = 0x50001:0xa280884
+  version_info.product_version    = 0x50001:0xa280884
+  version_info.file_flags_mask    = 0x3f
+  version_info.file_flags         = 0x0
+  version_info.file_os            = 0x40004
+  version_info.file_type          = 0x2
+  version_info.file_subtype       = 0x0
+  version_info.file_date          = 0x0:0x0
+  cv_record.data_size             = 35
+  cv_record.rva                   = 0x13e2
+  misc_record.data_size           = 0
+  misc_record.rva                 = 0x0
+  (code_file)                     = "C:\WINDOWS\system32\rpcrt4.dll"
+  (code_identifier)               = "411096ae91000"
+  (cv_record).cv_signature        = 0x53445352
+  (cv_record).signature           = bea45a72-1da1-41da-a3ba-86b3a2031153
+  (cv_record).age                 = 2
+  (cv_record).pdb_file_name       = "rpcrt4.pdb"
+  (misc_record)                   = (null)
+  (debug_file)                    = "rpcrt4.pdb"
+  (debug_identifier)              = "bea45a72-1da1-41da-a3ba-86b3a2031153-2"
+  (version)                       = "5.1.2600.2180"
+
+module[6]
+MINIDUMP_MODULE
+  base_of_image                   = 0x77f10000
+  size_of_image                   = 0x47000
+  checksum                        = 0x4d0d0
+  time_date_stamp                 = 0x43b34feb 2005-12-29T02:54:35Z
+  module_name_rva                 = 0x8fc
+  version_info.signature          = 0xfeef04bd
+  version_info.struct_version     = 0x10000
+  version_info.file_version       = 0x50001:0xa280b02
+  version_info.product_version    = 0x50001:0xa280b02
+  version_info.file_flags_mask    = 0x3f
+  version_info.file_flags         = 0x0
+  version_info.file_os            = 0x40004
+  version_info.file_type          = 0x2
+  version_info.file_subtype       = 0x0
+  version_info.file_date          = 0x0:0x0
+  cv_record.data_size             = 34
+  cv_record.rva                   = 0x1405
+  misc_record.data_size           = 0
+  misc_record.rva                 = 0x0
+  (code_file)                     = "C:\WINDOWS\system32\gdi32.dll"
+  (code_identifier)               = "43b34feb47000"
+  (cv_record).cv_signature        = 0x53445352
+  (cv_record).signature           = c0ea66be-00a6-4bd7-aef7-9e443a91869c
+  (cv_record).age                 = 2
+  (cv_record).pdb_file_name       = "gdi32.pdb"
+  (misc_record)                   = (null)
+  (debug_file)                    = "gdi32.pdb"
+  (debug_identifier)              = "c0ea66be-00a6-4bd7-aef7-9e443a91869c-2"
+  (version)                       = "5.1.2600.2818"
+
+module[7]
+MINIDUMP_MODULE
+  base_of_image                   = 0x77d40000
+  size_of_image                   = 0x90000
+  checksum                        = 0x9505c
+  time_date_stamp                 = 0x42260159 2005-03-02T18:09:29Z
+  module_name_rva                 = 0x93c
+  version_info.signature          = 0xfeef04bd
+  version_info.struct_version     = 0x10000
+  version_info.file_version       = 0x50001:0xa280a3e
+  version_info.product_version    = 0x50001:0xa280a3e
+  version_info.file_flags_mask    = 0x3f
+  version_info.file_flags         = 0x0
+  version_info.file_os            = 0x40004
+  version_info.file_type          = 0x2
+  version_info.file_subtype       = 0x0
+  version_info.file_date          = 0x0:0x0
+  cv_record.data_size             = 35
+  cv_record.rva                   = 0x1427
+  misc_record.data_size           = 0
+  misc_record.rva                 = 0x0
+  (code_file)                     = "C:\WINDOWS\system32\user32.dll"
+  (code_identifier)               = "4226015990000"
+  (cv_record).cv_signature        = 0x53445352
+  (cv_record).signature           = ee2b714d-83a3-4c9d-8802-7621272f8326
+  (cv_record).age                 = 2
+  (cv_record).pdb_file_name       = "user32.pdb"
+  (misc_record)                   = (null)
+  (debug_file)                    = "user32.pdb"
+  (debug_identifier)              = "ee2b714d-83a3-4c9d-8802-7621272f8326-2"
+  (version)                       = "5.1.2600.2622"
+
+module[8]
+MINIDUMP_MODULE
+  base_of_image                   = 0x77c10000
+  size_of_image                   = 0x58000
+  checksum                        = 0x57cd3
+  time_date_stamp                 = 0x41109752 2004-08-04T07:59:14Z
+  module_name_rva                 = 0x97e
+  version_info.signature          = 0xfeef04bd
+  version_info.struct_version     = 0x10000
+  version_info.file_version       = 0x70000:0xa280884
+  version_info.product_version    = 0x60001:0x21be0884
+  version_info.file_flags_mask    = 0x3f
+  version_info.file_flags         = 0x0
+  version_info.file_os            = 0x40004
+  version_info.file_type          = 0x1
+  version_info.file_subtype       = 0x0
+  version_info.file_date          = 0x0:0x0
+  cv_record.data_size             = 35
+  cv_record.rva                   = 0x144a
+  misc_record.data_size           = 0
+  misc_record.rva                 = 0x0
+  (code_file)                     = "C:\WINDOWS\system32\msvcrt.dll"
+  (code_identifier)               = "4110975258000"
+  (cv_record).cv_signature        = 0x53445352
+  (cv_record).signature           = a678f3c3-0ded-426b-8390-32b996987e38
+  (cv_record).age                 = 1
+  (cv_record).pdb_file_name       = "msvcrt.pdb"
+  (misc_record)                   = (null)
+  (debug_file)                    = "msvcrt.pdb"
+  (debug_identifier)              = "a678f3c3-0ded-426b-8390-32b996987e38-1"
+  (version)                       = "7.0.2600.2180"
+
+module[9]
+MINIDUMP_MODULE
+  base_of_image                   = 0x76390000
+  size_of_image                   = 0x1d000
+  checksum                        = 0x2a024
+  time_date_stamp                 = 0x411096ae 2004-08-04T07:56:30Z
+  module_name_rva                 = 0x9c0
+  version_info.signature          = 0xfeef04bd
+  version_info.struct_version     = 0x10000
+  version_info.file_version       = 0x50001:0xa280884
+  version_info.product_version    = 0x50001:0xa280884
+  version_info.file_flags_mask    = 0x3f
+  version_info.file_flags         = 0x0
+  version_info.file_os            = 0x40004
+  version_info.file_type          = 0x2
+  version_info.file_subtype       = 0x0
+  version_info.file_date          = 0x0:0x0
+  cv_record.data_size             = 34
+  cv_record.rva                   = 0x146d
+  misc_record.data_size           = 0
+  misc_record.rva                 = 0x0
+  (code_file)                     = "C:\WINDOWS\system32\imm32.dll"
+  (code_identifier)               = "411096ae1d000"
+  (cv_record).cv_signature        = 0x53445352
+  (cv_record).signature           = 2c17a49c-251b-4c8e-b9e2-ad13d7d9ea16
+  (cv_record).age                 = 2
+  (cv_record).pdb_file_name       = "imm32.pdb"
+  (misc_record)                   = (null)
+  (debug_file)                    = "imm32.pdb"
+  (debug_identifier)              = "2c17a49c-251b-4c8e-b9e2-ad13d7d9ea16-2"
+  (version)                       = "5.1.2600.2180"
+
+module[10]
+MINIDUMP_MODULE
+  base_of_image                   = 0x59a60000
+  size_of_image                   = 0xa1000
+  checksum                        = 0xa8824
+  time_date_stamp                 = 0x4110969a 2004-08-04T07:56:10Z
+  module_name_rva                 = 0xa00
+  version_info.signature          = 0xfeef04bd
+  version_info.struct_version     = 0x10000
+  version_info.file_version       = 0x50001:0xa280884
+  version_info.product_version    = 0x50001:0xa280884
+  version_info.file_flags_mask    = 0x3f
+  version_info.file_flags         = 0x0
+  version_info.file_os            = 0x40004
+  version_info.file_type          = 0x2
+  version_info.file_subtype       = 0x0
+  version_info.file_date          = 0x0:0x0
+  cv_record.data_size             = 36
+  cv_record.rva                   = 0x148f
+  misc_record.data_size           = 0
+  misc_record.rva                 = 0x0
+  (code_file)                     = "C:\WINDOWS\system32\dbghelp.dll"
+  (code_identifier)               = "4110969aa1000"
+  (cv_record).cv_signature        = 0x53445352
+  (cv_record).signature           = 39559573-e21b-46f2-8e28-6923be9e6a76
+  (cv_record).age                 = 1
+  (cv_record).pdb_file_name       = "dbghelp.pdb"
+  (misc_record)                   = (null)
+  (debug_file)                    = "dbghelp.pdb"
+  (debug_identifier)              = "39559573-e21b-46f2-8e28-6923be9e6a76-1"
+  (version)                       = "5.1.2600.2180"
+
+module[11]
+MINIDUMP_MODULE
+  base_of_image                   = 0x77c00000
+  size_of_image                   = 0x8000
+  checksum                        = 0x11d78
+  time_date_stamp                 = 0x411096b7 2004-08-04T07:56:39Z
+  module_name_rva                 = 0xa44
+  version_info.signature          = 0xfeef04bd
+  version_info.struct_version     = 0x10000
+  version_info.file_version       = 0x50001:0xa280884
+  version_info.product_version    = 0x50001:0xa280884
+  version_info.file_flags_mask    = 0x3f
+  version_info.file_flags         = 0x0
+  version_info.file_os            = 0x40004
+  version_info.file_type          = 0x2
+  version_info.file_subtype       = 0x0
+  version_info.file_date          = 0x0:0x0
+  cv_record.data_size             = 36
+  cv_record.rva                   = 0x14b3
+  misc_record.data_size           = 0
+  misc_record.rva                 = 0x0
+  (code_file)                     = "C:\WINDOWS\system32\version.dll"
+  (code_identifier)               = "411096b78000"
+  (cv_record).cv_signature        = 0x53445352
+  (cv_record).signature           = 180a90c4-0384-463e-82dd-c45b2c8ab76e
+  (cv_record).age                 = 2
+  (cv_record).pdb_file_name       = "version.pdb"
+  (misc_record)                   = (null)
+  (debug_file)                    = "version.pdb"
+  (debug_identifier)              = "180a90c4-0384-463e-82dd-c45b2c8ab76e-2"
+  (version)                       = "5.1.2600.2180"
+
+module[12]
+MINIDUMP_MODULE
+  base_of_image                   = 0x76bf0000
+  size_of_image                   = 0xb000
+  checksum                        = 0xa29b
+  time_date_stamp                 = 0x411096ca 2004-08-04T07:56:58Z
+  module_name_rva                 = 0xa88
+  version_info.signature          = 0xfeef04bd
+  version_info.struct_version     = 0x10000
+  version_info.file_version       = 0x50001:0xa280884
+  version_info.product_version    = 0x50001:0xa280884
+  version_info.file_flags_mask    = 0x3f
+  version_info.file_flags         = 0x0
+  version_info.file_os            = 0x40004
+  version_info.file_type          = 0x2
+  version_info.file_subtype       = 0x0
+  version_info.file_date          = 0x0:0x0
+  cv_record.data_size             = 34
+  cv_record.rva                   = 0x14d7
+  misc_record.data_size           = 0
+  misc_record.rva                 = 0x0
+  (code_file)                     = "C:\WINDOWS\system32\psapi.dll"
+  (code_identifier)               = "411096cab000"
+  (cv_record).cv_signature        = 0x53445352
+  (cv_record).signature           = a5c3a1f9-689f-43d8-ad22-8a0929388970
+  (cv_record).age                 = 2
+  (cv_record).pdb_file_name       = "psapi.pdb"
+  (misc_record)                   = (null)
+  (debug_file)                    = "psapi.pdb"
+  (debug_identifier)              = "a5c3a1f9-689f-43d8-ad22-8a0929388970-2"
+  (version)                       = "5.1.2600.2180"
+
+MinidumpMemoryList
+  region_count = 3
+
+region[0]
+MINIDUMP_MEMORY_DESCRIPTOR
+  start_of_memory_range = 0x7c90eb14
+  memory.data_size      = 0x100
+  memory.rva            = 0x1539
+Memory
+0xff83c4ec890424c744240401000000895c2408c74424100000000054e877000000c208009090909090558bec83ec508944240c64a1180000008b80a4010000890424c744240400000000c744240800000000c74424100000000054e8380000008b04248be55dc3908da424000000008d490090909090908bd40f349090909090c38da424000000008d64240090909090908d542408cd2ec3558bec9c81ecd00200008985dcfdffff898dd8fdffff8b45088b4d0489480c8d852cfdffff8988b80000008998a40000008990a800000089b0a000000089b89c0000008d4d0c8988c40000008b4d008988b40000008b4dfc8988c00000008c88bc0000008c989800
+
+region[1]
+MINIDUMP_MEMORY_DESCRIPTOR
+  start_of_memory_range = 0x12f31c
+  memory.data_size      = 0xce4
+  memory.rva            = 0x1639
+Memory
+0x00000000c0e9907ccb25807cb8070000000000000000000034ff1200b0fe12008037887c140000000100000000000000000000001000000027e0907c2e39917c0050fd7f00f0fd7f000000000400000034f312006947c788d4f31200a89a837cf825807c0000000098f312003225807cb8070000ffffffff00000000e4f31200ff1d4000b8070000ffffffffa8fa12008037887c0e1c4000a8fa120000000000ff792a0f64f91200b01b400000004000b0fe12000040020070fa1200084042000000000080fa120080fa12004e30867ca8fa12000000000000000000000000000018000002100000e3ef907c0000000079d900000000000048f41200000014003207917c0500000078071400000014000000000020f412003207917ca05f140018ee907cfa00000074f61200000000009615917ceb06917cf00298000100000000000000384f14009615917ceb06917c7801140008000000404f14000000a659985f1400080000000000000018ee907c90fea700400698003815917c184f1400eb06917c00000000800000000000a65988f69f0090f51200a569917cf8f41200d95c878880f5120043ef907c480485000500000090f5120088fea700a8212400000000000000000080f512002469917cf8fbfd7fa821240008000000f500000000000000384d850078019800a8fa120004000000a05f140096d4917c00000000b0d4917c0000000008069800184f140000000000104f140000000000184f140000004000000000010040020063003a005c0074006500730074005f006100700070002e006500780065000000000000002f00000028000000184f1400780185007801140028000000000000000000140084f3120010000000d8f5120018ee907cf006917cffffffffeb06917ce619917c88e6a7003003000001030000ff1b917c0000980080e6a70080069800400698000000980080e6a70000000000000000000000000080e6a7000818000088e6a700d759927c78019800081800000210000000009800f8f31200280a0000dcf6120018ee907cf006917cffffffffeb06917c0859927c00009800080000005859927c00000000000001000000a659000000005a6202000010000068fe030001000000dffe03000000010000000100fffffe7f0100000001c0c27700008500000000002dc0c27700000000684aaf590000a659241a917c80c0977c0000000000000000cd5c927c0050fd7f0000a65900000100080000004550fd7f0000000000000000000000000050fd7fcd5c927c05000000d4f61200f45c927c80e4977c05000000010000000050fd7f18f712007009917cc0e4977c172e817cff2d817c000000000000a6590000a6590210000000f0fd7f05000000e8f612000806980064f81200a89a837c002e817cffffffffff2d817cc8242400dd8ea75901000000000000004cf712003608a9590000a65901000000000000000100000060f71200e407a9596cf7120000004000000000010040020063003a005c0074006500730074005f006100700070002e0065007800650000006d05917c905f140000000000440d020000000000604f14000c0000007801140000000000a04e1400d84d8700400687004509917c0800000000000000000000004000000078011400a8038700404f14000510907c000000000000000078011400980387000800000008000000c0e4977cd04d8700f835887c7801140010000000a0e7ae591600000030fd1200d39b917c44000000620000000000a65950e9ae59d8eaae59a80387000100000014040000000000000100000002000000f800a659c003870001000000780114009508917c0000a659091b917c020000000900000040000000a2fd12009cfd1200404f1400a2fd1200d04d8700640187000000000000000000d04d870010000000d84d8700384f1400a803870010000000c00300000000870074fb1200404f14006cfe120018ee907cf006917cffffffffeb06917ca09d400000008700000000000400000000000000ffffff3fc04d8700ccfd12009c4d400004000000fa19917cb84d870064018700c04d8700063440000400000018000000c04d870079d90000c0038700fa31400000000000c04d8700c04d87000000000001000000b0fe120082294000c04d87000000000000000000c04d870048fe12008cfe120000000000e224400040fe12008cfe1200c04d8700d84d8700b0fe12008600817c54fa1200d8f9120000000000160018005479420079d90000000000000757917c00000200a4f91200a4f91200a4f91200020000000200000000000000c4f912000000000079d9000014fb12004cfa120014fb1200005a917c00fa1200a0fb120001000000655a917ca405817c74c1977ce705817c00000000f4fd120098fb120000000000a0fb12000000000090fb12000000800070fa120000000000000000000000000016001800547942000000000001000000000000000000000000000000000000003308917ca89a837c0000807c0000807ce800807c2cfa12001fe2907c11fa877cffffffffe06f817c000000006cfa12001c0000000f000000e06f817c8fc60000f0f312000060817cc8fa1200a89a837c7039867cfffffffff0ff1200da36847ca8fa1200099b837cb0fa120000000000b0fa12000000000000000000000000009cfb1200b8fb1200d4fa1200bf37907c9cfb1200e0ff1200b8fb120070fb1200b0ff1200d837907ce0ff120084fb12008b37907c9cfb1200e0ff1200b8fb120070fb1200a89a837c010000009cfb1200e0ff12006078937c9cfb1200e0ff1200b8fb120070fb1200a89a837c280a00009cfb12000200000018ee907c9032917cffffffff8832917c3364917c68fb1200000087003207917c02000000dc31917c1232917c8132917c8832917c1e000000c01e2400080200003807917c54fb12003207917cc4fb120018ee907c9032917c0000130000d01200beb4800088fe1200faea907c00000000b8fb12009cfb1200b8fb1200050000c000000000000000009e4240000200000001000000450000003f0001000000000000000000000000000000000000000000000000007f02ffff0000ffffffffffff0000000000002200000000000000ffff0000000018b72200000118b7220000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003b0000002300000023000000280a000002000000c1ab807c58bc420094fe12004500000088fe12009e4240001b0000004602010084fe1200230000007f0200000000220000000000000000000000000000000000801f0000ffff00000000000018b72200000100000000000018b72200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004509917c4e09917c38b622002400020024b42200020000009041917c0070fd7f0510907cccb22200000000009cb3220018ee907c7009917cc0e4977c6f3e917c623e917c08020000dcb62200b4b622001e000000000000000000000000000000000000002eb42200000000000f000000020000001e00200000fcfd7f2f63796764726976652f632f444f43554d457e312f4d4d454e544f7e312f4c4f43414c537e312f54656d7000000000000000000130b422000000004300000000000000001efcfd7f4509917c4e09917c5ad9000008b32200b4b622004500000070ff120000424000b8278700dc31917c00000000004c870000000020040000000000000007000000000000004042400000000000000000002e000000000000000cff12007b434100010000000700000084434100004d87002e39917cffffffff24000000240000002700000000000000584d870004000000b1944000244c87002a0000002f000000c0fe1200004d8700584d87000000a659b0b9a859015d400015aa400000000000b4070000784e14000000000001000000f40b00000000000000000000bc070000b8070000f40b0000a8fa120000000000009c4000599c400094b240004f752a0fc0ff1200ec534000010000003039870050398700ff752a0f00002400a02024000050fd7f050000c00100000005000000000000000000240084ff1200acfa1200e0ff1200d06f4000a70b7a0f00000000f0ff1200d76f817c00002400a02024000050fd7f050000c0c8ff1200a8fa1200ffffffffa89a837ce06f817c0000000000000000000000004354400000000000
+
+region[2]
+MINIDUMP_MEMORY_DESCRIPTOR
+  start_of_memory_range = 0x97f6e8
+  memory.data_size      = 0x918
+  memory.rva            = 0x231d
+Memory
+0x8000108020fa97009fd7907c0000000048f7970005000f0040061400000000004cf7970037b9807c00000000000000003103917c780114000000000061dc907cf1b8807c00000000ffffffff70f79700000000000000000054f797003082140001000000000000000200000000000000000000007cf7970020b9807c0e00000004000000006000000000a80078011400000000000882140092d5907c8b9b807c9c070000d0f89700780114009c07000038821400807f140020dea85910fa9700780114009807000088fb9700e07f14009c0700000000000078011400000000000882140000000000000000000000000078011400ba0300000000000000000000000000000000000000000000000000007801140000000000000000000000000000000000c0030000000000000000000000000000088214005c0057000600000078011400000000005c00730079007300740065006d0033000082140068011400000000000000000000821400d47f1400807f140070f8970061eea859e000a8000000a8001c4e000084f89700bdeea859e000a8000000a8001c4e0000a4f897005fefa8590000a8000000000006000000c4f89700e000a80060fe9700c8f897005abfa8590000a80000000000060000001c000000d47f1400807f1400380000006ce9907c88b9807cffffffff0000a80000000000807f140030fa97007fc3a859a0c4a859b0fb970060fe9700684f1400504500004c010400ca9610410000000000000000e0000e210b01070a00400000003a000000000000f1100000001000000000bf760000000000100000000200000500010005000100040000000000000000b00000000400009ba2000000001400285214000000000034fa97007801140034fa9700910e917c080614006d05917cc84d85005c4e8500684f140000000000b04800002852140078011400e00300003052140000000000000000000000000078011400c403000030821400380000000000000000000000000000000000000000000000000000003882140048050000780200003800000000100000ec000000b8470000400000000000000000000000000000000000140000000000807f140000000000000000000000000000000101a900000060fe9700dcff9700dcff97000050fd7f78fa970054fa9700ad9d917c8cfa9700c2066f7f0e000000000000001c01000078fa9700c84d850068fa970085ae807c78fc970024fc970083dba85978fc97008cfa9700800000008edba85914010000050000000100000096020000b8fc97003815917c9615917ceb06917c60fe970060fe9700c4fd9700d8fa9700000014003207917c21000000b80c14000000140030521400b0fa9700fffffffff4fc970018ee907c3807917cffffffff3207917cab06917ceb06917ccc4f140060fe9700684f1400e0004000e4fa9700a863917c74fb970018ee907c3808917cffffffff3308917c5b2c817c872c817c00000000f4fb9700000000000000000054fd970018ee907c4006140064fd97003815917c00e0fd7feb06917c684f140038821400780114000050fd7facfb970000000000000004000000000090fe97000000000018fb970050531400508b1400a89a837cffe9907cf60d817ca807000000000000ffe9907cf60d817c08000000000000000000000000000000585314003cfc97003882140000000000160e817cc4fd970060fe970058531400208f1400588b1400460f917c50531400208f1400780114003082140000000000b8fc970078011400b8fc9700910e917c080614006d05917c3882140060fe97000000000000000000960200003082140078011400ffe9907c38821400a807000000000000780114005853140058fc9700505314001000000000000000160e817c784e8500c4fd9700388214000000000024010000f00c00001000000044fc970000000000c7e2907ce721807cffffffffe8f69700388214001809000098fc9700acfc9700d2e2a859ffffffffe8f697003882140018090000c4fc97003882140060fe9700c4fd9700ccfc97004ee3a859ffffffffe8f69700000000003882140018090000e0fc9700f4fc970093b2a859ffffffffe8f69700000000003882140018090000c4fd970060fe9700a85a140078fd9700a5b3a85960fe9700c4fd9700684f1400e8f697000000000018090000000000000200000080fd9700c4fd970060fe970000000000f40b000000000000000000000000000000f0fd7f000000001cf3120000000000e40c000039160000cc020000940d00000000000000000000000000000000000002000000ac4f14006010000098fd97005eb7a8593916000000000000684f1400784e85000000000084ff9700d4fe97007bb9a85960fe9700c4fd9700684f14003849140084ff9700010000000000000008ff9700f40b0000090000000000000020000000200000006c0000008c000000380000001e000000c4000000dc000000a80000008401000064000000b801000030000000e801000080050000e8010000680700000000000068070000680700000000000005150000340000002915000039150000fc1600001d23000068070000600300008a070000c80a0000310a00002c130000f91400000c000000352c000000000000ffffffff5c0f0000c84d8500784e8500884e85000000000000000000000000004c010000fc39a6590000000002000000050000000100000000008500280a000001000001cc0200009c0000000b00000050000000040000000010000000000000000000000001000000000000000000000000000034ff970078baa859384914005c0f0000c84d850001000000884e8500684f140008ff970064ff970000000000a8070000b0fe1200f40b00009cfb120000000000b8fb12000000000000000000ae20140000000000884e8500784e8500c84d8500a8fa120011204000ffffffff5c0f0000a80700000000000078ff970064ff970000000000adbf807c2025807cecff97000100000084ff970003000000c0110000f40b0000f40b0000a8fa120000000000010067470c0000006cff9700bc070000ffffffff000000006c1a4000f40b000000fa12000000000000004000a09d4000b0fe120083b6807cb0fe120000004000a09d4000b0fe120000e0fd7f00069c86c0ff9700d8863f86ffffffffa89a837c90b6807c000000000000000000000000301a4000b0fe120000000000
+
+MINIDUMP_EXCEPTION
+  thread_id                                  = 0xbf4
+  exception_record.exception_code            = 0xc0000005
+  exception_record.exception_flags           = 0x0
+  exception_record.exception_record          = 0x0
+  exception_record.exception_address         = 0x40429e
+  exception_record.number_parameters         = 2
+  exception_record.exception_information[ 0] = 0x1
+  exception_record.exception_information[ 1] = 0x45
+  thread_context.data_size                   = 716
+  thread_context.rva                         = 0xac8
+
+CONTEXT_X86
+  context_flags                = 0x1003f
+  dr0                          = 0x0
+  dr1                          = 0x0
+  dr2                          = 0x0
+  dr3                          = 0x0
+  dr6                          = 0x0
+  dr7                          = 0x0
+  float_save.control_word      = 0xffff027f
+  float_save.status_word       = 0xffff0000
+  float_save.tag_word          = 0xffffffff
+  float_save.error_offset      = 0x0
+  float_save.error_selector    = 0x220000
+  float_save.data_offset       = 0x0
+  float_save.data_selector     = 0xffff0000
+  float_save.register_area[80] = 0x0000000018b72200000118b72200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+  float_save.cr0_npx_state     = 0x0
+  gs                           = 0x0
+  fs                           = 0x3b
+  es                           = 0x23
+  ds                           = 0x23
+  edi                          = 0xa28
+  esi                          = 0x2
+  ebx                          = 0x7c80abc1
+  edx                          = 0x42bc58
+  ecx                          = 0x12fe94
+  eax                          = 0x45
+  ebp                          = 0x12fe88
+  eip                          = 0x40429e
+  cs                           = 0x1b
+  eflags                       = 0x10246
+  esp                          = 0x12fe84
+  ss                           = 0x23
+  extended_registers[512]      = 0x7f0200000000220000000000000000000000000000000000801f0000ffff00000000000018b72200000100000000000018b72200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004509917c4e09917c38b622002400020024b42200020000009041917c0070fd7f0510907cccb22200000000009cb3220018ee907c7009917cc0e4977c6f3e917c623e917c08020000dcb62200b4b622001e000000000000000000000000000000000000002eb42200000000000f000000020000001e00200000fcfd7f2f63796764726976652f632f444f43554d457e312f4d4d454e544f7e312f4c4f43414c537e312f54656d7000000000000000000130b422000000004300000000000000001efcfd7f4509917c4e09917c5ad9000008b32200b4b62200
+
+MINIDUMP_SYSTEM_INFO
+  processor_architecture                     = 0x0
+  processor_level                            = 6
+  processor_revision                         = 0xd08
+  number_of_processors                       = 1
+  product_type                               = 1
+  major_version                              = 5
+  minor_version                              = 1
+  build_number                               = 2600
+  platform_id                                = 0x2
+  csd_version_rva                            = 0x768
+  suite_mask                                 = 0x100
+  (version)                                  = 512600 Service Pack 2
+  (cpu_info)                                 = GenuineIntel family 6 model 13 stepping 8
+
+MINIDUMP_MISC_INFO
+  size_of_info                 = 24
+  flags1                       = 3
+  process_id                   = 3932
+  process_create_time          = 0x45d35f73 2007-02-14T19:13:55Z
+  process_user_time            = 0
+  process_kernel_time          = 0
+  processor_max_mhz            = (invalid)
+  processor_current_mhz        = (invalid)
+  processor_mhz_limit          = (invalid)
+  processor_max_idle_state     = (invalid)
+  processor_current_idle_state = (invalid)
+  process_integrity_level      = (invalid)
+  process_execute_flags        = (invalid)
+  protected_process            = (invalid)
+  time_zone_id                 = (invalid)
+  time_zone                    = (invalid)
+  build_string                 = (invalid)
+  dbg_bld_str                  = (invalid)
+  xstate_data                  = (invalid)
+  process_cookie               = (invalid)
+
+MINIDUMP_BREAKPAD_INFO
+  validity             = 0x3
+  dump_thread_id       = 0x11c0
+  requesting_thread_id = 0xbf4
+
+
diff --git a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__long-help.snap b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__long-help.snap
index 04e0a0e..6215ef1 100644
--- a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__long-help.snap
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__long-help.snap
@@ -1,5 +1,6 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
+assertion_line: 380
 expression: stdout
 
 ---
@@ -22,6 +23,13 @@ FLAGS:
             The human-readable report does not have a specified format, and may not have as many details as the JSON
             format. It is intended for quickly inspecting a crash or debugging rust-minidump itself.
             
+        --dump
+            Dump the 'raw' contents of the minidump.
+            
+            This is an implementation of the functionality of the old minidump_dump tool. It minimally parses and
+            interprets the minidump in an attempt to produce a fairly 'raw' dump of the minidump's contents. This is
+            most useful for debugging minidump-stackwalk itself, or a misbehaving minidump generator.
+            
         --pretty
             Pretty-print --json output.
 
@@ -40,9 +48,9 @@ OPTIONS:
         --cyborg <cyborg>
             Combine --human and --json
             
-            Because this creates two output streams, you must specify a path to write the --json
-            output to. The --human output will be the 'primary' output and default to stdout, which
-            can be configured with --output-file as normal.
+            Because this creates two output streams, you must specify a path to write the --json output to. The --human
+            output will be the 'primary' output and default to stdout, which can be configured with --output-file as
+            normal.
             
         --output-file <output-file>
             Where to write the output to (if unspecified, stdout is used)
diff --git a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__markdown-help.snap b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__markdown-help.snap
index 17cf9b2..445f971 100644
--- a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__markdown-help.snap
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__markdown-help.snap
@@ -1,5 +1,6 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
+assertion_line: 417
 expression: stdout
 
 ---
@@ -27,6 +28,13 @@ Emit a human-readable report (the default).
 The human-readable report does not have a specified format, and may not have as many details as the JSON
 format. It is intended for quickly inspecting a crash or debugging rust-minidump itself.
 
+### `--dump`
+Dump the 'raw' contents of the minidump.
+
+This is an implementation of the functionality of the old minidump_dump tool. It minimally parses and
+interprets the minidump in an attempt to produce a fairly 'raw' dump of the minidump's contents. This is
+most useful for debugging minidump-stackwalk itself, or a misbehaving minidump generator.
+
 ### `--pretty`
 Pretty-print --json output.
 
@@ -45,9 +53,9 @@ Prints version information
 ### `--cyborg <cyborg>`
 Combine --human and --json
 
-Because this creates two output streams, you must specify a path to write the --json
-output to. The --human output will be the 'primary' output and default to stdout, which
-can be configured with --output-file as normal.
+Because this creates two output streams, you must specify a path to write the --json output to. The --human
+output will be the 'primary' output and default to stdout, which can be configured with --output-file as
+normal.
 
 ### `--output-file <output-file>`
 Where to write the output to (if unspecified, stdout is used)
diff --git a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__short-help.snap b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__short-help.snap
index 04e0a0e..6edec2d 100644
--- a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__short-help.snap
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__short-help.snap
@@ -1,5 +1,6 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
+assertion_line: 398
 expression: stdout
 
 ---
@@ -22,6 +23,13 @@ FLAGS:
             The human-readable report does not have a specified format, and may not have as many details as the JSON
             format. It is intended for quickly inspecting a crash or debugging rust-minidump itself.
             
+        --dump
+            Dump the 'raw' contents of the minidump.
+            
+            This is an implementation of the functionality of the old minidump_dump tool. It minimally parses and
+            interprets the minidump in an attempt to produce a fairly 'raw' dump of the minidump's contents. This is
+            most useful for debugging minidump-stackwalk itself, or a misbehaving minidump generator.
+            
         --pretty
             Pretty-print --json output.
 
@@ -40,9 +48,9 @@ OPTIONS:
         --cyborg <cyborg>
             Combine --human and --json
             
-            Because this creates two output streams, you must specify a path to write the --json
-            output to. The --human output will be the 'primary' output and default to stdout, which
-            can be configured with --output-file as normal.
+            Because this creates two output streams, you must specify a path to write the --json output to. The --human
+            output will be the 'primary' output and default to stdout, which can be configured with --output-file as
+            normal.
             
         --output-file <output-file>
             Where to write the output to (if unspecified, stdout is used)
diff --git a/minidump-stackwalk/tests/test-minidump-stackwalk.rs b/minidump-stackwalk/tests/test-minidump-stackwalk.rs
index 5337ac3..8c49718 100644
--- a/minidump-stackwalk/tests/test-minidump-stackwalk.rs
+++ b/minidump-stackwalk/tests/test-minidump-stackwalk.rs
@@ -37,6 +37,25 @@ fn test_output(file_name: &str) -> PathBuf {
     res
 }
 
+#[test]
+fn test_dump() {
+    let bin = env!("CARGO_BIN_EXE_minidump-stackwalk");
+    let output = Command::new(bin)
+        .arg("--dump")
+        .arg("../testdata/test.dmp")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    let stdout = String::from_utf8(output.stdout).unwrap();
+    let stderr = String::from_utf8(output.stderr).unwrap();
+
+    assert!(output.status.success());
+    insta::assert_snapshot!("dump", stdout);
+    assert_eq!(stderr, "");
+}
+
 #[test]
 fn test_json() {
     let bin = env!("CARGO_BIN_EXE_minidump-stackwalk");