Repository: luser/rust-minidump
Pull request #263 (2021-10-29 00:45:41)

Add experimental support for recovering function args + design "unstable/optional features" infra/docs
EDIT: this PR has scope-creeped into me designing the needed infrastructure for properly talking about experimental/optional features of minidump-processor/minidump-stackwalk. It was originally just introducing this feature, but I wasn't comfortable just landing it until I had designed this infra.

Original comment:

----

I ended up hacking this together today to help a coworker debug a weird crash report.
Spent the rest of the day polishing it up to make it usable.

luser: Fair enough! At one point I was exploring the idea of using this crate to provide a tool that could act as a gdb target using the gdb remote protocol, so that Linux/macOS minidumps could be post-mortem debugged similarly to the way Windows minidumps can with WinDbg/Visual Studio. The appeal of that approach was that you don't have to reinvent all the wheels that debuggers have, you just have to plumb the information from the minidump through to where gdb can get at it.
bholley: +1 to all of the reasons @Gankra gave. The gdb target thing would be cool though — even cooler if you wasmed the gdb instance and ran that in an in-browser terminal emulator inside of socorro!
gabrielesvelto: > The gdb target thing would be cool though — even cooler if you wasmed the gdb instance and ran that in an in-browser terminal emulator inside of socorro!

That escalated quickly :sweat_smile:

More seriously, having some kind of debugger-inside-Socorro would be an incredibly pleasant feature and would make crash inspection a much easier endeavor for those who don't do it every day. The biggest problem towards implementing something like that would be non-technical though: its access would have to be restricted because you'd need to be able to poke at the actual minidumps.
Gankra: Yeah I'm not sure function arguments could be exposed to non-security-clearance folks since that may contain private information, in theory.
luser: > Yeah I'm not sure function arguments could be exposed to non-security-clearance folks since that may contain private information, in theory.

Right, our general principle was that aside from the address that caused e.g. a segfault, any memory data that came directly from the minidump was assumed to potentially contain sensitive information.

We already had additional info available in Socorro behind a login when I left, so that doesn't seem like it would be a huge blocker? Especially if it was all implemented client-side, access to raw dumps is auth-protected so the code could just `fetch` the dump file and do whatever it wanted.
Gankra: Ok so anyway: r? @luser 

(at least to just the concept of including this machinery in the project)
luser: This feels like it'd fit better in the [minidump-tools](https://github.com/luser/rust-minidump/tree/master/minidump-tools) crate? That's where I've been putting ports of the weird little one-off tools I wrote to extract specific bits of information from minidumps over the years. Can this be refactored so it's a post-processing step after minidump processing, just iterating over the frames of each thread and attempting to recover the args if possible?
Gankra: It already is, actually. I just arbitrarily decided to hook it up in the stackwalk module because that's like, Where Stuff Happens in my head (after all this stackwalking work).
bholley: I think there is value in having this kind of information surfaced directly in the common tool, rather than users needing to dig for it. I believe socorro also includes the output of stackwalk (with appropriate auth), so this would get surfaced there for free.
luser: > I think there is value in having this kind of information surfaced directly in the common tool, rather than users needing to dig for it. I believe socorro also includes the output of stackwalk (with appropriate auth), so this would get surfaced there for free.

I'm not opposed to this, I pushed for getting as much data out of minidumps into a self-service form in crash-stats as possible when working on crash reporting. I don't want to speak negatively about this cool work that Gankra did, but if you're going to expose data like this from the crash reporting system, you need to make sure that you have high confidence in the data so you don't present misleading evidence to people who are investigating crashes.

For example, there's a longstanding issue with crashes on x86-64 where if the faulting address is a non-canonical pointer, the GPF from the CPU doesn't include the address, so operating systems report a bad value in the userland signal/exception (IIRC it shows up as 0 on Linux/macOS, and -1 on Windows). This isn't even Breakpad or Socorro's fault, but it has caused massive confusion over the years as a red herring to engineers.

Accurately recovering function arguments from the stack requires more debug info than is available in Breakpad-format symbol files. The DWARF and PDB files are also on the symbol server, and doing this correctly, the same way debuggers do, is plausible, but it's a lot of work. The code here is great and useful because it surfaces good-enough info with like 5% of the effort required, but I would be wary of presenting it in the crash-stats UI as objective reality.

If that's what you really want then ultimately I'm not going to stop you because you're using this for work and this is just a hobby project for me nowadays.
luser: That is to say, despite this repo being in my personal account namespace, at present I recognize @Gankra as the defacto maintainer since she's the one doing all the work. (Don't worry, you get all the power with none of the responsibility in this case! Take *that* Uncle Ben!)
bholley: I appreciate the words of caution. How about we surface the info, but caveat it appropriately? Can we enumerate the list of complications that might make it wrong, and simply link to that info from the dump?
Gankra: Rebased and update this feature to be a bit more principled:

