Repository: luser/rust-minidump
Pull request #443 (2022-02-10 01:32:13)

Implement MinidumpMemory64List stream
I happened to need to read a full dump and found this missing in the library, so I implemented it.

To reuse the existing code, I abstracted some functions on `MinidumpMemoryList` into a generic type `MinidumpMemoryListGeneric`, and made both `MinidumpMemoryList` and the new `MinidumpMemory64List` type aliases of the generic type.

wwylele: Addressed the comments and added a test. The attached full dump is relatively large (8MB)... 
Gankra: Oh another place where we enumerated implemented streams: https://github.com/luser/rust-minidump/blob/932e4fdcba191b9cad2bdf13609a585ba72272f5/minidump/src/minidump.rs#L4537
Gankra: I have been a bit slow to react to this because I have been pondering a Grand Vision For Memory in the background for a while, and this is forcing me to actually form it into something coherent instead of Cool Dream.

Basically, I had always been picturing that this API would end up being similar to how we unify MemoryInfo and LinuxMaps into UnifiedMemoryInfo (not concerned with naming here):

https://github.com/luser/rust-minidump/blob/932e4fdcba191b9cad2bdf13609a585ba72272f5/minidump/src/minidump.rs#L114-L123

I don't really love how I've done UnifiedMemoryInfo, so I have been considering introducing a new API to the `Minidump` type itself that would basically be `get_memory_info(&self) -> Option<UnifiedMemoryInfo>`.

Under the hood this would try to deserialize both known memory info streams for you, so as a user of rust-minidump you *really* don't need to care about which kind you have, unless you want to specifically drill into specific features of one of them.

I was *imagining* introducing MEMORY64 support to take a similar path: we have a UnifiedMemory type (again names not important) which is an enum(?) wrapping the two forms and you get it with `Minidump::get_memory` which deserializes both kinds and provides an abstract interface over both. I don't think it would even need to change the interface since they're both fine with talking about 64-bit addresses only.

Now with all that written out and said, it's concrete enough in my mind that I don't think your PR fundamentally changes the ability to introduce that? **Which is really good.** You don't actually try to introduce an abstraction over the two that anything uses. So e.g. minidump-processor won't actually learn to use MEMORY64 dumps with this PR (which is fine, limited scope is nice and easy to review!).

I am going to sleep on this and come back to it tomorrow.
Gankra: Apparently the primary mulling I did was "fuck I really need to document this procedure", so I wrote some WIP "contributing docs" with a section dedicated to adding a stream:

https://github.com/Gankra/rust-minidump/blob/contrib/CONTRIBUTING.md#adding-a-minidump-stream
wwylele: I added more stuff according to the guideline. For the unit test with synthesized data I currently only added `test_memory64_list` (which is mostly just `test_memory_list` but with a second memory block). All other tests `test_memory_list_*` could be duplicated for memory64 but it is kind of silly. I think we could start introducing some abstraction for these unit tests (an enum? a trait?). I left these tests out, and we can add them in once we decide the direction of abstraction.
Gankra: Also don't worry about the clippy failures, rust just updated and added some new lints we haven't fixed yet. Not your problem.

Patch:
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-stackwalk/src/main.rs b/minidump-stackwalk/src/main.rs
index 2113463..f17c35c 100644
--- a/minidump-stackwalk/src/main.rs
+++ b/minidump-stackwalk/src/main.rs
@@ -523,6 +523,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 +543,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..5a29f11 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;
 
@@ -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/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]);
+}