Repository: luser/rust-minidump
Pull request #432 (2022-02-08 11:33:05)

Switch to debugid crate instead of strings
The Debug ID and Code ID were represented by strings in the breakpad
format.  This switches this to the DebugId and CodeId types from the
debugid crate as also used by symbolic.

The benefit is strong types and that DebugId becomes Copy.

As a result of this some tests which used invalid Debug IDs had to be changed to use valid ones.  Also the CodeId is now normalised resulting in snapshot changes.  I went with uppercase as it seemed more similar to the current display, though lowercase would not need an extra string allocation so might be better.

flub: > Argh, sorry about all the merge conflicts. Friggin' dependencies.

My fault for sorting the dependencies...

> I'm not terribly confident in reviewing this, I really don't understand the semantics of debugid/codeid stuff. Especially once Breakpad's extensions and bugginess get involved. @luser do you have the time to spare?

Most of my confidence comes from the fact we run symbolic(ator) with this in prod, receiving lots of minidumps from breakpad etc, so its breakpad compatibility should be fine.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 0114f98..35119d4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -85,6 +85,7 @@ version = "0.9.6"
 dependencies = [
  "async-trait",
  "circular",
+ "debugid",
  "log",
  "minidump-common",
  "nom",
@@ -198,6 +199,15 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "debugid"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91cf5a8c2f2097e2a32627123508635d47ce10563d999ec1a95addf08b502ba"
+dependencies = [
+ "uuid",
+]
+
 [[package]]
 name = "derive_arbitrary"
 version = "1.0.2"
@@ -640,6 +650,7 @@ name = "minidump"
 version = "0.9.6"
 dependencies = [
  "arbitrary",
+ "debugid",
  "doc-comment",
  "encoding",
  "log",
@@ -652,6 +663,7 @@ dependencies = [
  "test-assembler",
  "thiserror",
  "time 0.3.7",
+ "uuid",
 ]
 
 [[package]]
@@ -660,6 +672,7 @@ version = "0.9.6"
 dependencies = [
  "arbitrary",
  "bitflags",
+ "debugid",
  "enum-primitive-derive",
  "log",
  "num-traits 0.2.14",
@@ -675,6 +688,7 @@ dependencies = [
  "async-trait",
  "breakpad-symbols",
  "clap",
+ "debugid",
  "doc-comment",
  "log",
  "memmap2",
@@ -1447,6 +1461,12 @@ dependencies = [
  "percent-encoding",
 ]
 
+[[package]]
+name = "uuid"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+
 [[package]]
 name = "vcpkg"
 version = "0.2.15"
diff --git a/breakpad-symbols/Cargo.toml b/breakpad-symbols/Cargo.toml
index 66ec8de..44e3d0e 100644
--- a/breakpad-symbols/Cargo.toml
+++ b/breakpad-symbols/Cargo.toml
@@ -16,10 +16,11 @@ travis-ci = { repository = "luser/rust-minidump" }
 [dependencies]
 async-trait = "0.1.51"
 circular = "0.3.0"
+debugid = "0.7"
+log = "0.4.1"
 minidump-common = { version = "0.9.6", path = "../minidump-common" }
-range-map = "0.1.5"
 nom = "~1.2.2"
-log = "0.4.1"
+range-map = "0.1.5"
 reqwest = { version = "0.11.6", features = ["gzip"] }
 tempfile = "3.3.0"
 thiserror = "1.0.30"
diff --git a/breakpad-symbols/src/lib.rs b/breakpad-symbols/src/lib.rs
index 3bba521..a0b93ec 100644
--- a/breakpad-symbols/src/lib.rs
+++ b/breakpad-symbols/src/lib.rs
@@ -18,7 +18,9 @@
 //! ```
 //! # std::env::set_current_dir(env!("CARGO_MANIFEST_DIR"));
 //! use breakpad_symbols::{SimpleSymbolSupplier, Symbolizer, SimpleFrame, SimpleModule};
+//! use debugid::DebugId;
 //! use std::path::PathBuf;
+//! use std::str::FromStr;
 //!
 //! #[tokio::main]
 //! async fn main() {
@@ -27,9 +29,8 @@
 //!     let symbolizer = Symbolizer::new(supplier);
 //!
 //!     // Simple function name lookup with debug file, debug id, address.
-//!     assert_eq!(symbolizer.get_symbol_at_address("test_app.pdb",
-//!         "5A9832E5287241C1838ED98914E9B7FF1",
-//!         0x1010)
+//!     let debug_id = DebugId::from_str("5A9832E5287241C1838ED98914E9B7FF1").unwrap();
+//!     assert_eq!(symbolizer.get_symbol_at_address("test_app.pdb", debug_id, 0x1010)
 //!         .await
 //!         .unwrap(),
 //!         "vswprintf");
@@ -37,6 +38,7 @@
 //! ```
 
 use async_trait::async_trait;
+use debugid::{CodeId, DebugId};
 use log::{debug, trace, warn};
 use reqwest::{Client, Url};
 use tempfile::NamedTempFile;
@@ -88,9 +90,9 @@ pub struct SimpleModule {
     pub base_address: Option<u64>,
     pub size: Option<u64>,
     pub code_file: Option<String>,
-    pub code_identifier: Option<String>,
+    pub code_identifier: Option<CodeId>,
     pub debug_file: Option<String>,
-    pub debug_id: Option<String>,
+    pub debug_id: Option<DebugId>,
     pub version: Option<String>,
 }
 
@@ -98,10 +100,10 @@ impl SimpleModule {
     /// Create a `SimpleModule` with the given `debug_file` and `debug_id`.
     ///
     /// Uses `default` for the remaining fields.
-    pub fn new(debug_file: &str, debug_id: &str) -> SimpleModule {
+    pub fn new(debug_file: &str, debug_id: DebugId) -> SimpleModule {
         SimpleModule {
             debug_file: Some(String::from(debug_file)),
-            debug_id: Some(String::from(debug_id)),
+            debug_id: Some(debug_id),
             ..SimpleModule::default()
         }
     }
@@ -119,16 +121,17 @@ impl Module for SimpleModule {
             .as_ref()
             .map_or(Cow::from(""), |s| Cow::Borrowed(&s[..]))
     }
-    fn code_identifier(&self) -> Cow<str> {
+    fn code_identifier(&self) -> CodeId {
         self.code_identifier
             .as_ref()
-            .map_or(Cow::from(""), |s| Cow::Borrowed(&s[..]))
+            .cloned()
+            .unwrap_or_else(CodeId::nil)
     }
     fn debug_file(&self) -> Option<Cow<str>> {
         self.debug_file.as_ref().map(|s| Cow::Borrowed(&s[..]))
     }
-    fn debug_identifier(&self) -> Option<Cow<str>> {
-        self.debug_id.as_ref().map(|s| Cow::Borrowed(&s[..]))
+    fn debug_identifier(&self) -> Option<DebugId> {
+        self.debug_id
     }
     fn version(&self) -> Option<Cow<str>> {
         self.version.as_ref().map(|s| Cow::Borrowed(&s[..]))
@@ -180,7 +183,7 @@ pub fn relative_symbol_path(module: &(dyn Module + Sync), extension: &str) -> Op
             // Windows file paths on non-Windows.
             let leaf = leafname(&debug_file);
             let filename = replace_or_add_extension(leaf, "pdb", extension);
-            [leaf, &debug_id[..], &filename[..]].join("/")
+            [leaf, &debug_id.breakpad().to_string(), &filename[..]].join("/")
         })
     })
 }
@@ -661,7 +664,7 @@ impl Symbolizer {
     pub async fn get_symbol_at_address(
         &self,
         debug_file: &str,
-        debug_id: &str,
+        debug_id: DebugId,
         address: u64,
     ) -> Option<String> {
         let k = (debug_file, debug_id);
@@ -682,6 +685,8 @@ impl Symbolizer {
     ///
     /// ```
     /// # std::env::set_current_dir(env!("CARGO_MANIFEST_DIR"));
+    /// use std::str::FromStr;
+    /// use debugid::DebugId;
     /// use breakpad_symbols::{SimpleSymbolSupplier,Symbolizer,SimpleFrame,SimpleModule};
     ///
     /// #[tokio::main]
@@ -690,7 +695,8 @@ impl Symbolizer {
     ///     let paths = vec!(PathBuf::from("../testdata/symbols/"));
     ///     let supplier = SimpleSymbolSupplier::new(paths);
     ///     let symbolizer = Symbolizer::new(supplier);
-    ///     let m = SimpleModule::new("test_app.pdb", "5A9832E5287241C1838ED98914E9B7FF1");
+    ///     let debug_id = DebugId::from_str("5A9832E5287241C1838ED98914E9B7FF1").unwrap();
+    ///     let m = SimpleModule::new("test_app.pdb", debug_id);
     ///     let mut f = SimpleFrame::with_instruction(0x1010);
     ///     let _ = symbolizer.fill_symbol(&m, &mut f).await;
     ///     assert_eq!(f.function.unwrap(), "vswprintf");
@@ -817,31 +823,33 @@ mod test {
     use std::fs::File;
     use std::io::Write;
     use std::path::{Path, PathBuf};
+    use std::str::FromStr;
 
     #[tokio::test]
     async fn test_relative_symbol_path() {
-        let m = SimpleModule::new("foo.pdb", "abcd1234");
+        let debug_id = DebugId::from_str("abcd1234-abcd-1234-abcd-abcd12345678-a").unwrap();
+        let m = SimpleModule::new("foo.pdb", debug_id);
         assert_eq!(
             &relative_symbol_path(&m, "sym").unwrap(),
-            "foo.pdb/abcd1234/foo.sym"
+            "foo.pdb/ABCD1234ABCD1234ABCDABCD12345678a/foo.sym"
         );
 
-        let m2 = SimpleModule::new("foo.pdb", "abcd1234");
+        let m2 = SimpleModule::new("foo.pdb", debug_id);
         assert_eq!(
             &relative_symbol_path(&m2, "bar").unwrap(),
-            "foo.pdb/abcd1234/foo.bar"
+            "foo.pdb/ABCD1234ABCD1234ABCDABCD12345678a/foo.bar"
         );
 
-        let m3 = SimpleModule::new("foo.xyz", "abcd1234");
+        let m3 = SimpleModule::new("foo.xyz", debug_id);
         assert_eq!(
             &relative_symbol_path(&m3, "sym").unwrap(),
-            "foo.xyz/abcd1234/foo.xyz.sym"
+            "foo.xyz/ABCD1234ABCD1234ABCDABCD12345678a/foo.xyz.sym"
         );
 
-        let m4 = SimpleModule::new("foo.xyz", "abcd1234");
+        let m4 = SimpleModule::new("foo.xyz", debug_id);
         assert_eq!(
             &relative_symbol_path(&m4, "bar").unwrap(),
-            "foo.xyz/abcd1234/foo.xyz.bar"
+            "foo.xyz/ABCD1234ABCD1234ABCDABCD12345678a/foo.xyz.bar"
         );
 
         let bad = SimpleModule::default();
@@ -854,7 +862,7 @@ mod test {
         assert!(relative_symbol_path(&bad2, "sym").is_none());
 
         let bad3 = SimpleModule {
-            debug_id: Some("foo".to_string()),
+            debug_id: Some(debug_id),
             ..SimpleModule::default()
         };
         assert!(relative_symbol_path(&bad3, "sym").is_none());
@@ -862,27 +870,28 @@ mod test {
 
     #[tokio::test]
     async fn test_relative_symbol_path_abs_paths() {
+        let debug_id = DebugId::from_str("abcd1234-abcd-1234-abcd-abcd12345678-a").unwrap();
         {
-            let m = SimpleModule::new("/path/to/foo.bin", "abcd1234");
+            let m = SimpleModule::new("/path/to/foo.bin", debug_id);
             assert_eq!(
                 &relative_symbol_path(&m, "sym").unwrap(),
-                "foo.bin/abcd1234/foo.bin.sym"
+                "foo.bin/ABCD1234ABCD1234ABCDABCD12345678a/foo.bin.sym"
             );
         }
 
         {
-            let m = SimpleModule::new("c:/path/to/foo.pdb", "abcd1234");
+            let m = SimpleModule::new("c:/path/to/foo.pdb", debug_id);
             assert_eq!(
                 &relative_symbol_path(&m, "sym").unwrap(),
-                "foo.pdb/abcd1234/foo.sym"
+                "foo.pdb/ABCD1234ABCD1234ABCDABCD12345678a/foo.sym"
             );
         }
 
         {
-            let m = SimpleModule::new("c:\\path\\to\\foo.pdb", "abcd1234");
+            let m = SimpleModule::new("c:\\path\\to\\foo.pdb", debug_id);
             assert_eq!(
                 &relative_symbol_path(&m, "sym").unwrap(),
-                "foo.pdb/abcd1234/foo.sym"
+                "foo.pdb/ABCD1234ABCD1234ABCDABCD12345678a/foo.sym"
             );
         }
     }
@@ -929,8 +938,18 @@ mod test {
         // Try loading symbols for each of two modules in each of the two
         // search paths.
         for &(path, file, id, sym) in [
-            (&paths[0], "foo.pdb", "abcd1234", "foo.pdb/abcd1234/foo.sym"),
-            (&paths[1], "bar.xyz", "ff9900", "bar.xyz/ff9900/bar.xyz.sym"),
+            (
+                &paths[0],
+                "foo.pdb",
+                DebugId::from_str("abcd1234-0000-0000-0000-abcd12345678-a").unwrap(),
+                "foo.pdb/ABCD1234000000000000ABCD12345678a/foo.sym",
+            ),
+            (
+                &paths[1],
+                "bar.xyz",
+                DebugId::from_str("ff990000-0000-0000-0000-abcd12345678-a").unwrap(),
+                "bar.xyz/FF990000000000000000ABCD12345678a/bar.xyz.sym",
+            ),
         ]
         .iter()
         {
@@ -950,8 +969,9 @@ mod test {
         }
 
         // Write a malformed symbol file, verify that it's found but fails to load.
-        let mal = SimpleModule::new("baz.pdb", "ffff0000");
-        let sym = "baz.pdb/ffff0000/baz.sym";
+        let debug_id = DebugId::from_str("ffff0000-0000-0000-0000-abcd12345678-a").unwrap();
+        let mal = SimpleModule::new("baz.pdb", debug_id);
+        let sym = "baz.pdb/FFFF0000000000000000ABCD12345678a/baz.sym";
         assert_eq!(
             supplier.locate_symbols(&mal).await,
             Err(SymbolError::NotFound)
@@ -973,10 +993,11 @@ mod test {
         // TODO: This could really use a MockSupplier
         let supplier = SimpleSymbolSupplier::new(vec![PathBuf::from(path)]);
         let symbolizer = Symbolizer::new(supplier);
-        let m1 = SimpleModule::new("foo.pdb", "abcd1234");
+        let debug_id = DebugId::from_str("abcd1234-abcd-1234-abcd-abcd12345678-a").unwrap();
+        let m1 = SimpleModule::new("foo.pdb", debug_id);
         write_symbol_file(
-            &path.join("foo.pdb/abcd1234/foo.sym"),
-            b"MODULE Linux x86 abcd1234 foo
+            &path.join("foo.pdb/ABCD1234ABCD1234ABCDABCD12345678a/foo.sym"),
+            b"MODULE Linux x86 ABCD1234ABCD1234ABCDABCD12345678a foo
 FILE 1 foo.c
 FUNC 1000 30 10 some func
 1000 30 100 1
@@ -992,13 +1013,14 @@ FUNC 1000 30 10 some func
 
         assert_eq!(
             symbolizer
-                .get_symbol_at_address("foo.pdb", "abcd1234", 0x1010)
+                .get_symbol_at_address("foo.pdb", debug_id, 0x1010)
                 .await
                 .unwrap(),
             "some func"
         );
 
-        let m2 = SimpleModule::new("bar.pdb", "ffff0000");
+        let debug_id = DebugId::from_str("ffff0000-0000-0000-0000-abcd12345678-a").unwrap();
+        let m2 = SimpleModule::new("bar.pdb", debug_id);
         let mut f2 = SimpleFrame::with_instruction(0x1010);
         // No symbols present, should not find anything.
         assert!(symbolizer.fill_symbol(&m2, &mut f2).await.is_err());
@@ -1008,8 +1030,8 @@ FUNC 1000 30 10 some func
         assert!(f2.source_line.is_none());
         // Results should be cached.
         write_symbol_file(
-            &path.join("bar.pdb/ffff0000/bar.sym"),
-            b"MODULE Linux x86 ffff0000 bar
+            &path.join("bar.pdb/ffff0000000000000000ABCD12345678a/bar.sym"),
+            b"MODULE Linux x86 ffff0000000000000000ABCD12345678a bar
 FILE 53 bar.c
 FUNC 1000 30 10 another func
 1000 30 7 53
@@ -1022,7 +1044,7 @@ FUNC 1000 30 10 another func
         assert!(f2.source_line.is_none());
         // This should also use cached results.
         assert!(symbolizer
-            .get_symbol_at_address("bar.pdb", "ffff0000", 0x1010)
+            .get_symbol_at_address("bar.pdb", debug_id, 0x1010)
             .await
             .is_none());
     }
diff --git a/minidump-common/Cargo.toml b/minidump-common/Cargo.toml
index af19b0e..abaf745 100644
--- a/minidump-common/Cargo.toml
+++ b/minidump-common/Cargo.toml
@@ -13,11 +13,12 @@ edition = "2018"
 travis-ci = { repository = "luser/rust-minidump" }
 
 [dependencies]
+arbitrary = { version = "1", optional = true, features = ["derive"] }
+bitflags = "1.3.2"
+debugid = "0.7"
 enum-primitive-derive = "0.2.2"
 log = "0.4.1"
 num-traits = "0.2"
 range-map = "0.1.5"
 scroll = { version = "0.10.2", features = ["derive"] }
 smart-default = "0.6.0"
-bitflags = "1.3.2"
-arbitrary = { version = "1", optional = true, features = ["derive"] }
diff --git a/minidump-common/src/traits.rs b/minidump-common/src/traits.rs
index e90f1b0..3e5c25e 100644
--- a/minidump-common/src/traits.rs
+++ b/minidump-common/src/traits.rs
@@ -1,5 +1,6 @@
 //! Some common traits used by minidump-related crates.
 
+use debugid::{CodeId, DebugId};
 use range_map::{Range, RangeMap};
 
 use std::borrow::Cow;
@@ -18,7 +19,7 @@ pub trait Module {
     /// builds of the same code module.  This may contain a uuid, timestamp,
     /// version number, or any combination of this or other information, in an
     /// implementation-defined format.
-    fn code_identifier(&self) -> Cow<str>;
+    fn code_identifier(&self) -> CodeId;
     /// The filename containing debugging information associated with the code
     /// module.  If debugging information is stored in a file separate from the
     /// code module itself (as is the case when .pdb or .dSYM files are used),
@@ -31,14 +32,14 @@ pub trait Module {
     /// the same as code_identifier when the debug_file and code_file are
     /// identical or when the same identifier is used to identify distinct
     /// debug and code files.
-    fn debug_identifier(&self) -> Option<Cow<str>>;
+    fn debug_identifier(&self) -> Option<DebugId>;
     /// A human-readable representation of the code module's version.
     fn version(&self) -> Option<Cow<str>>;
 }
 
-/// Implement Module for 2-tuples of &str for convenience.
+/// Implement Module for 2-tuples of (&str, DebugId) for convenience.
 /// `breakpad-symbols`' `Symbolizer::get_symbol_at_address` uses this.
-impl<'a> Module for (&'a str, &'a str) {
+impl<'a> Module for (&'a str, DebugId) {
     fn base_address(&self) -> u64 {
         0
     }
@@ -48,16 +49,16 @@ impl<'a> Module for (&'a str, &'a str) {
     fn code_file(&self) -> Cow<str> {
         Cow::Borrowed("")
     }
-    fn code_identifier(&self) -> Cow<str> {
-        Cow::Borrowed("")
+    fn code_identifier(&self) -> CodeId {
+        CodeId::nil()
     }
     fn debug_file(&self) -> Option<Cow<str>> {
         let &(file, _id) = self;
         Some(Cow::Borrowed(file))
     }
-    fn debug_identifier(&self) -> Option<Cow<str>> {
+    fn debug_identifier(&self) -> Option<DebugId> {
         let &(_file, id) = self;
-        Some(Cow::Borrowed(id))
+        Some(id)
     }
     fn version(&self) -> Option<Cow<str>> {
         None
diff --git a/minidump-processor/Cargo.toml b/minidump-processor/Cargo.toml
index ca056b8..42efaeb 100644
--- a/minidump-processor/Cargo.toml
+++ b/minidump-processor/Cargo.toml
@@ -23,16 +23,17 @@ symbolic-syms = []
 async-trait = "0.1.51"
 breakpad-symbols = { version = "0.9.6", path = "../breakpad-symbols", optional = true }
 clap = "2.34"
+debugid = "0.7"
 log = "0.4"
 memmap2 = "0.5.2"
 minidump = { version = "0.9.6", path = "../minidump" }
+scroll = "0.10.2"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
-scroll = "0.10.2"
 thiserror = "1.0.30"
 
 [dev-dependencies]
-test-assembler = "0.1.6"
-synth-minidump = { path = "../synth-minidump" }
 doc-comment = "0.3.3"
+synth-minidump = { path = "../synth-minidump" }
+test-assembler = "0.1.6"
 tokio =  { version = "1.12.0", features = ["full"] }
diff --git a/minidump-processor/src/process_state.rs b/minidump-processor/src/process_state.rs
index d8bb12c..76b63bc 100644
--- a/minidump-processor/src/process_state.rs
+++ b/minidump-processor/src/process_state.rs
@@ -717,10 +717,10 @@ Unknown streams encountered:
                     // filename | empty string
                     "debug_file": basename(module.debug_file().unwrap_or(Cow::Borrowed("")).borrow()),
                     // [[:xdigit:]]{33} | empty string
-                    "debug_id": module.debug_identifier().unwrap_or(Cow::Borrowed("")),
+                    "debug_id": module.debug_identifier().unwrap_or_default().breakpad().to_string(),
                     "end_addr": json_hex(module.raw.base_of_image + module.raw.size_of_image as u64),
                     "filename": &name,
-                    "code_id": module.code_identifier(),
+                    "code_id": module.code_identifier().as_str(),
                     "version": module.version(),
                     "cert_subject": self.cert_info.get(name),
 
@@ -781,7 +781,7 @@ Unknown streams encountered:
 
             "unloaded_modules": self.unloaded_modules.iter().map(|module| json!({
                 "base_addr": json_hex(module.raw.base_of_image),
-                "code_id": module.code_identifier(),
+                "code_id": module.code_identifier().as_str(),
                 "end_addr": json_hex(module.raw.base_of_image + module.raw.size_of_image as u64),
                 "filename": module.name,
                 "cert_subject": self.cert_info.get(&module.name),
diff --git a/minidump-processor/src/stackwalker/mod.rs b/minidump-processor/src/stackwalker/mod.rs
index 92503b5..b103ba4 100644
--- a/minidump-processor/src/stackwalker/mod.rs
+++ b/minidump-processor/src/stackwalker/mod.rs
@@ -230,6 +230,27 @@ async fn instruction_seems_valid_by_symbols<P>(
 where
     P: SymbolProvider + Sync,
 {
+    // Our input is a candidate return address, but we *really* want to validate the address
+    // of the call instruction *before* the return address. In theory this symbol-based
+    // analysis shouldn't *care* whether we're looking at the call or the instruction
+    // after it, but there is one corner case where the return address can be invalid
+    // but the instruction before it isn't: noreturn.
+    //
+    // If the *callee* is noreturn, then the caller has no obligation to have any instructions
+    // after the call! So e.g. on x86 if you CALL a noreturn function, the return address
+    // that's implicitly pushed *could* be one-past-the-end of the "function".
+    //
+    // This has been observed in practice with `+[NSThread exit]`!
+    //
+    // We don't otherwise need the instruction pointer to be terribly precise, so
+    // subtracting 1 from the address should be sufficient to handle this corner case.
+    let instruction = instruction.saturating_sub(1);
+
+    // NULL pointer is definitely not valid
+    if instruction == 0 {
+        return false;
+    }
+
     if let Some(module) = modules.module_at_address(instruction as u64) {
         // Create a dummy frame symbolizing implementation to feed into
         // our symbol provider with the address we're interested in. If
diff --git a/minidump/Cargo.toml b/minidump/Cargo.toml
index 34d7fc2..1cb23f9 100644
--- a/minidump/Cargo.toml
+++ b/minidump/Cargo.toml
@@ -12,16 +12,18 @@ readme = "README.md"
 edition = "2018"
 
 [dependencies]
-range-map = "0.1.5"
+arbitrary = { version = "1", optional = true, features = ["derive"] }
+debugid = "0.7"
+encoding = "0.2"
 log = "0.4.1"
+memmap2 = "0.5.2"
 minidump-common = { version = "0.9.6", path = "../minidump-common" }
 num-traits = "0.2"
-encoding = "0.2"
+range-map = "0.1.5"
 scroll = "0.10.2"
-memmap2 = "0.5.2"
-time = { version = "0.3.6", features = ["formatting"] }
 thiserror = "1.0.30"
-arbitrary = { version = "1", optional = true, features = ["derive"] }
+time = { version = "0.3.6", features = ["formatting"] }
+uuid = "0.8"
 
 [dev-dependencies]
 synth-minidump = { path = "../synth-minidump" }
diff --git a/minidump/src/minidump.rs b/minidump/src/minidump.rs
index b4be4d5..7dcc468 100644
--- a/minidump/src/minidump.rs
+++ b/minidump/src/minidump.rs
@@ -1,6 +1,7 @@
 // Copyright 2015 Ted Mielczarek. See the COPYRIGHT
 // file at the top-level directory of this distribution.
 
+use debugid::{CodeId, DebugId};
 use encoding::all::UTF_16LE;
 use encoding::{DecoderTrap, Encoding};
 use log::warn;
@@ -22,6 +23,7 @@ use std::ops::Deref;
 use std::path::Path;
 use std::str;
 use std::time::{Duration, SystemTime};
+use uuid::Uuid;
 
 pub use crate::context::*;
 use crate::strings::*;
@@ -880,7 +882,7 @@ impl MinidumpModule {
 
 "#,
             self.debug_file().unwrap_or(Cow::Borrowed("")),
-            self.debug_identifier().unwrap_or(Cow::Borrowed("")),
+            self.debug_identifier().unwrap_or_default(),
             self.version().unwrap_or(Cow::Borrowed("")),
         )?;
         Ok(())
@@ -907,12 +909,12 @@ impl Module for MinidumpModule {
     fn code_file(&self) -> Cow<'_, str> {
         Cow::Borrowed(&self.name)
     }
-    fn code_identifier(&self) -> Cow<'_, str> {
+    fn code_identifier(&self) -> CodeId {
         match self.codeview_info {
-            Some(CodeView::Elf(ref raw)) => Cow::Owned(bytes_to_hex(&raw.build_id)),
+            Some(CodeView::Elf(ref raw)) => CodeId::from_binary(&raw.build_id),
             _ => {
                 // TODO: Breakpad stubs this out on non-Windows.
-                Cow::Owned(format!(
+                CodeId::new(format!(
                     "{0:08X}{1:x}",
                     self.raw.time_date_stamp, self.raw.size_of_image
                 ))
@@ -928,15 +930,21 @@ impl Module for MinidumpModule {
             _ => None,
         }
     }
-    fn debug_identifier(&self) -> Option<Cow<'_, str>> {
+    fn debug_identifier(&self) -> Option<DebugId> {
         match self.codeview_info {
             Some(CodeView::Pdb70(ref raw)) => {
-                let id = format!("{:#}{:x}", raw.signature, raw.age,);
-                Some(Cow::Owned(id))
+                let uuid = Uuid::from_fields(
+                    raw.signature.data1,
+                    raw.signature.data2,
+                    raw.signature.data3,
+                    &raw.signature.data4,
+                )
+                .ok()?;
+                Some(DebugId::from_parts(uuid, raw.age))
             }
             Some(CodeView::Pdb20(ref raw)) => {
-                let id = format!("{:08X}{:x}", raw.signature, raw.age);
-                Some(Cow::Owned(id))
+                let uuid = Uuid::from_fields(raw.signature, 0, 0, &[0, 0, 0, 0]).ok()?;
+                Some(DebugId::from_parts(uuid, raw.age))
             }
             Some(CodeView::Elf(ref raw)) => {
                 // For backwards-compat (Linux minidumps have historically
@@ -956,7 +964,8 @@ impl Module for MinidumpModule {
                 } else {
                     raw.build_id.pread_with::<md::GUID>(0, LE).ok()
                 };
-                guid.map(|g| Cow::Owned(format!("{:#}0", g)))
+                guid.and_then(|g| Uuid::from_fields(g.data1, g.data2, g.data3, &g.data4).ok())
+                    .map(DebugId::from_uuid)
             }
             _ => None,
         }
@@ -1055,8 +1064,8 @@ impl Module for MinidumpUnloadedModule {
     fn code_file(&self) -> Cow<'_, str> {
         Cow::Borrowed(&self.name)
     }
-    fn code_identifier(&self) -> Cow<'_, str> {
-        Cow::Owned(format!(
+    fn code_identifier(&self) -> CodeId {
+        CodeId::new(format!(
             "{0:08X}{1:x}",
             self.raw.time_date_stamp, self.raw.size_of_image
         ))
@@ -1064,7 +1073,7 @@ impl Module for MinidumpUnloadedModule {
     fn debug_file(&self) -> Option<Cow<'_, str>> {
         None
     }
-    fn debug_identifier(&self) -> Option<Cow<'_, str>> {
+    fn debug_identifier(&self) -> Option<DebugId> {
         None
     }
     fn version(&self) -> Option<Cow<'_, str>> {
@@ -4832,11 +4841,14 @@ mod test {
         assert_eq!(modules[0].size(), 0xada542bd);
         assert_eq!(modules[0].code_file(), "single module");
         // time_date_stamp and size_of_image concatenated
-        assert_eq!(modules[0].code_identifier(), "B1054D2Aada542bd");
+        assert_eq!(
+            modules[0].code_identifier(),
+            CodeId::new("B1054D2Aada542bd".to_string())
+        );
         assert_eq!(modules[0].debug_file().unwrap(), "c:\\foo\\file.pdb");
         assert_eq!(
             modules[0].debug_identifier().unwrap(),
-            "ABCD1234F00DBEEF01020304050607081"
+            DebugId::from_breakpad("ABCD1234F00DBEEF01020304050607081").unwrap()
         );
     }
 
@@ -4862,7 +4874,10 @@ mod test {
         assert_eq!(modules[0].size(), 0xada542bd);
         assert_eq!(modules[0].code_file(), "single module");
         // time_date_stamp and size_of_image concatenated
-        assert_eq!(modules[0].code_identifier(), "B1054D2Aada542bd");
+        assert_eq!(
+            modules[0].code_identifier(),
+            CodeId::new("B1054D2Aada542bd".to_string())
+        );
     }
 
     #[test]
@@ -5817,25 +5832,28 @@ c70206ca83eb2852-de0206ca83eb2852  -w-s  10bac9000 fd:05 1196511 /usr/lib64/libt
         // The full build ID.
         assert_eq!(
             modules[0].code_identifier(),
-            "000102030405060708090a0b0c0d0e0f1011121314151617"
+            CodeId::new("000102030405060708090a0b0c0d0e0f1011121314151617".to_string())
         );
         assert_eq!(modules[0].debug_file().unwrap(), "module 1");
         // The first 16 bytes of the build ID interpreted as a GUID.
         assert_eq!(
             modules[0].debug_identifier().unwrap(),
-            "030201000504070608090A0B0C0D0E0F0"
+            DebugId::from_breakpad("030201000504070608090A0B0C0D0E0F0").unwrap()
         );
 
         assert_eq!(modules[1].base_address(), 0x200000000);
         assert_eq!(modules[1].code_file(), "module 2");
         // The full build ID.
-        assert_eq!(modules[1].code_identifier(), "0001020304050607");
+        assert_eq!(
+            modules[1].code_identifier(),
+            CodeId::new("0001020304050607".to_string())
+        );
         assert_eq!(modules[1].debug_file().unwrap(), "module 2");
         // The first 16 bytes of the build ID interpreted as a GUID, padded with
         // zeroes in this case.
         assert_eq!(
             modules[1].debug_identifier().unwrap(),
-            "030201000504070600000000000000000"
+            DebugId::from_breakpad("030201000504070600000000000000000").unwrap()
         );
     }
 


Test patch:
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 aae560a..4d8bb90 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,5 +1,6 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
+assertion_line: 121
 expression: stdout
 
 ---
@@ -85,7 +86,7 @@ expression: stdout
     {
       "base_addr": "0x00400000",
       "cert_subject": "rust-minidump",
-      "code_id": "45D35F6C2d000",
+      "code_id": "45d35f6c2d000",
       "corrupt_symbols": false,
       "debug_file": "test_app.pdb",
       "debug_id": "5A9832E5287241C1838ED98914E9B7FF1",
@@ -99,7 +100,7 @@ expression: stdout
     {
       "base_addr": "0x7c900000",
       "cert_subject": null,
-      "code_id": "411096B4b0000",
+      "code_id": "411096b4b0000",
       "corrupt_symbols": false,
       "debug_file": "ntdll.pdb",
       "debug_id": "36515FB5D04345E491F672FA2E2878C02",
@@ -113,7 +114,7 @@ expression: stdout
     {
       "base_addr": "0x7c800000",
       "cert_subject": null,
-      "code_id": "44AB9A84f4000",
+      "code_id": "44ab9a84f4000",
       "corrupt_symbols": false,
       "debug_file": "kernel32.pdb",
       "debug_id": "BCE8785C57B44245A669896B6A19B9542",
@@ -127,7 +128,7 @@ expression: stdout
     {
       "base_addr": "0x774e0000",
       "cert_subject": null,
-      "code_id": "42E5BE9313d000",
+      "code_id": "42e5be9313d000",
       "corrupt_symbols": false,
       "debug_file": "ole32.pdb",
       "debug_id": "683B65B246F4418796D2EE6D4C55EB112",
@@ -141,7 +142,7 @@ expression: stdout
     {
       "base_addr": "0x77dd0000",
       "cert_subject": null,
-      "code_id": "411096A79b000",
+      "code_id": "411096a79b000",
       "corrupt_symbols": false,
       "debug_file": "advapi32.pdb",
       "debug_id": "455D6C5F184D45BBB5C5F30F829751142",
@@ -155,7 +156,7 @@ expression: stdout
     {
       "base_addr": "0x77e70000",
       "cert_subject": null,
-      "code_id": "411096AE91000",
+      "code_id": "411096ae91000",
       "corrupt_symbols": false,
       "debug_file": "rpcrt4.pdb",
       "debug_id": "BEA45A721DA141DAA3BA86B3A20311532",
@@ -169,7 +170,7 @@ expression: stdout
     {
       "base_addr": "0x77f10000",
       "cert_subject": null,
-      "code_id": "43B34FEB47000",
+      "code_id": "43b34feb47000",
       "corrupt_symbols": false,
       "debug_file": "gdi32.pdb",
       "debug_id": "C0EA66BE00A64BD7AEF79E443A91869C2",
@@ -211,7 +212,7 @@ expression: stdout
     {
       "base_addr": "0x76390000",
       "cert_subject": null,
-      "code_id": "411096AE1d000",
+      "code_id": "411096ae1d000",
       "corrupt_symbols": false,
       "debug_file": "imm32.pdb",
       "debug_id": "2C17A49C251B4C8EB9E2AD13D7D9EA162",
@@ -225,7 +226,7 @@ expression: stdout
     {
       "base_addr": "0x59a60000",
       "cert_subject": null,
-      "code_id": "4110969Aa1000",
+      "code_id": "4110969aa1000",
       "corrupt_symbols": false,
       "debug_file": "dbghelp.pdb",
       "debug_id": "39559573E21B46F28E286923BE9E6A761",
@@ -239,7 +240,7 @@ expression: stdout
     {
       "base_addr": "0x77c00000",
       "cert_subject": null,
-      "code_id": "411096B78000",
+      "code_id": "411096b78000",
       "corrupt_symbols": false,
       "debug_file": "version.pdb",
       "debug_id": "180A90C40384463E82DDC45B2C8AB76E2",
@@ -253,7 +254,7 @@ expression: stdout
     {
       "base_addr": "0x76bf0000",
       "cert_subject": null,
-      "code_id": "411096CAb000",
+      "code_id": "411096cab000",
       "corrupt_symbols": false,
       "debug_file": "psapi.pdb",
       "debug_id": "A5C3A1F9689F43D8AD228A09293889702",
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 fdbbd4f..cc41d09 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,5 +1,6 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
+assertion_line: 97
 expression: stdout
 
 ---
@@ -85,7 +86,7 @@ expression: stdout
     {
       "base_addr": "0x00400000",
       "cert_subject": null,
-      "code_id": "45D35F6C2d000",
+      "code_id": "45d35f6c2d000",
       "corrupt_symbols": false,
       "debug_file": "test_app.pdb",
       "debug_id": "5A9832E5287241C1838ED98914E9B7FF1",
@@ -99,7 +100,7 @@ expression: stdout
     {
       "base_addr": "0x7c900000",
       "cert_subject": null,
-      "code_id": "411096B4b0000",
+      "code_id": "411096b4b0000",
       "corrupt_symbols": false,
       "debug_file": "ntdll.pdb",
       "debug_id": "36515FB5D04345E491F672FA2E2878C02",
@@ -113,7 +114,7 @@ expression: stdout
     {
       "base_addr": "0x7c800000",
       "cert_subject": null,
-      "code_id": "44AB9A84f4000",
+      "code_id": "44ab9a84f4000",
       "corrupt_symbols": false,
       "debug_file": "kernel32.pdb",
       "debug_id": "BCE8785C57B44245A669896B6A19B9542",
@@ -127,7 +128,7 @@ expression: stdout
     {
       "base_addr": "0x774e0000",
       "cert_subject": null,
-      "code_id": "42E5BE9313d000",
+      "code_id": "42e5be9313d000",
       "corrupt_symbols": false,
       "debug_file": "ole32.pdb",
       "debug_id": "683B65B246F4418796D2EE6D4C55EB112",
@@ -141,7 +142,7 @@ expression: stdout
     {
       "base_addr": "0x77dd0000",
       "cert_subject": null,
-      "code_id": "411096A79b000",
+      "code_id": "411096a79b000",
       "corrupt_symbols": false,
       "debug_file": "advapi32.pdb",
       "debug_id": "455D6C5F184D45BBB5C5F30F829751142",
@@ -155,7 +156,7 @@ expression: stdout
     {
       "base_addr": "0x77e70000",
       "cert_subject": null,
-      "code_id": "411096AE91000",
+      "code_id": "411096ae91000",
       "corrupt_symbols": false,
       "debug_file": "rpcrt4.pdb",
       "debug_id": "BEA45A721DA141DAA3BA86B3A20311532",
@@ -169,7 +170,7 @@ expression: stdout
     {
       "base_addr": "0x77f10000",
       "cert_subject": null,
-      "code_id": "43B34FEB47000",
+      "code_id": "43b34feb47000",
       "corrupt_symbols": false,
       "debug_file": "gdi32.pdb",
       "debug_id": "C0EA66BE00A64BD7AEF79E443A91869C2",
@@ -211,7 +212,7 @@ expression: stdout
     {
       "base_addr": "0x76390000",
       "cert_subject": null,
-      "code_id": "411096AE1d000",
+      "code_id": "411096ae1d000",
       "corrupt_symbols": false,
       "debug_file": "imm32.pdb",
       "debug_id": "2C17A49C251B4C8EB9E2AD13D7D9EA162",
@@ -225,7 +226,7 @@ expression: stdout
     {
       "base_addr": "0x59a60000",
       "cert_subject": null,
-      "code_id": "4110969Aa1000",
+      "code_id": "4110969aa1000",
       "corrupt_symbols": false,
       "debug_file": "dbghelp.pdb",
       "debug_id": "39559573E21B46F28E286923BE9E6A761",
@@ -239,7 +240,7 @@ expression: stdout
     {
       "base_addr": "0x77c00000",
       "cert_subject": null,
-      "code_id": "411096B78000",
+      "code_id": "411096b78000",
       "corrupt_symbols": false,
       "debug_file": "version.pdb",
       "debug_id": "180A90C40384463E82DDC45B2C8AB76E2",
@@ -253,7 +254,7 @@ expression: stdout
     {
       "base_addr": "0x76bf0000",
       "cert_subject": null,
-      "code_id": "411096CAb000",
+      "code_id": "411096cab000",
       "corrupt_symbols": false,
       "debug_file": "psapi.pdb",
       "debug_id": "A5C3A1F9689F43D8AD228A09293889702",
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 07bf5c8..539f35d 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,5 +1,6 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
+assertion_line: 656
 expression: stdout
 
 ---
@@ -54,56 +55,56 @@ expression: stdout
     {
       "base_addr": "0x00a003a0",
       "cert_subject": null,
-      "code_id": "B1054D2A1000",
+      "code_id": "b1054d2a1000",
       "end_addr": "0x00a013a0",
       "filename": "unused.dll"
     },
     {
       "base_addr": "0x00f00000",
       "cert_subject": null,
-      "code_id": "B1054D2A1000",
+      "code_id": "b1054d2a1000",
       "end_addr": "0x00f01000",
       "filename": "many.dll"
     },
     {
       "base_addr": "0x0af00220",
       "cert_subject": null,
-      "code_id": "B1054D2A2000",
+      "code_id": "b1054d2a2000",
       "end_addr": "0x0af02220",
       "filename": "solo.dll"
     },
     {
       "base_addr": "0x00f00100",
       "cert_subject": null,
-      "code_id": "B1054D2A1000",
+      "code_id": "b1054d2a1000",
       "end_addr": "0x00f01100",
       "filename": "many.dll"
     },
     {
       "base_addr": "0x00f003a0",
       "cert_subject": null,
-      "code_id": "B1054D2A1000",
+      "code_id": "b1054d2a1000",
       "end_addr": "0x00f013a0",
       "filename": "many.dll"
     },
     {
       "base_addr": "0x00f00220",
       "cert_subject": null,
-      "code_id": "B1054D2A2000",
+      "code_id": "b1054d2a2000",
       "end_addr": "0x00f02220",
       "filename": "solo.dll"
     },
     {
       "base_addr": "0x0af00220",
       "cert_subject": null,
-      "code_id": "B1054D2A2000",
+      "code_id": "b1054d2a2000",
       "end_addr": "0x0af02220",
       "filename": "solo.dll"
     },
     {
       "base_addr": "0x00f00000",
       "cert_subject": null,
-      "code_id": "B1054D2A1000",
+      "code_id": "b1054d2a1000",
       "end_addr": "0x00f01000",
       "filename": "many.dll"
     }
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 3a2307b..e058df7 100644
--- a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty.snap
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json-pretty.snap
@@ -1,5 +1,6 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
+assertion_line: 75
 expression: stdout
 
 ---
@@ -85,7 +86,7 @@ expression: stdout
     {
       "base_addr": "0x00400000",
       "cert_subject": null,
-      "code_id": "45D35F6C2d000",
+      "code_id": "45d35f6c2d000",
       "corrupt_symbols": false,
       "debug_file": "test_app.pdb",
       "debug_id": "5A9832E5287241C1838ED98914E9B7FF1",
@@ -99,7 +100,7 @@ expression: stdout
     {
       "base_addr": "0x7c900000",
       "cert_subject": null,
-      "code_id": "411096B4b0000",
+      "code_id": "411096b4b0000",
       "corrupt_symbols": false,
       "debug_file": "ntdll.pdb",
       "debug_id": "36515FB5D04345E491F672FA2E2878C02",
@@ -113,7 +114,7 @@ expression: stdout
     {
       "base_addr": "0x7c800000",
       "cert_subject": null,
-      "code_id": "44AB9A84f4000",
+      "code_id": "44ab9a84f4000",
       "corrupt_symbols": false,
       "debug_file": "kernel32.pdb",
       "debug_id": "BCE8785C57B44245A669896B6A19B9542",
@@ -127,7 +128,7 @@ expression: stdout
     {
       "base_addr": "0x774e0000",
       "cert_subject": null,
-      "code_id": "42E5BE9313d000",
+      "code_id": "42e5be9313d000",
       "corrupt_symbols": false,
       "debug_file": "ole32.pdb",
       "debug_id": "683B65B246F4418796D2EE6D4C55EB112",
@@ -141,7 +142,7 @@ expression: stdout
     {
       "base_addr": "0x77dd0000",
       "cert_subject": null,
-      "code_id": "411096A79b000",
+      "code_id": "411096a79b000",
       "corrupt_symbols": false,
       "debug_file": "advapi32.pdb",
       "debug_id": "455D6C5F184D45BBB5C5F30F829751142",
@@ -155,7 +156,7 @@ expression: stdout
     {
       "base_addr": "0x77e70000",
       "cert_subject": null,
-      "code_id": "411096AE91000",
+      "code_id": "411096ae91000",
       "corrupt_symbols": false,
       "debug_file": "rpcrt4.pdb",
       "debug_id": "BEA45A721DA141DAA3BA86B3A20311532",
@@ -169,7 +170,7 @@ expression: stdout
     {
       "base_addr": "0x77f10000",
       "cert_subject": null,
-      "code_id": "43B34FEB47000",
+      "code_id": "43b34feb47000",
       "corrupt_symbols": false,
       "debug_file": "gdi32.pdb",
       "debug_id": "C0EA66BE00A64BD7AEF79E443A91869C2",
@@ -211,7 +212,7 @@ expression: stdout
     {
       "base_addr": "0x76390000",
       "cert_subject": null,
-      "code_id": "411096AE1d000",
+      "code_id": "411096ae1d000",
       "corrupt_symbols": false,
       "debug_file": "imm32.pdb",
       "debug_id": "2C17A49C251B4C8EB9E2AD13D7D9EA162",
@@ -225,7 +226,7 @@ expression: stdout
     {
       "base_addr": "0x59a60000",
       "cert_subject": null,
-      "code_id": "4110969Aa1000",
+      "code_id": "4110969aa1000",
       "corrupt_symbols": false,
       "debug_file": "dbghelp.pdb",
       "debug_id": "39559573E21B46F28E286923BE9E6A761",
@@ -239,7 +240,7 @@ expression: stdout
     {
       "base_addr": "0x77c00000",
       "cert_subject": null,
-      "code_id": "411096B78000",
+      "code_id": "411096b78000",
       "corrupt_symbols": false,
       "debug_file": "version.pdb",
       "debug_id": "180A90C40384463E82DDC45B2C8AB76E2",
@@ -253,7 +254,7 @@ expression: stdout
     {
       "base_addr": "0x76bf0000",
       "cert_subject": null,
-      "code_id": "411096CAb000",
+      "code_id": "411096cab000",
       "corrupt_symbols": false,
       "debug_file": "psapi.pdb",
       "debug_id": "A5C3A1F9689F43D8AD228A09293889702",
diff --git a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json.snap b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json.snap
index d682d59..da81eb1 100644
--- a/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json.snap
+++ b/minidump-stackwalk/tests/snapshots/test_minidump_stackwalk__json.snap
@@ -1,6 +1,7 @@
 ---
 source: minidump-stackwalk/tests/test-minidump-stackwalk.rs
+assertion_line: 55
 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,"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,"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":[]}
diff --git a/minidump/tests/test_minidump.rs b/minidump/tests/test_minidump.rs
index 2fc9d24..3f9d393 100644
--- a/minidump/tests/test_minidump.rs
+++ b/minidump/tests/test_minidump.rs
@@ -1,6 +1,7 @@
 // Copyright 2015 Ted Mielczarek. See the COPYRIGHT
 // file at the top-level directory of this distribution.
 
+use debugid::{CodeId, DebugId};
 use memmap2::Mmap;
 use minidump::system_info::{Cpu, Os};
 use minidump::*;
@@ -57,22 +58,28 @@ fn test_module_list() {
     assert_eq!(modules[0].base_address(), 0x400000);
     assert_eq!(modules[0].size(), 0x2d000);
     assert_eq!(modules[0].code_file(), "c:\\test_app.exe");
-    assert_eq!(modules[0].code_identifier(), "45D35F6C2d000");
+    assert_eq!(
+        modules[0].code_identifier(),
+        CodeId::new("45D35F6C2d000".to_string())
+    );
     assert_eq!(modules[0].debug_file().unwrap(), "c:\\test_app.pdb");
     assert_eq!(
         modules[0].debug_identifier().unwrap(),
-        "5A9832E5287241C1838ED98914E9B7FF1"
+        DebugId::from_breakpad("5A9832E5287241C1838ED98914E9B7FF1").unwrap()
     );
     assert!(modules[0].version().is_none());
 
     assert_eq!(modules[12].base_address(), 0x76bf0000);
     assert_eq!(modules[12].size(), 0xb000);
     assert_eq!(modules[12].code_file(), "C:\\WINDOWS\\system32\\psapi.dll");
-    assert_eq!(modules[12].code_identifier(), "411096CAb000");
+    assert_eq!(
+        modules[12].code_identifier(),
+        CodeId::new("411096CAb000".to_string())
+    );
     assert_eq!(modules[12].debug_file().unwrap(), "psapi.pdb");
     assert_eq!(
         modules[12].debug_identifier().unwrap(),
-        "A5C3A1F9689F43D8AD228A09293889702"
+        DebugId::from_breakpad("A5C3A1F9689F43D8AD228A09293889702").unwrap()
     );
     assert_eq!(modules[12].version().unwrap(), "5.1.2600.2180");