* It's "hidden" behind a --recover-function-args flag in minidump-stackwalk
* Similarly it is now enabled in minidump-processor only if you set recover_function_args in ProcessorOptions (a new thing I added in the last evil_json PR).
Gankra: Ok so the second commit ([Add better machinery for introducing new optional/experimental features.](https://github.com/luser/rust-minidump/pull/263/commits/72bd4b04e40b85bae8773affc4cf041357ef23d0)) introduces a new scheme I have been mulling over for introducing unstable or expensive stuff in a way that lets people get the new stuff without breaking.

With that introduced, I'm more comfortable finally landing this.
Gankra: Having now properly formed a notion of what an unstable/stable feature is, I am finding myself mentally unblocked on a lot of gunk I've been meaning to garden. That last few commits:

* Add discussion of unstable features to the json-schema
* remove the sensitive/exploitability stubs completely, as they were always null and consumers should be assuming they're optional anyway.
* --raw-json is now --evil-json and properly marked as such to keep consistent with the rest of the code which refers to it as such. This is important because it is now *properly* marked as `[UNSTABLE]`, and consistent naming helps the documentation talk about unstable features. As a result, everything it uniquely outputs is now properly marked as `[UNSTABLE:evil_json]`.
Gankra: Also: yes I know I am betraying my own CONTRIBUTING docs by not actually testing this feature at all but I would need to make a new test input that's on x86 AND has symbols and god I am so tired.

Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8e4aa66..086bfdc 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -78,6 +78,32 @@ Wow that's a lot!
 Since streams are rarely perfectly unique, you can usually fill in this stuff by basing it (read: copy-pasting) the code for similar streams. For instance, if you're implementing something similar to MemoryList, then CTRL+Fing for "memory_list" in synth-minidump will generally show you what needs to be added.
 
 
+# Adding a New Analysis to minidump-processor/minidump-stackwalk
+
+Many potential analyses of minidumps can be done fairly modularly (e.g. argument recovery, exploitability estimation, disassembling the crash address, ...). For these kinds of analyses, there is some established infrastructure for adding them.
+
+New features should be added to minidump-processor (minidump-stackwalk) as unstable_all (--unstable-all) features, and the code should be given its own module. Places to modify:
+
+(FIXME: update the SHA for these links after the PR lands this message is part of lands!)
+
+* Add [a module for the feature to lib.rs](https://github.com/luser/rust-minidump/blob/d77b26b2e0d10e8f8fe5f8b944c0f142cde3a705/minidump-processor/src/lib.rs#L79)
+* Add [the feature to ProcessorOptions](https://github.com/luser/rust-minidump/blob/d77b26b2e0d10e8f8fe5f8b944c0f142cde3a705/minidump-processor/src/processor.rs#L20)
+* Fill in the values for [all of ProcessorOptions's ctors](https://github.com/luser/rust-minidump/blob/d77b26b2e0d10e8f8fe5f8b944c0f142cde3a705/minidump-processor/src/processor.rs#L26) (disabled for everything but unstable_all) 
+* Add fields for the data [to ProcessState](https://github.com/luser/rust-minidump/blob/d77b26b2e0d10e8f8fe5f8b944c0f142cde3a705/minidump-processor/src/process_state.rs#L182) (or one of its subfields)
+* Run your analysis in [process_minidump_with_options](https://github.com/luser/rust-minidump/blob/d77b26b2e0d10e8f8fe5f8b944c0f142cde3a705/minidump-processor/src/processor.rs#L73) (if the flag is set in `options`) and populate its ProcessState fields.
+* Add synthetic tests for your feature [in test_processor](https://github.com/luser/rust-minidump/blob/d77b26b2e0d10e8f8fe5f8b944c0f142cde3a705/minidump-processor/tests/test_processor.rs)
+* (Please) List your new feature in the pending release [in RELEASES.md](https://github.com/luser/rust-minidump/blob/master/RELEASES.md)
+
+* (Optional) Add support for this data to [ProcessState::print_internal](https://github.com/luser/rust-minidump/blob/d77b26b2e0d10e8f8fe5f8b944c0f142cde3a705/minidump-processor/src/process_state.rs#L465) (--human output)
+* (Optional) Add support for this data to [ProcessState::print_json](https://github.com/luser/rust-minidump/blob/d77b26b2e0d10e8f8fe5f8b944c0f142cde3a705/minidump-processor/src/process_state.rs#L672) (--json output)
+  * Update [the JSON Schema](https://github.com/luser/rust-minidump/blob/master/minidump-processor/json-schema.md)
+* If added to either output, then add [a flag to minidump-stackwalk](https://github.com/luser/rust-minidump/blob/d77b26b2e0d10e8f8fe5f8b944c0f142cde3a705/minidump-stackwalk/src/main.rs#L25)
+  * List it under the [--features flag](https://github.com/luser/rust-minidump/blob/d77b26b2e0d10e8f8fe5f8b944c0f142cde3a705/minidump-stackwalk/src/main.rs#L25)
+  * Update [the CLI docs in minidump-stackwalk's README.md](https://github.com/luser/rust-minidump/tree/master/minidump-stackwalk#minidump-stackwalk-cli-manual) with the output of --help-markdown
+  * Ensure your feature is tested [in test-minidump-stackwalk](https://github.com/luser/rust-minidump/blob/master/minidump-stackwalk/tests/test-minidump-stackwalk.rs) (may be picked up automatically)
+  * See the section below on `insta` for how to update the snapshots of these tests
+
+It's ok to not have *everything* here ready to go in your initial PR if you're concerned we might not want the feature at all. But if we do accept the feature, all of this should ideally be filled out.
 
 
 
@@ -267,7 +293,7 @@ fn test_evil_json() {
     let output = Command::new(bin)
         .arg("--json")
         .arg("--pretty")
-        .arg("--raw-json")
+        .arg("--evil-json")
         .arg("../testdata/evil.json")
         .arg("../testdata/test.dmp")
         .arg("../testdata/symbols/")
diff --git a/RELEASES.md b/RELEASES.md
index 76d73e7..9dd4979 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -170,6 +170,28 @@ As discussed in the top-level notes, allocations are generally bounded by the si
 
 # minidump-stackwalk/minidump-processor
 
+## Feature families (and --recover-function-args)
+
+You can now opt into enabling extra families of features with
+ProcessorOptions (minidump-processor) or `--features` (minidump-stackwalk).
+
+This allows us to introduce new/experimental things without messing up
+anyone with harder stability requirements.
+
+The major families are:
+
+* stable-basic (default)
+* stable-all (extra stuff)
+* unstable-all (experimental stuff)
+
+A new --recover-function-args (recover_function_args) feature has been
+added under the unstable-all category, which tries to guess the ABI
+and recover function arguments. This is currently very limited and only
+kind of works for x86 minidumps where the ABIs tend to be simpler and
+pass things on the stack.
+
+
+
 ## minidump_dump is now minidump-stackwalk --dump
 
 The old minidump_dump binary that was hidden away in the `minidump` crate has
@@ -187,6 +209,7 @@ 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
@@ -254,7 +277,11 @@ monitored, we may be able to include it in our codebase.
 See: https://github.com/luser/rust-minidump/blob/master/minidump-stackwalk/tests/test-minidump-stackwalk.rs
 
 
+## --raw-json is now --evil-json
+
+**BREAKING CHANGE (TO AN UNSTABLE FEATURE)**
 
+Mozilla's --raw-json flag has been renamed to --evil-json for consistency. It has now also been properly marked as unstable.
 
 
 # breakpad-symbols
diff --git a/breakpad-symbols/src/sym_file/walker.rs b/breakpad-symbols/src/sym_file/walker.rs
index ef79377..21e2cd1 100644
--- a/breakpad-symbols/src/sym_file/walker.rs
+++ b/breakpad-symbols/src/sym_file/walker.rs
@@ -746,6 +746,7 @@ pub fn eval_win_expr_for_fuzzer(
     eval_win_expr(expr, info, walker)
 }
 
+#[allow(clippy::map_flatten)]
 fn eval_win_expr(expr: &str, info: &StackInfoWin, walker: &mut dyn FrameWalker) -> Option<()> {
     // TODO?: do a bunch of heuristics to make this more robust.
     // So far I haven't encountered an in-the-wild example that needs the
diff --git a/minidump-common/src/format.rs b/minidump-common/src/format.rs
index 62e7f84..b75bb07 100644
--- a/minidump-common/src/format.rs
+++ b/minidump-common/src/format.rs
@@ -91,6 +91,20 @@ pub struct MINIDUMP_MEMORY_DESCRIPTOR {
     pub memory: MINIDUMP_LOCATION_DESCRIPTOR,
 }
 
+/// A large range of memory contained within a minidump (usually a full dump)
+/// consisting of a base address and a size.
+///
+/// This struct matches the [Microsoft struct][msdn] of the same name.
+///
+/// [msdn]: https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_memory_descriptor64
+#[derive(Debug, Copy, Clone, Default, Pread, SizeWith)]
+pub struct MINIDUMP_MEMORY_DESCRIPTOR64 {
+    /// The base address of this memory range from the process.
+    pub start_of_memory_range: u64,
+    /// The size of this data.
+    pub data_size: u64,
+}
+
 /// Information about a data stream contained in a minidump file.
 ///
 /// The minidump header contains a pointer to a list of these structs which allows locating
@@ -161,6 +175,15 @@ pub enum MINIDUMP_STREAM_TYPE {
     /// See [`MINIDUMP_SYSTEM_INFO`].
     SystemInfoStream = 7,
     ThreadExListStream = 8,
+    /// The list of large memory regions from the process contained within this dump
+    ///
+    /// See [`MINIDUMP_MEMORY_DESCRIPTOR64`].
+    ///
+    /// Microsoft declares a [`MINIDUMP_MEMORY64_LIST`][list] struct which is the actual format
+    /// of this stream, but it is a variable-length struct so no matching definition is provided
+    /// in this crate.
+    ///
+    /// [list]: https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_memory64_list
     Memory64ListStream = 9,
     CommentStreamA = 10,
     CommentStreamW = 11,
diff --git a/minidump-processor/fuzz/fuzz_targets/process.rs b/minidump-processor/fuzz/fuzz_targets/process.rs
index 88dcaaa..48b3eff 100644
--- a/minidump-processor/fuzz/fuzz_targets/process.rs
+++ b/minidump-processor/fuzz/fuzz_targets/process.rs
@@ -22,9 +22,11 @@ fuzz_target!(|data: (&[u8], &[u8])| {
         };
 
         let provider = minidump_processor::Symbolizer::new(supplier);
+        // Fuzz every possible feature
+        let options = minidump_processor::ProcessorOptions::unstable_all();
 
         let val: Result<_, _> = minidump_processor_fuzz::fuzzing_block_on(
-            minidump_processor::process_minidump(&dump, &provider),
+            minidump_processor::process_minidump_with_options(&dump, &provider, options),
         );
 
         if let Ok(v) = val {
diff --git a/minidump-processor/json-schema.md b/minidump-processor/json-schema.md
index 224374b..daaaa63 100644
--- a/minidump-processor/json-schema.md
+++ b/minidump-processor/json-schema.md
@@ -10,7 +10,20 @@ In the current implementation some data is always required and some fields alway
 
 So when we say the schema is "stable" we are really just saying that we aren't going to rename fields or change their type (int, string, object...) or semantics. And of course we'll always do our best to populate an existing field when the required data is available! While we can *technically* deprecate a field and stop emitting it completely under this definition of "stable", we will try to avoid doing this unless there is some significant issue with the field (or it never contained a useful value anyway).
 
-As we add more experimental and unreliable analyses (like trying to recover function args, trying to detect bitflips, trying to detect exploitable crashes, etc.) they may be hidden behind flags in minidump-stackwalk or [ProcessorOptions](https://docs.rs/minidump-processor/latest/minidump_processor/struct.ProcessorOptions.html). The precise details of how this will work and how this will be specified in this document still need to be hashed out (it would be *nice* if we could have "unstable" analyses that we can tentatively ship and remove later if they're a problem).
+# Optional Analyses
+
+As we add more experimental and unreliable analyses (like trying to recover function args, trying to detect bitflips, trying to detect exploitable crashes, etc.) they may be hidden behind flags in minidump-stackwalk or [ProcessorOptions](https://docs.rs/minidump-processor/latest/minidump_processor/struct.ProcessorOptions.html).
+
+The default configuration will be fairly conservative, and our strictest stability guarantees can only be given for that configuation. If you opt into a feature that's marked **\[UNSTABLE\]**, its presence and representation in the schema is also unstable. These fields will be marked as `[UNSTABLE:my_feature_name]` in the schema, where `my_feature_name` is the name of the option in ProcessorOptions, and `--my-feature-name` is the name of the feature in minidump-stackwalk.
+
+Features are grouped under three families which can be used for bulk enabling:
+
+* stable_basic (`--features=stable-basic`): the default configuration, guaranteed stable, always there. All fields in the schema are stable_basic unless otherwise stated (In the future some stable-basic features may be made optional, but they will still be enabled by default.)
+
+* stable_all (`--features=stable-all`): also guaranteed stable, but disabled by default.
+
+* unstable_all (`--features=unstable-all`): unstable, may change or be removed.
+
 
 # Types
 
@@ -321,7 +334,7 @@ anyway.
   "main_module": <u32>,
 
   // Whether any modules have code signing information (redundant).
-  "modules_contains_cert_info": <bool>,
+  [UNSTABLE:evil_json] "modules_contains_cert_info": <bool>,
 
   // All the known modules that are currently mapped into the process.
   //
@@ -388,7 +401,7 @@ anyway.
       // e.g.
       // * "Microsoft Windows"
       // * "Mozilla Corporation"
-      "cert_subject": <string>,
+      [UNSTABLE:evil_json] "cert_subject": <string>,
 
 
 
@@ -434,7 +447,7 @@ anyway.
       "end_addr": <hexstring>,
       "code_id": <string>,
       "filename": <string>,
-      "cert_subject": <string>,
+      [UNSTABLE:evil_json] "cert_subject": <string>,
     }
   ], // unloaded_modules
 
@@ -494,31 +507,6 @@ anyway.
     ] // records
   }, // mac_crash_info
 
-
-
-
-
-
-  // Ostensibly where extra-security-sensitive information should be stored,
-  // to facilitate stripping it out for users who have lower "security clearance"
-  // or whatever. It's unclear how useful this is given crash info is already
-  // prone to containing random sensitive data, but hey, it's here if you want it?
-  "sensitive": {
-
-    // This feature is completely unimplemented, but remains here for historical
-    // reasons. (I stubbed it out and never bothered to remove it. We should just
-    // deprecate this since users have to assume it's optional anyway.)
-    //
-    // If this **was** implemented, it would contain an enum-string describing
-    // how "exploitable" rust-minidump thinks the crash is. e.g. crashing on
-    // an assertion is no concern, crashing on a null pointer is a *little*
-    // concerning, and segfaulting the instruction pointer is a *huge* concern.
-    //
-    // This is a feature breakpad supports, but the signal-to-noise ratio isn't
-    // very good, so we found people were just ignoring it. As such, we haven't
-    // bothered to reimplement this, and may never.
-    "exploitability": <string>,
-  } // sensitive
 }
 ```
 
@@ -536,3 +524,20 @@ anyway.
 `crashing_thread.thread_index` renamed to `crashing_thread.threads_index`
 
 This was actually always supposed to be the name, we just typoed it and didn't notice before publishing. It's soon enough that we'd rather just fix it rather than eternally have two copies of the field. Sorry!
+
+
+## 0.10.0 (Upcoming Release)
+
+### sensitive and exploitability fully removed
+
+The top level `sensitive` field and its child `exploitability` field have been removed from the schema since they were already optional and never contained any real output. These features were never implemented, but they were stubbed out and made it into the schema simply because we were emitting these dummy fields.
+
+While the idea of a "sensitive" section that can be stripped for data-security purposes is appealing, in reality it isn't really useful because *lots* of information in this report potentially contains sensitive user information. It's up to your organization to decide who can see which fields.
+
+### evil-json now used uniformly and properly indicated as unstable
+
+The evil_json feature has always been an "ideally temporary" hack for Mozilla's workflow, and has therefore always been defacto unstable, but we didn't have the terminology to refer to this. Now it's properly marked as unstable. Mozilla is free to mess with it however it wants.
+
+The codebase also interchangeably referred to the evil_json feature as both evil_json and raw_json. It is now always evil_json to properly express that this is an evil feature that you should not use unless you are mozilla (and if you are mozilla, you should also stop using it).
+
+This changes the `--raw-json` flag of minidump-stackwalk to `--evil-json`
\ No newline at end of file
diff --git a/minidump-processor/src/arg_recovery.rs b/minidump-processor/src/arg_recovery.rs
new file mode 100644
index 0000000..a092ea5
--- /dev/null
+++ b/minidump-processor/src/arg_recovery.rs
@@ -0,0 +1,235 @@
+use crate::{CallStack, CallingConvention, FunctionArg, FunctionArgs};
+use minidump::{CpuContext, MinidumpMemory, MinidumpRawContext};
+
+// # Recovering x86 function arguments
+//
+// This approach is very hacky and very likely to produce incorrect values
+// in many situations. But sometimes it will be right, and that's useful, right?
+//
+// x86 has two common calling conventions which are very friendly to recovering
+// function arguments: cdecl and thiscall.
+//
+// In both conventions, arguments are unconditionally pushed to the
+// stack in reverse order, so in theory all we need to do is jump to the
+// top of the caller's stack frame and just start reading the values off!
+//
+// (thiscall requires some special handling for the implicit "this" arg, see below.)
+//
+// This leaves us with several problems to solve:
+//
+// 1. determining the calling convention
+// 2. determining the number (and name) of arguments
+// 3. determining the size of arguments
+//
+//
+//
+// ## Determining the calling convention
+//
+// We don't have the kind of debuginfo that would tell use what the calling
+// convention was, but we can make an educated guess based on the name of
+// the function:
+//
+// * If the function name contains a ::, it's probably a C++ member function,
+//   in which case it's thiscall
+// * Otherwise, assume it's cdecl
+//
+// It's a blunt heuristic that will misclassify static functions and always
+// mishandle anything that's manually defined to be fastcall or whatever else,
+// but it should do the right thing for *most* functions!
+//
+//
+//
+// ## Determining the number of arguments
+//
+// We assume a function name includes its argument list if it contains
+// both an open-paren "(" and close-paren ")" in the right order. Everything
+// between the first open-paren and last close-paren is assumed to be the
+// argument list.
+//
+// The number of arguments is then just "the number of commas in the argument list".
+// However C++ templates introduce "fake" commas. This can be easily handled by
+// tracking the nesting level of `<` and `>` and only considering a comma "real"
+// if the nesting level is 0. We similarly handle `(` and `)` to try to handle
+// function pointer types.
+//
+// thiscall functions have an implicit first argument "this". Windows (Visual C++)
+// toolchains will pass "this" via $eax instead of on the stack. Other (gcc)
+// toolchains will pass "this" just like any other argument (so it will be at
+// the top of the stack frame).
+//
+//
+//
+// # Determining the size of arguments
+//
+// Rather than attempting to parse and resolve C++ types (*laughs and cries at the same time*),
+// we just unconditionally assume all arguments are pointer-sized. This is intuitively true
+// most of the time. The major exceptions are `bool` and `uint64_t`. Maybe those
+// are worth carving out special cases for, but until then: it's all pointers!
+
+/// Try to recover function arguments
+pub fn fill_arguments(call_stack: &mut CallStack, stack_memory: Option<&MinidumpMemory>) {
+    // Collect up all the results at once to avoid borrowing issues.
+    let args = call_stack
+        .frames
+        .iter()
+        .enumerate()
+        .map(|(frame_idx, frame)| {
+            // Only x86 is implemented because it has friendly calling conventions.
+            // and we need the function name to make any guesses at what the arguments are.
+            if let (Some(mem), Some(func_name), MinidumpRawContext::X86(ctx)) =
+                (stack_memory, &frame.function_name, &frame.context.raw)
+            {
+                const POINTER_WIDTH: u64 = 4;
+
+                if let Some((calling_convention, argument_list)) = parse_x86_arg_list(func_name) {
+                    // We're assuming this is either cdecl or thiscall. In either case,
+                    // all the arguments are saved at the top of the caller's stackframe
+                    // in reverse order (which in fact means we can start at the top
+                    // of the frame and read them off *in order*).
+
+                    // The stack grows down, so the maximum address in the stack
+                    // is actually the base of the stack. Since we're walking down
+                    // the stack, the base of the stack is a good upper-bound
+                    // (and default value) for any stack/frame pointer.
+                    let stack_base = mem.base_address.saturating_add(mem.size);
+
+                    let caller_stack_pointer = call_stack
+                        .frames
+                        .get(frame_idx + 1)
+                        .map(|f| f.context.get_stack_pointer())
+                        .unwrap_or(stack_base);
+                    let caller_frame_pointer = call_stack
+                        .frames
+                        .get(frame_idx + 2)
+                        .map(|f| f.context.get_stack_pointer())
+                        .unwrap_or(stack_base);
+
+                    let mut read_head = caller_stack_pointer;
+                    let mut pop_value = || {
+                        if read_head < caller_frame_pointer {
+                            let val = mem.get_memory_at_address::<u32>(read_head);
+                            read_head += POINTER_WIDTH;
+                            val.map(|val| val as u64)
+                        } else {
+                            None
+                        }
+                    };
+
+                    let mut args = Vec::new();
+
+                    // Handle the first argument of thiscall
+                    match calling_convention {
+                        CallingConvention::WindowsThisCall => {
+                            // On windows, "this" is passed in eax
+                            let value = ctx
+                                .get_register("eax", &frame.context.valid)
+                                .map(|x| x as u64);
+                            args.push(FunctionArg {
+                                name: String::from("this"),
+                                value,
+                            });
+                        }
+                        CallingConvention::OtherThisCall => {
+                            // Everywhere else, "this" is passed like a normal value
+                            let value = pop_value();
+                            args.push(FunctionArg {
+                                name: String::from("this"),
+                                value,
+                            });
+                        }
+                        CallingConvention::Cdecl => {
+                            // Nothing to do
+                        }
+                    }
+
+                    // Now handle the rest
+                    args.extend(argument_list.iter().map(|&arg_name| {
+                        let value = pop_value();
+                        FunctionArg {
+                            name: String::from(arg_name),
+                            value,
+                        }
+                    }));
+
+                    return Some(FunctionArgs {
+                        calling_convention,
+                        args,
+                    });
+                }
+            }
+            None
+        })
+        .collect::<Vec<_>>();
+
+    // Now write the values back to the call stack
+    for (frame, args) in call_stack.frames.iter_mut().zip(args) {
+        frame.arguments = args;
+    }
+}
+
+fn parse_x86_arg_list(func_name: &str) -> Option<(CallingConvention, Vec<&str>)> {
+    if let Some((func_name, arg_list)) = func_name.split_once('(') {
+        if let Some((arg_list, _junk)) = arg_list.rsplit_once(')') {
+            let calling_convention = if func_name.contains("::") {
+                // Assume this is a C++ method (thiscall)
+                let windows = true; // TODO
+                if windows {
+                    CallingConvention::WindowsThisCall
+                } else {
+                    CallingConvention::OtherThisCall
+                }
+            } else {
+                CallingConvention::Cdecl
+                // Assume this is a static function (cdecl)
+            };
+
+            let mut args = Vec::new();
+
+            // Now parse the arguments out
+            let mut arg_start = 0;
+            let mut template_depth = 0;
+            let mut paren_depth = 0;
+
+            // Only consider a comma a "real" argument separator if we aren't
+            // currently nested inside of templates (`<>`) or parens (`()`).
+            for (idx, c) in arg_list.bytes().enumerate() {
+                match c as char {
+                    '<' => template_depth += 1,
+                    '>' => {
+                        if template_depth > 0 {
+                            template_depth -= 1;
+                        } else {
+                            // Parser is lost
+                            return None;
+                        }
+                    }
+                    '(' => paren_depth += 1,
+                    ')' => {
+                        if paren_depth > 0 {
+                            paren_depth -= 1;
+                        } else {
+                            // Parser is lost
+                            return None;
+                        }
+                    }
+                    ',' => {
+                        if template_depth == 0 && paren_depth == 0 {
+                            args.push(arg_list[arg_start..idx].trim());
+                            arg_start = idx + 1;
+                        }
+                    }
+                    _ => {}
+                }
+            }
+
+            // Whole function name parsed, the remainder is the last argument.
+            args.push(arg_list[arg_start..].trim());
+
+            // Only accept the result if all nesting was balanced
+            if template_depth == 0 && paren_depth == 0 {
+                return Some((calling_convention, args));
+            }
+        }
+    }
+    None
+}
diff --git a/minidump-processor/src/lib.rs b/minidump-processor/src/lib.rs
index 61ade2c..1bffe94 100644
--- a/minidump-processor/src/lib.rs
+++ b/minidump-processor/src/lib.rs
@@ -76,6 +76,7 @@
 #[cfg(doctest)]
 doc_comment::doctest!("../README.md");
 
+mod arg_recovery;
 mod evil;
 mod process_state;
 mod processor;
diff --git a/minidump-processor/src/process_state.rs b/minidump-processor/src/process_state.rs
index 932c916..1bac5b7 100644
--- a/minidump-processor/src/process_state.rs
+++ b/minidump-processor/src/process_state.rs
@@ -36,6 +36,32 @@ pub enum FrameTrust {
     Context,
 }
 
+#[derive(Debug)]
+pub enum CallingConvention {
+    Cdecl,
+    WindowsThisCall,
+    OtherThisCall,
+}
+
+/// Arguments for this function
+#[derive(Debug)]
+pub struct FunctionArgs {
+    /// What we assumed the calling convention was
+    pub calling_convention: CallingConvention,
+
+    /// The actual arguments
+    pub args: Vec<FunctionArg>,
+}
+
+/// A function argument
+#[derive(Debug)]
+pub struct FunctionArg {
+    /// The name of the argument (usually actually just the type)
+    pub name: String,
+    /// The value of the argument
+    pub value: Option<u64>,
+}
+
 /// A single stack frame produced from unwinding a thread's stack.
 #[derive(Debug)]
 pub struct StackFrame {
@@ -136,6 +162,9 @@ pub struct StackFrame {
 
     /// The CPU context containing register state for this frame.
     pub context: MinidumpContext,
+
+    /// Any function args we recovered.
+    pub arguments: Option<FunctionArgs>,
 }
 
 /// Information about the results of unwinding a thread's stack.
@@ -274,6 +303,7 @@ impl StackFrame {
             source_file_name: None,
             source_line: None,
             source_line_base: None,
+            arguments: None,
             trust,
             context,
         }
@@ -430,6 +460,38 @@ impl CallStack {
             writeln!(f)?;
             print_registers(f, &frame.context)?;
             writeln!(f, "    Found by: {}", frame.trust.description())?;
+
+            if let Some(args) = &frame.arguments {
+                use MinidumpRawContext::*;
+                let pointer_width = match &frame.context.raw {
+                    X86(_) | Ppc(_) | Sparc(_) | Arm(_) | Mips(_) => 4,
+                    Ppc64(_) | Amd64(_) | Arm64(_) | OldArm64(_) => 8,
+                };
+
+                let cc_summary = match args.calling_convention {
+                    CallingConvention::Cdecl => "cdecl [static function]",
+                    CallingConvention::WindowsThisCall => "windows thiscall [C++ member function]",
+                    CallingConvention::OtherThisCall => {
+                        "non-windows thiscall [C++ member function]"
+                    }
+                };
+
+                writeln!(f, "    Arguments (assuming {})", cc_summary)?;
+                for (idx, arg) in args.args.iter().enumerate() {
+                    if let Some(val) = arg.value {
+                        if pointer_width == 4 {
+                            writeln!(f, "        arg {} ({}) = 0x{:08x}", idx, arg.name, val)?;
+                        } else {
+                            writeln!(f, "        arg {} ({}) = 0x{:016x}", idx, arg.name, val)?;
+                        }
+                    } else {
+                        writeln!(f, "        arg {} ({}) = <unknown>", idx, arg.name)?;
+                    }
+                }
+                // Add an extra new-line between frames when there's function arguments to make
+                // it more readable.
+                writeln!(f)?;
+            }
         }
         Ok(())
     }
@@ -725,6 +787,7 @@ Unknown streams encountered:
 
             // the first module is always the main one
             "main_module": 0,
+            // [UNSTABLE:evil_json]
             "modules_contains_cert_info": !self.cert_info.is_empty(),
             "modules": self.modules.iter().map(|module| {
                 let full_name = module.code_file();
@@ -749,6 +812,7 @@ Unknown streams encountered:
                     "filename": &name,
                     "code_id": module.code_identifier().as_str(),
                     "version": module.version(),
+                    // [UNSTABLE:evil_json]
                     "cert_subject": self.cert_info.get(name),
 
                     // These are all just metrics for debugging minidump-processor's execution
@@ -813,12 +877,6 @@ Unknown streams encountered:
                 "filename": module.name,
                 "cert_subject": self.cert_info.get(&module.name),
             })).collect::<Vec<_>>(),
-
-            "sensitive": {
-                // TODO: Issue #25
-                // low | medium | high | interesting | none | ERROR: *
-                "exploitability": null,
-            }
         });
 
         if let Some(requesting_thread) = self.requesting_thread {
diff --git a/minidump-processor/src/processor.rs b/minidump-processor/src/processor.rs
index 14650e3..d171a91 100644
--- a/minidump-processor/src/processor.rs
+++ b/minidump-processor/src/processor.rs
@@ -8,18 +8,139 @@ use std::time::{Duration, SystemTime};
 
 use minidump::{self, *};
 
+use crate::arg_recovery;
 use crate::evil;
 use crate::process_state::{CallStack, CallStackInfo, LinuxStandardBase, ProcessState};
 use crate::stackwalker;
 use crate::symbols::*;
 use crate::system_info::SystemInfo;
 
-/// Various advanced options for the processor.
-#[derive(Default, Debug, Clone)]
+/// Configuration of the processor's exact behaviour.
+///
+/// This can be used to either:
+///
+/// * enable extra features that are disabled by default
+/// * lock in the features you want enabled to minimize future changes
+///
+/// All fields are `pub`, but the type is `non_exhaustive`.
+/// Recommended usage is to call one of the constructors to get a baseline
+/// set of features, and then manually set any values you particularly care about.
+///
+/// If we decide an unstable feature exposed by these flags is a bad idea,
+/// we may remove its functionality and turn it into a noop, but the flag
+/// will remain to avoid breaking code. Similarly, if a feature seems to be
+/// too bloated, its implementation may be hidden behind a cargo feature
+/// flag, producing a similar result if that feature is statically disabled.
+///
+/// In either of these cases, a `warn` diagnostic will be emitted if you
+/// try to use request a feature whose implementation does not exist.
+///
+/// [`process_minidump`][] uses [`ProcessorOptions::stable_basic`][], which
+/// is also exposed as [`Default::default`].
+///
+/// ## Example:
+///
+/// ```
+/// use minidump_processor::ProcessorOptions;
+///
+/// // Happy with the default-enabled features
+/// let mut options = ProcessorOptions::stable_basic();
+/// // But specifically want this cool unstable feature
+/// options.recover_function_args = true;
+/// ```
+///
+#[derive(Debug, Clone)]
 #[non_exhaustive]
 pub struct ProcessorOptions<'a> {
-    /// The evil "raw json" mozilla's legacy infrastructure relies on (to be phased out).
+    /// **\[UNSTABLE\]** The evil "raw json" mozilla's legacy infrastructure relies on.
+    ///
+    /// Please don't use this. If you have to use this, you know who you are.
     pub evil_json: Option<&'a Path>,
+
+    /// **\[UNSTABLE\]** Whether to try to heuristically recover function arguments in backtraces.
+    ///
+    /// Currently this only work for x86, and assumes everything is either cdecl or thiscall
+    /// (inferred from whether the symbol name looks like a static function or a method).
+    pub recover_function_args: bool,
+}
+
+impl ProcessorOptions<'_> {
+    /// "Do the normal stuff everyone should want"
+    ///
+    /// * `evil_json: None`
+    /// * `recover_function_args: false`
+    ///
+    /// Unlike stable_all, you shouldn't expect this to change its results much.
+    ///
+    /// It will specifically always try to:
+    ///
+    /// * Perform full backtraces and symbolication of every thread.
+    /// * Produce detailed system info (OS, Cpu, Versions...)
+    /// * Produce detailed crash info (Crashing thread, crash address, formatted error...)
+    /// * List loaded and unloaded modules
+    pub fn stable_basic() -> Self {
+        ProcessorOptions {
+            evil_json: None,
+            recover_function_args: false,
+        }
+    }
+
+    /// "Turn all the stable features on"
+    ///
+    /// * `evil_json: None`
+    /// * `recover_function_args: false`
+    ///
+    /// (At this precise moment this is identical to stable_basic, but may diverge
+    /// as we introduce more features.)
+    ///
+    /// Everything included by stable_basic, but willing to enable more interesting
+    /// features and spend extra time trying to find extra insights. This is the default
+    /// place that unstable features will "graduate" to when they're deemed good enough.
+    pub fn stable_all() -> Self {
+        ProcessorOptions {
+            evil_json: None,
+            recover_function_args: false,
+        }
+    }
+
+    /// "Turn EVERYTHING on, even the experimental stuff!"
+    ///
+    /// * `evil_json: None`
+    /// * `recover_function_args: true`
+    ///
+    /// (evil_json is still "disabled" because you need to give it needs a path.)
+    ///
+    /// Some of this stuff can be really jank, use at your own risk!
+    pub fn unstable_all() -> Self {
+        ProcessorOptions {
+            evil_json: None,
+            recover_function_args: true,
+        }
+    }
+
+    /// Check if any of the enabled features are deprecated or disabled
+    /// and emit warnings if they are.
+    fn check_deprecated_and_disabled(&self) {
+        // Currently nothing is deprecated / disableable, but here's the template.
+
+        /*
+        use log::warn;
+
+        if self.my_bad_feature {
+            warn!("Deprecated ProcessorOption my_bad_feature has been removed and does nothing.")
+        }
+
+        if !cfg!(feature = "my-optional-feature") && self.my_optional_feature {
+            warn!("Disabled ProcessorOption my_optional_feature must be enabled via cargo.")
+        }
+        */
+    }
+}
+
+impl Default for ProcessorOptions<'_> {
+    fn default() -> Self {
+        Self::stable_basic()
+    }
 }
 
 /// An error encountered during minidump processing.
@@ -35,7 +156,10 @@ pub enum ProcessError {
     MissingThreadList,
 }
 
-/// Unwind all threads in `dump` and return a `ProcessState`.
+/// Unwind all threads in `dump` and return a report as a `ProcessState`.
+///
+/// This is equivalent to [`process_minidump_with_options`] with
+/// [`ProcessorOptions::stable_basic`][].
 ///
 /// # Examples
 ///
@@ -69,7 +193,10 @@ where
     process_minidump_with_options(dump, symbol_provider, ProcessorOptions::default()).await
 }
 
-/// The same as [`process_minidump`] but with extra options.
+/// Process `dump` with the given options and return a report as a `ProcessState`.
+///
+/// See [`ProcessorOptions`][] for details on the specific features that can be
+/// enabled and how to choose them.
 pub async fn process_minidump_with_options<'a, T, P>(
     dump: &Minidump<'a, T>,
     symbol_provider: &P,
@@ -79,6 +206,8 @@ where
     T: Deref<Target = [u8]> + 'a,
     P: SymbolProvider + Sync,
 {
+    options.check_deprecated_and_disabled();
+
     // Thread list is required for processing.
     let thread_list = dump
         .get_stream::<MinidumpThreadList>()
@@ -234,17 +363,16 @@ where
             .unwrap_or(false)
         {
             requesting_thread = Some(i);
-            exception_context
-                .as_deref()
-                .or_else(|| thread_context.as_deref())
+            exception_context.as_deref().or(thread_context.as_deref())
         } else {
             thread_context.as_deref()
         };
 
-        let stack = thread.stack_memory(&memory_list);
+        let stack_memory = thread.stack_memory(&memory_list);
 
         let mut stack =
-            stackwalker::walk_stack(&context, stack.as_deref(), &modules, symbol_provider).await;
+            stackwalker::walk_stack(&context, stack_memory.as_deref(), &modules, symbol_provider)
+                .await;
         stack.thread_id = id;
         for frame in &mut stack.frames {
             // If the frame doesn't have a loaded module, try to find an unloaded module
@@ -272,6 +400,10 @@ where
 
         stack.last_error_value = thread.last_error(system_info.cpu, &memory_list);
 
+        if options.recover_function_args {
+            arg_recovery::fill_arguments(&mut stack, stack_memory.as_deref());
+        }
+
         threads.push(stack);
     }
 
diff --git a/minidump-processor/src/stackwalker/mod.rs b/minidump-processor/src/stackwalker/mod.rs
index b103ba4..c237cb0 100644
--- a/minidump-processor/src/stackwalker/mod.rs
+++ b/minidump-processor/src/stackwalker/mod.rs
@@ -211,6 +211,7 @@ where
     } else {
         info = CallStackInfo::MissingContext;
     }
+
     CallStack {
         frames,
         info,
diff --git a/minidump-stackwalk/README.md b/minidump-stackwalk/README.md
index 8c8e86a..f2ea5ae 100644
--- a/minidump-stackwalk/README.md
+++ b/minidump-stackwalk/README.md
@@ -19,6 +19,11 @@ Full documentation of the CLI can be found in the "minidump-stackwalk CLI manual
 (`--help` will produce the same output).
 
 
+# Enabling Extra Analysis
+
+The [--features](#--features-features) flag can be used to blindly opt into "more analysis". See that
+entry in the CLI docs for more details on choosing an appropriate value, but the tl;dr is that you can
+turn everything on and checkout "what's new" with `--features=unstable-all`.
 
 
 # Output Formats
@@ -215,6 +220,12 @@ Pretty-print --json output.
 Provide a briefer --human report.
 
 Only provides the top-level summary and a backtrace of the crashing thread.
+
+### `--recover-function-args`
+**[UNSTABLE]** Heuristically recover function arguments
+
+This is an experimental feature, which currently only shows up in --human output.
+
 ### `-h, --help`
 Prints help information
 
@@ -230,6 +241,35 @@ Because this creates two output streams, you must specify a path to write the --
 output will be the 'primary' output and default to stdout, which can be configured with --output-file as
 normal.
 
+### `--features <features>`
+Specify at a high-level how much analysis to perform.
+
+This flag provides a way to more blindly opt into Extra Analysis without having to know about the specific
+features of minidump-stackwalk. This is equivalent to ProcessorOptions in minidump-processor. The current
+supported values are:
+
+* stable-basic (default): give me solid detailed analysis that most people would want.
+* stable-all: turn on extra detailed analysis.
+* unstable-all: turn on the weird and experimental stuff.
+
+stable-all enables: nothing (currently identical to stable-basic)
+
+unstable-all enables: `--recover-function-args`
+
+minidump-stackwalk wants to be a reliable and stable tool, but we also want to be able to introduce new
+features which may be experimental or expensive. To balance these two concerns, new features will usually be
+disabled by default and given a specific flag, but still more easily 'discovered' by anyone who uses this
+flag.
+
+Anyone using minidump-stackwalk who is *really* worried about the output being stable should probably not
+use this flag in production, but its use is recommended for casual
+human usage or for checking "what's new".
+
+Features under unstable-all may be deprecated and become noops. Features which require additional input
+(such as `--evil-json`) cannot be affected by this, and must still be manually 'discovered'.
+
+
+\[default: stable-basic]  [possible values: stable-basic, stable-all, unstable-all]
 ### `--output-file <output-file>`
 Where to write the output to (if unspecified, stdout is used)
 
@@ -245,8 +285,8 @@ happened the way it did, --verbose=trace is very useful (all unwinder logging wi
 
 
 \[default: error]  [possible values: off, error, warn, info, debug, trace]
-### `--raw-json <raw-json>`
-An input JSON file with the extra information.
+### `--evil-json <evil-json>`
+**[UNSTABLE]** An input JSON file with the extra information.
 
 This is a gross hack for some legacy side-channel information that mozilla uses. It will hopefully be phased
 out and deprecated in favour of just using custom streams in the minidump itself.
@@ -334,3 +374,4 @@ recommend using a version of dump_syms to generate them.
 See:
 * symbol file docs: https://chromium.googlesource.com/breakpad/breakpad/+/master/docs/symbol_files.md
 * mozilla's dump_syms (co-developed with this program): https://github.com/mozilla/dump_syms
+
diff --git a/minidump-stackwalk/src/main.rs b/minidump-stackwalk/src/main.rs
index 2113463..fe6a7fb 100644
--- a/minidump-stackwalk/src/main.rs
+++ b/minidump-stackwalk/src/main.rs
@@ -76,6 +76,39 @@ minidump-stackwalk itself, or a misbehaving minidump generator.\n\n\n")
         .group(ArgGroup::with_name("output-format")
             .args(&["json", "human", "cyborg", "dump", "help-markdown"])
         )
+        .arg(
+            Arg::with_name("features")
+                .long("features")
+                .possible_values(&["stable-basic", "stable-all", "unstable-all"])
+                .default_value("stable-basic")
+                .takes_value(true)
+                .long_help("Specify at a high-level how much analysis to perform.
+
+This flag provides a way to more blindly opt into Extra Analysis without having to know about \
+the specific features of minidump-stackwalk. This is equivalent to ProcessorOptions in \
+minidump-processor. The current supported values are:
+
+* stable-basic (default): give me solid detailed analysis that most people would want.
+* stable-all: turn on extra detailed analysis.
+* unstable-all: turn on the weird and experimental stuff.
+
+stable-all enables: nothing (currently identical to stable-basic)
+
+unstable-all enables: `--recover-function-args`
+
+minidump-stackwalk wants to be a reliable and stable tool, but we also want to be able to \
+introduce new features which may be experimental or expensive. To balance these two concerns, \
+new features will usually be disabled by default and given a specific flag, but still more \
+easily 'discovered' by anyone who uses this flag.
+
+Anyone using minidump-stackwalk who is *really* worried about the output being stable \
+should probably not use this flag in production, but its use is recommended for casual
+human usage or for checking \"what's new\".
+
+Features under unstable-all may be deprecated and become noops. Features which require \
+additional input (such as `--evil-json`) cannot be affected by this, and must still be \
+manually 'discovered'.\n\n\n")
+        )
         .arg(
             Arg::with_name("output-file")
                 .long("output-file")
@@ -110,17 +143,24 @@ be prefixed with `unwind:`).\n\n\n")
                 .long("brief")
                 .help("Provide a briefer --human report.
 
-Only provides the top-level summary and a backtrace of the crashing thread.")
+Only provides the top-level summary and a backtrace of the crashing thread.\n\n\n")
         )
         .arg(
-            Arg::with_name("raw-json")
-                .long("raw-json")
+            Arg::with_name("evil-json")
+                .long("evil-json")
                 .takes_value(true)
-                .long_help("An input JSON file with the extra information.
+                .long_help("**[UNSTABLE]** An input JSON file with the extra information.
 
 This is a gross hack for some legacy side-channel information that mozilla uses. It will \
 hopefully be phased out and deprecated in favour of just using custom streams in the \
 minidump itself.\n\n\n")
+        )
+        .arg(
+            Arg::with_name("recover-function-args")
+                .long("recover-function-args")
+                .help("**[UNSTABLE]** Heuristically recover function arguments
+
+This is an experimental feature, which currently only shows up in --human output.\n\n\n")
         )
         .arg(
             Arg::with_name("symbols-url")
@@ -312,9 +352,19 @@ async fn main() {
         );
     }));
 
-    let mut options = ProcessorOptions::default();
+    // Pick the default options
+    let mut options = match matches.value_of("features").unwrap() {
+        "stable-basic" => ProcessorOptions::stable_basic(),
+        "stable-all" => ProcessorOptions::stable_all(),
+        "unstable-all" => ProcessorOptions::unstable_all(),
+        _ => unimplemented!("unknown --features value"),
+    };
 
-    options.evil_json = matches.value_of_os("raw-json").map(Path::new);
+    // Now overload the defaults
+    options.evil_json = matches.value_of_os("evil-json").map(Path::new);
+    if matches.is_present("recover-function-args") {
+        options.recover_function_args = true;
+    }
 
     let temp_dir = std::env::temp_dir();
 
@@ -523,6 +573,7 @@ where
     // 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 memory64_list = dump.get_stream::<MinidumpMemory64List<'_>>().ok();
     let misc_info = dump.get_stream::<MinidumpMiscInfo>().ok();
 
     if let Ok(thread_list) = dump.get_stream::<MinidumpThreadList<'_>>() {
@@ -542,6 +593,9 @@ where
     if let Some(memory_list) = memory_list {
         memory_list.print(output)?;
     }
+    if let Some(memory64_list) = memory64_list {
+        memory64_list.print(output)?;
+    }
     if let Ok(memory_info_list) = dump.get_stream::<MinidumpMemoryInfoList<'_>>() {
         memory_info_list.print(output)?;
     }
diff --git a/minidump/fuzz/fuzz_targets/parse.rs b/minidump/fuzz/fuzz_targets/parse.rs
index b53e441..25ee39a 100644
--- a/minidump/fuzz/fuzz_targets/parse.rs
+++ b/minidump/fuzz/fuzz_targets/parse.rs
@@ -4,9 +4,9 @@ use libfuzzer_sys::fuzz_target;
 use minidump::{
     MinidumpAssertion, MinidumpBreakpadInfo, MinidumpCrashpadInfo, MinidumpException,
     MinidumpLinuxCpuInfo, MinidumpLinuxEnviron, MinidumpLinuxLsbRelease, MinidumpLinuxMaps,
-    MinidumpLinuxProcStatus, MinidumpMacCrashInfo, MinidumpMemoryInfoList, MinidumpMemoryList,
-    MinidumpMiscInfo, MinidumpModuleList, MinidumpSystemInfo, MinidumpThreadList,
-    MinidumpThreadNames, MinidumpUnloadedModuleList,
+    MinidumpLinuxProcStatus, MinidumpMacCrashInfo, MinidumpMemory64List, MinidumpMemoryInfoList,
+    MinidumpMemoryList, MinidumpMiscInfo, MinidumpModuleList, MinidumpSystemInfo,
+    MinidumpThreadList, MinidumpThreadNames, MinidumpUnloadedModuleList,
 };
 
 fuzz_target!(|data: &[u8]| {
@@ -21,6 +21,7 @@ fuzz_target!(|data: &[u8]| {
         let _ = dump.get_stream::<MinidumpLinuxMaps>();
         let _ = dump.get_stream::<MinidumpLinuxProcStatus>();
         let _ = dump.get_stream::<MinidumpMacCrashInfo>();
+        let _ = dump.get_stream::<MinidumpMemory64List>();
         let _ = dump.get_stream::<MinidumpMemoryInfoList>();
         let _ = dump.get_stream::<MinidumpMemoryList>();
         let _ = dump.get_stream::<MinidumpMiscInfo>();
diff --git a/minidump/src/lib.rs b/minidump/src/lib.rs
index aa053d7..6bdedf7 100644
--- a/minidump/src/lib.rs
+++ b/minidump/src/lib.rs
@@ -322,6 +322,9 @@
 //! * `MINIDUMP_THREAD_EX_LIST` (yes, the stream with "EX_LIST" in the name isn't an
 //!   EX list, names are hard.)
 //!
+//! The stream [`MinidumpMemory64List`] is a variant of list stream. It starts with
+//! a `u64` count of entries, a 64-bit shared RVA for all entries, then followed by
+//! an array of entires [`MINIDUMP_MEMORY_DESCRIPTOR64`][format::MINIDUMP_MEMORY_DESCRIPTOR64].
 //!
 //!
 //! ### EX List Streams
diff --git a/minidump/src/minidump.rs b/minidump/src/minidump.rs
index 564f497..3ba88f6 100644
--- a/minidump/src/minidump.rs
+++ b/minidump/src/minidump.rs
@@ -12,6 +12,7 @@ use scroll::{self, Pread, BE, LE};
 use std::borrow::Cow;
 use std::collections::BTreeMap;
 use std::collections::HashMap;
+use std::convert::TryInto;
 use std::fmt;
 use std::fs::File;
 use std::io;
@@ -330,10 +331,11 @@ pub struct MinidumpSystemInfo {
 }
 
 /// A region of memory from the process that wrote the minidump.
+/// This is the underlying generic type for [MinidumpMemory] and [MinidumpMemory64].
 #[derive(Clone, Debug)]
-pub struct MinidumpMemory<'a> {
+pub struct MinidumpMemoryBase<'a, Descriptor> {
     /// The raw `MINIDUMP_MEMORY_DESCRIPTOR` from the minidump.
-    pub desc: md::MINIDUMP_MEMORY_DESCRIPTOR,
+    pub desc: Descriptor,
     /// The starting address of this range of memory.
     pub base_address: u64,
     /// The length of this range of memory.
@@ -342,6 +344,12 @@ pub struct MinidumpMemory<'a> {
     pub bytes: &'a [u8],
 }
 
+/// A region of memory from the process that wrote the minidump.
+pub type MinidumpMemory<'a> = MinidumpMemoryBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR>;
+
+/// A large region of memory from the process that wrote the minidump (usually a full dump).
+pub type MinidumpMemory64<'a> = MinidumpMemoryBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR64>;
+
 #[allow(clippy::large_enum_variant)]
 #[derive(Debug)]
 pub enum RawMacCrashInfo {
@@ -496,14 +504,21 @@ pub struct MinidumpException<'a> {
 }
 
 /// A list of memory regions included in a minidump.
+/// This is the underlying generic type for [MinidumpMemoryList] and [MinidumpMemory64List].
 #[derive(Debug)]
-pub struct MinidumpMemoryList<'a> {
-    /// The memory regions, in the order they were stored in the minidump.
-    regions: Vec<MinidumpMemory<'a>>,
+pub struct MinidumpMemoryListBase<'a, Descriptor> {
+    /// The memory regions, in the order they were stored in the  minidump.
+    regions: Vec<MinidumpMemoryBase<'a, Descriptor>>,
     /// Map from address range to index in regions. Use `MinidumpMemoryList::memory_at_address`.
     regions_by_addr: RangeMap<u64, usize>,
 }
 
+/// A list of memory regions included in a minidump.
+pub type MinidumpMemoryList<'a> = MinidumpMemoryListBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR>;
+
+/// A list of large memory regions included in a minidump (usually a full dump).
+pub type MinidumpMemory64List<'a> = MinidumpMemoryListBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR64>;
+
 /// Information about an assertion that caused a crash.
 #[derive(Debug)]
 pub struct MinidumpAssertion {
@@ -1521,6 +1536,45 @@ impl<'a> MinidumpMemory<'a> {
         })
     }
 
+    /// Write a human-readable description of this `MinidumpMemory` to `f`.
+    ///
+    /// This is very verbose, it is the format used by `minidump_dump`.
+    pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
+        write!(
+            f,
+            "MINIDUMP_MEMORY_DESCRIPTOR
+  start_of_memory_range = {:#x}
+  memory.data_size      = {:#x}
+  memory.rva            = {:#x}
+Memory
+",
+            self.desc.start_of_memory_range, self.desc.memory.data_size, self.desc.memory.rva,
+        )?;
+        self.print_contents(f)?;
+        writeln!(f)
+    }
+}
+
+impl<'a> MinidumpMemory64<'a> {
+    /// Write a human-readable description of this `MinidumpMemory64` to `f`.
+    ///
+    /// This is very verbose, it is the format used by `minidump_dump`.
+    pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
+        write!(
+            f,
+            "MINIDUMP_MEMORY_DESCRIPTOR64
+  start_of_memory_range = {:#x}
+  memory.data_size      = {:#x}
+Memory
+",
+            self.desc.start_of_memory_range, self.desc.data_size,
+        )?;
+        self.print_contents(f)?;
+        writeln!(f)
+    }
+}
+
+impl<'a, Descriptor> MinidumpMemoryBase<'a, Descriptor> {
     /// Get `mem::size_of::<T>()` bytes of memory at `addr` from this region.
     ///
     /// Return `None` if the requested address range falls out of the bounds
@@ -1541,24 +1595,6 @@ impl<'a> MinidumpMemory<'a> {
         self.bytes.pread_with::<T>(start, LE).ok()
     }
 
-    /// Write a human-readable description of this `MinidumpMemory` to `f`.
-    ///
-    /// This is very verbose, it is the format used by `minidump_dump`.
-    pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
-        write!(
-            f,
-            "MINIDUMP_MEMORY_DESCRIPTOR
-  start_of_memory_range = {:#x}
-  memory.data_size      = {:#x}
-  memory.rva            = {:#x}
-Memory
-",
-            self.desc.start_of_memory_range, self.desc.memory.data_size, self.desc.memory.rva,
-        )?;
-        self.print_contents(f)?;
-        writeln!(f)
-    }
-
     /// Write the contents of this `MinidumpMemory` to `f` as a hex string.
     pub fn print_contents<T: Write>(&self, f: &mut T) -> io::Result<()> {
         write!(f, "0x")?;
@@ -1580,30 +1616,35 @@ Memory
     }
 }
 
-impl<'mdmp> MinidumpMemoryList<'mdmp> {
-    /// Return an empty `MinidumpMemoryList`.
-    pub fn new() -> MinidumpMemoryList<'mdmp> {
-        MinidumpMemoryList {
+impl<'mdmp, Descriptor> MinidumpMemoryListBase<'mdmp, Descriptor> {
+    /// Return an empty `MinidumpMemoryListBase`.
+    pub fn new() -> MinidumpMemoryListBase<'mdmp, Descriptor> {
+        MinidumpMemoryListBase {
             regions: vec![],
             regions_by_addr: RangeMap::new(),
         }
     }
 
-    /// Create a `MinidumpMemoryList` from a list of `MinidumpMemory`s.
-    pub fn from_regions(regions: Vec<MinidumpMemory<'mdmp>>) -> MinidumpMemoryList<'mdmp> {
+    /// Create a `MinidumpMemoryListBase` from a list of `MinidumpMemoryBase`s.
+    pub fn from_regions(
+        regions: Vec<MinidumpMemoryBase<'mdmp, Descriptor>>,
+    ) -> MinidumpMemoryListBase<'mdmp, Descriptor> {
         let regions_by_addr = regions
             .iter()
             .enumerate()
             .map(|(i, region)| (region.memory_range(), i))
             .into_rangemap_safe();
-        MinidumpMemoryList {
+        MinidumpMemoryListBase {
             regions,
             regions_by_addr,
         }
     }
 
-    /// Return a `MinidumpMemory` containing memory at `address`, if one exists.
-    pub fn memory_at_address(&self, address: u64) -> Option<&MinidumpMemory<'mdmp>> {
+    /// Return a `MinidumpMemoryBase` containing memory at `address`, if one exists.
+    pub fn memory_at_address(
+        &self,
+        address: u64,
+    ) -> Option<&MinidumpMemoryBase<'mdmp, Descriptor>> {
         self.regions_by_addr
             .get(address)
             .map(|&index| &self.regions[index])
@@ -1611,21 +1652,27 @@ impl<'mdmp> MinidumpMemoryList<'mdmp> {
 
     /// Iterate over the memory regions in the order contained in the minidump.
     ///
-    /// The iterator returns items of [MinidumpMemory] as `&'slf MinidumpMemory<'mdmp>`.
+    /// The iterator returns items of [MinidumpMemoryBase] as `&'slf MinidumpMemoryBase<'mdmp, Descriptor>`.
     /// That is the lifetime of the item is bound to the lifetime of the iterator itself
-    /// (`'slf`), while the slice inside [MinidumpMemory] pointing at the memory itself has
+    /// (`'slf`), while the slice inside [MinidumpMemoryBase] pointing at the memory itself has
     /// the lifetime of the [Minidump] struct ('mdmp).
-    pub fn iter<'slf>(&'slf self) -> impl Iterator<Item = &'slf MinidumpMemory<'mdmp>> {
+    pub fn iter<'slf>(
+        &'slf self,
+    ) -> impl Iterator<Item = &'slf MinidumpMemoryBase<'mdmp, Descriptor>> {
         self.regions.iter()
     }
 
     /// Iterate over the memory regions in order by memory address.
-    pub fn by_addr<'slf>(&'slf self) -> impl Iterator<Item = &'slf MinidumpMemory<'mdmp>> {
+    pub fn by_addr<'slf>(
+        &'slf self,
+    ) -> impl Iterator<Item = &'slf MinidumpMemoryBase<'mdmp, Descriptor>> {
         self.regions_by_addr
             .ranges_values()
             .map(move |&(_, index)| &self.regions[index])
     }
+}
 
+impl<'mdmp> MinidumpMemoryList<'mdmp> {
     /// Write a human-readable description of this `MinidumpMemoryList` to `f`.
     ///
     /// This is very verbose, it is the format used by `minidump_dump`.
@@ -1646,7 +1693,28 @@ impl<'mdmp> MinidumpMemoryList<'mdmp> {
     }
 }
 
-impl<'a> Default for MinidumpMemoryList<'a> {
+impl<'mdmp> MinidumpMemory64List<'mdmp> {
+    /// Write a human-readable description of this `MinidumpMemory64List` to `f`.
+    ///
+    /// This is very verbose, it is the format used by `minidump_dump`.
+    pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
+        write!(
+            f,
+            "MinidumpMemory64List
+  region_count = {}
+
+",
+            self.regions.len()
+        )?;
+        for (i, region) in self.regions.iter().enumerate() {
+            writeln!(f, "region[{}]", i)?;
+            region.print(f)?;
+        }
+        Ok(())
+    }
+}
+
+impl<'a, Descriptor> Default for MinidumpMemoryListBase<'a, Descriptor> {
     fn default() -> Self {
         Self::new()
     }
@@ -1677,6 +1745,68 @@ impl<'a> MinidumpStream<'a> for MinidumpMemoryList<'a> {
     }
 }
 
+impl<'a> MinidumpStream<'a> for MinidumpMemory64List<'a> {
+    const STREAM_TYPE: MINIDUMP_STREAM_TYPE = MINIDUMP_STREAM_TYPE::Memory64ListStream;
+
+    fn read(
+        bytes: &'a [u8],
+        all: &'a [u8],
+        endian: scroll::Endian,
+    ) -> Result<MinidumpMemory64List<'a>, Error> {
+        let mut offset = 0;
+        let u: u64 = bytes
+            .gread_with(&mut offset, endian)
+            .or(Err(Error::StreamReadFailure))?;
+
+        let mut rva: u64 = bytes
+            .gread_with(&mut offset, endian)
+            .or(Err(Error::StreamReadFailure))?;
+
+        let (count, counted_size) = ensure_count_in_bound(
+            bytes,
+            u.try_into().map_err(|_| Error::StreamReadFailure)?,
+            md::MINIDUMP_MEMORY_DESCRIPTOR64::size_with(&endian),
+            offset,
+        )?;
+
+        if bytes.len() != counted_size {
+            return Err(Error::StreamSizeMismatch {
+                expected: counted_size,
+                actual: bytes.len(),
+            });
+        }
+
+        let mut raw_entries = Vec::with_capacity(count);
+        for _ in 0..count {
+            let raw: md::MINIDUMP_MEMORY_DESCRIPTOR64 = bytes
+                .gread_with(&mut offset, endian)
+                .or(Err(Error::StreamReadFailure))?;
+            raw_entries.push(raw);
+        }
+
+        let mut regions = Vec::with_capacity(raw_entries.len());
+        for raw in raw_entries {
+            let start = rva;
+            let end = rva
+                .checked_add(raw.data_size)
+                .ok_or(Error::StreamReadFailure)?;
+            let bytes = all
+                .get(start as usize..end as usize)
+                .ok_or(Error::StreamReadFailure)?;
+
+            regions.push(MinidumpMemory64 {
+                desc: raw,
+                base_address: raw.start_of_memory_range,
+                size: raw.data_size,
+                bytes,
+            });
+
+            rva = end;
+        }
+        Ok(MinidumpMemory64List::from_regions(regions))
+    }
+}
+
 impl<'a> MinidumpStream<'a> for MinidumpMemoryInfoList<'a> {
     const STREAM_TYPE: MINIDUMP_STREAM_TYPE = MINIDUMP_STREAM_TYPE::MemoryInfoListStream;
 
@@ -4121,9 +4251,9 @@ impl MinidumpAssertion {
   type                                       = {}
 
 ",
-            self.expression().unwrap_or_else(String::new),
-            self.function().unwrap_or_else(String::new),
-            self.file().unwrap_or_else(String::new),
+            self.expression().unwrap_or_default(),
+            self.function().unwrap_or_default(),
+            self.file().unwrap_or_default(),
             self.raw.line,
             self.raw._type,
         )?;
@@ -4586,6 +4716,7 @@ where
     /// * [`MinidumpLinuxProcStatus`][]
     /// * [`MinidumpMacCrashInfo`][]
     /// * [`MinidumpMemoryList`][]
+    /// * [`MinidumpMemory64List`][]
     /// * [`MinidumpMemoryInfoList`][]
     /// * [`MinidumpMiscInfo`][]
     /// * [`MinidumpModuleList`][]
@@ -4635,7 +4766,7 @@ where
     /// If there are multiple copies of the same stream type (which should not happen for
     /// well-formed Minidumps), then only one of them will be yielded, arbitrarily.
     pub fn unimplemented_streams(&self) -> impl Iterator<Item = MinidumpUnimplementedStream> + '_ {
-        static UNIMPLEMENTED_STREAMS: [MINIDUMP_STREAM_TYPE; 33] = [
+        static UNIMPLEMENTED_STREAMS: [MINIDUMP_STREAM_TYPE; 32] = [
             // Presumably will never have an implementation:
             MINIDUMP_STREAM_TYPE::UnusedStream,
             MINIDUMP_STREAM_TYPE::ReservedStream0,
@@ -4643,7 +4774,6 @@ where
             MINIDUMP_STREAM_TYPE::LastReservedStream,
             // Presumably should be implemented:
             MINIDUMP_STREAM_TYPE::ThreadExListStream,
-            MINIDUMP_STREAM_TYPE::Memory64ListStream,
             MINIDUMP_STREAM_TYPE::CommentStreamA,
             MINIDUMP_STREAM_TYPE::CommentStreamW,
             MINIDUMP_STREAM_TYPE::HandleDataStream,
@@ -5537,6 +5667,34 @@ c70206ca83eb2852-de0206ca83eb2852  -w-s  10bac9000 fd:05 1196511 /usr/lib64/libt
         assert_eq!(&regions[0].bytes, &CONTENTS);
     }
 
+    #[test]
+    fn test_memory64_list() {
+        const CONTENTS0: &[u8] = b"memory_contents";
+        const CONTENTS1: &[u8] = b"another_block";
+        let memory0 = Memory::with_section(
+            Section::with_endian(Endian::Little).append_bytes(CONTENTS0),
+            0x309d68010bd21b2c,
+        );
+        let memory1 = Memory::with_section(
+            Section::with_endian(Endian::Little).append_bytes(CONTENTS1),
+            0x1234,
+        );
+        let dump = SynthMinidump::with_endian(Endian::Little)
+            .add_memory64(memory0)
+            .add_memory64(memory1);
+        let dump = read_synth_dump(dump).unwrap();
+        let memory_list = dump.get_stream::<MinidumpMemory64List<'_>>().unwrap();
+        let regions = memory_list.iter().collect::<Vec<_>>();
+        assert_eq!(regions.len(), 2);
+        assert_eq!(regions[0].base_address, 0x309d68010bd21b2c);
+        assert_eq!(regions[0].size, CONTENTS0.len() as u64);
+        assert_eq!(&regions[0].bytes, &CONTENTS0);
+
+        assert_eq!(regions[1].base_address, 0x1234);
+        assert_eq!(regions[1].size, CONTENTS1.len() as u64);
+        assert_eq!(&regions[1].bytes, &CONTENTS1);
+    }
+
     #[test]
     fn test_memory_list_lifetimes() {
         // A memory list should not own any of the minidump data.
diff --git a/synth-minidump/src/lib.rs b/synth-minidump/src/lib.rs
index 042a052..0bb7f0c 100644
--- a/synth-minidump/src/lib.rs
+++ b/synth-minidump/src/lib.rs
@@ -41,6 +41,8 @@ pub struct SynthMinidump {
     thread_names_list: Option<ListStream<ThreadName>>,
     /// List of memory regions in this minidump.
     memory_list: Option<ListStream<Section>>,
+    /// List of memory regions stored in `Memory64List` stream in this minidump.
+    memory64_list: Option<Memory64ListStream>,
     /// List of extra info about memory regions in this minidump.
     memory_info_list: Option<ExListStream<MemoryInfo>>,
     /// Crashpad extension containing annotations.
@@ -55,6 +57,8 @@ pub struct SynthMinidump {
     linux_environ: Option<SimpleStream>,
     /// /proc/self/status string
     linux_proc_status: Option<SimpleStream>,
+    /// Continuous memory used by `Memory64List` stream
+    memory64_section: Option<Section>,
 }
 
 /// A block of data contained in a minidump.
@@ -163,6 +167,7 @@ impl SynthMinidump {
             .D64(&flags);
         section.start().set_const(0);
         assert_eq!(section.size(), mem::size_of::<md::MINIDUMP_HEADER>() as u64);
+        let memory64_section = Section::with_endian(endian);
 
         SynthMinidump {
             section,
@@ -194,6 +199,10 @@ impl SynthMinidump {
                 md::MINIDUMP_STREAM_TYPE::MemoryListStream,
                 endian,
             )),
+            memory64_list: Some(Memory64ListStream::new(
+                endian,
+                &memory64_section.file_offset(),
+            )),
             memory_info_list: Some(ExListStream::new(
                 md::MINIDUMP_STREAM_TYPE::MemoryInfoListStream,
                 mem::size_of::<md::MINIDUMP_MEMORY_INFO>(),
@@ -205,6 +214,7 @@ impl SynthMinidump {
             linux_cpu_info: None,
             linux_proc_status: None,
             crashpad_info: None,
+            memory64_section: Some(memory64_section),
         }
     }
 
@@ -257,6 +267,19 @@ impl SynthMinidump {
         self.add(memory)
     }
 
+    /// Add `memory` to `self`'s memory64 list
+    pub fn add_memory64(mut self, memory: Memory) -> SynthMinidump {
+        self.memory64_list = self
+            .memory64_list
+            .take()
+            .map(|memory64_list| memory64_list.add_memory(&memory));
+        self.memory64_section = self
+            .memory64_section
+            .take()
+            .map(|memory64_section| memory64_section.append_section(memory.section));
+        self
+    }
+
     /// Add `info` to `self`, adding it to the memory info list stream as well.
     pub fn add_memory_info(mut self, info: MemoryInfo) -> SynthMinidump {
         self.memory_info_list = self
@@ -391,6 +414,12 @@ impl SynthMinidump {
         // Add memory list stream if any memory regions were added.
         let memories = self.memory_list.take();
         self = self.finish_list(memories);
+        // Add memory64 list stream if any memory regions were added.
+        if let Some(memories64) = self.memory64_list.take() {
+            if !memories64.is_empty() {
+                self = self.add_stream(memories64);
+            }
+        }
         // Add memory info list stream if any memory infos were added.
         let memory_infos = self.memory_info_list.take();
         self = self.finish_ex_list(memory_infos);
@@ -425,6 +454,9 @@ impl SynthMinidump {
         if let Some(stream) = self.linux_environ.take() {
             self = self.add_stream(stream);
         }
+        if let Some(memory64_section) = self.memory64_section.take() {
+            self = self.add(memory64_section);
+        }
 
         let SynthMinidump {
             section,
@@ -620,6 +652,60 @@ impl<T: ListItem> Stream for ListStream<T> {
     }
 }
 
+pub struct Memory64ListStream {
+    section: Section,
+    count: u64,
+    count_label: Label,
+}
+
+impl Memory64ListStream {
+    pub fn new(endian: Endian, memory64_rva: &Label) -> Self {
+        let count_label = Label::new();
+        let section = Section::with_endian(endian)
+            .D64(&count_label)
+            .D64(memory64_rva);
+        Self {
+            section,
+            count: 0,
+            count_label,
+        }
+    }
+
+    pub fn add_memory(mut self, memory: &Memory) -> Self {
+        self.count += 1;
+        self.section = self.section.D64(memory.address).D64(memory.section.size());
+        self
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.count == 0
+    }
+}
+
+impl From<Memory64ListStream> for Section {
+    fn from(list: Memory64ListStream) -> Self {
+        // Finalize the entry count.
+        list.count_label.set_const(list.count as u64);
+        list.section
+    }
+}
+
+impl DumpSection for Memory64ListStream {
+    fn file_offset(&self) -> Label {
+        self.section.file_offset()
+    }
+
+    fn file_size(&self) -> Label {
+        self.section.file_size()
+    }
+}
+
+impl Stream for Memory64ListStream {
+    fn stream_type(&self) -> u32 {
+        md::MINIDUMP_STREAM_TYPE::Memory64ListStream as u32
+    }
+}
+
 /// A stream containing a list of dump entries, using the extended header format.
 pub struct ExList<T: ListItem> {
     /// The stream's contents.


Test patch:
diff --git a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__human-stable-all.snap b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__human-stable-all.snap
new file mode 100644
index 0000000..4804073
--- /dev/null
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__human-stable-all.snap
@@ -0,0 +1,53 @@
+---
+source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
+assertion_line: 225
+expression: stdout
+
+---
+Operating system: Windows NT
+                  5.1.2600 Service Pack 2
+CPU: x86
+     GenuineIntel family 6 model 13 stepping 8
+     1 CPU
+
+Crash reason:  EXCEPTION_ACCESS_VIOLATION_WRITE
+Crash address: 0x45
+Process uptime: 0 seconds
+
+Thread 0  (crashed)
+ 0  test_app.exe + 0x429e
+    eip = 0x0040429e   esp = 0x0012fe84   ebp = 0x0012fe88   ebx = 0x7c80abc1
+    esi = 0x00000002   edi = 0x00000a28   eax = 0x00000045   ecx = 0x0012fe94
+    edx = 0x0042bc58   efl = 0x00010246
+    Found by: given as instruction pointer in context
+ 1  test_app.exe + 0x41ff
+    eip = 0x00404200   esp = 0x0012fe90   ebp = 0x0012ff70
+    Found by: previous frame's frame pointer
+ 2  test_app.exe + 0x53eb
+    eip = 0x004053ec   esp = 0x0012ff78   ebp = 0x0012ffc0
+    Found by: previous frame's frame pointer
+ 3  kernel32.dll + 0x16fd6
+    eip = 0x7c816fd7   esp = 0x0012ffc8   ebp = 0x0012fff0
+    Found by: previous frame's frame pointer
+
+
+Loaded modules:
+0x00400000 - 0x0042cfff  test_app.exe  ???  (main)
+0x59a60000 - 0x59b00fff  dbghelp.dll  5.1.2600.2180
+0x76390000 - 0x763acfff  imm32.dll  5.1.2600.2180
+0x76bf0000 - 0x76bfafff  psapi.dll  5.1.2600.2180
+0x774e0000 - 0x7761cfff  ole32.dll  5.1.2600.2726
+0x77c00000 - 0x77c07fff  version.dll  5.1.2600.2180
+0x77c10000 - 0x77c67fff  msvcrt.dll  7.0.2600.2180
+0x77d40000 - 0x77dcffff  user32.dll  5.1.2600.2622
+0x77dd0000 - 0x77e6afff  advapi32.dll  5.1.2600.2180
+0x77e70000 - 0x77f00fff  rpcrt4.dll  5.1.2600.2180
+0x77f10000 - 0x77f56fff  gdi32.dll  5.1.2600.2818
+0x7c800000 - 0x7c8f3fff  kernel32.dll  5.1.2600.2945
+0x7c900000 - 0x7c9affff  ntdll.dll  5.1.2600.2180
+
+Unloaded modules:
+
+Unimplemented streams encountered:
+Stream 0x00000000 UnusedStream (Official) @ 0x00000000
+
diff --git a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__human-unstable-all.snap b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__human-unstable-all.snap
new file mode 100644
index 0000000..027126d
--- /dev/null
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__human-unstable-all.snap
@@ -0,0 +1,53 @@
+---
+source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
+assertion_line: 246
+expression: stdout
+
+---
+Operating system: Windows NT
+                  5.1.2600 Service Pack 2
+CPU: x86
+     GenuineIntel family 6 model 13 stepping 8
+     1 CPU
+
+Crash reason:  EXCEPTION_ACCESS_VIOLATION_WRITE
+Crash address: 0x45
+Process uptime: 0 seconds
+
+Thread 0  (crashed)
+ 0  test_app.exe + 0x429e
+    eip = 0x0040429e   esp = 0x0012fe84   ebp = 0x0012fe88   ebx = 0x7c80abc1
+    esi = 0x00000002   edi = 0x00000a28   eax = 0x00000045   ecx = 0x0012fe94
+    edx = 0x0042bc58   efl = 0x00010246
+    Found by: given as instruction pointer in context
+ 1  test_app.exe + 0x41ff
+    eip = 0x00404200   esp = 0x0012fe90   ebp = 0x0012ff70
+    Found by: previous frame's frame pointer
+ 2  test_app.exe + 0x53eb
+    eip = 0x004053ec   esp = 0x0012ff78   ebp = 0x0012ffc0
+    Found by: previous frame's frame pointer
+ 3  kernel32.dll + 0x16fd6
+    eip = 0x7c816fd7   esp = 0x0012ffc8   ebp = 0x0012fff0
+    Found by: previous frame's frame pointer
+
+
+Loaded modules:
+0x00400000 - 0x0042cfff  test_app.exe  ???  (main)
+0x59a60000 - 0x59b00fff  dbghelp.dll  5.1.2600.2180
+0x76390000 - 0x763acfff  imm32.dll  5.1.2600.2180
+0x76bf0000 - 0x76bfafff  psapi.dll  5.1.2600.2180
+0x774e0000 - 0x7761cfff  ole32.dll  5.1.2600.2726
+0x77c00000 - 0x77c07fff  version.dll  5.1.2600.2180
+0x77c10000 - 0x77c67fff  msvcrt.dll  7.0.2600.2180
+0x77d40000 - 0x77dcffff  user32.dll  5.1.2600.2622
+0x77dd0000 - 0x77e6afff  advapi32.dll  5.1.2600.2180
+0x77e70000 - 0x77f00fff  rpcrt4.dll  5.1.2600.2180
+0x77f10000 - 0x77f56fff  gdi32.dll  5.1.2600.2818
+0x7c800000 - 0x7c8f3fff  kernel32.dll  5.1.2600.2945
+0x7c900000 - 0x7c9affff  ntdll.dll  5.1.2600.2180
+
+Unloaded modules:
+
+Unimplemented streams encountered:
+Stream 0x00000000 UnusedStream (Official) @ 0x00000000
+
diff --git a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty-evil-symbols.snap b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty-evil-symbols.snap
index 4d8bb90..c2c7056 100644
--- a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty-evil-symbols.snap
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty-evil-symbols.snap
@@ -1,6 +1,6 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
-assertion_line: 121
+assertion_line: 184
 expression: stdout
 
 ---
@@ -268,9 +268,6 @@ expression: stdout
   ],
   "modules_contains_cert_info": true,
   "pid": 3932,
-  "sensitive": {
-    "exploitability": null
-  },
   "status": "OK",
   "system_info": {
     "cpu_arch": "x86",
diff --git a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty-symbols.snap b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty-symbols.snap
index cc41d09..3379946 100644
--- a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty-symbols.snap
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty-symbols.snap
@@ -1,6 +1,6 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
-assertion_line: 97
+assertion_line: 160
 expression: stdout
 
 ---
@@ -268,9 +268,6 @@ expression: stdout
   ],
   "modules_contains_cert_info": false,
   "pid": 3932,
-  "sensitive": {
-    "exploitability": null
-  },
   "status": "OK",
   "system_info": {
     "cpu_arch": "x86",
diff --git a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty-unloaded.snap b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty-unloaded.snap
index 539f35d..33aeafb 100644
--- a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty-unloaded.snap
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty-unloaded.snap
@@ -1,6 +1,6 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
-assertion_line: 656
+assertion_line: 761
 expression: stdout
 
 ---
@@ -17,9 +17,6 @@ expression: stdout
   "modules": [],
   "modules_contains_cert_info": false,
   "pid": null,
-  "sensitive": {
-    "exploitability": null
-  },
   "status": "OK",
   "system_info": {
     "cpu_arch": "x86",
diff --git a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty.snap b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty.snap
index e058df7..9c2001c 100644
--- a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty.snap
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty.snap
@@ -1,6 +1,6 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
-assertion_line: 75
+assertion_line: 94
 expression: stdout
 
 ---
@@ -268,9 +268,6 @@ expression: stdout
   ],
   "modules_contains_cert_info": false,
   "pid": 3932,
-  "sensitive": {
-    "exploitability": null
-  },
   "status": "OK",
   "system_info": {
     "cpu_arch": "x86",
diff --git a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-stable-all.snap b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-stable-all.snap
new file mode 100644
index 0000000..4134754
--- /dev/null
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-stable-all.snap
@@ -0,0 +1,345 @@
+---
+source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
+assertion_line: 116
+expression: stdout
+
+---
+{
+  "crash_info": {
+    "address": "0x00000045",
+    "assertion": null,
+    "crashing_thread": 0,
+    "type": "EXCEPTION_ACCESS_VIOLATION_WRITE"
+  },
+  "crashing_thread": {
+    "frame_count": 4,
+    "frames": [
+      {
+        "file": null,
+        "frame": 0,
+        "function": null,
+        "function_offset": null,
+        "line": null,
+        "missing_symbols": true,
+        "module": "test_app.exe",
+        "module_offset": "0x0000429e",
+        "offset": "0x0040429e",
+        "registers": {
+          "eax": "0x00000045",
+          "ebp": "0x0012fe88",
+          "ebx": "0x7c80abc1",
+          "ecx": "0x0012fe94",
+          "edi": "0x00000a28",
+          "edx": "0x0042bc58",
+          "efl": "0x00010246",
+          "eip": "0x0040429e",
+          "esi": "0x00000002",
+          "esp": "0x0012fe84"
+        },
+        "trust": "context"
+      },
+      {
+        "file": null,
+        "frame": 1,
+        "function": null,
+        "function_offset": null,
+        "line": null,
+        "missing_symbols": true,
+        "module": "test_app.exe",
+        "module_offset": "0x000041ff",
+        "offset": "0x004041ff",
+        "trust": "frame_pointer"
+      },
+      {
+        "file": null,
+        "frame": 2,
+        "function": null,
+        "function_offset": null,
+        "line": null,
+        "missing_symbols": true,
+        "module": "test_app.exe",
+        "module_offset": "0x000053eb",
+        "offset": "0x004053eb",
+        "trust": "frame_pointer"
+      },
+      {
+        "file": null,
+        "frame": 3,
+        "function": null,
+        "function_offset": null,
+        "line": null,
+        "missing_symbols": true,
+        "module": "kernel32.dll",
+        "module_offset": "0x00016fd6",
+        "offset": "0x7c816fd6",
+        "trust": "frame_pointer"
+      }
+    ],
+    "last_error_value": null,
+    "thread_name": null,
+    "threads_index": 0
+  },
+  "lsb_release": null,
+  "mac_crash_info": null,
+  "main_module": 0,
+  "modules": [
+    {
+      "base_addr": "0x00400000",
+      "cert_subject": null,
+      "code_id": "45d35f6c2d000",
+      "corrupt_symbols": false,
+      "debug_file": "test_app.pdb",
+      "debug_id": "5A9832E5287241C1838ED98914E9B7FF1",
+      "end_addr": "0x0042d000",
+      "filename": "test_app.exe",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": null
+    },
+    {
+      "base_addr": "0x7c900000",
+      "cert_subject": null,
+      "code_id": "411096b4b0000",
+      "corrupt_symbols": false,
+      "debug_file": "ntdll.pdb",
+      "debug_id": "36515FB5D04345E491F672FA2E2878C02",
+      "end_addr": "0x7c9b0000",
+      "filename": "ntdll.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2180"
+    },
+    {
+      "base_addr": "0x7c800000",
+      "cert_subject": null,
+      "code_id": "44ab9a84f4000",
+      "corrupt_symbols": false,
+      "debug_file": "kernel32.pdb",
+      "debug_id": "BCE8785C57B44245A669896B6A19B9542",
+      "end_addr": "0x7c8f4000",
+      "filename": "kernel32.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2945"
+    },
+    {
+      "base_addr": "0x774e0000",
+      "cert_subject": null,
+      "code_id": "42e5be9313d000",
+      "corrupt_symbols": false,
+      "debug_file": "ole32.pdb",
+      "debug_id": "683B65B246F4418796D2EE6D4C55EB112",
+      "end_addr": "0x7761d000",
+      "filename": "ole32.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2726"
+    },
+    {
+      "base_addr": "0x77dd0000",
+      "cert_subject": null,
+      "code_id": "411096a79b000",
+      "corrupt_symbols": false,
+      "debug_file": "advapi32.pdb",
+      "debug_id": "455D6C5F184D45BBB5C5F30F829751142",
+      "end_addr": "0x77e6b000",
+      "filename": "advapi32.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2180"
+    },
+    {
+      "base_addr": "0x77e70000",
+      "cert_subject": null,
+      "code_id": "411096ae91000",
+      "corrupt_symbols": false,
+      "debug_file": "rpcrt4.pdb",
+      "debug_id": "BEA45A721DA141DAA3BA86B3A20311532",
+      "end_addr": "0x77f01000",
+      "filename": "rpcrt4.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2180"
+    },
+    {
+      "base_addr": "0x77f10000",
+      "cert_subject": null,
+      "code_id": "43b34feb47000",
+      "corrupt_symbols": false,
+      "debug_file": "gdi32.pdb",
+      "debug_id": "C0EA66BE00A64BD7AEF79E443A91869C2",
+      "end_addr": "0x77f57000",
+      "filename": "gdi32.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2818"
+    },
+    {
+      "base_addr": "0x77d40000",
+      "cert_subject": null,
+      "code_id": "4226015990000",
+      "corrupt_symbols": false,
+      "debug_file": "user32.pdb",
+      "debug_id": "EE2B714D83A34C9D88027621272F83262",
+      "end_addr": "0x77dd0000",
+      "filename": "user32.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2622"
+    },
+    {
+      "base_addr": "0x77c10000",
+      "cert_subject": null,
+      "code_id": "4110975258000",
+      "corrupt_symbols": false,
+      "debug_file": "msvcrt.pdb",
+      "debug_id": "A678F3C30DED426B839032B996987E381",
+      "end_addr": "0x77c68000",
+      "filename": "msvcrt.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "7.0.2600.2180"
+    },
+    {
+      "base_addr": "0x76390000",
+      "cert_subject": null,
+      "code_id": "411096ae1d000",
+      "corrupt_symbols": false,
+      "debug_file": "imm32.pdb",
+      "debug_id": "2C17A49C251B4C8EB9E2AD13D7D9EA162",
+      "end_addr": "0x763ad000",
+      "filename": "imm32.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2180"
+    },
+    {
+      "base_addr": "0x59a60000",
+      "cert_subject": null,
+      "code_id": "4110969aa1000",
+      "corrupt_symbols": false,
+      "debug_file": "dbghelp.pdb",
+      "debug_id": "39559573E21B46F28E286923BE9E6A761",
+      "end_addr": "0x59b01000",
+      "filename": "dbghelp.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2180"
+    },
+    {
+      "base_addr": "0x77c00000",
+      "cert_subject": null,
+      "code_id": "411096b78000",
+      "corrupt_symbols": false,
+      "debug_file": "version.pdb",
+      "debug_id": "180A90C40384463E82DDC45B2C8AB76E2",
+      "end_addr": "0x77c08000",
+      "filename": "version.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2180"
+    },
+    {
+      "base_addr": "0x76bf0000",
+      "cert_subject": null,
+      "code_id": "411096cab000",
+      "corrupt_symbols": false,
+      "debug_file": "psapi.pdb",
+      "debug_id": "A5C3A1F9689F43D8AD228A09293889702",
+      "end_addr": "0x76bfb000",
+      "filename": "psapi.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2180"
+    }
+  ],
+  "modules_contains_cert_info": false,
+  "pid": 3932,
+  "status": "OK",
+  "system_info": {
+    "cpu_arch": "x86",
+    "cpu_count": 1,
+    "cpu_info": "GenuineIntel family 6 model 13 stepping 8",
+    "cpu_microcode_version": null,
+    "os": "Windows NT",
+    "os_ver": "5.1.2600 Service Pack 2"
+  },
+  "thread_count": 2,
+  "threads": [
+    {
+      "frame_count": 4,
+      "frames": [
+        {
+          "file": null,
+          "frame": 0,
+          "function": null,
+          "function_offset": null,
+          "line": null,
+          "missing_symbols": true,
+          "module": "test_app.exe",
+          "module_offset": "0x0000429e",
+          "offset": "0x0040429e",
+          "trust": "context"
+        },
+        {
+          "file": null,
+          "frame": 1,
+          "function": null,
+          "function_offset": null,
+          "line": null,
+          "missing_symbols": true,
+          "module": "test_app.exe",
+          "module_offset": "0x000041ff",
+          "offset": "0x004041ff",
+          "trust": "frame_pointer"
+        },
+        {
+          "file": null,
+          "frame": 2,
+          "function": null,
+          "function_offset": null,
+          "line": null,
+          "missing_symbols": true,
+          "module": "test_app.exe",
+          "module_offset": "0x000053eb",
+          "offset": "0x004053eb",
+          "trust": "frame_pointer"
+        },
+        {
+          "file": null,
+          "frame": 3,
+          "function": null,
+          "function_offset": null,
+          "line": null,
+          "missing_symbols": true,
+          "module": "kernel32.dll",
+          "module_offset": "0x00016fd6",
+          "offset": "0x7c816fd6",
+          "trust": "frame_pointer"
+        }
+      ],
+      "last_error_value": null,
+      "thread_name": null
+    },
+    {
+      "frame_count": 0,
+      "frames": [],
+      "last_error_value": null,
+      "thread_name": null
+    }
+  ],
+  "unloaded_modules": []
+}
diff --git a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-unstable-all.snap b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-unstable-all.snap
new file mode 100644
index 0000000..741c47b
--- /dev/null
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-unstable-all.snap
@@ -0,0 +1,345 @@
+---
+source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
+assertion_line: 138
+expression: stdout
+
+---
+{
+  "crash_info": {
+    "address": "0x00000045",
+    "assertion": null,
+    "crashing_thread": 0,
+    "type": "EXCEPTION_ACCESS_VIOLATION_WRITE"
+  },
+  "crashing_thread": {
+    "frame_count": 4,
+    "frames": [
+      {
+        "file": null,
+        "frame": 0,
+        "function": null,
+        "function_offset": null,
+        "line": null,
+        "missing_symbols": true,
+        "module": "test_app.exe",
+        "module_offset": "0x0000429e",
+        "offset": "0x0040429e",
+        "registers": {
+          "eax": "0x00000045",
+          "ebp": "0x0012fe88",
+          "ebx": "0x7c80abc1",
+          "ecx": "0x0012fe94",
+          "edi": "0x00000a28",
+          "edx": "0x0042bc58",
+          "efl": "0x00010246",
+          "eip": "0x0040429e",
+          "esi": "0x00000002",
+          "esp": "0x0012fe84"
+        },
+        "trust": "context"
+      },
+      {
+        "file": null,
+        "frame": 1,
+        "function": null,
+        "function_offset": null,
+        "line": null,
+        "missing_symbols": true,
+        "module": "test_app.exe",
+        "module_offset": "0x000041ff",
+        "offset": "0x004041ff",
+        "trust": "frame_pointer"
+      },
+      {
+        "file": null,
+        "frame": 2,
+        "function": null,
+        "function_offset": null,
+        "line": null,
+        "missing_symbols": true,
+        "module": "test_app.exe",
+        "module_offset": "0x000053eb",
+        "offset": "0x004053eb",
+        "trust": "frame_pointer"
+      },
+      {
+        "file": null,
+        "frame": 3,
+        "function": null,
+        "function_offset": null,
+        "line": null,
+        "missing_symbols": true,
+        "module": "kernel32.dll",
+        "module_offset": "0x00016fd6",
+        "offset": "0x7c816fd6",
+        "trust": "frame_pointer"
+      }
+    ],
+    "last_error_value": null,
+    "thread_name": null,
+    "threads_index": 0
+  },
+  "lsb_release": null,
+  "mac_crash_info": null,
+  "main_module": 0,
+  "modules": [
+    {
+      "base_addr": "0x00400000",
+      "cert_subject": null,
+      "code_id": "45d35f6c2d000",
+      "corrupt_symbols": false,
+      "debug_file": "test_app.pdb",
+      "debug_id": "5A9832E5287241C1838ED98914E9B7FF1",
+      "end_addr": "0x0042d000",
+      "filename": "test_app.exe",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": null
+    },
+    {
+      "base_addr": "0x7c900000",
+      "cert_subject": null,
+      "code_id": "411096b4b0000",
+      "corrupt_symbols": false,
+      "debug_file": "ntdll.pdb",
+      "debug_id": "36515FB5D04345E491F672FA2E2878C02",
+      "end_addr": "0x7c9b0000",
+      "filename": "ntdll.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2180"
+    },
+    {
+      "base_addr": "0x7c800000",
+      "cert_subject": null,
+      "code_id": "44ab9a84f4000",
+      "corrupt_symbols": false,
+      "debug_file": "kernel32.pdb",
+      "debug_id": "BCE8785C57B44245A669896B6A19B9542",
+      "end_addr": "0x7c8f4000",
+      "filename": "kernel32.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2945"
+    },
+    {
+      "base_addr": "0x774e0000",
+      "cert_subject": null,
+      "code_id": "42e5be9313d000",
+      "corrupt_symbols": false,
+      "debug_file": "ole32.pdb",
+      "debug_id": "683B65B246F4418796D2EE6D4C55EB112",
+      "end_addr": "0x7761d000",
+      "filename": "ole32.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2726"
+    },
+    {
+      "base_addr": "0x77dd0000",
+      "cert_subject": null,
+      "code_id": "411096a79b000",
+      "corrupt_symbols": false,
+      "debug_file": "advapi32.pdb",
+      "debug_id": "455D6C5F184D45BBB5C5F30F829751142",
+      "end_addr": "0x77e6b000",
+      "filename": "advapi32.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2180"
+    },
+    {
+      "base_addr": "0x77e70000",
+      "cert_subject": null,
+      "code_id": "411096ae91000",
+      "corrupt_symbols": false,
+      "debug_file": "rpcrt4.pdb",
+      "debug_id": "BEA45A721DA141DAA3BA86B3A20311532",
+      "end_addr": "0x77f01000",
+      "filename": "rpcrt4.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2180"
+    },
+    {
+      "base_addr": "0x77f10000",
+      "cert_subject": null,
+      "code_id": "43b34feb47000",
+      "corrupt_symbols": false,
+      "debug_file": "gdi32.pdb",
+      "debug_id": "C0EA66BE00A64BD7AEF79E443A91869C2",
+      "end_addr": "0x77f57000",
+      "filename": "gdi32.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2818"
+    },
+    {
+      "base_addr": "0x77d40000",
+      "cert_subject": null,
+      "code_id": "4226015990000",
+      "corrupt_symbols": false,
+      "debug_file": "user32.pdb",
+      "debug_id": "EE2B714D83A34C9D88027621272F83262",
+      "end_addr": "0x77dd0000",
+      "filename": "user32.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2622"
+    },
+    {
+      "base_addr": "0x77c10000",
+      "cert_subject": null,
+      "code_id": "4110975258000",
+      "corrupt_symbols": false,
+      "debug_file": "msvcrt.pdb",
+      "debug_id": "A678F3C30DED426B839032B996987E381",
+      "end_addr": "0x77c68000",
+      "filename": "msvcrt.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "7.0.2600.2180"
+    },
+    {
+      "base_addr": "0x76390000",
+      "cert_subject": null,
+      "code_id": "411096ae1d000",
+      "corrupt_symbols": false,
+      "debug_file": "imm32.pdb",
+      "debug_id": "2C17A49C251B4C8EB9E2AD13D7D9EA162",
+      "end_addr": "0x763ad000",
+      "filename": "imm32.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2180"
+    },
+    {
+      "base_addr": "0x59a60000",
+      "cert_subject": null,
+      "code_id": "4110969aa1000",
+      "corrupt_symbols": false,
+      "debug_file": "dbghelp.pdb",
+      "debug_id": "39559573E21B46F28E286923BE9E6A761",
+      "end_addr": "0x59b01000",
+      "filename": "dbghelp.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2180"
+    },
+    {
+      "base_addr": "0x77c00000",
+      "cert_subject": null,
+      "code_id": "411096b78000",
+      "corrupt_symbols": false,
+      "debug_file": "version.pdb",
+      "debug_id": "180A90C40384463E82DDC45B2C8AB76E2",
+      "end_addr": "0x77c08000",
+      "filename": "version.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2180"
+    },
+    {
+      "base_addr": "0x76bf0000",
+      "cert_subject": null,
+      "code_id": "411096cab000",
+      "corrupt_symbols": false,
+      "debug_file": "psapi.pdb",
+      "debug_id": "A5C3A1F9689F43D8AD228A09293889702",
+      "end_addr": "0x76bfb000",
+      "filename": "psapi.dll",
+      "loaded_symbols": false,
+      "missing_symbols": false,
+      "symbol_url": null,
+      "version": "5.1.2600.2180"
+    }
+  ],
+  "modules_contains_cert_info": false,
+  "pid": 3932,
+  "status": "OK",
+  "system_info": {
+    "cpu_arch": "x86",
+    "cpu_count": 1,
+    "cpu_info": "GenuineIntel family 6 model 13 stepping 8",
+    "cpu_microcode_version": null,
+    "os": "Windows NT",
+    "os_ver": "5.1.2600 Service Pack 2"
+  },
+  "thread_count": 2,
+  "threads": [
+    {
+      "frame_count": 4,
+      "frames": [
+        {
+          "file": null,
+          "frame": 0,
+          "function": null,
+          "function_offset": null,
+          "line": null,
+          "missing_symbols": true,
+          "module": "test_app.exe",
+          "module_offset": "0x0000429e",
+          "offset": "0x0040429e",
+          "trust": "context"
+        },
+        {
+          "file": null,
+          "frame": 1,
+          "function": null,
+          "function_offset": null,
+          "line": null,
+          "missing_symbols": true,
+          "module": "test_app.exe",
+          "module_offset": "0x000041ff",
+          "offset": "0x004041ff",
+          "trust": "frame_pointer"
+        },
+        {
+          "file": null,
+          "frame": 2,
+          "function": null,
+          "function_offset": null,
+          "line": null,
+          "missing_symbols": true,
+          "module": "test_app.exe",
+          "module_offset": "0x000053eb",
+          "offset": "0x004053eb",
+          "trust": "frame_pointer"
+        },
+        {
+          "file": null,
+          "frame": 3,
+          "function": null,
+          "function_offset": null,
+          "line": null,
+          "missing_symbols": true,
+          "module": "kernel32.dll",
+          "module_offset": "0x00016fd6",
+          "offset": "0x7c816fd6",
+          "trust": "frame_pointer"
+        }
+      ],
+      "last_error_value": null,
+      "thread_name": null
+    },
+    {
+      "frame_count": 0,
+      "frames": [],
+      "last_error_value": null,
+      "thread_name": null
+    }
+  ],
+  "unloaded_modules": []
+}
diff --git a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json.snap b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json.snap
index da81eb1..a515f6d 100644
--- a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json.snap
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json.snap
@@ -1,7 +1,7 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
-assertion_line: 55
-expression: stdout
+assertion_line: 357
+expression: json_out
 
 ---
-{"crash_info":{"address":"0x00000045","assertion":null,"crashing_thread":0,"type":"EXCEPTION_ACCESS_VIOLATION_WRITE"},"crashing_thread":{"frame_count":4,"frames":[{"file":null,"frame":0,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"test_app.exe","module_offset":"0x0000429e","offset":"0x0040429e","registers":{"eax":"0x00000045","ebp":"0x0012fe88","ebx":"0x7c80abc1","ecx":"0x0012fe94","edi":"0x00000a28","edx":"0x0042bc58","efl":"0x00010246","eip":"0x0040429e","esi":"0x00000002","esp":"0x0012fe84"},"trust":"context"},{"file":null,"frame":1,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"test_app.exe","module_offset":"0x000041ff","offset":"0x004041ff","trust":"frame_pointer"},{"file":null,"frame":2,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"test_app.exe","module_offset":"0x000053eb","offset":"0x004053eb","trust":"frame_pointer"},{"file":null,"frame":3,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"kernel32.dll","module_offset":"0x00016fd6","offset":"0x7c816fd6","trust":"frame_pointer"}],"last_error_value":null,"thread_name":null,"threads_index":0},"lsb_release":null,"mac_crash_info":null,"main_module":0,"modules":[{"base_addr":"0x00400000","cert_subject":null,"code_id":"45d35f6c2d000","corrupt_symbols":false,"debug_file":"test_app.pdb","debug_id":"5A9832E5287241C1838ED98914E9B7FF1","end_addr":"0x0042d000","filename":"test_app.exe","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":null},{"base_addr":"0x7c900000","cert_subject":null,"code_id":"411096b4b0000","corrupt_symbols":false,"debug_file":"ntdll.pdb","debug_id":"36515FB5D04345E491F672FA2E2878C02","end_addr":"0x7c9b0000","filename":"ntdll.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2180"},{"base_addr":"0x7c800000","cert_subject":null,"code_id":"44ab9a84f4000","corrupt_symbols":false,"debug_file":"kernel32.pdb","debug_id":"BCE8785C57B44245A669896B6A19B9542","end_addr":"0x7c8f4000","filename":"kernel32.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2945"},{"base_addr":"0x774e0000","cert_subject":null,"code_id":"42e5be9313d000","corrupt_symbols":false,"debug_file":"ole32.pdb","debug_id":"683B65B246F4418796D2EE6D4C55EB112","end_addr":"0x7761d000","filename":"ole32.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2726"},{"base_addr":"0x77dd0000","cert_subject":null,"code_id":"411096a79b000","corrupt_symbols":false,"debug_file":"advapi32.pdb","debug_id":"455D6C5F184D45BBB5C5F30F829751142","end_addr":"0x77e6b000","filename":"advapi32.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2180"},{"base_addr":"0x77e70000","cert_subject":null,"code_id":"411096ae91000","corrupt_symbols":false,"debug_file":"rpcrt4.pdb","debug_id":"BEA45A721DA141DAA3BA86B3A20311532","end_addr":"0x77f01000","filename":"rpcrt4.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2180"},{"base_addr":"0x77f10000","cert_subject":null,"code_id":"43b34feb47000","corrupt_symbols":false,"debug_file":"gdi32.pdb","debug_id":"C0EA66BE00A64BD7AEF79E443A91869C2","end_addr":"0x77f57000","filename":"gdi32.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2818"},{"base_addr":"0x77d40000","cert_subject":null,"code_id":"4226015990000","corrupt_symbols":false,"debug_file":"user32.pdb","debug_id":"EE2B714D83A34C9D88027621272F83262","end_addr":"0x77dd0000","filename":"user32.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2622"},{"base_addr":"0x77c10000","cert_subject":null,"code_id":"4110975258000","corrupt_symbols":false,"debug_file":"msvcrt.pdb","debug_id":"A678F3C30DED426B839032B996987E381","end_addr":"0x77c68000","filename":"msvcrt.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"7.0.2600.2180"},{"base_addr":"0x76390000","cert_subject":null,"code_id":"411096ae1d000","corrupt_symbols":false,"debug_file":"imm32.pdb","debug_id":"2C17A49C251B4C8EB9E2AD13D7D9EA162","end_addr":"0x763ad000","filename":"imm32.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2180"},{"base_addr":"0x59a60000","cert_subject":null,"code_id":"4110969aa1000","corrupt_symbols":false,"debug_file":"dbghelp.pdb","debug_id":"39559573E21B46F28E286923BE9E6A761","end_addr":"0x59b01000","filename":"dbghelp.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2180"},{"base_addr":"0x77c00000","cert_subject":null,"code_id":"411096b78000","corrupt_symbols":false,"debug_file":"version.pdb","debug_id":"180A90C40384463E82DDC45B2C8AB76E2","end_addr":"0x77c08000","filename":"version.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2180"},{"base_addr":"0x76bf0000","cert_subject":null,"code_id":"411096cab000","corrupt_symbols":false,"debug_file":"psapi.pdb","debug_id":"A5C3A1F9689F43D8AD228A09293889702","end_addr":"0x76bfb000","filename":"psapi.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2180"}],"modules_contains_cert_info":false,"pid":3932,"sensitive":{"exploitability":null},"status":"OK","system_info":{"cpu_arch":"x86","cpu_count":1,"cpu_info":"GenuineIntel family 6 model 13 stepping 8","cpu_microcode_version":null,"os":"Windows NT","os_ver":"5.1.2600 Service Pack 2"},"thread_count":2,"threads":[{"frame_count":4,"frames":[{"file":null,"frame":0,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"test_app.exe","module_offset":"0x0000429e","offset":"0x0040429e","trust":"context"},{"file":null,"frame":1,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"test_app.exe","module_offset":"0x000041ff","offset":"0x004041ff","trust":"frame_pointer"},{"file":null,"frame":2,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"test_app.exe","module_offset":"0x000053eb","offset":"0x004053eb","trust":"frame_pointer"},{"file":null,"frame":3,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"kernel32.dll","module_offset":"0x00016fd6","offset":"0x7c816fd6","trust":"frame_pointer"}],"last_error_value":null,"thread_name":null},{"frame_count":0,"frames":[],"last_error_value":null,"thread_name":null}],"unloaded_modules":[]}
+{"crash_info":{"address":"0x00000045","assertion":null,"crashing_thread":0,"type":"EXCEPTION_ACCESS_VIOLATION_WRITE"},"crashing_thread":{"frame_count":4,"frames":[{"file":null,"frame":0,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"test_app.exe","module_offset":"0x0000429e","offset":"0x0040429e","registers":{"eax":"0x00000045","ebp":"0x0012fe88","ebx":"0x7c80abc1","ecx":"0x0012fe94","edi":"0x00000a28","edx":"0x0042bc58","efl":"0x00010246","eip":"0x0040429e","esi":"0x00000002","esp":"0x0012fe84"},"trust":"context"},{"file":null,"frame":1,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"test_app.exe","module_offset":"0x000041ff","offset":"0x004041ff","trust":"frame_pointer"},{"file":null,"frame":2,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"test_app.exe","module_offset":"0x000053eb","offset":"0x004053eb","trust":"frame_pointer"},{"file":null,"frame":3,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"kernel32.dll","module_offset":"0x00016fd6","offset":"0x7c816fd6","trust":"frame_pointer"}],"last_error_value":null,"thread_name":null,"threads_index":0},"lsb_release":null,"mac_crash_info":null,"main_module":0,"modules":[{"base_addr":"0x00400000","cert_subject":null,"code_id":"45d35f6c2d000","corrupt_symbols":false,"debug_file":"test_app.pdb","debug_id":"5A9832E5287241C1838ED98914E9B7FF1","end_addr":"0x0042d000","filename":"test_app.exe","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":null},{"base_addr":"0x7c900000","cert_subject":null,"code_id":"411096b4b0000","corrupt_symbols":false,"debug_file":"ntdll.pdb","debug_id":"36515FB5D04345E491F672FA2E2878C02","end_addr":"0x7c9b0000","filename":"ntdll.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2180"},{"base_addr":"0x7c800000","cert_subject":null,"code_id":"44ab9a84f4000","corrupt_symbols":false,"debug_file":"kernel32.pdb","debug_id":"BCE8785C57B44245A669896B6A19B9542","end_addr":"0x7c8f4000","filename":"kernel32.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2945"},{"base_addr":"0x774e0000","cert_subject":null,"code_id":"42e5be9313d000","corrupt_symbols":false,"debug_file":"ole32.pdb","debug_id":"683B65B246F4418796D2EE6D4C55EB112","end_addr":"0x7761d000","filename":"ole32.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2726"},{"base_addr":"0x77dd0000","cert_subject":null,"code_id":"411096a79b000","corrupt_symbols":false,"debug_file":"advapi32.pdb","debug_id":"455D6C5F184D45BBB5C5F30F829751142","end_addr":"0x77e6b000","filename":"advapi32.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2180"},{"base_addr":"0x77e70000","cert_subject":null,"code_id":"411096ae91000","corrupt_symbols":false,"debug_file":"rpcrt4.pdb","debug_id":"BEA45A721DA141DAA3BA86B3A20311532","end_addr":"0x77f01000","filename":"rpcrt4.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2180"},{"base_addr":"0x77f10000","cert_subject":null,"code_id":"43b34feb47000","corrupt_symbols":false,"debug_file":"gdi32.pdb","debug_id":"C0EA66BE00A64BD7AEF79E443A91869C2","end_addr":"0x77f57000","filename":"gdi32.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2818"},{"base_addr":"0x77d40000","cert_subject":null,"code_id":"4226015990000","corrupt_symbols":false,"debug_file":"user32.pdb","debug_id":"EE2B714D83A34C9D88027621272F83262","end_addr":"0x77dd0000","filename":"user32.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2622"},{"base_addr":"0x77c10000","cert_subject":null,"code_id":"4110975258000","corrupt_symbols":false,"debug_file":"msvcrt.pdb","debug_id":"A678F3C30DED426B839032B996987E381","end_addr":"0x77c68000","filename":"msvcrt.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"7.0.2600.2180"},{"base_addr":"0x76390000","cert_subject":null,"code_id":"411096ae1d000","corrupt_symbols":false,"debug_file":"imm32.pdb","debug_id":"2C17A49C251B4C8EB9E2AD13D7D9EA162","end_addr":"0x763ad000","filename":"imm32.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2180"},{"base_addr":"0x59a60000","cert_subject":null,"code_id":"4110969aa1000","corrupt_symbols":false,"debug_file":"dbghelp.pdb","debug_id":"39559573E21B46F28E286923BE9E6A761","end_addr":"0x59b01000","filename":"dbghelp.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2180"},{"base_addr":"0x77c00000","cert_subject":null,"code_id":"411096b78000","corrupt_symbols":false,"debug_file":"version.pdb","debug_id":"180A90C40384463E82DDC45B2C8AB76E2","end_addr":"0x77c08000","filename":"version.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2180"},{"base_addr":"0x76bf0000","cert_subject":null,"code_id":"411096cab000","corrupt_symbols":false,"debug_file":"psapi.pdb","debug_id":"A5C3A1F9689F43D8AD228A09293889702","end_addr":"0x76bfb000","filename":"psapi.dll","loaded_symbols":false,"missing_symbols":false,"symbol_url":null,"version":"5.1.2600.2180"}],"modules_contains_cert_info":false,"pid":3932,"status":"OK","system_info":{"cpu_arch":"x86","cpu_count":1,"cpu_info":"GenuineIntel family 6 model 13 stepping 8","cpu_microcode_version":null,"os":"Windows NT","os_ver":"5.1.2600 Service Pack 2"},"thread_count":2,"threads":[{"frame_count":4,"frames":[{"file":null,"frame":0,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"test_app.exe","module_offset":"0x0000429e","offset":"0x0040429e","trust":"context"},{"file":null,"frame":1,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"test_app.exe","module_offset":"0x000041ff","offset":"0x004041ff","trust":"frame_pointer"},{"file":null,"frame":2,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"test_app.exe","module_offset":"0x000053eb","offset":"0x004053eb","trust":"frame_pointer"},{"file":null,"frame":3,"function":null,"function_offset":null,"line":null,"missing_symbols":true,"module":"kernel32.dll","module_offset":"0x00016fd6","offset":"0x7c816fd6","trust":"frame_pointer"}],"last_error_value":null,"thread_name":null},{"frame_count":0,"frames":[],"last_error_value":null,"thread_name":null}],"unloaded_modules":[]}
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 6215ef1..430baec 100644
--- a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__long-help.snap
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__long-help.snap
@@ -1,6 +1,6 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
-assertion_line: 380
+assertion_line: 465
 expression: stdout
 
 ---
@@ -37,6 +37,12 @@ FLAGS:
             Provide a briefer --human report.
             
             Only provides the top-level summary and a backtrace of the crashing thread.
+            
+        --recover-function-args
+            **[UNSTABLE]** Heuristically recover function arguments
+            
+            This is an experimental feature, which currently only shows up in --human output.
+            
     -h, --help
             Prints help information
 
@@ -52,6 +58,35 @@ OPTIONS:
             output will be the 'primary' output and default to stdout, which can be configured with --output-file as
             normal.
             
+        --features <features>
+            Specify at a high-level how much analysis to perform.
+            
+            This flag provides a way to more blindly opt into Extra Analysis without having to know about the specific
+            features of minidump-stackwalk. This is equivalent to ProcessorOptions in minidump-processor. The current
+            supported values are:
+            
+            * stable-basic (default): give me solid detailed analysis that most people would want.
+            * stable-all: turn on extra detailed analysis.
+            * unstable-all: turn on the weird and experimental stuff.
+            
+            stable-all enables: nothing (currently identical to stable-basic)
+            
+            unstable-all enables: `--recover-function-args`
+            
+            minidump-stackwalk wants to be a reliable and stable tool, but we also want to be able to introduce new
+            features which may be experimental or expensive. To balance these two concerns, new features will usually be
+            disabled by default and given a specific flag, but still more easily 'discovered' by anyone who uses this
+            flag.
+            
+            Anyone using minidump-stackwalk who is *really* worried about the output being stable should probably not
+            use this flag in production, but its use is recommended for casual
+            human usage or for checking "what's new".
+            
+            Features under unstable-all may be deprecated and become noops. Features which require additional input
+            (such as `--evil-json`) cannot be affected by this, and must still be manually 'discovered'.
+            
+            
+             [default: stable-basic]  [possible values: stable-basic, stable-all, unstable-all]
         --output-file <output-file>
             Where to write the output to (if unspecified, stdout is used)
 
@@ -67,8 +102,8 @@ OPTIONS:
             
             
              [default: error]  [possible values: off, error, warn, info, debug, trace]
-        --raw-json <raw-json>
-            An input JSON file with the extra information.
+        --evil-json <evil-json>
+            **[UNSTABLE]** An input JSON file with the extra information.
             
             This is a gross hack for some legacy side-channel information that mozilla uses. It will hopefully be phased
             out and deprecated in favour of just using custom streams in the minidump itself.
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 445f971..0c7f6b5 100644
--- a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__markdown-help.snap
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__markdown-help.snap
@@ -1,6 +1,6 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
-assertion_line: 417
+assertion_line: 502
 expression: stdout
 
 ---
@@ -42,6 +42,12 @@ Pretty-print --json output.
 Provide a briefer --human report.
 
 Only provides the top-level summary and a backtrace of the crashing thread.
+
+### `--recover-function-args`
+**[UNSTABLE]** Heuristically recover function arguments
+
+This is an experimental feature, which currently only shows up in --human output.
+
 ### `-h, --help`
 Prints help information
 
@@ -57,6 +63,35 @@ Because this creates two output streams, you must specify a path to write the --
 output will be the 'primary' output and default to stdout, which can be configured with --output-file as
 normal.
 
+### `--features <features>`
+Specify at a high-level how much analysis to perform.
+
+This flag provides a way to more blindly opt into Extra Analysis without having to know about the specific
+features of minidump-stackwalk. This is equivalent to ProcessorOptions in minidump-processor. The current
+supported values are:
+
+* stable-basic (default): give me solid detailed analysis that most people would want.
+* stable-all: turn on extra detailed analysis.
+* unstable-all: turn on the weird and experimental stuff.
+
+stable-all enables: nothing (currently identical to stable-basic)
+
+unstable-all enables: `--recover-function-args`
+
+minidump-stackwalk wants to be a reliable and stable tool, but we also want to be able to introduce new
+features which may be experimental or expensive. To balance these two concerns, new features will usually be
+disabled by default and given a specific flag, but still more easily 'discovered' by anyone who uses this
+flag.
+
+Anyone using minidump-stackwalk who is *really* worried about the output being stable should probably not
+use this flag in production, but its use is recommended for casual
+human usage or for checking "what's new".
+
+Features under unstable-all may be deprecated and become noops. Features which require additional input
+(such as `--evil-json`) cannot be affected by this, and must still be manually 'discovered'.
+
+
+\[default: stable-basic]  [possible values: stable-basic, stable-all, unstable-all]
 ### `--output-file <output-file>`
 Where to write the output to (if unspecified, stdout is used)
 
@@ -72,8 +107,8 @@ happened the way it did, --verbose=trace is very useful (all unwinder logging wi
 
 
 \[default: error]  [possible values: off, error, warn, info, debug, trace]
-### `--raw-json <raw-json>`
-An input JSON file with the extra information.
+### `--evil-json <evil-json>`
+**[UNSTABLE]** An input JSON file with the extra information.
 
 This is a gross hack for some legacy side-channel information that mozilla uses. It will hopefully be phased
 out and deprecated in favour of just using custom streams in the minidump itself.
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 6edec2d..080babd 100644
--- a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__short-help.snap
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__short-help.snap
@@ -1,6 +1,6 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
-assertion_line: 398
+assertion_line: 483
 expression: stdout
 
 ---
@@ -37,6 +37,12 @@ FLAGS:
             Provide a briefer --human report.
             
             Only provides the top-level summary and a backtrace of the crashing thread.
+            
+        --recover-function-args
+            **[UNSTABLE]** Heuristically recover function arguments
+            
+            This is an experimental feature, which currently only shows up in --human output.
+            
     -h, --help
             Prints help information
 
@@ -52,6 +58,35 @@ OPTIONS:
             output will be the 'primary' output and default to stdout, which can be configured with --output-file as
             normal.
             
+        --features <features>
+            Specify at a high-level how much analysis to perform.
+            
+            This flag provides a way to more blindly opt into Extra Analysis without having to know about the specific
+            features of minidump-stackwalk. This is equivalent to ProcessorOptions in minidump-processor. The current
+            supported values are:
+            
+            * stable-basic (default): give me solid detailed analysis that most people would want.
+            * stable-all: turn on extra detailed analysis.
+            * unstable-all: turn on the weird and experimental stuff.
+            
+            stable-all enables: nothing (currently identical to stable-basic)
+            
+            unstable-all enables: `--recover-function-args`
+            
+            minidump-stackwalk wants to be a reliable and stable tool, but we also want to be able to introduce new
+            features which may be experimental or expensive. To balance these two concerns, new features will usually be
+            disabled by default and given a specific flag, but still more easily 'discovered' by anyone who uses this
+            flag.
+            
+            Anyone using minidump-stackwalk who is *really* worried about the output being stable should probably not
+            use this flag in production, but its use is recommended for casual
+            human usage or for checking "what's new".
+            
+            Features under unstable-all may be deprecated and become noops. Features which require additional input
+            (such as `--evil-json`) cannot be affected by this, and must still be manually 'discovered'.
+            
+            
+             [default: stable-basic]  [possible values: stable-basic, stable-all, unstable-all]
         --output-file <output-file>
             Where to write the output to (if unspecified, stdout is used)
 
@@ -67,8 +102,8 @@ OPTIONS:
             
             
              [default: error]  [possible values: off, error, warn, info, debug, trace]
-        --raw-json <raw-json>
-            An input JSON file with the extra information.
+        --evil-json <evil-json>
+            **[UNSTABLE]** An input JSON file with the extra information.
             
             This is a gross hack for some legacy side-channel information that mozilla uses. It will hopefully be phased
             out and deprecated in favour of just using custom streams in the minidump itself.
diff --git a/minidump-stackwalk/tests/test-minidump-stackwalk.rs b/minidump-stackwalk/tests/test-minidump-stackwalk.rs
index 8c49718..b578e7b 100644
--- a/minidump-stackwalk/tests/test-minidump-stackwalk.rs
+++ b/minidump-stackwalk/tests/test-minidump-stackwalk.rs
@@ -95,6 +95,50 @@ fn test_json_pretty() {
     assert_eq!(stderr, "");
 }
 
+#[test]
+fn test_json_stable_all() {
+    let bin = env!("CARGO_BIN_EXE_minidump-stackwalk");
+    let output = Command::new(bin)
+        .arg("--json")
+        .arg("--pretty")
+        .arg("--features")
+        .arg("stable-all")
+        .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!("json-stable-all", stdout);
+    assert_eq!(stderr, "");
+}
+
+#[test]
+fn test_json_unstable_all() {
+    let bin = env!("CARGO_BIN_EXE_minidump-stackwalk");
+    let output = Command::new(bin)
+        .arg("--json")
+        .arg("--pretty")
+        .arg("--features")
+        .arg("unstable-all")
+        .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!("json-unstable-all", stdout);
+    assert_eq!(stderr, "");
+}
+
 #[test]
 fn test_json_symbols() {
     // For a while this didn't parse right
@@ -124,7 +168,7 @@ fn test_evil_json() {
     let output = Command::new(bin)
         .arg("--json")
         .arg("--pretty")
-        .arg("--raw-json")
+        .arg("--evil-json")
         .arg("../testdata/evil.json")
         .arg("../testdata/test.dmp")
         .arg("../testdata/symbols/")
@@ -160,6 +204,48 @@ fn test_human() {
     assert_eq!(stderr, "");
 }
 
+#[test]
+fn test_human_stable_all() {
+    let bin = env!("CARGO_BIN_EXE_minidump-stackwalk");
+    let output = Command::new(bin)
+        .arg("--human")
+        .arg("--features")
+        .arg("stable-all")
+        .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!("human-stable-all", stdout);
+    assert_eq!(stderr, "");
+}
+
+#[test]
+fn test_human_unstable_all() {
+    let bin = env!("CARGO_BIN_EXE_minidump-stackwalk");
+    let output = Command::new(bin)
+        .arg("--human")
+        .arg("--features")
+        .arg("unstable-all")
+        .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!("human-unstable-all", stdout);
+    assert_eq!(stderr, "");
+}
+
 #[test]
 fn test_human_symbols() {
     // For a while this didn't parse right
diff --git a/minidump/tests/test_minidump.rs b/minidump/tests/test_minidump.rs
index 58401b8..0ccbbea 100644
--- a/minidump/tests/test_minidump.rs
+++ b/minidump/tests/test_minidump.rs
@@ -351,3 +351,21 @@ fn test_linux_os_version() {
         "#1 SMP Mon Nov 6 16:00:12 UTC 2017",
     );
 }
+
+#[test]
+fn test_full_dump_memory() {
+    let path = get_test_minidump_path("full-dump.dmp");
+    let dump = Minidump::read_path(&path).unwrap();
+    let memory_list = dump.get_stream::<MinidumpMemory64List<'_>>().unwrap();
+    assert_eq!(memory_list.iter().count(), 54);
+    let blocks: Vec<_> = memory_list.iter().take(3).collect();
+    assert_eq!(blocks[0].base_address, 0x007FFE0000);
+    assert_eq!(blocks[0].size, 0x1000);
+    assert_eq!(blocks[0].bytes[0..8], [0, 0, 0, 0, 0, 0, 0xA0, 0x0F]);
+    assert_eq!(blocks[1].base_address, 0x007FFE9000);
+    assert_eq!(blocks[1].size, 0x1000);
+    assert_eq!(blocks[1].bytes[0..8], [0x48, 0x61, 0x6C, 0x54, 0, 0, 0, 0]);
+    assert_eq!(blocks[2].base_address, 0x9897D0D000);
+    assert_eq!(blocks[2].size, 0x3000);
+    assert_eq!(blocks[2].bytes[0..8], [0, 0, 0, 0, 0, 0, 0, 0]);
+}