diff --git a/Cargo.lock b/Cargo.lock index 6ede57f..aaec90d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,12 +1,63 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "num-traits", +] [[package]] name = "fallible-iterator" -version = "0.2.0" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "futures-core" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] [[package]] name = "getopts" @@ -17,21 +68,108 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "js-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + [[package]] name = "pdb" version = "0.8.0" dependencies = [ + "chrono", "fallible-iterator", "getopts", + "memchr", "scroll", "uuid", ] +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + [[package]] name = "scroll" -version = "0.11.0" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-width" @@ -41,6 +179,55 @@ checksum = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" [[package]] name = "uuid" -version = "1.0.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +dependencies = [ + "unicode-ident", +] diff --git a/Cargo.toml b/Cargo.toml index 542f0c4..ab83abc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,12 +13,20 @@ exclude = [ ] [dependencies] -fallible-iterator = "0.2.0" -scroll = "0.11.0" -uuid = "1.0.0" +fallible-iterator = "0.3.0" +scroll = "0.13.0" +uuid = "1.26.0" +memchr = { version = "2", default-features = false } +chrono = { version = "0.4.45", default-features = false, optional = true } + +[features] +default = ["std", "chrono"] +chrono = ["dep:chrono"] +alloc = ["chrono?/alloc"] +std = ["alloc"] +nightly = [] [dev-dependencies] -# for examples/ getopts = "0.2.21" [package.metadata.release] diff --git a/README.md b/README.md index 1b0ddeb..d7fb3ac 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ fn main() -> pdb::Result<()> { match symbol.parse() { Ok(pdb::SymbolData::Public(data)) if data.function => { // we found the location of a function! - let rva = data.offset.to_rva(&address_map).unwrap_or_default + let rva = data.offset.to_rva(&address_map).unwrap_or_default(); println!("{} is {}", rva, data.name); } _ => {} diff --git a/examples/pdb2hpp.rs b/examples/pdb2hpp.rs index 8fa60f6..a00572e 100644 --- a/examples/pdb2hpp.rs +++ b/examples/pdb2hpp.rs @@ -184,13 +184,13 @@ impl<'p> Class<'p> { // find the method list match type_finder.find(data.method_list)?.parse()? { pdb::TypeData::MethodList(method_list) => { - for pdb::MethodListEntry { + for &pdb::MethodListEntry { attributes, method_type, .. - } in method_list.methods + } in method_list.iter() { - // hooray + let method = Method::find( data.name, attributes, @@ -365,7 +365,7 @@ fn argument_list( match type_finder.find(type_index)?.parse()? { pdb::TypeData::ArgumentList(data) => { let mut args: Vec = Vec::new(); - for arg_type in data.arguments { + for &arg_type in data.iter() { args.push(type_name(type_finder, arg_type, needed_types)?); } Ok(args) diff --git a/fixtures/symbol_server/README.md b/fixtures/README.md similarity index 67% rename from fixtures/symbol_server/README.md rename to fixtures/README.md index 3b36d3b..e782183 100644 --- a/fixtures/symbol_server/README.md +++ b/fixtures/README.md @@ -1,5 +1,7 @@ -# `fixtures/symbol_server/` +# Fixtures -Microsoft operates a [public symbol server](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/microsoft-public-symbols). The files are large and owned by Microsoft, so we do not host them in this repository. +Microsoft operates a [public symbol server](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/microsoft-public-symbols). + +The files are large and owned by Microsoft, so we do not host them in this repository. Before running tests, please run `scripts/download` from the project root folder (where Cargo.lock is located) to seed the files. diff --git a/fixtures/self/Makefile b/fixtures/cpp/Makefile similarity index 100% rename from fixtures/self/Makefile rename to fixtures/cpp/Makefile diff --git a/fixtures/self/README.md b/fixtures/cpp/README.md similarity index 100% rename from fixtures/self/README.md rename to fixtures/cpp/README.md diff --git a/fixtures/self/foo.cpp b/fixtures/cpp/foo.cpp similarity index 100% rename from fixtures/self/foo.cpp rename to fixtures/cpp/foo.cpp diff --git a/fixtures/self/foo.exe b/fixtures/cpp/foo.exe similarity index 100% rename from fixtures/self/foo.exe rename to fixtures/cpp/foo.exe diff --git a/fixtures/self/foo.pdb b/fixtures/cpp/foo.pdb similarity index 100% rename from fixtures/self/foo.pdb rename to fixtures/cpp/foo.pdb diff --git a/fixtures/rust/basic/Cargo.lock b/fixtures/rust/basic/Cargo.lock new file mode 100644 index 0000000..8aacfb8 --- /dev/null +++ b/fixtures/rust/basic/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "big-msf-rust-fixture" +version = "0.1.0" diff --git a/fixtures/rust/basic/Cargo.toml b/fixtures/rust/basic/Cargo.toml new file mode 100644 index 0000000..420d5a3 --- /dev/null +++ b/fixtures/rust/basic/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "big-msf-rust-fixture" +version = "0.1.0" \ No newline at end of file diff --git a/fixtures/rust/basic/src/main.rs b/fixtures/rust/basic/src/main.rs new file mode 100644 index 0000000..5492e10 --- /dev/null +++ b/fixtures/rust/basic/src/main.rs @@ -0,0 +1,97 @@ +// src/main.rs + +use std::collections::HashMap; + +#[derive(Debug)] +struct Point { + x: i32, + y: i32, + label: String, +} + +#[derive(Debug)] +enum Shape { + Point(Point), + Circle { center: Point, radius: f64 }, + Polygon(Vec), +} + +impl Shape { + fn area(&self) -> f64 { + match self { + Shape::Point(_) => 0.0, + Shape::Circle { radius, .. } => std::f64::consts::PI * radius * radius, + Shape::Polygon(points) => { + let n = points.len(); + if n < 3 { + return 0.0; + } + let mut sum = 0.0; + for i in 0..n { + let a = &points[i]; + let b = &points[(i + 1) % n]; + sum += (a.x * b.y - b.x * a.y) as f64; + } + sum.abs() / 2.0 + } + } + } +} + +trait Describe { + fn describe(&self) -> String; +} + +impl Describe for Shape { + fn describe(&self) -> String { + match self { + Shape::Point(p) => format!("Point at ({}, {})", p.x, p.y), + Shape::Circle { center, radius } => { + format!("Circle at ({}, {}) r={}", center.x, center.y, radius) + } + Shape::Polygon(points) => format!("Polygon with {} vertices", points.len()), + } + } +} + +fn build_shapes() -> Vec { + let mut shapes = Vec::new(); + + shapes.push(Shape::Point(Point { x: 0, y: 0, label: "origin".into() })); + shapes.push(Shape::Circle { + center: Point { x: 10, y: 10, label: "c".into() }, + radius: 5.0, + }); + shapes.push(Shape::Polygon(vec![ + Point { x: 0, y: 0, label: "a".into() }, + Point { x: 4, y: 0, label: "b".into() }, + Point { x: 4, y: 4, label: "c".into() }, + Point { x: 0, y: 4, label: "d".into() }, + ])); + + shapes +} + +fn count_by_kind(shapes: &[Shape]) -> HashMap<&'static str, usize> { + let mut counts = HashMap::new(); + for s in shapes { + let key = match s { + Shape::Point(_) => "point", + Shape::Circle { .. } => "circle", + Shape::Polygon(_) => "polygon", + }; + *counts.entry(key).or_insert(0) += 1; + } + counts +} + +fn main() { + let shapes = build_shapes(); + + for s in &shapes { + println!("{} (area = {:.2})", s.describe(), s.area()); + } + + let counts = count_by_kind(&shapes); + println!("counts: {:?}", counts); +} \ No newline at end of file diff --git a/fixtures/rust/big-msf/Cargo.lock b/fixtures/rust/big-msf/Cargo.lock new file mode 100644 index 0000000..ff28cf5 --- /dev/null +++ b/fixtures/rust/big-msf/Cargo.lock @@ -0,0 +1,354 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "big-msf-rust-fixture" +version = "0.1.0" +dependencies = [ + "anyhow", + "ms-pdb-msf", +] + +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "js-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "ms-pdb-msf" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8434ff1e27d78bf7726e15d8bbeee7699f29593a6a74f7bc30f7a2f3cdf1a3d5" +dependencies = [ + "anyhow", + "bitvec", + "pow2", + "static_assertions", + "sync_file", + "tracing", + "uuid", + "zerocopy 0.8.57", + "zerocopy-derive 0.8.57", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pow2" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae2effd3c9b3ac6ee244ab9add985d6e1be66c318bad1fac682666f42578d88" +dependencies = [ + "zerocopy 0.7.35", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_file" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cfc24238213e42ffb35314aad7a7f5d46649c5fbba3ea95ef24f7debb95874e" +dependencies = [ + "wasi", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "uuid" +version = "1.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.5", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" +dependencies = [ + "zerocopy-derive 0.8.57", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] diff --git a/fixtures/rust/big-msf/Cargo.toml b/fixtures/rust/big-msf/Cargo.toml new file mode 100644 index 0000000..eb95a13 --- /dev/null +++ b/fixtures/rust/big-msf/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "big-msf-rust-fixture" +version = "0.1.0" + +[dependencies] +ms-pdb-msf = "0.1.7" +anyhow = "1.0.104" \ No newline at end of file diff --git a/fixtures/rust/big-msf/src/main.rs b/fixtures/rust/big-msf/src/main.rs new file mode 100644 index 0000000..aa3e03e --- /dev/null +++ b/fixtures/rust/big-msf/src/main.rs @@ -0,0 +1,28 @@ +extern crate ms_pdb_msf; + +use ms_pdb_msf::*; +use std::{io::Read, path::Path}; + +fn main() -> anyhow::Result<()> { + + let options = CreateOptions::default(); + let path: &Path = "test.pdb".as_ref(); + let mut msf = Msf::create(path, options.clone())?; + + let stream1_data = b"Hello, PDB Stream 1!"; + let stream1 = msf.new_stream_data(stream1_data)?; + println!("Created stream 1 with index: {}", stream1); + + let stream2_data = b"Type information data"; + let stream2 = msf.new_stream_data(stream2_data)?; + println!("Created stream 2 with index: {}", stream2); + + let stream3_data = b"Debug information data"; + let stream3 = msf.new_stream_data(stream3_data)?; + println!("Created stream 3 with index: {}", stream3); + + msf.commit()?; + println!("MSF file written to: {:?}", path); + + Ok(()) +} \ No newline at end of file diff --git a/fixtures/rust/big-msf/test.pdb b/fixtures/rust/big-msf/test.pdb new file mode 100644 index 0000000..927b1bf Binary files /dev/null and b/fixtures/rust/big-msf/test.pdb differ diff --git a/fixtures/small1.pdb b/fixtures/small1.pdb new file mode 100644 index 0000000..5171e3d Binary files /dev/null and b/fixtures/small1.pdb differ diff --git a/fixtures/small2.pdb b/fixtures/small2.pdb new file mode 100644 index 0000000..932743f Binary files /dev/null and b/fixtures/small2.pdb differ diff --git a/scripts/download.ps1 b/scripts/download.ps1 new file mode 100644 index 0000000..7941f0e --- /dev/null +++ b/scripts/download.ps1 @@ -0,0 +1,7 @@ +Invoke-WebRequest ` + https://msdl.microsoft.com/download/symbols/wntdll.pdb/0ea7c70545374958ad3307514bdfc8642/wntdll.pdb ` + -OutFile fixtures/symbol_server/0ea7c70545374958ad3307514bdfc8642-wntdll.pdb + + Invoke-WebRequest ` + https://msdl.microsoft.com/download/symbols/ntkrnlmp.pdb/3844dbb920174967be7aa4a2c20430fa2/ntkrnlmp.pdb ` + -OutFile fixtures/symbol_server/3844dbb920174967be7aa4a2c20430fa2-ntkrnlmp.pdb diff --git a/src/common.rs b/src/common.rs deleted file mode 100644 index f5953d7..0000000 --- a/src/common.rs +++ /dev/null @@ -1,1291 +0,0 @@ -// Copyright 2017 pdb Developers -// -// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -// copied, modified, or distributed except according to those terms. - -use std::borrow::Cow; -use std::fmt; -use std::io; -use std::mem; -use std::ops::{Add, AddAssign, Sub}; -use std::result; -use std::slice; - -use scroll::ctx::TryFromCtx; -use scroll::{Endian, Pread, LE}; - -use crate::tpi::constants; - -/// An error that occurred while reading or parsing the PDB. -#[non_exhaustive] -#[derive(Debug)] -pub enum Error { - /// The input data was not recognized as a MSF (PDB) file. - UnrecognizedFileFormat, - - /// The MSF header specifies an invalid page size. - InvalidPageSize(u32), - - /// MSF referred to page number out of range. - /// - /// This likely indicates file corruption. - PageReferenceOutOfRange(u32), - - /// The requested stream is not stored in this file. - StreamNotFound(u32), - - /// A stream requested by name was not found. - StreamNameNotFound, - - /// Invalid length or alignment of a stream. - InvalidStreamLength(&'static str), - - /// An IO error occurred while reading from the data source. - IoError(io::Error), - - /// Unexpectedly reached end of input. - UnexpectedEof, - - /// This data might be understandable, but the code needed to understand it hasn't been written. - UnimplementedFeature(&'static str), - - /// The global shared symbol table is missing. - GlobalSymbolsNotFound, - - /// A symbol record's length value was impossibly small. - SymbolTooShort, - - /// Support for symbols of this kind is not implemented. - UnimplementedSymbolKind(u16), - - /// The type information header was invalid. - InvalidTypeInformationHeader(&'static str), - - /// A type record's length value was impossibly small. - TypeTooShort, - - /// Type or Id not found. - TypeNotFound(u32), - - /// Type or Id not indexed -- the requested type (`.0`) is larger than the maximum index covered - /// by the `ItemFinder` (`.1`). - TypeNotIndexed(u32, u32), - - /// Support for types of this kind is not implemented. - UnimplementedTypeKind(u16), - - /// Type index is not a cross module reference. - NotACrossModuleRef(u32), - - /// Cross module reference not found in imports. - CrossModuleRefNotFound(u32), - - /// Variable-length numeric parsing encountered an unexpected prefix. - UnexpectedNumericPrefix(u16), - - /// Required mapping for virtual addresses (OMAP) was not found. - AddressMapNotFound, - - /// A parse error from scroll. - ScrollError(scroll::Error), - - /// This debug subsection kind is unknown or unimplemented. - UnimplementedDebugSubsection(u32), - - /// This source file checksum kind is unknown or unimplemented. - UnimplementedFileChecksumKind(u8), - - /// There is no source file checksum at the given offset. - InvalidFileChecksumOffset(u32), - - /// The lines table is missing. - LinesNotFound, - - /// A binary annotation was compressed incorrectly. - InvalidCompressedAnnotation, - - /// An unknown binary annotation was encountered. - UnknownBinaryAnnotation(u32), -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::IoError(error) => Some(error), - _ => None, - } - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> ::std::result::Result<(), fmt::Error> { - match self { - Self::PageReferenceOutOfRange(p) => { - write!(f, "MSF referred to page number ({}) out of range", p) - } - Self::InvalidPageSize(n) => write!( - f, - "The MSF header specifies an invalid page size ({} bytes)", - n - ), - Self::StreamNotFound(s) => { - write!(f, "The requested stream ({}) is not stored in this file", s) - } - Self::InvalidStreamLength(s) => write!( - f, - "{} stream has an invalid length or alignment for its records", - s - ), - Self::IoError(ref e) => write!(f, "IO error while reading PDB: {}", e), - Self::UnimplementedFeature(feature) => { - write!(f, "Unimplemented PDB feature: {}", feature) - } - Self::UnimplementedSymbolKind(kind) => write!( - f, - "Support for symbols of kind {:#06x} is not implemented", - kind - ), - Self::InvalidTypeInformationHeader(reason) => { - write!(f, "The type information header was invalid: {}", reason) - } - Self::TypeNotFound(type_index) => write!(f, "Type {} not found", type_index), - Self::TypeNotIndexed(type_index, indexed_count) => write!( - f, - "Type {} not indexed (index covers {})", - type_index, indexed_count - ), - Self::UnimplementedTypeKind(kind) => write!( - f, - "Support for types of kind {:#06x} is not implemented", - kind - ), - Self::NotACrossModuleRef(index) => { - write!(f, "Type {:#06x} is not a cross module reference", index) - } - Self::CrossModuleRefNotFound(index) => write!( - f, - "Cross module reference {:#06x} not found in imports", - index - ), - Self::UnexpectedNumericPrefix(prefix) => write!( - f, - "Variable-length numeric parsing encountered an unexpected prefix ({:#06x}", - prefix - ), - Self::UnimplementedDebugSubsection(kind) => write!( - f, - "Debug module subsection of kind {:#06x} is not implemented", - kind - ), - Self::UnimplementedFileChecksumKind(kind) => { - write!(f, "Unknown source file checksum kind {}", kind) - } - Self::InvalidFileChecksumOffset(offset) => { - write!(f, "Invalid source file checksum offset {:#x}", offset) - } - Self::UnknownBinaryAnnotation(num) => write!(f, "Unknown binary annotation {}", num), - _ => fmt::Debug::fmt(self, f), - } - } -} - -impl From for Error { - fn from(e: io::Error) -> Self { - Self::IoError(e) - } -} - -impl From for Error { - fn from(e: scroll::Error) -> Self { - match e { - // Convert a couple of scroll errors into EOF. - scroll::Error::BadOffset(_) | scroll::Error::TooBig { .. } => Self::UnexpectedEof, - _ => Self::ScrollError(e), - } - } -} - -/// The result type returned by this crate. -pub type Result = result::Result; - -/// Implements `Pread` using the inner type. -macro_rules! impl_pread { - ($type:ty) => { - impl<'a> TryFromCtx<'a, Endian> for $type { - type Error = scroll::Error; - - fn try_from_ctx(this: &'a [u8], le: Endian) -> scroll::Result<(Self, usize)> { - TryFromCtx::try_from_ctx(this, le).map(|(i, s)| (Self(i), s)) - } - } - }; -} - -/// Displays the type as hexadecimal number. Debug prints the type name around. -macro_rules! impl_hex_fmt { - ($type:ty) => { - impl fmt::Display for $type { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:#x}", self.0) - } - } - - impl fmt::Debug for $type { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, concat!(stringify!($type), "({})"), self) - } - } - }; -} - -/// Implements bidirectional conversion traits for the newtype. -macro_rules! impl_convert { - ($type:ty, $inner:ty) => { - impl From<$inner> for $type { - fn from(offset: $inner) -> Self { - Self(offset) - } - } - - impl From<$type> for $inner { - fn from(string_ref: $type) -> Self { - string_ref.0 - } - } - }; -} - -/// Declares that the given value represents `None`. -/// -/// - `Type::none` and `Default::default` return the none value. -/// - `Type::is_some` and `Type::is_none` check for the none value. -macro_rules! impl_opt { - ($type:ty, $none:literal) => { - impl $type { - /// Returns an index that points to no value. - #[inline] - pub const fn none() -> Self { - Self($none) - } - - /// Returns `true` if the index points to a valid value. - #[inline] - #[must_use] - pub fn is_some(self) -> bool { - self.0 != $none - } - - /// Returns `true` if the index indicates the absence of a value. - #[inline] - #[must_use] - pub fn is_none(self) -> bool { - self.0 == $none - } - } - - impl Default for $type { - #[inline] - fn default() -> Self { - Self::none() - } - } - }; -} - -/// Implements common functionality for virtual addresses. -macro_rules! impl_va { - ($type:ty) => { - impl $type { - /// Checked addition of an offset. Returns `None` if overflow occurred. - pub fn checked_add(self, offset: u32) -> Option { - Some(Self(self.0.checked_add(offset)?)) - } - - /// Checked computation of an offset between two addresses. Returns `None` if `other` is - /// larger. - pub fn checked_sub(self, other: Self) -> Option { - self.0.checked_sub(other.0) - } - - /// Saturating addition of an offset, clipped at the numeric bounds. - pub fn saturating_add(self, offset: u32) -> Self { - Self(self.0.saturating_add(offset)) - } - - /// Saturating computation of an offset between two addresses, clipped at zero. - pub fn saturating_sub(self, other: Self) -> u32 { - self.0.saturating_sub(other.0) - } - - /// Wrapping (modular) addition of an offset. - pub fn wrapping_add(self, offset: u32) -> Self { - Self(self.0.wrapping_add(offset)) - } - - /// Wrapping (modular) computation of an offset between two addresses. - pub fn wrapping_sub(self, other: Self) -> u32 { - self.0.wrapping_sub(other.0) - } - } - - impl Add for $type { - type Output = Self; - - /// Adds the given offset to this address. - #[inline] - fn add(mut self, offset: u32) -> Self { - self.0 += offset; - self - } - } - - impl AddAssign for $type { - /// Adds the given offset to this address. - #[inline] - fn add_assign(&mut self, offset: u32) { - self.0 += offset; - } - } - - impl Sub for $type { - type Output = u32; - - fn sub(self, other: Self) -> Self::Output { - self.0 - other.0 - } - } - - impl_convert!($type, u32); - impl_hex_fmt!($type); - }; -} - -/// A Relative Virtual Address as it appears in a PE file. -/// -/// RVAs are always relative to the image base address, as it is loaded into process memory. This -/// address is reported by debuggers in stack traces and may refer to symbols or instruction -/// pointers. -#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Rva(pub u32); - -impl_va!(Rva); - -/// A Relative Virtual Address in an unoptimized PE file. -/// -/// An internal RVA points into the PDB internal address space and may not correspond to RVAs of the -/// executable. It can be converted into an actual [`Rva`] suitable for debugging purposes using -/// [`to_rva`](Self::to_rva). -#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct PdbInternalRva(pub u32); - -impl_va!(PdbInternalRva); -impl_pread!(PdbInternalRva); - -/// Implements common functionality for section offsets. -macro_rules! impl_section_offset { - ($type:ty) => { - impl $type { - /// Creates a new section offset. - pub fn new(section: u16, offset: u32) -> Self { - Self { offset, section } - } - - /// Returns whether this section offset points to a valid section or into the void. - pub fn is_valid(self) -> bool { - self.section != 0 - } - - /// Checked addition of an offset. Returns `None` if overflow occurred. - /// - /// This does not check whether the offset is still valid within the given section. If - /// the offset is out of bounds, the conversion to `Rva` will return `None`. - pub fn checked_add(mut self, offset: u32) -> Option { - self.offset = self.offset.checked_add(offset)?; - Some(self) - } - - /// Saturating addition of an offset, clipped at the numeric bounds. - /// - /// This does not check whether the offset is still valid within the given section. If - /// the offset is out of bounds, the conversion to `Rva` will return `None`. - pub fn saturating_add(mut self, offset: u32) -> Self { - self.offset = self.offset.saturating_add(offset); - self - } - - /// Wrapping (modular) addition of an offset. - /// - /// This does not check whether the offset is still valid within the given section. If - /// the offset is out of bounds, the conversion to `Rva` will return `None`. - pub fn wrapping_add(mut self, offset: u32) -> Self { - self.offset = self.offset.wrapping_add(offset); - self - } - } - - impl Add for $type { - type Output = Self; - - /// Adds the given offset to this section offset. - /// - /// This does not check whether the offset is still valid within the given section. If - /// the offset is out of bounds, the conversion to `Rva` will return `None`. - #[inline] - fn add(mut self, offset: u32) -> Self { - self.offset += offset; - self - } - } - - impl AddAssign for $type { - /// Adds the given offset to this section offset. - /// - /// This does not check whether the offset is still valid within the given section. If - /// the offset is out of bounds, the conversion to `Rva` will return `None`. - #[inline] - fn add_assign(&mut self, offset: u32) { - self.offset += offset; - } - } - - impl PartialOrd for $type { - /// Compares offsets if they reside in the same section. - #[inline] - fn partial_cmp(&self, other: &Self) -> Option { - if self.section == other.section { - Some(self.offset.cmp(&other.offset)) - } else { - None - } - } - } - - impl fmt::Debug for $type { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct(stringify!($type)) - .field("section", &format_args!("{:#x}", self.section)) - .field("offset", &format_args!("{:#x}", self.offset)) - .finish() - } - } - }; -} - -/// An offset relative to a PE section. -/// -/// This offset can be converted to an `Rva` to receive the address relative to the entire image. -/// Note that this offset applies to the actual PE headers. The PDB debug information actually -/// stores [`PdbInternalSectionOffset`]s. -#[derive(Clone, Copy, Default, Eq, Hash, PartialEq)] -pub struct SectionOffset { - /// The memory offset relative from the start of the section's memory. - pub offset: u32, - - /// The index of the section in the PE's section headers list, incremented by `1`. A value of - /// `0` indicates an invalid or missing reference. - pub section: u16, -} - -impl_section_offset!(SectionOffset); - -/// An offset relative to a PE section in the original unoptimized binary. -/// -/// For optimized Microsoft binaries, this offset points to a virtual address space before the -/// rearrangement of sections has been performed. This kind of offset is usually stored in PDB debug -/// information. It can be converted to an RVA in the transformed address space of the optimized -/// binary using [`to_rva`](PdbInternalSectionOffset::to_rva). Likewise, there is a conversion to [`SectionOffset`] in the actual address -/// space. -/// -/// For binaries and their PDBs that have not been optimized, both address spaces are equal and the -/// offsets are interchangeable. The conversion operations are cheap no-ops in this case. -#[derive(Clone, Copy, Default, Eq, Hash, PartialEq)] -pub struct PdbInternalSectionOffset { - /// The memory offset relative from the start of the section's memory. - pub offset: u32, - - /// The index of the section in the PDB's section headers list, incremented by `1`. A value of - /// `0` indicates an invalid or missing reference. - pub section: u16, -} - -impl<'t> TryFromCtx<'t, Endian> for PdbInternalSectionOffset { - type Error = scroll::Error; - - fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { - let mut offset = 0; - let data = Self { - offset: this.gread_with(&mut offset, le)?, - section: this.gread_with(&mut offset, le)?, - }; - Ok((data, offset)) - } -} - -impl_section_offset!(PdbInternalSectionOffset); - -/// Index of a PDB stream. -/// -/// This index can either refer to a stream, or indicate the absence of a stream. Check -/// [`is_none`](Self::is_none) to see whether a stream should exist. -/// -/// Use [`get`](Self::get) to load data for this stream. -#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct StreamIndex(pub u16); - -impl StreamIndex { - /// Returns the MSF stream number, if this stream is not a NULL stream. - #[inline] - pub(crate) fn msf_number(self) -> Option { - match self.0 { - 0xffff => None, - index => Some(u32::from(index)), - } - } -} - -impl fmt::Display for StreamIndex { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.msf_number() { - Some(number) => write!(f, "{}", number), - None => write!(f, "None"), - } - } -} - -impl fmt::Debug for StreamIndex { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "StreamIndex({})", self) - } -} - -impl_opt!(StreamIndex, 0xffff); -impl_pread!(StreamIndex); - -/// An index into either the [`TypeInformation`](crate::TypeInformation) or -/// [`IdInformation`](crate::IdInformation) stream. -pub trait ItemIndex: - Copy + Default + fmt::Debug + fmt::Display + PartialEq + PartialOrd + From + Into -{ - /// Returns `true` if this is a cross module reference. - /// - /// When compiling with LTO, the compiler may reference types and ids across modules. In such - /// cases, a lookup in the global streams will not succeed. Instead, the import must be resolved - /// using cross module references: - /// - /// 1. Look up the index in [`CrossModuleImports`](crate::CrossModuleImports) of the current - /// module. - /// 2. Use [`StringTable`](crate::StringTable) to resolve the name of the referenced module. - /// 3. Find the [`Module`](crate::Module) with the same module name and load its - /// [`ModuleInfo`](crate::ModuleInfo). Note that this comparison needs to be done - /// case-insensitively as the name in the DBI stream and name table are known to not - /// have matching cases. - /// 4. Resolve the [`Local`] index into a global one using - /// [`CrossModuleExports`](crate::CrossModuleExports). - /// - /// Cross module references are specially formatted indexes with the most significant bit set to - /// `1`. The remaining bits are divided into a module and index offset into the - /// [`CrossModuleImports`](crate::CrossModuleImports) section. - fn is_cross_module(self) -> bool { - (self.into() & 0x8000_0000) != 0 - } -} - -/// Index of [`TypeData`](crate::TypeData) in the [`TypeInformation`](crate::TypeInformation) stream. -/// -/// If this index is a [cross module reference](ItemIndex::is_cross_module), it must be resolved -/// before lookup in the stream. -#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct TypeIndex(pub u32); - -impl_convert!(TypeIndex, u32); -impl_hex_fmt!(TypeIndex); -impl_pread!(TypeIndex); - -impl ItemIndex for TypeIndex {} - -/// Index of an [`Id`](crate::Id) in [`IdInformation`](crate::IdInformation) stream. -/// -/// If this index is a [cross module reference](ItemIndex::is_cross_module), it must be resolved -/// before lookup in the stream. -#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct IdIndex(pub u32); - -impl_convert!(IdIndex, u32); -impl_hex_fmt!(IdIndex); -impl_pread!(IdIndex); - -impl ItemIndex for IdIndex {} - -/// An [`ItemIndex`] that is local to a module. -/// -/// This index is usually part of a [`CrossModuleRef`](crate::CrossModuleRef). It cannot be used to -/// query the [`TypeInformation`](crate::TypeInformation) or [`IdInformation`](crate::IdInformation) -/// streams directly. Instead, it must be looked up in the -/// [`CrossModuleImports`](crate::CrossModuleImports) of the module it belongs to in order to obtain -/// the global index. -/// -/// See [`ItemIndex::is_cross_module`] for more information. -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Local(pub I); - -impl fmt::Display for Local -where - I: ItemIndex + fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -/// A reference to a string in the string table. -/// -/// This type stores an offset into the global string table of the PDB. To retrieve the string -/// value, use [`to_raw_string`](Self::to_raw_string), [`to_string_lossy`](Self::to_string_lossy) or -/// methods on [`StringTable`](crate::StringTable). -#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct StringRef(pub u32); - -impl_convert!(StringRef, u32); -impl_hex_fmt!(StringRef); -impl_pread!(StringRef); - -/// Index of a file entry in the module. -/// -/// Use the [`LineProgram`](crate::LineProgram) to resolve information on the file from this offset. -#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct FileIndex(pub u32); - -impl_convert!(FileIndex, u32); -impl_hex_fmt!(FileIndex); -impl_pread!(FileIndex); - -/// A reference into the symbol table of a module. -/// -/// To retrieve the symbol referenced by this index, use -/// [`ModuleInfo::symbols_at`](crate::ModuleInfo::symbols_at). When iterating, use -/// [`SymbolIter::seek`](crate::SymbolIter::seek) to jump between symbols. -#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct SymbolIndex(pub u32); - -impl_convert!(SymbolIndex, u32); -impl_hex_fmt!(SymbolIndex); -impl_pread!(SymbolIndex); - -/// A register referred to by its number. -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Register(pub u16); - -impl_convert!(Register, u16); -impl_pread!(Register); - -/// Provides little-endian access to a &[u8]. -#[derive(Debug, Default, Clone)] -pub(crate) struct ParseBuffer<'b>(&'b [u8], usize); - -macro_rules! def_parse { - ( $( ($n:ident, $t:ty) ),* $(,)* ) => { - $(#[doc(hidden)] - #[inline] - #[allow(unused)] - pub fn $n(&mut self) -> Result<$t> { - self.parse() - })* - } -} - -macro_rules! def_peek { - ( $( ($n:ident, $t:ty) ),* $(,)* ) => { - $(#[doc(hidden)] - #[inline] - pub fn $n(&mut self) -> Result<$t> { - Ok(self.0.pread_with(self.1, LE)?) - })* - } -} - -impl<'b> ParseBuffer<'b> { - /// Return the remaining length of the buffer. - #[inline] - pub fn len(&self) -> usize { - self.0.len() - self.1 - } - - /// Determines whether this ParseBuffer has been consumed. - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Return the position within the parent slice. - #[inline] - pub fn pos(&self) -> usize { - self.1 - } - - /// Seek to the given absolute position. - #[inline] - pub fn seek(&mut self, pos: usize) { - self.1 = std::cmp::min(pos, self.0.len()); - } - - /// Truncates the buffer at the given absolute position. - #[inline] - pub fn truncate(&mut self, len: usize) -> Result<()> { - if self.0.len() >= len { - self.0 = &self.0[..len]; - Ok(()) - } else { - Err(Error::UnexpectedEof) - } - } - - /// Align the current position to the next multiple of `alignment` bytes. - #[inline] - pub fn align(&mut self, alignment: usize) -> Result<()> { - let diff = self.1 % alignment; - if diff > 0 { - if self.len() < (alignment - diff) { - return Err(Error::UnexpectedEof); - } - self.1 += alignment - diff; - } - Ok(()) - } - - /// Parse an object that implements `Pread`. - pub fn parse(&mut self) -> Result - where - T: TryFromCtx<'b, Endian, [u8]>, - T::Error: From, - Error: From, - { - Ok(self.0.gread_with(&mut self.1, LE)?) - } - - /// Parse an object that implements `Pread` with the given context. - pub fn parse_with(&mut self, ctx: C) -> Result - where - T: TryFromCtx<'b, C, [u8]>, - T::Error: From, - Error: From, - C: Copy, - { - Ok(self.0.gread_with(&mut self.1, ctx)?) - } - - def_parse!( - (parse_u8, u8), - (parse_u16, u16), - (parse_i16, i16), - (parse_u32, u32), - (parse_i32, i32), - (parse_u64, u64), - (parse_i64, i64), - ); - - def_peek!((peek_u8, u8), (peek_u16, u16),); - - /// Parse a NUL-terminated string from the input. - #[inline] - pub fn parse_cstring(&mut self) -> Result> { - let input = &self.0[self.1..]; - let null_idx = input.iter().position(|ch| *ch == 0); - - if let Some(idx) = null_idx { - self.1 += idx + 1; - Ok(RawString::from(&input[..idx])) - } else { - Err(Error::UnexpectedEof) - } - } - - /// Parse a u8-length-prefixed string from the input. - #[inline] - pub fn parse_u8_pascal_string(&mut self) -> Result> { - let length = self.parse_u8()? as usize; - Ok(RawString::from(self.take(length)?)) - } - - /// Take n bytes from the input - #[inline] - pub fn take(&mut self, n: usize) -> Result<&'b [u8]> { - let input = &self.0[self.1..]; - if input.len() >= n { - self.1 += n; - Ok(&input[..n]) - } else { - Err(Error::UnexpectedEof) - } - } -} - -impl<'b> From<&'b [u8]> for ParseBuffer<'b> { - fn from(buf: &'b [u8]) -> Self { - ParseBuffer(buf, 0) - } -} - -impl<'b> fmt::LowerHex for ParseBuffer<'b> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> result::Result<(), fmt::Error> { - write!(f, "ParseBuf::from(\"")?; - for byte in self.0 { - write!(f, "\\x{:02x}", byte)?; - } - write!(f, "\").as_bytes() at offset {}", self.1) - } -} - -/// Value of an enumerate type. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[allow(missing_docs)] -pub enum Variant { - U8(u8), - U16(u16), - U32(u32), - U64(u64), - I8(i8), - I16(i16), - I32(i32), - I64(i64), -} - -impl fmt::Display for Variant { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::U8(value) => write!(f, "{}", value), - Self::U16(value) => write!(f, "{}", value), - Self::U32(value) => write!(f, "{}", value), - Self::U64(value) => write!(f, "{}", value), - Self::I8(value) => write!(f, "{}", value), - Self::I16(value) => write!(f, "{}", value), - Self::I32(value) => write!(f, "{}", value), - Self::I64(value) => write!(f, "{}", value), - } - } -} - -impl<'a> TryFromCtx<'a, Endian> for Variant { - type Error = Error; - - fn try_from_ctx(this: &'a [u8], le: Endian) -> Result<(Self, usize)> { - let mut offset = 0; - - let variant = match this.gread_with(&mut offset, le)? { - value if value < constants::LF_NUMERIC => Self::U16(value), - constants::LF_CHAR => Self::U8(this.gread_with(&mut offset, le)?), - constants::LF_SHORT => Self::I16(this.gread_with(&mut offset, le)?), - constants::LF_LONG => Self::I32(this.gread_with(&mut offset, le)?), - constants::LF_QUADWORD => Self::I64(this.gread_with(&mut offset, le)?), - constants::LF_USHORT => Self::U16(this.gread_with(&mut offset, le)?), - constants::LF_ULONG => Self::U32(this.gread_with(&mut offset, le)?), - constants::LF_UQUADWORD => Self::U64(this.gread_with(&mut offset, le)?), - _ if cfg!(debug_assertions) => unreachable!(), - other => return Err(Error::UnexpectedNumericPrefix(other)), - }; - - Ok((variant, offset)) - } -} - -/// `RawString` refers to a `&[u8]` that physically resides somewhere inside a PDB data structure. -/// -/// A `RawString` may not be valid UTF-8. -#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct RawString<'b>(&'b [u8]); - -impl fmt::Debug for RawString<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "RawString({:?})", self.to_string()) - } -} - -impl fmt::Display for RawString<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_string()) - } -} - -impl<'b> RawString<'b> { - /// Return the raw bytes of this string, as found in the PDB file. - #[inline] - pub fn as_bytes(&self) -> &'b [u8] { - self.0 - } - - /// Return the length of this string in bytes. - #[inline] - pub fn len(&self) -> usize { - self.0.len() - } - - /// Returns a boolean indicating if this string is empty. - #[inline] - pub fn is_empty(&self) -> bool { - self.0.len() == 0 - } - - /// Returns a UTF-8 `String`, substituting in replacement characters as needed. - /// - /// This uses [`String::from_utf8_lossy`] and thus avoids copying in cases where the original - /// string was valid UTF-8. This is the expected case for strings that appear in PDB files, - /// since they are almost always composed of printable 7-bit ASCII characters. - #[inline] - pub fn to_string(&self) -> Cow<'b, str> { - String::from_utf8_lossy(self.0) - } -} - -impl<'b> From> for &'b [u8] { - fn from(str: RawString<'b>) -> Self { - str.as_bytes() - } -} - -impl<'b> From<&'b str> for RawString<'b> { - fn from(buf: &'b str) -> Self { - RawString(buf.as_bytes()) - } -} - -impl<'b> From<&'b [u8]> for RawString<'b> { - fn from(buf: &'b [u8]) -> Self { - RawString(buf) - } -} - -/// Cast a binary slice to a slice of types. -/// -/// This function performs a cast of a binary slice to a slice of some type, returning `Some` if the -/// following two conditions are met: -/// -/// 1. The size of the slize must be a multiple of the type's size. -/// 2. The slice must be aligned to the alignment of the type. -/// -/// Note that this function will not convert any endianness. The types must be capable of reading -/// endianness correclty in case data from other hosts is read. -pub(crate) fn cast_aligned(data: &[u8]) -> Option<&[T]> { - let alignment = mem::align_of::(); - let size = mem::size_of::(); - - let ptr = data.as_ptr(); - let bytes = data.len(); - - match (bytes % size, ptr.align_offset(alignment)) { - (0, 0) => Some(unsafe { slice::from_raw_parts(ptr as *const T, bytes / size) }), - (_, _) => None, - } -} - -#[cfg(test)] -mod tests { - mod parse_buffer { - use crate::common::*; - - #[test] - fn test_parse_u8() { - let vec: Vec = vec![1, 2, 3, 4]; - let mut buf = ParseBuffer::from(vec.as_slice()); - assert_eq!(buf.pos(), 0); - - assert_eq!(buf.peek_u8().expect("peek"), 1); - assert_eq!(buf.peek_u8().expect("peek"), 1); - assert_eq!(buf.peek_u8().expect("peek"), 1); - let val = buf.parse_u8().unwrap(); - assert_eq!(buf.len(), 3); - assert_eq!(buf.pos(), 1); - assert_eq!(val, 1); - - assert_eq!(buf.peek_u8().expect("peek"), 2); - let val = buf.parse_u8().unwrap(); - assert_eq!(buf.len(), 2); - assert_eq!(buf.pos(), 2); - assert_eq!(val, 2); - - assert_eq!(buf.peek_u8().expect("peek"), 3); - let val = buf.parse_u8().unwrap(); - assert_eq!(buf.len(), 1); - assert_eq!(buf.pos(), 3); - assert_eq!(val, 3); - - assert_eq!(buf.peek_u8().expect("peek"), 4); - let val = buf.parse_u8().unwrap(); - assert_eq!(buf.len(), 0); - assert_eq!(buf.pos(), 4); - assert_eq!(val, 4); - - match buf.parse_u8() { - Err(Error::UnexpectedEof) => (), - _ => panic!("expected EOF"), - } - } - - #[test] - fn test_parse_u16() { - let vec: Vec = vec![1, 2, 3]; - let mut buf = ParseBuffer::from(vec.as_slice()); - - assert_eq!(buf.peek_u16().expect("peek"), 0x0201); - assert_eq!(buf.peek_u16().expect("peek"), 0x0201); - - let val = buf.parse_u16().unwrap(); - assert_eq!(buf.len(), 1); - assert_eq!(buf.pos(), 2); - assert_eq!(val, 0x0201); - - match buf.parse_u16() { - Err(Error::UnexpectedEof) => (), - _ => panic!("expected EOF"), - } - - buf.take(1).unwrap(); - match buf.parse_u16() { - Err(Error::UnexpectedEof) => (), - _ => panic!("expected EOF"), - } - } - - #[test] - fn test_parse_u32() { - let vec: Vec = vec![1, 2, 3, 4, 5, 6, 7]; - let mut buf = ParseBuffer::from(vec.as_slice()); - - let val = buf.parse_u32().unwrap(); - assert_eq!(buf.len(), 3); - assert_eq!(buf.pos(), 4); - assert_eq!(val, 0x0403_0201); - - match buf.parse_u32() { - Err(Error::UnexpectedEof) => (), - _ => panic!("expected EOF"), - } - - buf.take(1).unwrap(); - assert_eq!(buf.pos(), 5); - match buf.parse_u32() { - Err(Error::UnexpectedEof) => (), - _ => panic!("expected EOF"), - } - - buf.take(1).unwrap(); - assert_eq!(buf.pos(), 6); - match buf.parse_u32() { - Err(Error::UnexpectedEof) => (), - _ => panic!("expected EOF"), - } - - buf.take(1).unwrap(); - assert_eq!(buf.pos(), 7); - match buf.parse_u32() { - Err(Error::UnexpectedEof) => (), - _ => panic!("expected EOF"), - } - } - - #[test] - fn test_parse_u64() { - let vec: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; - let mut buf = ParseBuffer::from(vec.as_slice()); - - let val = buf.parse_u64().unwrap(); - assert_eq!(val, 0x0807_0605_0403_0201); - - match buf.parse_u64() { - Err(Error::UnexpectedEof) => (), - _ => panic!("expected EOF"), - } - } - - #[test] - fn test_parse_i32() { - let vec: Vec = vec![254, 255, 255, 255, 5, 6, 7]; - let mut buf = ParseBuffer::from(vec.as_slice()); - - let val = buf.parse_i32().unwrap(); - assert_eq!(buf.len(), 3); - assert_eq!(val, -2); - assert_eq!(buf.pos(), 4); - - match buf.parse_u32() { - Err(Error::UnexpectedEof) => (), - _ => panic!("expected EOF"), - } - - buf.take(1).unwrap(); - match buf.parse_u32() { - Err(Error::UnexpectedEof) => (), - _ => panic!("expected EOF"), - } - - buf.take(1).unwrap(); - match buf.parse_u32() { - Err(Error::UnexpectedEof) => (), - _ => panic!("expected EOF"), - } - - buf.take(1).unwrap(); - match buf.parse_u32() { - Err(Error::UnexpectedEof) => (), - _ => panic!("expected EOF"), - } - } - - #[test] - fn test_parse_cstring() { - let mut buf = ParseBuffer::from(&b"hello\x00world\x00\x00\x01"[..]); - - let val = buf.parse_cstring().unwrap(); - assert_eq!(buf.len(), 8); - assert_eq!(buf.pos(), 6); - assert_eq!(val, RawString::from(&b"hello"[..])); - - let val = buf.parse_cstring().unwrap(); - assert_eq!(buf.len(), 2); - assert_eq!(buf.pos(), 12); - assert_eq!(val, RawString::from(&b"world"[..])); - - let val = buf.parse_cstring().unwrap(); - assert_eq!(buf.len(), 1); - assert_eq!(buf.pos(), 13); - assert_eq!(val, RawString::from(&b""[..])); - - match buf.parse_cstring() { - Err(Error::UnexpectedEof) => (), - _ => panic!("expected EOF"), - } - } - - #[test] - fn test_parse_u8_pascal_string() { - let mut buf = ParseBuffer::from(&b"\x05hello\x05world\x00\x01"[..]); - - let val = buf.parse_u8_pascal_string().unwrap(); - assert_eq!(buf.len(), 8); - assert_eq!(buf.pos(), 6); - assert_eq!(val, RawString::from(&b"hello"[..])); - - let val = buf.parse_u8_pascal_string().unwrap(); - assert_eq!(buf.len(), 2); - assert_eq!(buf.pos(), 12); - assert_eq!(val, RawString::from(&b"world"[..])); - - let val = buf.parse_u8_pascal_string().unwrap(); - assert_eq!(buf.len(), 1); - assert_eq!(buf.pos(), 13); - assert_eq!(val, RawString::from(&b""[..])); - - match buf.parse_u8_pascal_string() { - Err(Error::UnexpectedEof) => (), - _ => panic!("expected EOF"), - } - } - - #[test] - fn test_parse_buffer_align() { - let mut buf = ParseBuffer::from(&b"1234"[..]); - buf.take(1).unwrap(); - assert!(buf.align(4).is_ok()); - assert_eq!(buf.pos(), 4); - assert_eq!(buf.len(), 0); - - let mut buf = ParseBuffer::from(&b"1234"[..]); - buf.take(3).unwrap(); - assert!(buf.align(4).is_ok()); - assert_eq!(buf.pos(), 4); - assert_eq!(buf.len(), 0); - - let mut buf = ParseBuffer::from(&b"12345"[..]); - buf.take(3).unwrap(); - assert!(buf.align(4).is_ok()); - assert_eq!(buf.pos(), 4); - assert_eq!(buf.len(), 1); - - let mut buf = ParseBuffer::from(&b"123"[..]); - buf.take(3).unwrap(); - assert!(buf.align(4).is_err()); - } - - #[test] - fn test_seek() { - let mut buf = ParseBuffer::from(&b"hello"[..]); - buf.seek(5); - assert_eq!(buf.pos(), 5); - buf.seek(2); - assert_eq!(buf.pos(), 2); - buf.seek(10); - assert_eq!(buf.pos(), 5); - } - } - - mod newtypes { - use crate::common::*; - - // These tests use SymbolIndex as a proxy for all other types. - - #[test] - fn test_format_newtype() { - let val = SymbolIndex(0x42); - assert_eq!(format!("{}", val), "0x42"); - } - - #[test] - fn test_debug_newtype() { - let val = SymbolIndex(0x42); - assert_eq!(format!("{:?}", val), "SymbolIndex(0x42)"); - } - - #[test] - fn test_pread() { - let mut buf = ParseBuffer::from(&[0x42, 0, 0, 0][..]); - let val = buf.parse::().expect("parse"); - assert_eq!(val, SymbolIndex(0x42)); - assert!(buf.is_empty()); - } - } - - mod cast_aligned { - use crate::common::cast_aligned; - use std::slice; - - #[test] - fn test_cast_aligned() { - let data: &[u32] = &[1, 2, 3]; - - let ptr = data.as_ptr() as *const u8; - let bin: &[u8] = unsafe { slice::from_raw_parts(ptr, 12) }; - - assert_eq!(cast_aligned(bin), Some(data)); - } - - #[test] - fn test_cast_empty() { - let data: &[u32] = &[]; - - let ptr = data.as_ptr() as *const u8; - let bin: &[u8] = unsafe { slice::from_raw_parts(ptr, 0) }; - - assert_eq!(cast_aligned(bin), Some(data)); - } - - #[test] - fn test_cast_unaligned() { - let data: &[u32] = &[1, 2, 3]; - - let ptr = data.as_ptr() as *const u8; - let bin: &[u8] = unsafe { slice::from_raw_parts(ptr.offset(2), 8) }; - - assert_eq!(cast_aligned::(bin), None); - } - - #[test] - fn test_cast_wrong_size() { - let data: &[u32] = &[1, 2, 3]; - - let ptr = data.as_ptr() as *const u8; - let bin: &[u8] = unsafe { slice::from_raw_parts(ptr, 11) }; - - assert_eq!(cast_aligned::(bin), None); - } - } -} diff --git a/src/common/error.rs b/src/common/error.rs new file mode 100644 index 0000000..ee3398b --- /dev/null +++ b/src/common/error.rs @@ -0,0 +1,203 @@ +use core::fmt; + +use crate::io::IoError; + +/// An error that occurred while reading or parsing the PDB. +#[non_exhaustive] +#[derive(Debug)] +pub enum Error { + /// The input data was not recognized as a MSF (PDB) file. + UnrecognizedFileFormat, + + /// The MSF header specifies an invalid page size. + InvalidPageSize(u32), + + /// MSF referred to page number out of range. + /// + /// This likely indicates file corruption. + PageReferenceOutOfRange(u32), + + /// The requested stream is not stored in this file. + StreamNotFound(u32), + + /// A stream requested by name was not found. + StreamNameNotFound, + + /// Invalid length or alignment of a stream. + InvalidStreamLength(&'static str), + + /// An IO error occurred while reading from the data source. + IoError(IoError), + + /// Unexpectedly reached end of input. + UnexpectedEof, + + /// This data might be understandable, but the code needed to understand it hasn't been written. + UnimplementedFeature(&'static str), + + /// The global shared symbol table is missing. + GlobalSymbolsNotFound, + + /// A symbol record's length value was impossibly small. + SymbolTooShort, + + /// Support for symbols of this kind is not implemented. + UnimplementedSymbolKind(u16), + + /// The type information header was invalid. + InvalidTypeInformationHeader(&'static str), + + /// A type record's length value was impossibly small. + TypeTooShort, + + /// Type or Id not found. + TypeNotFound(u32), + + /// Type or Id not indexed -- the requested type (`.0`) is larger than the maximum index covered + /// by the `ItemFinder` (`.1`). + TypeNotIndexed(u32, u32), + + /// Support for types of this kind is not implemented. + UnimplementedTypeKind(u16), + + /// Type index is not a cross module reference. + NotACrossModuleRef(u32), + + /// Cross module reference not found in imports. + CrossModuleRefNotFound(u32), + + /// Variable-length numeric parsing encountered an unexpected prefix. + UnexpectedNumericPrefix(u16), + + /// Required mapping for virtual addresses (OMAP) was not found. + AddressMapNotFound, + + /// A parse error from scroll. + ScrollError(scroll::Error), + + /// This debug subsection kind is unknown or unimplemented. + UnimplementedDebugSubsection(u32), + + /// This source file checksum kind is unknown or unimplemented. + UnimplementedFileChecksumKind(u8), + + /// There is no source file checksum at the given offset. + InvalidFileChecksumOffset(u32), + + /// The lines table is missing. + LinesNotFound, + + /// A binary annotation was compressed incorrectly. + InvalidCompressedAnnotation, + + /// An unknown binary annotation was encountered. + UnknownBinaryAnnotation(u32), +} + +#[cfg(feature = "std")] +impl From for Error { + + #[inline] + fn from(err: std::io::Error) -> Self { + Self::IoError(err.into()) + } +} + +impl core::error::Error for Error { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + match self { + Self::IoError(error) => Some(error), + _ => None, + } + } +} + +impl From for crate::Error { + fn from(err: IoError) -> Self { + Error::IoError(err) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> ::core::result::Result<(), fmt::Error> { + match self { + Self::PageReferenceOutOfRange(p) => { + write!(f, "MSF referred to page number ({}) out of range", p) + } + Self::InvalidPageSize(n) => write!( + f, + "The MSF header specifies an invalid page size ({} bytes)", + n + ), + Self::StreamNotFound(s) => { + write!(f, "The requested stream ({}) is not stored in this file", s) + } + Self::InvalidStreamLength(s) => write!( + f, + "{} stream has an invalid length or alignment for its records", + s + ), + // Self::IoError(ref e) => write!(f, "IO error while reading PDB: {}", e), + Self::UnimplementedFeature(feature) => { + write!(f, "Unimplemented PDB feature: {}", feature) + } + Self::UnimplementedSymbolKind(kind) => write!( + f, + "Support for symbols of kind {:#06x} is not implemented", + kind + ), + Self::InvalidTypeInformationHeader(reason) => { + write!(f, "The type information header was invalid: {}", reason) + } + Self::TypeNotFound(type_index) => write!(f, "Type {} not found", type_index), + Self::TypeNotIndexed(type_index, indexed_count) => write!( + f, + "Type {} not indexed (index covers {})", + type_index, indexed_count + ), + Self::UnimplementedTypeKind(kind) => write!( + f, + "Support for types of kind {:#06x} is not implemented", + kind + ), + Self::NotACrossModuleRef(index) => { + write!(f, "Type {:#06x} is not a cross module reference", index) + } + Self::CrossModuleRefNotFound(index) => write!( + f, + "Cross module reference {:#06x} not found in imports", + index + ), + Self::UnexpectedNumericPrefix(prefix) => write!( + f, + "Variable-length numeric parsing encountered an unexpected prefix ({:#06x}", + prefix + ), + Self::UnimplementedDebugSubsection(kind) => write!( + f, + "Debug module subsection of kind {:#06x} is not implemented", + kind + ), + Self::UnimplementedFileChecksumKind(kind) => { + write!(f, "Unknown source file checksum kind {}", kind) + } + Self::InvalidFileChecksumOffset(offset) => { + write!(f, "Invalid source file checksum offset {:#x}", offset) + } + Self::UnknownBinaryAnnotation(num) => write!(f, "Unknown binary annotation {}", num), + _ => fmt::Debug::fmt(self, f), + } + } +} + +impl From for Error { + fn from(e: scroll::Error) -> Self { + match e { + scroll::Error::BadOffset(_) | scroll::Error::TooBig { .. } => Self::UnexpectedEof, + _ => Self::ScrollError(e), + } + } +} + +/// Result type used throughout the PDB crate. +pub type Result = core::result::Result; \ No newline at end of file diff --git a/src/common/mod.rs b/src/common/mod.rs new file mode 100644 index 0000000..2dbcdad --- /dev/null +++ b/src/common/mod.rs @@ -0,0 +1,355 @@ +// Copyright 2026 PDB Developers +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use core::mem; +use core::slice; + +mod types; +mod parse_buffer; +mod error; + +pub use types::*; +pub use parse_buffer::*; +pub use error::*; + +/// Cast a binary slice to a slice of types. +/// +/// This function performs a cast of a binary slice to a slice of some type, returning `Some` if the +/// following two conditions are met: +/// +/// 1. The size of the slize must be a multiple of the type's size. +/// 2. The slice must be aligned to the alignment of the type. +/// +/// Note that this function will not convert any endianness. The types must be capable of reading +/// endianness correclty in case data from other hosts is read. +pub(crate) fn cast_aligned(data: &[u8]) -> Option<&[T]> { + let alignment = mem::align_of::(); + let size = mem::size_of::(); + + let ptr = data.as_ptr(); + let bytes = data.len(); + + match (bytes % size, ptr.align_offset(alignment)) { + (0, 0) => Some(unsafe { slice::from_raw_parts(ptr as *const T, bytes / size) }), + (_, _) => None, + } +} + +#[cfg(all(test, feature = "alloc"))] +mod tests { + mod parse_buffer { + use alloc::vec; + use alloc::vec::Vec; + use crate::common::*; + + #[test] + fn test_parse_u8() { + let vec: Vec = vec![1, 2, 3, 4]; + let mut buf = ParseBuffer::from(vec.as_slice()); + assert_eq!(buf.pos(), 0); + + assert_eq!(buf.peek_u8().expect("peek"), 1); + assert_eq!(buf.peek_u8().expect("peek"), 1); + assert_eq!(buf.peek_u8().expect("peek"), 1); + let val = buf.parse_u8().unwrap(); + assert_eq!(buf.len(), 3); + assert_eq!(buf.pos(), 1); + assert_eq!(val, 1); + + assert_eq!(buf.peek_u8().expect("peek"), 2); + let val = buf.parse_u8().unwrap(); + assert_eq!(buf.len(), 2); + assert_eq!(buf.pos(), 2); + assert_eq!(val, 2); + + assert_eq!(buf.peek_u8().expect("peek"), 3); + let val = buf.parse_u8().unwrap(); + assert_eq!(buf.len(), 1); + assert_eq!(buf.pos(), 3); + assert_eq!(val, 3); + + assert_eq!(buf.peek_u8().expect("peek"), 4); + let val = buf.parse_u8().unwrap(); + assert_eq!(buf.len(), 0); + assert_eq!(buf.pos(), 4); + assert_eq!(val, 4); + + match buf.parse_u8() { + Err(Error::UnexpectedEof) => (), + _ => panic!("expected EOF"), + } + } + + #[test] + fn test_parse_u16() { + let vec: Vec = vec![1, 2, 3]; + let mut buf = ParseBuffer::from(vec.as_slice()); + + assert_eq!(buf.peek_u16().expect("peek"), 0x0201); + assert_eq!(buf.peek_u16().expect("peek"), 0x0201); + + let val = buf.parse_u16().unwrap(); + assert_eq!(buf.len(), 1); + assert_eq!(buf.pos(), 2); + assert_eq!(val, 0x0201); + + match buf.parse_u16() { + Err(Error::UnexpectedEof) => (), + _ => panic!("expected EOF"), + } + + buf.take(1).unwrap(); + match buf.parse_u16() { + Err(Error::UnexpectedEof) => (), + _ => panic!("expected EOF"), + } + } + + #[test] + fn test_parse_u32() { + let vec: Vec = vec![1, 2, 3, 4, 5, 6, 7]; + let mut buf = ParseBuffer::from(vec.as_slice()); + + let val = buf.parse_u32().unwrap(); + assert_eq!(buf.len(), 3); + assert_eq!(buf.pos(), 4); + assert_eq!(val, 0x0403_0201); + + match buf.parse_u32() { + Err(Error::UnexpectedEof) => (), + _ => panic!("expected EOF"), + } + + buf.take(1).unwrap(); + assert_eq!(buf.pos(), 5); + match buf.parse_u32() { + Err(Error::UnexpectedEof) => (), + _ => panic!("expected EOF"), + } + + buf.take(1).unwrap(); + assert_eq!(buf.pos(), 6); + match buf.parse_u32() { + Err(Error::UnexpectedEof) => (), + _ => panic!("expected EOF"), + } + + buf.take(1).unwrap(); + assert_eq!(buf.pos(), 7); + match buf.parse_u32() { + Err(Error::UnexpectedEof) => (), + _ => panic!("expected EOF"), + } + } + + #[test] + fn test_parse_u64() { + let vec: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let mut buf = ParseBuffer::from(vec.as_slice()); + + let val = buf.parse_u64().unwrap(); + assert_eq!(val, 0x0807_0605_0403_0201); + + match buf.parse_u64() { + Err(Error::UnexpectedEof) => (), + _ => panic!("expected EOF"), + } + } + + #[test] + fn test_parse_i32() { + let vec: Vec = vec![254, 255, 255, 255, 5, 6, 7]; + let mut buf = ParseBuffer::from(vec.as_slice()); + + let val = buf.parse_i32().unwrap(); + assert_eq!(buf.len(), 3); + assert_eq!(val, -2); + assert_eq!(buf.pos(), 4); + + match buf.parse_u32() { + Err(Error::UnexpectedEof) => (), + _ => panic!("expected EOF"), + } + + buf.take(1).unwrap(); + match buf.parse_u32() { + Err(Error::UnexpectedEof) => (), + _ => panic!("expected EOF"), + } + + buf.take(1).unwrap(); + match buf.parse_u32() { + Err(Error::UnexpectedEof) => (), + _ => panic!("expected EOF"), + } + + buf.take(1).unwrap(); + match buf.parse_u32() { + Err(Error::UnexpectedEof) => (), + _ => panic!("expected EOF"), + } + } + + #[test] + fn test_parse_cstring() { + let mut buf = ParseBuffer::from(&b"hello\x00world\x00\x00\x01"[..]); + + let val = buf.parse_cstring().unwrap(); + assert_eq!(buf.len(), 8); + assert_eq!(buf.pos(), 6); + assert_eq!(val, RawString::from(&b"hello"[..])); + + let val = buf.parse_cstring().unwrap(); + assert_eq!(buf.len(), 2); + assert_eq!(buf.pos(), 12); + assert_eq!(val, RawString::from(&b"world"[..])); + + let val = buf.parse_cstring().unwrap(); + assert_eq!(buf.len(), 1); + assert_eq!(buf.pos(), 13); + assert_eq!(val, RawString::from(&b""[..])); + + match buf.parse_cstring() { + Err(Error::UnexpectedEof) => (), + _ => panic!("expected EOF"), + } + } + + #[test] + fn test_parse_u8_pascal_string() { + let mut buf = ParseBuffer::from(&b"\x05hello\x05world\x00\x01"[..]); + + let val = buf.parse_u8_pascal_string().unwrap(); + assert_eq!(buf.len(), 8); + assert_eq!(buf.pos(), 6); + assert_eq!(val, RawString::from(&b"hello"[..])); + + let val = buf.parse_u8_pascal_string().unwrap(); + assert_eq!(buf.len(), 2); + assert_eq!(buf.pos(), 12); + assert_eq!(val, RawString::from(&b"world"[..])); + + let val = buf.parse_u8_pascal_string().unwrap(); + assert_eq!(buf.len(), 1); + assert_eq!(buf.pos(), 13); + assert_eq!(val, RawString::from(&b""[..])); + + match buf.parse_u8_pascal_string() { + Err(Error::UnexpectedEof) => (), + _ => panic!("expected EOF"), + } + } + + #[test] + fn test_parse_buffer_align() { + let mut buf = ParseBuffer::from(&b"1234"[..]); + buf.take(1).unwrap(); + assert!(buf.align(4).is_ok()); + assert_eq!(buf.pos(), 4); + assert_eq!(buf.len(), 0); + + let mut buf = ParseBuffer::from(&b"1234"[..]); + buf.take(3).unwrap(); + assert!(buf.align(4).is_ok()); + assert_eq!(buf.pos(), 4); + assert_eq!(buf.len(), 0); + + let mut buf = ParseBuffer::from(&b"12345"[..]); + buf.take(3).unwrap(); + assert!(buf.align(4).is_ok()); + assert_eq!(buf.pos(), 4); + assert_eq!(buf.len(), 1); + + let mut buf = ParseBuffer::from(&b"123"[..]); + buf.take(3).unwrap(); + assert!(buf.align(4).is_err()); + } + + #[test] + fn test_seek() { + let mut buf = ParseBuffer::from(&b"hello"[..]); + buf.seek(5); + assert_eq!(buf.pos(), 5); + buf.seek(2); + assert_eq!(buf.pos(), 2); + buf.seek(10); + assert_eq!(buf.pos(), 5); + } + } + + mod newtypes { + use alloc::format; + use crate::common::*; + + // These tests use SymbolIndex as a proxy for all other types. + + #[test] + fn test_format_newtype() { + let val = SymbolIndex(0x42); + assert_eq!(format!("{}", val), "0x42"); + } + + #[test] + fn test_debug_newtype() { + let val = SymbolIndex(0x42); + assert_eq!(format!("{:?}", val), "SymbolIndex(0x42)"); + } + + #[test] + fn test_pread() { + let mut buf = ParseBuffer::from(&[0x42, 0, 0, 0][..]); + let val = buf.parse::().expect("parse"); + assert_eq!(val, SymbolIndex(0x42)); + assert!(buf.is_empty()); + } + } + + mod cast_aligned { + use crate::common::cast_aligned; + use core::slice; + + #[test] + fn test_cast_aligned() { + let data: &[u32] = &[1, 2, 3]; + + let ptr = data.as_ptr() as *const u8; + let bin: &[u8] = unsafe { slice::from_raw_parts(ptr, 12) }; + + assert_eq!(cast_aligned(bin), Some(data)); + } + + #[test] + fn test_cast_empty() { + let data: &[u32] = &[]; + + let ptr = data.as_ptr() as *const u8; + let bin: &[u8] = unsafe { slice::from_raw_parts(ptr, 0) }; + + assert_eq!(cast_aligned(bin), Some(data)); + } + + #[test] + fn test_cast_unaligned() { + let data: &[u32] = &[1, 2, 3]; + + let ptr = data.as_ptr() as *const u8; + let bin: &[u8] = unsafe { slice::from_raw_parts(ptr.offset(2), 8) }; + + assert_eq!(cast_aligned::(bin), None); + } + + #[test] + fn test_cast_wrong_size() { + let data: &[u32] = &[1, 2, 3]; + + let ptr = data.as_ptr() as *const u8; + let bin: &[u8] = unsafe { slice::from_raw_parts(ptr, 11) }; + + assert_eq!(cast_aligned::(bin), None); + } + } +} diff --git a/src/common/parse_buffer.rs b/src/common/parse_buffer.rs new file mode 100644 index 0000000..206cc6c --- /dev/null +++ b/src/common/parse_buffer.rs @@ -0,0 +1,181 @@ +use core::fmt; + +use crate::*; +use scroll::{ctx::TryFromCtx, Pread, Endian, LE}; + +/// Provides little-endian access to a &[u8]. +#[derive(Debug, Default, Clone)] +pub struct ParseBuffer<'b>(&'b [u8], usize); + +macro_rules! def_parse { + ( $( ($n:ident, $t:ty) ),* $(,)* ) => { + $(#[doc(hidden)] + #[inline] + #[allow(unused)] + pub fn $n(&mut self) -> Result<$t> { + self.parse() + })* + } +} + +macro_rules! def_peek { + ( $( ($n:ident, $t:ty) ),* $(,)* ) => { + $(#[doc(hidden)] + #[inline] + pub fn $n(&mut self) -> Result<$t> { + Ok(self.0.pread_with(self.1, LE)?) + })* + } +} + +impl<'b> ParseBuffer<'b> { + /// Return the remaining length of the buffer. + #[inline] + pub const fn len(&self) -> usize { + self.0.len() - self.1 + } + + /// Determines whether this ParseBuffer has been consumed. + #[inline] + pub const fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Return the position within the parent slice. + #[inline] + pub const fn pos(&self) -> usize { + self.1 + } + + /// Seek to the given absolute position. + #[inline] + pub fn seek(&mut self, pos: usize) { + self.1 = core::cmp::min(pos, self.0.len()); + } + + /// Truncates the buffer at the given absolute position. + #[inline] + pub fn truncate(&mut self, len: usize) -> Result<()> { + if self.0.len() >= len { + self.0 = &self.0[..len]; + Ok(()) + } else { + Err(Error::UnexpectedEof) + } + } + + /// Align the current position to the next multiple of `alignment` bytes. + #[inline] + pub const fn align(&mut self, alignment: usize) -> Result<()> { + let diff = self.1 % alignment; + if diff > 0 { + if self.len() < (alignment - diff) { + return Err(Error::UnexpectedEof); + } + self.1 += alignment - diff; + } + Ok(()) + } + + /// Parse an object that implements `Pread`. + pub fn parse(&mut self) -> Result + where + T: TryFromCtx<'b, Endian, [u8]>, + T::Error: From, + Error: From, + { + Ok(self.0.gread_with(&mut self.1, LE)?) + } + + /// Parse an object that implements `Pread` with the given context. + pub fn parse_with(&mut self, ctx: C) -> Result + where + T: TryFromCtx<'b, C, [u8]>, + T::Error: From, + Error: From, + C: Copy, + { + Ok(self.0.gread_with(&mut self.1, ctx)?) + } + + def_parse!( + (parse_u8, u8), + (parse_u16, u16), + (parse_i16, i16), + (parse_u32, u32), + (parse_i32, i32), + (parse_u64, u64), + (parse_i64, i64), + ); + + def_peek!((peek_u8, u8), (peek_u16, u16),); + + /// Takes a fixed-size array of bytes from the input. + /// + /// Unlike [`Self::take`], this returns an owned `[u8; N]` rather than a + /// borrowed `&[u8]`, which is convenient for fields with a known size at + /// compile time (GUIDs, magic strings, etc.). + /// + /// # Errors + /// + /// Returns [`Error::UnexpectedEof`] if fewer than `N` bytes remain. + #[inline] + pub fn take_array(&mut self) -> Result<[u8; N]> { + let slice = self.take(N)?; + let mut out = [0u8; N]; + out.copy_from_slice(slice); + + Ok(out) + } + + /// Parse a NUL-terminated string from the input. + #[inline] + pub fn parse_cstring(&mut self) -> Result> { + let input = &self.0[self.1..]; + let null_idx = input.iter().position(|ch| *ch == 0); + + if let Some(idx) = null_idx { + self.1 += idx + 1; + Ok(RawString::from(&input[..idx])) + } else { + Err(Error::UnexpectedEof) + } + } + + /// Parse a u8-length-prefixed string from the input. + #[inline] + pub fn parse_u8_pascal_string(&mut self) -> Result> { + let length = self.parse_u8()? as usize; + Ok(RawString::from(self.take(length)?)) + } + + /// Take n bytes from the input + #[inline] + pub fn take(&mut self, n: usize) -> Result<&'b [u8]> { + let input = &self.0[self.1..]; + if input.len() >= n { + self.1 += n; + Ok(&input[..n]) + } else { + Err(Error::UnexpectedEof) + } + } +} + +impl<'b> From<&'b [u8]> for ParseBuffer<'b> { + + #[inline] + fn from(buf: &'b [u8]) -> Self { + ParseBuffer(buf, 0) + } +} + +impl<'b> fmt::LowerHex for ParseBuffer<'b> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::result::Result<(), fmt::Error> { + write!(f, "ParseBuf::from(\"")?; + for byte in self.0 { + write!(f, "\\x{:02x}", byte)?; + } + write!(f, "\").as_bytes() at offset {}", self.1) + } +} diff --git a/src/common/types.rs b/src/common/types.rs new file mode 100644 index 0000000..bd7bd89 --- /dev/null +++ b/src/common/types.rs @@ -0,0 +1,606 @@ +#[cfg(feature = "alloc")] +use alloc::borrow::Cow; + +#[cfg(feature = "alloc")] +use alloc::string::String; + +use core::fmt; +use core::ops::{Add, AddAssign, Sub}; + +use scroll::ctx::TryFromCtx; +use scroll::{Endian, Pread, LE}; + +use crate::{Error, Result}; + +use crate::tpi::constants; + +/// Implements `Pread` using the inner type. +macro_rules! impl_pread { + ($type:ty) => { + impl<'a> TryFromCtx<'a, Endian> for $type { + type Error = scroll::Error; + + fn try_from_ctx(this: &'a [u8], le: Endian) -> scroll::Result<(Self, usize)> { + TryFromCtx::try_from_ctx(this, le).map(|(i, s)| (Self(i), s)) + } + } + }; +} + +/// Displays the type as hexadecimal number. Debug prints the type name around. +macro_rules! impl_hex_fmt { + ($type:ty) => { + impl fmt::Display for $type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:#x}", self.0) + } + } + + impl fmt::Debug for $type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, concat!(stringify!($type), "({})"), self) + } + } + }; +} + +/// Implements bidirectional conversion traits for the newtype. +macro_rules! impl_convert { + ($type:ty, $inner:ty) => { + impl From<$inner> for $type { + fn from(offset: $inner) -> Self { + Self(offset) + } + } + + impl From<$type> for $inner { + fn from(string_ref: $type) -> Self { + string_ref.0 + } + } + }; +} + +/// Declares that the given value represents `None`. +/// +/// - `Type::none` and `Default::default` return the none value. +/// - `Type::is_some` and `Type::is_none` check for the none value. +macro_rules! impl_opt { + ($type:ty, $none:literal) => { + impl $type { + /// Returns an index that points to no value. + #[inline] + pub const fn none() -> Self { + Self($none) + } + + /// Returns `true` if the index points to a valid value. + #[inline] + #[must_use] + pub fn is_some(self) -> bool { + self.0 != $none + } + + /// Returns `true` if the index indicates the absence of a value. + #[inline] + #[must_use] + pub fn is_none(self) -> bool { + self.0 == $none + } + } + + impl Default for $type { + #[inline] + fn default() -> Self { + Self::none() + } + } + }; +} + +/// Implements common functionality for virtual addresses. +macro_rules! impl_va { + ($type:ty) => { + impl $type { + /// Checked addition of an offset. Returns `None` if overflow occurred. + pub fn checked_add(self, offset: u32) -> Option { + Some(Self(self.0.checked_add(offset)?)) + } + + /// Checked computation of an offset between two addresses. Returns `None` if `other` is + /// larger. + pub fn checked_sub(self, other: Self) -> Option { + self.0.checked_sub(other.0) + } + + /// Saturating addition of an offset, clipped at the numeric bounds. + pub fn saturating_add(self, offset: u32) -> Self { + Self(self.0.saturating_add(offset)) + } + + /// Saturating computation of an offset between two addresses, clipped at zero. + pub fn saturating_sub(self, other: Self) -> u32 { + self.0.saturating_sub(other.0) + } + + /// Wrapping (modular) addition of an offset. + pub fn wrapping_add(self, offset: u32) -> Self { + Self(self.0.wrapping_add(offset)) + } + + /// Wrapping (modular) computation of an offset between two addresses. + pub fn wrapping_sub(self, other: Self) -> u32 { + self.0.wrapping_sub(other.0) + } + } + + impl Add for $type { + type Output = Self; + + /// Adds the given offset to this address. + #[inline] + fn add(mut self, offset: u32) -> Self { + self.0 += offset; + self + } + } + + impl AddAssign for $type { + /// Adds the given offset to this address. + #[inline] + fn add_assign(&mut self, offset: u32) { + self.0 += offset; + } + } + + impl Sub for $type { + type Output = u32; + + fn sub(self, other: Self) -> Self::Output { + self.0 - other.0 + } + } + + impl_convert!($type, u32); + impl_hex_fmt!($type); + }; +} + +/// A Relative Virtual Address as it appears in a PE file. +/// +/// RVAs are always relative to the image base address, as it is loaded into process memory. This +/// address is reported by debuggers in stack traces and may refer to symbols or instruction +/// pointers. +#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Rva(pub u32); + +impl_va!(Rva); + +/// A Relative Virtual Address in an unoptimized PE file. +/// +/// An internal RVA points into the PDB internal address space and may not correspond to RVAs of the +/// executable. It can be converted into an actual [`Rva`] suitable for debugging purposes using +/// [`to_rva`](Self::to_rva). +#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct PdbInternalRva(pub u32); + +impl_va!(PdbInternalRva); +impl_pread!(PdbInternalRva); + +/// Implements common functionality for section offsets. +macro_rules! impl_section_offset { + ($type:ty) => { + impl $type { + /// Creates a new section offset. + pub fn new(section: u16, offset: u32) -> Self { + Self { offset, section } + } + + /// Returns whether this section offset points to a valid section or into the void. + pub fn is_valid(self) -> bool { + self.section != 0 + } + + /// Checked addition of an offset. Returns `None` if overflow occurred. + /// + /// This does not check whether the offset is still valid within the given section. If + /// the offset is out of bounds, the conversion to `Rva` will return `None`. + pub fn checked_add(mut self, offset: u32) -> Option { + self.offset = self.offset.checked_add(offset)?; + Some(self) + } + + /// Saturating addition of an offset, clipped at the numeric bounds. + /// + /// This does not check whether the offset is still valid within the given section. If + /// the offset is out of bounds, the conversion to `Rva` will return `None`. + pub fn saturating_add(mut self, offset: u32) -> Self { + self.offset = self.offset.saturating_add(offset); + self + } + + /// Wrapping (modular) addition of an offset. + /// + /// This does not check whether the offset is still valid within the given section. If + /// the offset is out of bounds, the conversion to `Rva` will return `None`. + pub fn wrapping_add(mut self, offset: u32) -> Self { + self.offset = self.offset.wrapping_add(offset); + self + } + } + + impl Add for $type { + type Output = Self; + + /// Adds the given offset to this section offset. + /// + /// This does not check whether the offset is still valid within the given section. If + /// the offset is out of bounds, the conversion to `Rva` will return `None`. + #[inline] + fn add(mut self, offset: u32) -> Self { + self.offset += offset; + self + } + } + + impl AddAssign for $type { + /// Adds the given offset to this section offset. + /// + /// This does not check whether the offset is still valid within the given section. If + /// the offset is out of bounds, the conversion to `Rva` will return `None`. + #[inline] + fn add_assign(&mut self, offset: u32) { + self.offset += offset; + } + } + + impl PartialOrd for $type { + /// Compares offsets if they reside in the same section. + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + if self.section == other.section { + Some(self.offset.cmp(&other.offset)) + } else { + None + } + } + } + + impl fmt::Debug for $type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!($type)) + .field("section", &format_args!("{:#x}", self.section)) + .field("offset", &format_args!("{:#x}", self.offset)) + .finish() + } + } + }; +} + +/// An offset relative to a PE section. +/// +/// This offset can be converted to an `Rva` to receive the address relative to the entire image. +/// Note that this offset applies to the actual PE headers. The PDB debug information actually +/// stores [`PdbInternalSectionOffset`]s. +#[derive(Clone, Copy, Default, Eq, Hash, PartialEq)] +pub struct SectionOffset { + /// The memory offset relative from the start of the section's memory. + pub offset: u32, + + /// The index of the section in the PE's section headers list, incremented by `1`. A value of + /// `0` indicates an invalid or missing reference. + pub section: u16, +} + +impl_section_offset!(SectionOffset); + +/// An offset relative to a PE section in the original unoptimized binary. +/// +/// For optimized Microsoft binaries, this offset points to a virtual address space before the +/// rearrangement of sections has been performed. This kind of offset is usually stored in PDB debug +/// information. It can be converted to an RVA in the transformed address space of the optimized +/// binary using [`to_rva`](PdbInternalSectionOffset::to_rva). Likewise, there is a conversion to [`SectionOffset`] in the actual address +/// space. +/// +/// For binaries and their PDBs that have not been optimized, both address spaces are equal and the +/// offsets are interchangeable. The conversion operations are cheap no-ops in this case. +#[derive(Clone, Copy, Default, Eq, Hash, PartialEq)] +pub struct PdbInternalSectionOffset { + /// The memory offset relative from the start of the section's memory. + pub offset: u32, + + /// The index of the section in the PDB's section headers list, incremented by `1`. A value of + /// `0` indicates an invalid or missing reference. + pub section: u16, +} + +impl<'t> TryFromCtx<'t, Endian> for PdbInternalSectionOffset { + type Error = scroll::Error; + + fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { + let mut offset = 0; + let data = Self { + offset: this.gread_with(&mut offset, le)?, + section: this.gread_with(&mut offset, le)?, + }; + Ok((data, offset)) + } +} + +impl_section_offset!(PdbInternalSectionOffset); + +/// Index of a PDB stream. +/// +/// This index can either refer to a stream, or indicate the absence of a stream. Check +/// [`is_none`](Self::is_none) to see whether a stream should exist. +/// +/// Use [`get`](Self::get) to load data for this stream. +#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct StreamIndex(pub u16); + +impl StreamIndex { + /// Returns the MSF stream number, if this stream is not a NULL stream. + #[inline] + pub(crate) fn msf_number(self) -> Option { + match self.0 { + 0xFFFF => None, + index => Some(u32::from(index)), + } + } +} + +impl fmt::Display for StreamIndex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.msf_number() { + Some(number) => write!(f, "{}", number), + None => write!(f, "None"), + } + } +} + +impl fmt::Debug for StreamIndex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "StreamIndex({})", self) + } +} + +impl_opt!(StreamIndex, 0xFFFF); +impl_pread!(StreamIndex); + +/// An index into either the [`TypeInformation`](crate::TypeInformation) or +/// [`IdInformation`](crate::IdInformation) stream. +pub trait ItemIndex: + Copy + Default + fmt::Debug + fmt::Display + PartialEq + PartialOrd + From + Into +{ + /// Returns `true` if this is a cross module reference. + /// + /// When compiling with LTO, the compiler may reference types and ids across modules. In such + /// cases, a lookup in the global streams will not succeed. Instead, the import must be resolved + /// using cross module references: + /// + /// 1. Look up the index in [`CrossModuleImports`](crate::CrossModuleImports) of the current + /// module. + /// 2. Use [`StringTable`](crate::StringTable) to resolve the name of the referenced module. + /// 3. Find the [`Module`](crate::Module) with the same module name and load its + /// [`ModuleInfo`](crate::ModuleInfo). Note that this comparison needs to be done + /// case-insensitively as the name in the DBI stream and name table are known to not + /// have matching cases. + /// 4. Resolve the [`Local`] index into a global one using + /// [`CrossModuleExports`](crate::CrossModuleExports). + /// + /// Cross module references are specially formatted indexes with the most significant bit set to + /// `1`. The remaining bits are divided into a module and index offset into the + /// [`CrossModuleImports`](crate::CrossModuleImports) section. + fn is_cross_module(self) -> bool { + (self.into() & 0x8000_0000) != 0 + } +} + +/// Index of [`TypeData`](crate::TypeData) in the [`TypeInformation`](crate::TypeInformation) stream. +/// +/// If this index is a [cross module reference](ItemIndex::is_cross_module), it must be resolved +/// before lookup in the stream. +#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct TypeIndex(pub u32); + +impl_convert!(TypeIndex, u32); +impl_hex_fmt!(TypeIndex); +impl_pread!(TypeIndex); + +impl ItemIndex for TypeIndex {} + +/// Index of an [`Id`](crate::Id) in [`IdInformation`](crate::IdInformation) stream. +/// +/// If this index is a [cross module reference](ItemIndex::is_cross_module), it must be resolved +/// before lookup in the stream. +#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct IdIndex(pub u32); + +impl_convert!(IdIndex, u32); +impl_hex_fmt!(IdIndex); +impl_pread!(IdIndex); + +impl ItemIndex for IdIndex {} + +/// An [`ItemIndex`] that is local to a module. +/// +/// This index is usually part of a [`CrossModuleRef`](crate::CrossModuleRef). It cannot be used to +/// query the [`TypeInformation`](crate::TypeInformation) or [`IdInformation`](crate::IdInformation) +/// streams directly. Instead, it must be looked up in the +/// [`CrossModuleImports`](crate::CrossModuleImports) of the module it belongs to in order to obtain +/// the global index. +/// +/// See [`ItemIndex::is_cross_module`] for more information. +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Local(pub I); + +impl fmt::Display for Local +where + I: ItemIndex + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// A reference to a string in the string table. +/// +/// This type stores an offset into the global string table of the PDB. To retrieve the string +/// value, use [`to_raw_string`](Self::to_raw_string), [`to_string_lossy`](Self::to_string_lossy) or +/// methods on [`StringTable`](crate::StringTable). +#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct StringRef(pub u32); + +impl_convert!(StringRef, u32); +impl_hex_fmt!(StringRef); +impl_pread!(StringRef); + +/// Index of a file entry in the module. +/// +/// Use the [`LineProgram`](crate::LineProgram) to resolve information on the file from this offset. +#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct FileIndex(pub u32); + +impl_convert!(FileIndex, u32); +impl_hex_fmt!(FileIndex); +impl_pread!(FileIndex); + +/// A reference into the symbol table of a module. +/// +/// To retrieve the symbol referenced by this index, use +/// [`ModuleInfo::symbols_at`](crate::ModuleInfo::symbols_at). When iterating, use +/// [`SymbolIter::seek`](crate::SymbolIter::seek) to jump between symbols. +#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct SymbolIndex(pub u32); + +impl_convert!(SymbolIndex, u32); +impl_hex_fmt!(SymbolIndex); +impl_pread!(SymbolIndex); + +/// A register referred to by its number. +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Register(pub u16); + +impl_convert!(Register, u16); +impl_pread!(Register); + +/// Value of an enumerate type. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[allow(missing_docs)] +pub enum Variant { + U8(u8), + U16(u16), + U32(u32), + U64(u64), + I8(i8), + I16(i16), + I32(i32), + I64(i64), +} + +impl fmt::Display for Variant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::U8(value) => write!(f, "{}", value), + Self::U16(value) => write!(f, "{}", value), + Self::U32(value) => write!(f, "{}", value), + Self::U64(value) => write!(f, "{}", value), + Self::I8(value) => write!(f, "{}", value), + Self::I16(value) => write!(f, "{}", value), + Self::I32(value) => write!(f, "{}", value), + Self::I64(value) => write!(f, "{}", value), + } + } +} + +impl<'a> TryFromCtx<'a, Endian> for Variant { + type Error = Error; + + fn try_from_ctx(this: &'a [u8], le: Endian) -> Result<(Self, usize)> { + let mut offset = 0; + + let variant = match this.gread_with(&mut offset, le)? { + value if value < constants::LF_NUMERIC => Self::U16(value), + constants::LF_CHAR => Self::U8(this.gread_with(&mut offset, le)?), + constants::LF_SHORT => Self::I16(this.gread_with(&mut offset, le)?), + constants::LF_LONG => Self::I32(this.gread_with(&mut offset, le)?), + constants::LF_QUADWORD => Self::I64(this.gread_with(&mut offset, le)?), + constants::LF_USHORT => Self::U16(this.gread_with(&mut offset, le)?), + constants::LF_ULONG => Self::U32(this.gread_with(&mut offset, le)?), + constants::LF_UQUADWORD => Self::U64(this.gread_with(&mut offset, le)?), + _ if cfg!(debug_assertions) => unreachable!(), + other => return Err(Error::UnexpectedNumericPrefix(other)), + }; + + Ok((variant, offset)) + } +} + +/// `RawString` refers to a `&[u8]` that physically resides somewhere inside a PDB data structure. +/// +/// A `RawString` may not be valid UTF-8. +#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct RawString<'b>(&'b [u8]); + +impl fmt::Debug for RawString<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let str = core::str::from_utf8(self.0).map_err(|err| fmt::Error)?; + write!(f, "RawString({:?})", str) + } +} + +impl fmt::Display for RawString<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let str = core::str::from_utf8(self.0).map_err(|err| fmt::Error)?; + write!(f, "{}", str) + } +} + +impl<'b> RawString<'b> { + /// Return the raw bytes of this string, as found in the PDB file. + #[inline] + pub const fn as_bytes(&self) -> &'b [u8] { + self.0 + } + + /// Return the length of this string in bytes. + #[inline] + pub const fn len(&self) -> usize { + self.0.len() + } + + /// Returns a boolean indicating if this string is empty. + #[inline] + pub const fn is_empty(&self) -> bool { + self.0.len() == 0 + } + + /// Returns a UTF-8 `String`, substituting in replacement characters as needed. + /// + /// This uses [`String::from_utf8_lossy`] and thus avoids copying in cases where the original + /// string was valid UTF-8. This is the expected case for strings that appear in PDB files, + /// since they are almost always composed of printable 7-bit ASCII characters. + #[cfg(feature = "alloc")] + #[inline] + pub fn to_string(&self) -> Cow<'b, str> { + String::from_utf8_lossy(self.0) + } +} + +impl<'b> From> for &'b [u8] { + fn from(str: RawString<'b>) -> Self { + str.as_bytes() + } +} + +impl<'b> From<&'b str> for RawString<'b> { + fn from(buf: &'b str) -> Self { + RawString(buf.as_bytes()) + } +} + +impl<'b> From<&'b [u8]> for RawString<'b> { + fn from(buf: &'b [u8]) -> Self { + RawString(buf) + } +} \ No newline at end of file diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..ed0ad3f --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,53 @@ +/// Stream 0: the previous MSF stream directory. +/// +/// This is the "old directory" from before the current stream directory +/// was written. It is only meaningful in Small MSF files; in Big MSF it +/// is typically a nil stream. +pub const OLD_DIRECTORY_STREAM: u32 = 0; + +/// Stream 1: the PDB Information Stream. +/// +/// Contains the PDB version, the GUID and age used to match the PDB to its +/// EXE, and the **named stream map** that maps names like `/names` and +/// `/src/headerblock` to stream indices. +/// +/// See . +pub const PDB_STREAM: u32 = 1; + +/// Stream 2: the Type Information (TPI) Stream. +/// +/// Contains CodeView type records for the entire program, and the index of +/// the TPI hash stream used for fast lookup by name. +/// +/// See . +pub const TPI_STREAM: u32 = 2; + +/// Stream 3: the Debug Information (DBI) Stream. +/// +/// Contains module/compiland info, section contributions, source file info, +/// FPO/PGO data, and the indices of the Public, Global, and per-module +/// streams. +/// +/// See . +pub const DBI_STREAM: u32 = 3; + +/// Size of a `NewDBIHdr` in bytes. +/// +/// This is the fixed portion of the DBI stream that precedes the module list +/// and other substreams. It is stable across PDB versions that use the modern +/// DBI header layout (VC7 and later), regardless of whether the containing +/// MSF file is Small or Big. +/// +/// Some older VC6-era PDBs use the pre-`NewDBIHdr` layout, which has a +/// different signature, field order, and size. Those are not parsed by this +/// crate and are rejected by [`DBIHeader::parse_buf`] before this length is +/// used. +pub const DBI_HEADER_LEN: usize = 64; + +/// Stream 4: the Id Information (IPI) Stream. +/// +/// Contains CodeView type records for "id" types (compiler-generated +/// types referenced by the DBI stream), and the index of the IPI hash stream. +/// +/// See . +pub const IPI_STREAM: u32 = 4; \ No newline at end of file diff --git a/src/dbi.rs b/src/dbi.rs index ce21c70..fb0014c 100644 --- a/src/dbi.rs +++ b/src/dbi.rs @@ -1,4 +1,4 @@ -// Copyright 2017 pdb Developers +// Copyright 2026 PDB Developers // // Licensed under the Apache License, Version 2.0, or the MIT license { stream: Stream<'s>, @@ -49,7 +55,9 @@ pub struct DebugInformation<'s> { header_len: usize, } +#[cfg(feature = "alloc")] impl<'s> DebugInformation<'s> { + #[cfg(feature = "alloc")] pub(crate) fn parse(stream: Stream<'s>) -> Result { let mut buf = stream.parse_buffer(); let header = DBIHeader::parse_buf(&mut buf)?; @@ -62,7 +70,8 @@ impl<'s> DebugInformation<'s> { }) } - pub(crate) fn header(&self) -> DBIHeader { + /// Returns a copy of the parsed DBI header. + pub(crate) const fn header(&self) -> DBIHeader { self.header } @@ -81,7 +90,7 @@ impl<'s> DebugInformation<'s> { /// checked for matching the image. /// /// [`PDBInformation::age`]: crate::PDBInformation::age - pub fn age(&self) -> Option { + pub const fn age(&self) -> Option { match self.header.age { 0 => None, age => Some(age), @@ -94,9 +103,8 @@ impl<'s> DebugInformation<'s> { // drop the header buf.take(self.header_len)?; let modules_buf = buf.take(self.header.module_list_size as usize)?; - Ok(ModuleIter { - buf: modules_buf.into(), - }) + + Ok(ModuleIter::new(modules_buf)) } /// Returns an iterator that can traverse the section contributions list in sequential order. @@ -109,108 +117,223 @@ impl<'s> DebugInformation<'s> { } } -/// The version of the PDB format. +/// Version of the DBI stream header. /// -/// This version type is used in multiple locations: the DBI header, and the PDBI header. +/// See: +#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] -#[derive(Debug, Copy, Clone)] #[allow(missing_docs)] -pub enum HeaderVersion { +pub enum DbiVersion { V41, V50, V60, V70, + V80, V110, + V140, + OtherValue(u32), +} + +impl From for DbiVersion { + #[allow(clippy::inconsistent_digit_grouping)] + fn from(v: u32) -> Self { + match v { + 930_803 => Self::V41, + 1996_0307 => Self::V50, + 1997_0606 => Self::V60, + 1999_0903 => Self::V70, + 2003_0901 => Self::V80, + 2009_1201 => Self::V110, + 2014_0508 => Self::V140, + _ => Self::OtherValue(v), + } + } +} + +/// The version of the PDB format. +/// +/// This version type is used in multiple locations: the DBI header and the PDBI header. +/// +/// The numeric values are taken from LLVM's `PdbStreamVersion` enum. +/// See: +#[non_exhaustive] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[allow(missing_docs)] +pub enum PdbHeaderVersion { + /// Visual C++ 2.0 + Vc2, + /// Visual C++ 4.0 + Vc4, + /// Visual C++ 4.1 + Vc41, + /// Visual C++ 5.0 + Vc50, + /// Visual C++ 6.0 / Visual Studio 98 + Vc98, + /// Visual C++ 7.0, pre-release (deprecated) + Vc70Dep, + /// Visual C++ 7.0 / Visual Studio .NET (2002) + Vc70, + /// Visual C++ 8.0 / Visual Studio 2005 + Vc80, + /// Visual C++ 11.0 / Visual Studio 2012 + Vc110, + /// Visual C++ 14.0 / Visual Studio 2015 + Vc140, + /// A version value not covered by the variants above. OtherValue(u32), } -impl From for HeaderVersion { +impl fmt::Display for PdbHeaderVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Vc2 => f.write_str("VC2"), + Self::Vc4 => f.write_str("VC4"), + Self::Vc41 => f.write_str("VC4.1"), + Self::Vc50 => f.write_str("VC5"), + Self::Vc98 => f.write_str("VC6"), + Self::Vc70Dep => f.write_str("VC7 (pre-release)"), + Self::Vc70 => f.write_str("VC7"), + Self::Vc80 => f.write_str("VC8"), + Self::Vc110 => f.write_str("VC11"), + Self::Vc140 => f.write_str("VC14"), + Self::OtherValue(v) => write!(f, "Unknown (0x{v:08x})"), + } + } +} + +impl PdbHeaderVersion { + /// Returns `true` if this version's info stream includes a GUID field. + /// + /// The GUID was introduced in VC7 (`20000404`). Earlier PDBs go + /// directly from `age` to `names_size`. + pub const fn has_guid(self) -> bool { + match self { + Self::Vc2 | Self::Vc4 | Self::Vc41 | Self::Vc50 | Self::Vc98 | Self::Vc70Dep => false, + Self::Vc70 | Self::Vc80 | Self::Vc110 | Self::Vc140 => true, + Self::OtherValue(v) => v >= 2000_0404, + } + } +} + +impl From for PdbHeaderVersion { #[allow(clippy::inconsistent_digit_grouping)] fn from(v: u32) -> Self { match v { - 93_08_03 => Self::V41, - 1996_03_07 => Self::V50, - 1997_06_06 => Self::V60, - 1999_09_03 => Self::V70, - 2009_12_01 => Self::V110, + 1994_1610 => Self::Vc2, + 1995_0623 => Self::Vc4, + 1995_0814 => Self::Vc41, + 1996_0307 => Self::Vc50, + 1997_0604 => Self::Vc98, + 1999_0604 => Self::Vc70Dep, + 2000_0404 => Self::Vc70, + 2003_0901 => Self::Vc80, + 2009_1201 => Self::Vc110, + 2014_0508 => Self::Vc140, + + 1996_1031 => Self::Vc98, // VC5/VC6 TPI + 2004_0203 => Self::Vc80, // VC8+ TPI + _ => Self::OtherValue(v), } } } -/// A DBI header -- `NewDBIHdr`, really -- parsed from a stream. +/// DBI (Debug Information) stream header - `NewDBIHdr` format. /// -/// Reference: +/// The DBI header appears at the start of the DBI stream (stream 3) and contains +/// metadata about the debug information, including stream indices for symbols, +/// module information, and various debug subsections. +/// +/// # Reference +/// +/// Based on the Microsoft PDB implementation: /// #[derive(Debug, Copy, Clone)] -#[allow(dead_code)] // reason = "unused fields added for completeness" -pub(crate) struct DBIHeader { +#[allow(dead_code)] +pub struct DBIHeader { + /// DBI stream signature (should be `0xffffffff`) pub signature: u32, - pub version: HeaderVersion, + + /// DBI format version (e.g., `VC70`, `VC80`, `VC110`) + pub version: DbiVersion, + + /// DBI age (incremented when the PDB is modified) pub age: u32, + + /// Stream index containing global symbols pub gs_symbols_stream: StreamIndex, - /* - https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/dbi/dbi.h#L143-L155: - union { - struct { - USHORT usVerPdbDllMin : 8; // minor version and - USHORT usVerPdbDllMaj : 7; // major version and - USHORT fNewVerFmt : 1; // flag telling us we have rbld stored elsewhere (high bit of original major version) - } vernew; // that built this pdb last. - struct { - USHORT usVerPdbDllRbld: 4; - USHORT usVerPdbDllMin : 7; - USHORT usVerPdbDllMaj : 5; - } verold; - USHORT usVerAll; - }; - */ + /// Internal version of the PDB DLL that last built this PDB. + /// + /// This is a packed field containing version information. + /// See the union in the reference for bit layout. pub internal_version: u16, + + /// Stream index containing public symbols pub ps_symbols_stream: StreamIndex, - // "build version of the pdb dll that built this pdb last." + + /// Build version of the PDB DLL that last built this PDB pub pdb_dll_build_version: u16, + /// Stream index containing symbol records pub symbol_records_stream: StreamIndex, - // "rbld version of the pdb dll that built this pdb last." + /// Rbld (rebuild) version of the PDB DLL that last built this PDB pub pdb_dll_rbld_version: u16, + + /// Size of the module list substream (in bytes) pub module_list_size: u32, + + /// Size of the section contribution substream (in bytes) pub section_contribution_size: u32, + + /// Size of the section map substream (in bytes) pub section_map_size: u32, + + /// Size of the file info substream (in bytes) pub file_info_size: u32, - // "size of the Type Server Map substream" + /// Size of the Type Server Map substream (in bytes) pub type_server_map_size: u32, - // "index of MFC type server" + /// Index of the MFC type server pub mfc_type_server_index: u32, - // "size of optional DbgHdr info appended to the end of the stream" + /// Size of optional DbgHdr info appended to the end of the stream (in bytes) pub debug_header_size: u32, - // "number of bytes in EC substream, or 0 if EC no EC enabled Mods" + /// Size of the EC (Edit & Continue) substream (in bytes) + /// + /// If 0, EC is not enabled for this module. pub ec_substream_size: u32, - /* - https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/dbi/dbi.h#L187-L192: - USHORT fIncLink:1; // true if linked incrmentally (really just if ilink thunks are present) - USHORT fStripped:1; // true if PDB::CopyTo stripped the private data out - USHORT fCTypes:1; // true if this PDB is using CTypes. - USHORT unused:13; // reserved, must be 0. - */ + /// DBI flags. + /// + /// Bit layout: + /// - Bit 0: `fIncLink` - true if linked incrementally (ilink thunks present) + /// - Bit 1: `fStripped` - true if private data was stripped (via PDB::CopyTo) + /// - Bit 2: `fCTypes` - true if using CTypes + /// - Bits 3-15: Reserved (must be 0) pub flags: u16, + /// Target machine type (e.g., `IMAGE_FILE_MACHINE_AMD64`, `IMAGE_FILE_MACHINE_I386`) pub machine_type: u16, + + /// Reserved field (should be 0) pub reserved: u32, } impl DBIHeader { + /// Parses a `DBIHeader` from a stream. + #[cfg(feature = "alloc")] pub fn parse(stream: Stream<'_>) -> Result { Self::parse_buf(&mut stream.parse_buffer()) } - fn parse_buf(buf: &mut ParseBuffer<'_>) -> Result { + /// Parses a `DBIHeader` from a [`ParseBuffer`] positioned at the start of + /// the DBI stream. + pub fn parse_buf(buf: &mut ParseBuffer<'_>) -> Result { let header = Self { signature: buf.parse_u32()?, version: From::from(buf.parse_u32()?), @@ -234,7 +357,7 @@ impl DBIHeader { reserved: buf.parse_u32()?, }; - if header.signature != u32::max_value() { + if header.signature != u32::MAX { // this is likely a DBIHdr, not a NewDBIHdr // it could be promoted: // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/dbi/dbi.cpp#L291-L313 @@ -484,29 +607,53 @@ pub struct Module<'m> { impl<'m> Module<'m> { /// The `DBIModuleInfo` from the module info substream in the DBI stream. - pub(crate) fn info(&self) -> &DBIModuleInfo { + pub(crate) const fn info(&self) -> &DBIModuleInfo { &self.info } - /// The module name. + + /// Returns the module name as a UTF-8 lossy string. /// /// Usually either a full path to an object file or a string of the form `Import:`. + #[cfg(feature = "alloc")] + #[inline] pub fn module_name(&self) -> Cow<'m, str> { self.module_name.to_string() } + + /// Returns the raw bytes of the module name. + #[inline] + pub fn module_name_bytes(&self) -> &[u8] { + self.module_name.as_bytes() + } + /// The object file name. /// /// May be the same as `module_name` for object files passed directly /// to the linker. For modules from static libraries, this is usually /// the full path to the archive. + #[cfg(feature = "alloc")] + #[inline] pub fn object_file_name(&self) -> Cow<'m, str> { self.object_file_name.to_string() } + + /// Returns the raw bytes of the object file name. + #[inline] + pub fn object_file_name_bytes(&self) -> &[u8] { + self.object_file_name.as_bytes() + } } /// A `ModuleIter` iterates over the modules in the DBI section, producing `Module`s. #[derive(Debug)] -pub struct ModuleIter<'m> { - buf: ParseBuffer<'m>, +pub struct ModuleIter<'m>(ParseBuffer<'m>); + +impl<'m> ModuleIter<'m> { + + #[inline] + pub fn new(buffer: impl Into>) -> Self { + Self(buffer.into()) + } } impl<'m> FallibleIterator for ModuleIter<'m> { @@ -515,14 +662,15 @@ impl<'m> FallibleIterator for ModuleIter<'m> { fn next(&mut self) -> result::Result, Self::Error> { // see if we're at EOF - if self.buf.is_empty() { + if self.0.is_empty() { return Ok(None); } - let info = DBIModuleInfo::parse(&mut self.buf)?; - let module_name = self.buf.parse_cstring()?; - let object_file_name = self.buf.parse_cstring()?; - self.buf.align(4)?; + let info = DBIModuleInfo::parse(&mut self.0)?; + let module_name = self.0.parse_cstring()?; + let object_file_name = self.0.parse_cstring()?; + self.0.align(4)?; + Ok(Some(Module { info, module_name, @@ -584,34 +732,63 @@ impl<'c> FallibleIterator for DBISectionContributionIter<'c> { } } -/// A `DbgDataHdr`, which contains a series of (optional) MSF stream numbers. +/// Extra debug streams referenced by the DBI header. +/// +/// This struct corresponds to the `DbgDataHdr` structure in the Microsoft PDB implementation. +/// It contains optional stream indices for various debug data subsections. +/// +/// Each field is a `StreamIndex` where `0xffff` indicates the stream is not present. +/// +/// # Reference +/// +/// - Struct definition: +/// +/// - Array indices: +/// +/// +/// # Notes +/// +/// This struct may be truncated in the PDB file. Missing fields are treated as +/// `StreamIndex::none()` as long as the read stops on a `u16` boundary. #[derive(Debug, Copy, Clone)] -#[allow(dead_code)] // reason = "unused fields added for completeness" +#[allow(dead_code)] pub(crate) struct DBIExtraStreams { - // The struct itself is defined at: - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/dbi/dbi.h#L250-L274 - // It's just an array of stream numbers; `u16`s where 0xffff means "no stream". - // - // The array indices are: - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/langapi/include/pdb.h#L439-L449 - // We'll map those to fields. - // - // The struct itself can be truncated. This is an internal struct; we'll treat missing fields as - // StreamIndex::none() even if it's a short read, so long as the short read stops on a u16 boundary. + /// FPO (Frame Pointer Omission) debug data stream pub fpo: StreamIndex, + + /// Exception handling data stream pub exception: StreamIndex, + + /// Fixup data stream pub fixup: StreamIndex, + + /// OMAP (Optimized Map) from source to destination addresses pub omap_to_src: StreamIndex, + + /// OMAP from destination to source addresses pub omap_from_src: StreamIndex, + + /// PE section headers stream pub section_headers: StreamIndex, + + /// Token to RID (Record ID) mapping stream pub token_rid_map: StreamIndex, + + /// XDATA (exception handling data) stream pub xdata: StreamIndex, + + /// PDATA (procedure data) stream pub pdata: StreamIndex, + + /// Frame data stream (stack frame information) pub framedata: StreamIndex, + + /// Original PE section headers (before optimization) pub original_section_headers: StreamIndex, } impl DBIExtraStreams { + #[cfg(feature = "alloc")] pub(crate) fn new(debug_info: &DebugInformation<'_>) -> Result { // calculate the location of the extra stream information let header = debug_info.header; @@ -623,14 +800,11 @@ impl DBIExtraStreams { + header.type_server_map_size + header.ec_substream_size) as usize; - // seek let mut buf = debug_info.stream.parse_buffer(); buf.take(offset)?; - // grab that section as bytes let bytes = buf.take(header.debug_header_size as _)?; - // parse those bytes let mut extra_streams_buf = ParseBuffer::from(bytes); Self::parse(&mut extra_streams_buf) } @@ -666,9 +840,10 @@ impl DBIExtraStreams { } } -#[cfg(test)] +#[cfg(all(test, feature = "alloc"))] mod tests { use crate::dbi::*; + use alloc::vec; #[test] fn test_dbi_extra_streams() { diff --git a/src/framedata.rs b/src/framedata.rs index a258f33..6ccd8db 100644 --- a/src/framedata.rs +++ b/src/framedata.rs @@ -7,11 +7,14 @@ //! Facilities for parsing legacy FPO and FrameData streams. -use std::cmp::Ordering; -use std::fmt; +use core::cmp::Ordering; +use core::fmt; use crate::common::*; + +#[cfg(feature = "alloc")] use crate::msf::Stream; + use crate::FallibleIterator; /// A compiler specific frame type. @@ -83,16 +86,25 @@ impl fmt::Display for FrameType { /// /// [`struct tagFRAMEDATA`]: https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L4635 #[repr(C)] -struct NewFrameData { - code_start: u32, - code_size: u32, - locals_size: u32, - params_size: u32, - max_stack_size: u32, - frame_func: u32, - prolog_size: u16, - saved_regs_size: u16, - flags: u32, +pub struct NewFrameData { + /// Starting RVA of the function + pub code_start: u32, + /// Size of the function code in bytes + pub code_size: u32, + /// Size of local variables in bytes + pub locals_size: u32, + /// Size of parameters in bytes + pub params_size: u32, + /// Maximum stack size used by the function + pub max_stack_size: u32, + /// Frame function index + pub frame_func: u32, + /// Size of prolog in bytes + pub prolog_size: u16, + /// Size of saved registers area + pub saved_regs_size: u16, + /// Flags (has SEH, has EH, is function start) + pub flags: u32, } impl NewFrameData { @@ -100,47 +112,47 @@ impl NewFrameData { PdbInternalRva(u32::from_le(self.code_start)) } - pub fn code_size(&self) -> u32 { + pub const fn code_size(&self) -> u32 { u32::from_le(self.code_size) } - pub fn locals_size(&self) -> u32 { + pub const fn locals_size(&self) -> u32 { u32::from_le(self.locals_size) } - pub fn params_size(&self) -> u32 { + pub const fn params_size(&self) -> u32 { u32::from_le(self.params_size) } - pub fn max_stack_size(&self) -> u32 { + pub const fn max_stack_size(&self) -> u32 { u32::from_le(self.max_stack_size) } - pub fn frame_func(&self) -> StringRef { + pub const fn frame_func(&self) -> StringRef { StringRef(u32::from_le(self.frame_func)) } - pub fn prolog_size(&self) -> u16 { + pub const fn prolog_size(&self) -> u16 { u16::from_le(self.prolog_size) } - pub fn saved_regs_size(&self) -> u16 { + pub const fn saved_regs_size(&self) -> u16 { u16::from_le(self.saved_regs_size) } - pub fn has_seh(&self) -> bool { + pub const fn has_seh(&self) -> bool { self.flags() & 1 != 0 } - pub fn has_eh(&self) -> bool { + pub const fn has_eh(&self) -> bool { self.flags() & 2 != 0 } - pub fn is_function_start(&self) -> bool { + pub const fn is_function_start(&self) -> bool { self.flags() & 4 != 0 } - fn flags(&self) -> u32 { + const fn flags(&self) -> u32 { u32::from_le(self.flags) } } @@ -583,7 +595,7 @@ impl<'s> FrameTable<'s> { mod tests { use super::*; - use std::mem; + use core::mem; #[test] fn test_new_frame_data() { diff --git a/src/io/cursor.rs b/src/io/cursor.rs new file mode 100644 index 0000000..e725c4a --- /dev/null +++ b/src/io/cursor.rs @@ -0,0 +1,117 @@ +use core::cmp; +use crate::io::*; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Cursor { + inner: T, + pos: u64, +} + +impl Cursor { + pub fn new(inner: T) -> Cursor { + Cursor { pos: 0, inner } + } + pub fn into_inner(self) -> T { + self.inner + } + + pub fn get_ref(&self) -> &T { + &self.inner + } + + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner + } + + pub fn position(&self) -> u64 { + self.pos + } + + + pub fn set_position(&mut self, pos: u64) { + self.pos = pos; + } +} + +impl Seek for Cursor +where + T: AsRef<[u8]>, +{ + fn seek(&mut self, style: SeekFrom) -> Result { + let (base_pos, offset) = match style { + SeekFrom::Start(n) => { + self.pos = n; + return Ok(n); + } + SeekFrom::End(n) => (self.inner.as_ref().len() as u64, n), + SeekFrom::Current(n) => (self.pos, n), + }; + let new_pos = if offset >= 0 { + base_pos.checked_add(offset as u64) + } else { + base_pos.checked_sub((offset.wrapping_neg()) as u64) + }; + match new_pos { + Some(n) => { + self.pos = n; + Ok(self.pos) + } + None => Err(IoError::new( + ErrorKind::InvalidInput, + "invalid seek to a negative or overflowing position", + )), + } + } +} + +impl Read for Cursor +where + T: AsRef<[u8]>, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + let n = Read::read(&mut self.fill_buf()?, buf)?; + self.pos += n as u64; + Ok(n) + } + + fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { + let n = buf.len(); + Read::read_exact(&mut self.fill_buf()?, buf)?; + self.pos += n as u64; + Ok(()) + } +} + +impl BufRead for Cursor +where + T: AsRef<[u8]>, +{ + fn fill_buf(&mut self) -> Result<&[u8]> { + let amt = cmp::min(self.pos, self.inner.as_ref().len() as u64); + Ok(&self.inner.as_ref()[(amt as usize)..]) + } + fn consume(&mut self, amt: usize) { + self.pos += amt as u64; + } +} + +// Non-resizing write implementation +#[inline] +fn slice_write(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> Result { + let pos = cmp::min(*pos_mut, slice.len() as u64); + let amt = (&mut slice[(pos as usize)..]).write(buf)?; + *pos_mut += amt as u64; + Ok(amt) +} + +impl Write for Cursor<&mut [u8]> { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + slice_write(&mut self.pos, self.inner, buf) + } + + #[inline] + fn flush(&mut self) -> Result<()> { + Ok(()) + } +} \ No newline at end of file diff --git a/src/io/error.rs b/src/io/error.rs new file mode 100644 index 0000000..15aab0e --- /dev/null +++ b/src/io/error.rs @@ -0,0 +1,240 @@ +use core::{convert::From, fmt, result}; + +pub type Result = result::Result; + +#[derive(Clone, PartialEq, Eq)] +pub struct IoError { + repr: Repr, +} + +impl core::error::Error for IoError {} + +impl fmt::Debug for IoError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.repr, f) + } +} + +#[derive(Clone, PartialEq, Eq)] +enum Repr { + Simple(ErrorKind), + Custom(Custom), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct Custom { + kind: ErrorKind, + error: &'static str, +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[non_exhaustive] +pub enum ErrorKind { + /// An entity was not found, often a file. + NotFound, + /// The operation lacked the necessary privileges to complete. + PermissionDenied, + /// The connection was refused by the remote server. + ConnectionRefused, + /// The connection was reset by the remote server. + ConnectionReset, + /// The connection was aborted (terminated) by the remote server. + ConnectionAborted, + /// The network operation failed because it was not connected yet. + NotConnected, + /// A socket address could not be bound because the address is already in + /// use elsewhere. + AddrInUse, + /// A nonexistent interface was requested or the requested address was not + /// local. + AddrNotAvailable, + /// The operation failed because a pipe was closed. + BrokenPipe, + /// An entity already exists, often a file. + AlreadyExists, + /// The operation needs to block to complete, but the blocking operation was + /// requested to not occur. + WouldBlock, + /// A parameter was incorrect. + InvalidInput, + /// Data not valid for the operation were encountered. + /// + /// Unlike [`InvalidInput`], this typically means that the operation + /// parameters were valid, however the error was caused by malformed + /// input data. + /// + /// For example, a function that reads a file into a string will error with + /// `InvalidData` if the file's contents are not valid UTF-8. + /// + /// [`InvalidInput`]: ErrorKind::InvalidInput + InvalidData, + /// The I/O operation's timeout expired, causing it to be canceled. + TimedOut, + /// An error returned when an operation could not be completed because a + /// call to [`write`] returned [`Ok(0)`]. + /// + /// This typically means that an operation could only succeed if it wrote a + /// particular number of bytes but only a smaller number of bytes could be + /// written. + /// + /// [`write`]: crate::io::Write::write + /// [`Ok(0)`]: Ok + WriteZero, + /// This operation was interrupted. + /// + /// Interrupted operations can typically be retried. + Interrupted, + /// Any I/O error not part of this list. + /// + /// Errors that are `Other` now may move to a different or a new + /// [`ErrorKind`] variant in the future. It is not recommended to match + /// an error against `Other` and to expect any additional characteristics, + /// e.g., a specific [`Error::raw_os_error`] return value. + Other, + + /// An error returned when an operation could not be completed because an + /// "end of file" was reached prematurely. + /// + /// This typically means that an operation could only succeed if it read a + /// particular number of bytes but only a smaller number of bytes could be + /// read. + UnexpectedEof, + + /// Any I/O error from the standard library that's not part of this list. + /// + /// Errors that are `Uncategorized` now may move to a different or a new + /// [`ErrorKind`] variant in the future. It is not recommended to match + /// an error against `Uncategorized`; use a wildcard match (`_`) instead. + #[doc(hidden)] + Uncategorized, +} + +impl ErrorKind { + pub(crate) fn as_str(&self) -> &'static str { + match *self { + ErrorKind::NotFound => "entity not found", + ErrorKind::PermissionDenied => "permission denied", + ErrorKind::ConnectionRefused => "connection refused", + ErrorKind::ConnectionReset => "connection reset", + ErrorKind::ConnectionAborted => "connection aborted", + ErrorKind::NotConnected => "not connected", + ErrorKind::AddrInUse => "address in use", + ErrorKind::AddrNotAvailable => "address not available", + ErrorKind::BrokenPipe => "broken pipe", + ErrorKind::AlreadyExists => "entity already exists", + ErrorKind::WouldBlock => "operation would block", + ErrorKind::InvalidInput => "invalid input parameter", + ErrorKind::InvalidData => "invalid data", + ErrorKind::TimedOut => "timed out", + ErrorKind::WriteZero => "write zero", + ErrorKind::Interrupted => "operation interrupted", + ErrorKind::Other => "other os error", + ErrorKind::UnexpectedEof => "unexpected end of file", + ErrorKind::Uncategorized => "uncategorized", + } + } +} + +#[cfg(feature = "std")] +impl From for ErrorKind { + + fn from(k: std::io::ErrorKind) -> Self { + match k { + std::io::ErrorKind::NotFound => ErrorKind::NotFound, + std::io::ErrorKind::PermissionDenied => ErrorKind::PermissionDenied, + std::io::ErrorKind::ConnectionRefused => ErrorKind::ConnectionRefused, + std::io::ErrorKind::ConnectionReset => ErrorKind::ConnectionReset, + std::io::ErrorKind::ConnectionAborted => ErrorKind::ConnectionAborted, + std::io::ErrorKind::NotConnected => ErrorKind::NotConnected, + std::io::ErrorKind::AddrInUse => ErrorKind::AddrInUse, + std::io::ErrorKind::AddrNotAvailable => ErrorKind::AddrNotAvailable, + std::io::ErrorKind::BrokenPipe => ErrorKind::BrokenPipe, + std::io::ErrorKind::AlreadyExists => ErrorKind::AlreadyExists, + std::io::ErrorKind::WouldBlock => ErrorKind::WouldBlock, + std::io::ErrorKind::InvalidInput => ErrorKind::InvalidInput, + std::io::ErrorKind::InvalidData => ErrorKind::InvalidData, + std::io::ErrorKind::TimedOut => ErrorKind::TimedOut, + std::io::ErrorKind::WriteZero => ErrorKind::WriteZero, + std::io::ErrorKind::Interrupted => ErrorKind::Interrupted, + std::io::ErrorKind::Other => ErrorKind::Other, + std::io::ErrorKind::UnexpectedEof => ErrorKind::UnexpectedEof, + _ => ErrorKind::Uncategorized, + } + } +} + +impl From for IoError { + + #[inline] + fn from(kind: ErrorKind) -> IoError { + IoError { + repr: Repr::Simple(kind), + } + } +} + +#[cfg(feature = "std")] +impl From for IoError { + + #[inline] + fn from(err: std::io::Error) -> Self { + Self::from(ErrorKind::from(err.kind())) + } +} + +impl IoError { + + pub fn new(kind: ErrorKind, error: &'static str) -> IoError { + Self::_new(kind, error.into()) + } + + fn _new(kind: ErrorKind, error: &'static str) -> IoError { + IoError { + repr: Repr::Custom(Custom { kind, error }), + } + } + + pub fn get_ref(&self) -> Option<&&'static str> { + match self.repr { + Repr::Simple(..) => None, + Repr::Custom(ref c) => Some(&c.error), + } + } + + pub fn into_inner(self) -> Option<&'static str> { + match self.repr { + Repr::Simple(..) => None, + Repr::Custom(c) => Some(c.error), + } + } + + pub fn kind(&self) -> ErrorKind { + match self.repr { + Repr::Custom(ref c) => c.kind, + Repr::Simple(kind) => kind, + } + } +} + +impl fmt::Debug for Repr { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Repr::Custom(ref c) => fmt::Debug::fmt(&c, fmt), + Repr::Simple(kind) => fmt.debug_tuple("Kind").field(&kind).finish(), + } + } +} + +impl fmt::Display for IoError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.repr { + Repr::Custom(ref c) => c.error.fmt(fmt), + Repr::Simple(kind) => write!(fmt, "{}", kind.as_str()), + } + } +} + +fn _assert_error_is_sync_send() { + fn _is_sync_send() {} + _is_sync_send::(); +} \ No newline at end of file diff --git a/src/io/impls.rs b/src/io/impls.rs new file mode 100644 index 0000000..dd3af07 --- /dev/null +++ b/src/io/impls.rs @@ -0,0 +1,155 @@ +use core::{cmp, fmt, mem}; +use crate::io::*; + +impl Read for &mut R { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result { + (**self).read(buf) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { + (**self).read_exact(buf) + } +} + +impl Write for &mut W { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + (**self).write(buf) + } + + #[inline] + fn flush(&mut self) -> Result<()> { + (**self).flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> Result<()> { + (**self).write_all(buf) + } + + #[inline] + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> Result<()> { + (**self).write_fmt(fmt) + } +} + +impl Seek for &mut S { + #[inline] + fn seek(&mut self, pos: SeekFrom) -> Result { + (**self).seek(pos) + } +} + +impl BufRead for &mut B { + #[inline] + fn fill_buf(&mut self) -> Result<&[u8]> { + (**self).fill_buf() + } + + #[inline] + fn consume(&mut self, amt: usize) { + (**self).consume(amt) + } +} + +impl Read for &[u8] { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result { + let amt = cmp::min(buf.len(), self.len()); + let (a, b) = self.split_at(amt); + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if amt == 1 { + buf[0] = a[0]; + } else { + buf[..amt].copy_from_slice(a); + } + + *self = b; + Ok(amt) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { + if buf.len() > self.len() { + return Err(IoError::new( + ErrorKind::UnexpectedEof, + "failed to fill whole buffer", + )); + } + let (a, b) = self.split_at(buf.len()); + + if buf.len() == 1 { + buf[0] = a[0]; + } else { + buf.copy_from_slice(a); + } + + *self = b; + Ok(()) + } +} + +impl BufRead for &[u8] { + #[inline] + fn fill_buf(&mut self) -> Result<&[u8]> { + Ok(*self) + } + + #[inline] + fn consume(&mut self, amt: usize) { + *self = &self[amt..]; + } +} + +impl Write for &mut [u8] { + #[inline] + fn write(&mut self, data: &[u8]) -> Result { + let amt = cmp::min(data.len(), self.len()); + let (a, b) = mem::replace(self, &mut []).split_at_mut(amt); + a.copy_from_slice(&data[..amt]); + *self = b; + Ok(amt) + } + + #[inline] + fn write_all(&mut self, data: &[u8]) -> Result<()> { + if self.write(data)? == data.len() { + Ok(()) + } else { + Err(IoError::new( + ErrorKind::WriteZero, + "failed to write whole buffer", + )) + } + } + + #[inline] + fn flush(&mut self) -> Result<()> { + Ok(()) + } +} + +#[cfg(feature = "alloc")] +impl Write for alloc::vec::Vec { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + self.extend_from_slice(buf); + Ok(buf.len()) + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> Result<()> { + self.extend_from_slice(buf); + Ok(()) + } + + #[inline] + fn flush(&mut self) -> Result<()> { + Ok(()) + } +} \ No newline at end of file diff --git a/src/io/mod.rs b/src/io/mod.rs new file mode 100644 index 0000000..31963a0 --- /dev/null +++ b/src/io/mod.rs @@ -0,0 +1,9 @@ +mod cursor; +mod traits; +mod impls; +mod error; + +pub use cursor::*; +pub use traits::*; +pub use impls::*; +pub use error::*; \ No newline at end of file diff --git a/src/io/traits.rs b/src/io/traits.rs new file mode 100644 index 0000000..ef9e611 --- /dev/null +++ b/src/io/traits.rs @@ -0,0 +1,552 @@ +use core::{cmp, fmt, slice}; + +use crate::io::ErrorKind; + +use crate::io::{IoError, Result}; + +#[cfg(feature = "alloc")] +pub use alloc::vec::Vec; + +#[cfg(feature = "alloc")] +struct Guard<'a> { + buf: &'a mut Vec, + len: usize, +} + +#[cfg(feature = "alloc")] +impl Drop for Guard<'_> { + fn drop(&mut self) { + unsafe { + self.buf.set_len(self.len); + } + } +} + +#[cfg(feature = "alloc")] +fn read_to_end(r: &mut R, buf: &mut Vec) -> Result { + read_to_end_with_reservation(r, buf, |_| 32) +} + +#[cfg(feature = "alloc")] +fn read_to_end_with_reservation( + r: &mut R, + buf: &mut Vec, + mut reservation_size: F, +) -> Result +where + R: Read + ?Sized, + F: FnMut(&R) -> usize, +{ + let start_len = buf.len(); + let mut g = Guard { + len: buf.len(), + buf, + }; + loop { + if g.len == g.buf.len() { + unsafe { + + g.buf.reserve(reservation_size(r)); + let capacity = g.buf.capacity(); + g.buf.set_len(capacity); + #[allow(deprecated)] + r.initializer().initialize(&mut g.buf[g.len..]); + } + } + + let buf = &mut g.buf[g.len..]; + match r.read(buf) { + Ok(0) => return Ok(g.len - start_len), + Ok(n) => { + + assert!(n <= buf.len()); + g.len += n; + } + Err(ref e) if e.kind() == ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } +} + +pub trait Read { + + fn read(&mut self, buf: &mut [u8]) -> Result; + + #[cfg(feature = "alloc")] + fn read_to_end(&mut self, buf: &mut Vec) -> Result { + read_to_end(self, buf) + } + + + #[cfg(feature = "alloc")] + fn read_to_string(&mut self, buf: &mut alloc::string::String) -> Result { + use crate::io::{IoError, ErrorKind}; + + let mut bytes = Vec::new(); + let n = self.read_to_end(&mut bytes)?; + match core::str::from_utf8(&bytes) { + Ok(s) => { + buf.push_str(s); + Ok(n) + } + Err(_) => Err(IoError::new( + ErrorKind::InvalidData, + "stream did not contain valid UTF-8", + )), + } + } + + #[inline] + unsafe fn initializer(&self) -> Initializer { + Initializer::zeroing() + } + + fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> { + while !buf.is_empty() { + match self.read(buf) { + Ok(0) => break, + Ok(n) => { + let tmp = buf; + buf = &mut tmp[n..]; + } + Err(ref e) if e.kind() == ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } + if !buf.is_empty() { + Err(IoError::new( + ErrorKind::UnexpectedEof, + "failed to fill whole buffer", + )) + } else { + Ok(()) + } + } + + fn by_ref(&mut self) -> &mut Self + where + Self: Sized, + { + self + } + + fn bytes(self) -> Bytes + where + Self: Sized, + { + Bytes { inner: self } + } + + fn chain(self, next: R) -> Chain + where + Self: Sized, + { + Chain { + first: self, + second: next, + done_first: false, + } + } + + + fn take(self, limit: u64) -> Take + where + Self: Sized, + { + Take { inner: self, limit } + } +} + +#[derive(Debug)] +pub struct Initializer(bool); + +#[allow(deprecated)] +impl Initializer { + /// Returns a new `Initializer` which will zero out buffers. + #[inline] + pub fn zeroing() -> Initializer { + Initializer(true) + } + + #[inline] + pub unsafe fn nop() -> Initializer { + Initializer(false) + } + + #[inline] + pub fn should_initialize(&self) -> bool { + self.0 + } + + #[inline] + pub fn initialize(&self, buf: &mut [u8]) { + if self.should_initialize() { + unsafe { core::ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len()) } + } + } +} + +pub trait Write { + + fn write(&mut self, buf: &[u8]) -> Result; + + fn flush(&mut self) -> Result<()>; + + fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { + while !buf.is_empty() { + match self.write(buf) { + Ok(0) => { + return Err(IoError::new( + ErrorKind::WriteZero, + "failed to write whole buffer", + )); + } + Ok(n) => buf = &buf[n..], + Err(ref e) if e.kind() == ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } + Ok(()) + } + + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> Result<()> { + + struct Adaptor<'a, T: ?Sized + 'a> { + inner: &'a mut T, + error: Result<()>, + } + + impl fmt::Write for Adaptor<'_, T> { + fn write_str(&mut self, s: &str) -> fmt::Result { + match self.inner.write_all(s.as_bytes()) { + Ok(()) => Ok(()), + Err(e) => { + self.error = Err(e); + Err(fmt::Error) + } + } + } + } + + let mut output = Adaptor { + inner: self, + error: Ok(()), + }; + match fmt::write(&mut output, fmt) { + Ok(()) => Ok(()), + Err(..) => { + // check if the error came from the underlying `Write` or not + if output.error.is_err() { + output.error + } else { + Err(IoError::new(ErrorKind::Other, "formatter error")) + } + } + } + } + + fn by_ref(&mut self) -> &mut Self + where + Self: Sized, + { + self + } +} + +pub trait Seek { + + fn seek(&mut self, pos: SeekFrom) -> Result; +} + +#[derive(Copy, PartialEq, Eq, Clone, Debug)] +pub enum SeekFrom { + Start(u64), + End(i64), + Current(i64), +} + +#[derive(Debug)] +pub struct Bytes { + inner: R, +} + +impl Iterator for Bytes { + type Item = Result; + + fn next(&mut self) -> Option> { + let mut byte = 0; + loop { + return match self.inner.read(slice::from_mut(&mut byte)) { + Ok(0) => None, + Ok(..) => Some(Ok(byte)), + Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => Some(Err(e)), + }; + } + } +} + +pub trait BufRead: Read { + + fn fill_buf(&mut self) -> Result<&[u8]>; + + fn consume(&mut self, amt: usize); + + #[cfg(feature = "alloc")] + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> Result { + read_until(self, byte, buf) + } + + #[cfg(feature = "alloc")] + fn read_line(&mut self, buf: &mut alloc::string::String) -> Result { + let mut bytes = Vec::new(); + let n = self.read_until(b'\n', &mut bytes)?; + match core::str::from_utf8(&bytes) { + Ok(s) => { + buf.push_str(s); + Ok(n) + } + Err(_) => Err(IoError::new( + ErrorKind::InvalidData, + "stream did not contain valid UTF-8", + )), + } + } +} + +#[cfg(feature = "alloc")] +fn read_until(r: &mut R, delim: u8, buf: &mut Vec) -> Result { + let mut total = 0; + loop { + let available = match r.fill_buf() { + Ok(b) => b, + Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + }; + if available.is_empty() { + return Ok(total); + } + + let (done, used) = match memchr::memchr(delim, available) { + Some(i) => { + buf.extend_from_slice(&available[..=i]); + (true, i + 1) + } + None => { + buf.extend_from_slice(available); + (false, available.len()) + } + }; + r.consume(used); + total += used; + if done { + return Ok(total); + } + } +} + +pub struct Chain { + first: T, + second: U, + done_first: bool, +} + +impl Chain { + + pub fn into_inner(self) -> (T, U) { + (self.first, self.second) + } + + pub fn get_ref(&self) -> (&T, &U) { + (&self.first, &self.second) + } + + pub fn get_mut(&mut self) -> (&mut T, &mut U) { + (&mut self.first, &mut self.second) + } +} + +impl fmt::Debug for Chain { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Chain") + .field("t", &self.first) + .field("u", &self.second) + .finish() + } +} + +impl Read for Chain { + fn read(&mut self, buf: &mut [u8]) -> Result { + if !self.done_first { + match self.first.read(buf)? { + 0 if !buf.is_empty() => self.done_first = true, + n => return Ok(n), + } + } + self.second.read(buf) + } + + #[allow(deprecated)] + unsafe fn initializer(&self) -> Initializer { + let initializer = self.first.initializer(); + if initializer.should_initialize() { + initializer + } else { + self.second.initializer() + } + } +} + +impl BufRead for Chain { + fn fill_buf(&mut self) -> Result<&[u8]> { + if !self.done_first { + match self.first.fill_buf()? { + buf if buf.is_empty() => { + self.done_first = true; + } + buf => return Ok(buf), + } + } + self.second.fill_buf() + } + + fn consume(&mut self, amt: usize) { + if !self.done_first { + self.first.consume(amt) + } else { + self.second.consume(amt) + } + } +} + +#[derive(Debug)] +pub struct Take { + inner: T, + limit: u64, +} + +impl Take { + + pub fn limit(&self) -> u64 { + self.limit + } + + pub fn set_limit(&mut self, limit: u64) { + self.limit = limit; + } + + pub fn into_inner(self) -> T { + self.inner + } + + pub fn get_ref(&self) -> &T { + &self.inner + } + + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl Read for Take { + fn read(&mut self, buf: &mut [u8]) -> Result { + + if self.limit == 0 { + return Ok(0); + } + + let max = cmp::min(buf.len() as u64, self.limit) as usize; + let n = self.inner.read(&mut buf[..max])?; + self.limit -= n as u64; + Ok(n) + } + + #[allow(deprecated)] + unsafe fn initializer(&self) -> Initializer { + self.inner.initializer() + } + + #[cfg(feature = "alloc")] + fn read_to_end(&mut self, buf: &mut Vec) -> Result { + read_to_end_with_reservation(self, buf, |self_| cmp::min(self_.limit, 32) as usize) + } +} + +impl BufRead for Take { + fn fill_buf(&mut self) -> Result<&[u8]> { + + if self.limit == 0 { + return Ok(&[]); + } + + let buf = self.inner.fill_buf()?; + let cap = cmp::min(buf.len() as u64, self.limit) as usize; + Ok(&buf[..cap]) + } + + fn consume(&mut self, amt: usize) { + let amt = cmp::min(amt as u64, self.limit) as usize; + self.limit -= amt as u64; + self.inner.consume(amt); + } +} + +#[cfg(feature = "std")] +impl crate::io::traits::Read for std::fs::File { + + #[inline] + fn read(&mut self, buf: &mut [u8]) -> std::result::Result { + use std::io::Read; + std::io::Read::read(self, buf).map_err(|e| crate::io::IoError::from(e)) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> std::result::Result<(), crate::io::IoError> { + use std::io::Read; + std::io::Read::read_exact(self, buf).map_err(|e| crate::io::IoError::from(e)) + } +} + +#[cfg(feature = "std")] +impl crate::io::traits::Seek for std::fs::File { + fn seek(&mut self, pos: crate::io::SeekFrom) -> std::result::Result { + use std::io::Seek; + let pos = match pos { + crate::io::SeekFrom::Start(n) => std::io::SeekFrom::Start(n), + crate::io::SeekFrom::End(n) => std::io::SeekFrom::End(n), + crate::io::SeekFrom::Current(n) => std::io::SeekFrom::Current(n), + }; + std::io::Seek::seek(self, pos).map_err(|e| crate::io::IoError::from(e)) + } +} + +#[cfg(feature = "std")] +impl> Read for std::io::Cursor { + + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result { + use std::io::Read; + std::io::Read::read(self, buf).map_err(|e| IoError::from(e)) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { + use std::io::Read; + std::io::Read::read_exact(self, buf).map_err(|e| IoError::from(e)) + } +} + +#[cfg(feature = "std")] +impl> Seek for std::io::Cursor { + + #[inline] + fn seek(&mut self, pos: SeekFrom) -> Result { + use std::io::Seek; + let pos = match pos { + SeekFrom::Start(n) => std::io::SeekFrom::Start(n), + SeekFrom::End(n) => std::io::SeekFrom::End(n), + SeekFrom::Current(n) => std::io::SeekFrom::Current(n), + }; + std::io::Seek::seek(self, pos).map_err(|e| IoError::from(e)) + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 7275e20..fd0ed7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2017 pdb Developers +// Copyright 2026 PDB Developers // // Licensed under the Apache License, Version 2.0, or the MIT license 2000); //! ``` - +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(feature = "nightly", feature(core_io))] +#![cfg_attr(feature = "nightly", feature(alloc_io))] #![warn(missing_docs)] +#![allow(unused)] + +#[cfg(feature = "alloc")] +extern crate alloc; + +// #[cfg(not(feature = "alloc"))] +pub mod noalloc; -// modules +mod constants; mod common; + +#[cfg(not(feature = "nightly"))] +mod io; + +#[cfg(feature = "nightly")] +mod io { + pub use core::io::*; + pub use core::io::Error as IoError; + pub use alloc::io::Read; +} + mod dbi; + +#[cfg(feature = "alloc")] mod framedata; + +#[cfg(feature = "alloc")] mod modi; + mod msf; + +#[cfg(feature = "alloc")] mod omap; + mod pdb; mod pdbi; mod pe; mod source; mod strings; mod symbol; + mod tpi; -// exports +#[cfg(feature = "alloc")] +pub use crate::msf::*; + +pub use crate::constants::*; pub use crate::common::*; + pub use crate::dbi::*; + +#[cfg(feature = "alloc")] pub use crate::framedata::*; + +#[cfg(feature = "alloc")] pub use crate::modi::*; + +#[cfg(feature = "alloc")] pub use crate::omap::*; + pub use crate::pdb::*; pub use crate::pdbi::*; pub use crate::pe::*; pub use crate::source::*; pub use crate::strings::*; pub use crate::symbol::*; + pub use crate::tpi::*; // re-export FallibleIterator for convenience diff --git a/src/modi/c13.rs b/src/modi/c13.rs index 223bb99..5977380 100644 --- a/src/modi/c13.rs +++ b/src/modi/c13.rs @@ -1,6 +1,12 @@ -use std::fmt; -use std::mem; -use std::slice; +use core::fmt; +use core::mem; +use core::mem::replace; +use core::mem::transmute; +use core::slice; +use core::slice::Iter; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; use scroll::{ctx::TryFromCtx, Endian, Pread}; @@ -38,7 +44,7 @@ enum DebugSubsectionKind { impl DebugSubsectionKind { fn parse(value: u32) -> Result> { if (0xf1..=0xfd).contains(&value) { - Ok(Some(unsafe { std::mem::transmute(value) })) + Ok(Some(unsafe { transmute(value) })) } else if value == constants::DEBUG_S_IGNORE { Ok(None) } else { @@ -164,7 +170,7 @@ impl<'a> TryFromCtx<'a, DebugInlineeLinesHeader> for InlineeSourceLine<'a> { let extra_files = if header.has_extra_files() { let file_count = buf.parse::()? as usize; - buf.take(file_count * std::mem::size_of::())? + buf.take(file_count * size_of::())? } else { &[] }; @@ -491,18 +497,18 @@ impl<'t> TryFromCtx<'t, Endian> for DebugLinesBlockHeader { impl DebugLinesBlockHeader { /// The byte size of all line and column records combined. fn data_size(&self) -> usize { - self.block_size as usize - std::mem::size_of::() + self.block_size as usize - size_of::() } /// The byte size of all line number entries combined. fn line_size(&self) -> usize { - self.num_lines as usize * std::mem::size_of::() + self.num_lines as usize * size_of::() } /// The byte size of all column number entries combined. fn column_size(&self, subsection: DebugLinesHeader) -> usize { if subsection.has_columns() { - self.num_lines as usize * std::mem::size_of::() + self.num_lines as usize * size_of::() } else { 0 } @@ -588,7 +594,7 @@ impl FileChecksumKind { /// Parses the checksum kind from its raw value. fn parse(value: u8) -> Result { if value <= 3 { - Ok(unsafe { std::mem::transmute(value) }) + Ok(unsafe { transmute(value) }) } else { Err(Error::UnimplementedFileChecksumKind(value)) } @@ -751,11 +757,13 @@ impl<'a> DebugCrossScopeImportsSubsection<'a> { /// /// This can be used to resolve cross module references. See [`ItemIndex::is_cross_module`] for more /// information. +#[cfg(feature = "alloc")] #[derive(Clone, Debug, Default)] pub struct CrossModuleImports<'a> { modules: Vec>, } +#[cfg(feature = "alloc")] impl<'a> CrossModuleImports<'a> { /// Creates `CrossModuleImports` from the imports debug subsection. fn from_section(section: DebugCrossScopeImportsSubsection<'a>) -> Result { @@ -921,11 +929,13 @@ impl<'a> FallibleIterator for CrossModuleExportIter<'a> { /// /// Other modules can import types and ids from this module by using [cross module /// references](ItemIndex::is_cross_module). +#[cfg(feature = "alloc")] #[derive(Clone, Debug, Default)] pub struct CrossModuleExports { raw_exports: Vec, } +#[cfg(feature = "alloc")] impl CrossModuleExports { fn from_section(section: DebugCrossScopeExportsSubsection<'_>) -> Result { let raw_exports = section.exports().collect()?; @@ -985,7 +995,7 @@ impl CrossModuleExports { #[derive(Clone)] pub struct LineIterator<'a> { /// Iterator over all subsections in the current module. - sections: std::slice::Iter<'a, DebugLinesSubsection<'a>>, + sections: Iter<'a, DebugLinesSubsection<'a>>, /// Iterator over all blocks in the current lines subsection. blocks: DebugLinesBlockIterator<'a>, /// Iterator over lines in the current block. @@ -1033,7 +1043,7 @@ impl<'a> FallibleIterator for LineIterator<'a> { kind: line_entry.kind, }; - let mut last_info = match std::mem::replace(&mut self.last_info, Some(line_info)) { + let mut last_info = match replace(&mut self.last_info, Some(line_info)) { Some(last_info) => last_info, None => continue, }; @@ -1223,7 +1233,7 @@ impl<'a> FallibleIterator for InlineeLineIterator<'a> { // Finish the previous record and emit it. The current record is stored so that the // length can be inferred from subsequent operators or the next line info. - if let Some(last_info) = std::mem::replace(&mut self.last_info, Some(line_info)) { + if let Some(last_info) = replace(&mut self.last_info, Some(line_info)) { return Ok(Some(last_info)); } } @@ -1313,11 +1323,13 @@ impl<'a> FallibleIterator for FileIterator<'a> { } } +#[cfg(feature = "alloc")] pub struct LineProgram<'a> { file_checksums: DebugFileChecksumsSubsection<'a>, line_sections: Vec>, } +#[cfg(feature = "alloc")] impl<'a> LineProgram<'a> { pub(crate) fn parse(data: &'a [u8]) -> Result { let mut file_checksums = DebugFileChecksumsSubsection::default(); @@ -1415,7 +1427,7 @@ impl<'a> LineProgram<'a> { } } -#[cfg(test)] +#[cfg(all(test, feature = "alloc"))] mod tests { use super::*; diff --git a/src/modi/mod.rs b/src/modi/mod.rs index c0e2dc7..3108620 100644 --- a/src/modi/mod.rs +++ b/src/modi/mod.rs @@ -1,8 +1,13 @@ -use std::fmt; +use core::fmt; use crate::common::*; + +#[cfg(feature = "alloc")] use crate::dbi::Module; + +#[cfg(feature = "alloc")] use crate::msf::Stream; + use crate::symbol::SymbolIter; use crate::FallibleIterator; diff --git a/src/msf/big.rs b/src/msf/big.rs new file mode 100644 index 0000000..f5fe5ca --- /dev/null +++ b/src/msf/big.rs @@ -0,0 +1,487 @@ + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +use super::*; + +pub const MAGIC: &[u8] = b"Microsoft C/C++ MSF 7.00\r\n\x1a\x44\x53\x00\x00\x00"; + +/// Raw MSF header (52 bytes) as stored at page 0, offset 0. +/// +/// Contains the magic identifier, page size, and directory metadata needed +/// to parse the container format. +/// +/// # Field Descriptions +/// +/// - `magic`: 32-byte format identifier +/// - `page_size`: Page size in bytes (must be power of two, 256-8MB) +/// - `free_page_map`: Page number of the free page map +/// - `pages_used`: Total number of pages in the file +/// - `directory_size`: Size of the stream directory in bytes +/// - `_reserved`: Reserved (should be zero) +/// +/// See: +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct RawHeader { + /// 32-byte magic string, e.g. `"Microsoft C/C++ MSF 7.00..."` + magic: [u8; 32], + /// Page size in bytes (power of two, >= 256) + page_size: u32, + /// Free page map page number + free_page_map: u32, + /// Total page count in the file + pages_used: u32, + /// Stream directory size in bytes + directory_size: u32, + /// Reserved (should be zero) + _reserved: u32, +} + +impl<'t> TryFromCtx<'t, Endian> for RawHeader { + type Error = scroll::Error; + + fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { + let mut offset = 0; + let data = Self { + magic: { + let mut tmp = [0; 32]; + this.gread_inout_with(&mut offset, &mut tmp, le)?; + tmp + }, + page_size: this.gread_with(&mut offset, le)?, + free_page_map: this.gread_with(&mut offset, le)?, + pages_used: this.gread_with(&mut offset, le)?, + directory_size: this.gread_with(&mut offset, le)?, + _reserved: this.gread_with(&mut offset, le)?, + }; + Ok((data, offset)) + } +} + +/// MSF 7.00 container with source access and stream lookup. +#[cfg(feature = "alloc")] +#[derive(Debug)] +pub struct BigMSF<'s, S: Source<'s>> { + /// Header information (page size, max page number) + header: MSFHeader, + /// Data source providing page access + source: S, + /// Stream table in various stages of discovery + stream_table: StreamTable<'s>, +} + +#[cfg(feature = "alloc")] +impl<'s, S: Source<'s>> BigMSF<'s, S> { + + /// Creates a new BigMSF instance by parsing the MSF header. + /// + /// # Overview + /// + /// The MSF format uses a two-level indirection to locate the stream table: + /// + /// 1. **Header** → Contains directory size and page size + /// 2. **Pages after header** → List of page numbers for the stream table + /// 3. **Stream table pages** → The actual stream directory + /// + /// This method parses the header and captures the first level of indirection + /// as a `StreamTable::HeaderOnly` state. + /// + /// # Page Size Validation + /// + /// The page size must be: + /// - A power of two (`count_ones() == 1`) + /// - At least 256 bytes (0x100) + /// - At most 8 MB (128 * 0x10000) + /// + /// # Arguments + /// + /// * `source` - The data source providing page access + /// * `header_view` - The raw header bytes from page 0 + /// + /// # Returns + /// + /// A `BigMSF` instance ready for stream lookup, or an error if the + /// header is invalid or the format is unrecognized. + pub fn new(source: S, header_view: SourceView<'_>) -> Result> { + let mut buf = ParseBuffer::from(header_view.as_slice()); + let header: RawHeader = buf.parse()?; + + if header.magic != MAGIC { + return Err(Error::UnrecognizedFileFormat); + } + + if header.page_size.count_ones() != 1 + || header.page_size < 0x100 + || header.page_size > (128 * 0x10000) + { + return Err(Error::InvalidPageSize(header.page_size)); + } + + let header_object = MSFHeader { + page_size: header.page_size as usize, + maximum_valid_page_number: header.pages_used, + }; + + let size_of_stream_table_in_pages = + header_object.pages_needed_to_store(header.directory_size as usize); + + + let size_of_stream_table_page_list_in_pages = + header_object.pages_needed_to_store(size_of_stream_table_in_pages * 4); + + let mut stream_table_page_list_page_list = PageList::new(header_object.page_size); + for _ in 0..size_of_stream_table_page_list_in_pages { + let n = buf.parse_u32()?; + stream_table_page_list_page_list.push(header_object.validate_page_number(n)?); + } + + stream_table_page_list_page_list.truncate(size_of_stream_table_in_pages * 4); + + Ok(BigMSF { + header: header_object, + source, + stream_table: StreamTable::HeaderOnly { + size_in_bytes: header.directory_size as usize, + stream_table_location_location: stream_table_page_list_page_list, + }, + }) + } + + /// Locates the stream table's page list from the header-only information. + /// + /// This is the second step in the three-stage discovery process: + /// + /// 1. `HeaderOnly` → We know where the page list *locations* are + /// 2. `TableFound` → We've found the page list *locations* + /// 3. `Available` → We've loaded the actual stream table + /// + /// # How it works + /// + /// The header told us where to find the list of pages that contain the + /// stream table's page list. This method: + /// + /// 1. Reads the page numbers from `stream_table_location_location` + /// 2. Parses the page numbers from those pages into a `PageList` + /// 3. Truncates to the correct size + /// 4. Transitions to `StreamTable::TableFound` state + /// + /// The resulting `PageList` points to the actual stream table data. + fn find_stream_table(&mut self) -> Result<()> { + let mut new_stream_table: Option> = None; + + if let StreamTable::HeaderOnly { + size_in_bytes, + ref stream_table_location_location, + } = self.stream_table + { + let location_location = self.source.view_pages(stream_table_location_location)?; + + let mut page_list = PageList::new(self.header.page_size); + let mut buf = ParseBuffer::from(location_location.as_slice()); + while !buf.is_empty() { + let n = buf.parse_u32()?; + page_list.push(self.header.validate_page_number(n)?); + } + + page_list.truncate(size_in_bytes); + + new_stream_table = Some(StreamTable::TableFound { + stream_table_location: page_list, + }); + } + + if let Some(st) = new_stream_table { + self.stream_table = st; + } + + Ok(()) + } + + /// Ensures the stream table data is loaded and available. + /// + /// This method coordinates the full discovery process: + /// + /// 1. If still in `HeaderOnly` state, calls `find_stream_table()` + /// 2. If in `TableFound` state, loads the stream table data + /// 3. Transitions to `StreamTable::Available` state + /// + /// # Panics + /// + /// Panics if the stream table is not available after this method returns. + /// This should never happen and indicates a bug in the implementation. + fn make_stream_table_available(&mut self) -> Result<()> { + + if let StreamTable::HeaderOnly { .. } = self.stream_table { + self.find_stream_table()?; + } + + let mut new_stream_table = None; + if let StreamTable::TableFound { + ref stream_table_location, + } = self.stream_table + { + let stream_table_view = self.source.view_pages(stream_table_location)?; + new_stream_table = Some(StreamTable::Available { stream_table_view }); + } + + if let Some(st) = new_stream_table { + self.stream_table = st; + } + + assert!(matches!(self.stream_table, StreamTable::Available { .. })); + + Ok(()) + } + + /// Looks up a stream by number and returns its page list. + /// + /// # Stream Table Layout + /// + /// The stream table is structured as: + /// + /// ```text + /// [stream_count] (u32) + /// [stream 0 size] (u32) + /// [stream 1 size] (u32) + /// ... + /// [stream N size] (u32) + /// [stream 0 pages...] (u32 per page) + /// [stream 1 pages...] (u32 per page) + /// ... + /// [stream N pages...] (u32 per page) + /// ``` + /// + /// Where `0xffffffff` indicates a stream does not exist. + /// + /// # How It Works + /// + /// 1. Ensures the stream table is available + /// 2. Parses the stream count + /// 3. Calculates how many pages to skip for streams before the requested one + /// 4. Reads the requested stream's size and page numbers + /// 5. Returns a `PageList` pointing to the stream's data + /// + /// # Arguments + /// + /// * `stream_number` - The index of the stream to look up + /// + /// # Returns + /// + /// A `PageList` containing the pages for the requested stream, + /// or an error if the stream doesn't exist. + fn look_up_stream(&mut self, stream_number: u32) -> Result { + + self.make_stream_table_available()?; + + let header = self.header; + + let bytes_in_stream: u32; + let page_list: PageList; + + if let StreamTable::Available { + ref stream_table_view, + } = self.stream_table + { + let stream_table_slice = stream_table_view.as_slice(); + let mut stream_table = ParseBuffer::from(stream_table_slice); + let stream_count = stream_table.parse_u32()?; + + if stream_number >= stream_count { + return Err(Error::StreamNotFound(stream_number)); + } + + let mut page_numbers_to_skip: usize = 0; + for _ in 0..stream_number { + let bytes = stream_table.parse_u32()?; + if bytes != u32::MAX { + page_numbers_to_skip += header.pages_needed_to_store(bytes as usize); + } + } + + bytes_in_stream = stream_table.parse_u32()?; + + if bytes_in_stream == u32::MAX { + return Err(Error::StreamNotFound(stream_number)); + } + let pages_in_stream = header.pages_needed_to_store(bytes_in_stream as usize); + + let _ = stream_table.take((stream_count - stream_number - 1) as usize * 4)?; + let _ = stream_table.take(page_numbers_to_skip * 4)?; + + let mut list = PageList::new(header.page_size); + + for _ in 0..pages_in_stream { + let page_number = stream_table.parse_u32()?; + list.push(self.header.validate_page_number(page_number)?); + } + + list.truncate(bytes_in_stream as usize); + + page_list = list; + } else { + unreachable!(); + } + + Ok(page_list) + } +} + +#[cfg(feature = "alloc")] +impl<'s, S: Source<'s>> MsfImpl<'s, S> for BigMSF<'s, S> { + + #[inline] + fn get(&mut self, stream_number: u32, limit: Option) -> Result> { + let mut page_list = self.look_up_stream(stream_number)?; + + if let Some(limit) = limit { + page_list.truncate(limit); + } + + let view = self.source.view_pages(&page_list)?; + + let stream = Stream::new(view); + + Ok(stream) + } + + #[inline] + fn kind(&self) -> MsfKind { + MsfKind::Big + } + + #[inline] + fn stream_count(&mut self) -> Result { + self.make_stream_table_available()?; + + if let StreamTable::Available { ref stream_table_view } = self.stream_table { + let mut stream_table = ParseBuffer::from(stream_table_view.as_slice()); + stream_table.parse_u32() + } else { + unreachable!() + } + } + + #[inline] + fn has_stream(&mut self, stream_number: u32) -> Result { + self.make_stream_table_available()?; + + if let StreamTable::Available { ref stream_table_view } = self.stream_table { + let stream_table_slice = stream_table_view.as_slice(); + let mut stream_table = ParseBuffer::from(stream_table_slice); + + let stream_count = stream_table.parse_u32()?; + if stream_number >= stream_count { + return Ok(false); + } + + // Skip preceding stream sizes + let _ = stream_table.take(stream_number as usize * 4)?; + + let size = stream_table.parse_u32()?; + Ok(size != u32::MAX) + } else { + unreachable!() + } + } + + #[inline] + fn stream_size(&mut self, stream_number: u32) -> Result> { + self.make_stream_table_available()?; + + if let StreamTable::Available { ref stream_table_view } = self.stream_table { + let stream_table_slice = stream_table_view.as_slice(); + let mut stream_table = ParseBuffer::from(stream_table_slice); + + let stream_count = stream_table.parse_u32()?; + if stream_number >= stream_count { + return Err(Error::StreamNotFound(stream_number)); + } + + // Skip preceding stream sizes (4 bytes each). + let _ = stream_table.take(stream_number as usize * 4)?; + + let size = stream_table.parse_u32()?; + if size == u32::MAX { + Ok(None) + } else { + Ok(Some(size)) + } + } else { + unreachable!() + } + } +} + +#[cfg(all(test, feature = "alloc"))] +mod tests { + use alloc::vec::Vec; + use alloc::vec; + use super::*; + use crate::source::MemorySource; + + /// Creates a complete valid MSF 7.00 file. + fn create_test_msf(page_size: u32) -> Vec { + let page_size = page_size as usize; + let mut file = Vec::new(); + + // Page 0: Header (52 bytes) + padding + // directory_size = 0 means no stream table + let header = create_test_header(page_size as u32, 2, 0); + file.extend_from_slice(&header); + let header_padding = page_size - header.len(); + file.extend(vec![0u8; header_padding]); + + // Page 1: Empty directory (just padding to fill the page) + file.extend(vec![0u8; page_size]); + + file + } + + fn create_test_header(page_size: u32, pages_used: u32, directory_size: u32) -> Vec { + let mut header = Vec::with_capacity(52); + header.extend_from_slice(MAGIC); + header.extend_from_slice(&page_size.to_le_bytes()); + header.extend_from_slice(&0u32.to_le_bytes()); + header.extend_from_slice(&pages_used.to_le_bytes()); + header.extend_from_slice(&directory_size.to_le_bytes()); + header.extend_from_slice(&0u32.to_le_bytes()); + header + } + + #[test] + fn test_header_parsing_valid() { + let file = create_test_msf(4096); + let mut source = MemorySource::new(&file); + let header_view = source.view(&[SourceSlice { offset: 0, size: 52 }]).unwrap(); + + let msf = BigMSF::new(source, header_view).unwrap(); + + assert_eq!(msf.header.page_size, 4096); + assert_eq!(msf.header.maximum_valid_page_number, 2); + assert!(matches!(msf.stream_table, StreamTable::HeaderOnly { .. })); + } + + #[test] + fn test_header_parsing_invalid_magic() { + let mut file = create_test_msf(4096); + file[0] = 0xFF; // Corrupt magic + + let mut source = MemorySource::new(&file); + let header_view = source.view(&[SourceSlice { offset: 0, size: 52 }]).unwrap(); + + let result = BigMSF::new(source, header_view); + assert!(matches!(result, Err(Error::UnrecognizedFileFormat))); + } + + #[test] + fn test_invalid_page_size() { + // Page size must be power of two + let file = create_test_msf(3000); + let mut source = MemorySource::new(&file); + let header_view = source.view(&[SourceSlice { offset: 0, size: 52 }]).unwrap(); + + let result = BigMSF::new(source, header_view); + assert!(matches!(result, Err(Error::InvalidPageSize(3000)))); + } +} \ No newline at end of file diff --git a/src/msf/mod.rs b/src/msf/mod.rs index eb2e348..e44006c 100644 --- a/src/msf/mod.rs +++ b/src/msf/mod.rs @@ -1,428 +1,118 @@ -// Copyright 2017 pdb Developers +// Copyright 2026 PDB Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. -use std::fmt; -use std::ops::Deref; +#[cfg(feature = "alloc")] +use alloc::boxed::Box; use scroll::{ctx::TryFromCtx, Endian, Pread}; use crate::common::*; +use crate::io::ErrorKind; use crate::source::*; +mod types; +pub mod big; +pub mod small; mod page_list; -use self::page_list::PageList; -type PageNumber = u32; +pub use types::*; -#[derive(Debug, Copy, Clone)] -struct Header { - page_size: usize, - maximum_valid_page_number: PageNumber, -} - -impl Header { - fn pages_needed_to_store(&self, bytes: usize) -> usize { - (bytes + (self.page_size - 1)) / self.page_size - } +#[cfg(feature = "alloc")] +pub use page_list::PageList; - fn validate_page_number(&self, page_number: u32) -> Result { - if page_number == 0 || page_number > self.maximum_valid_page_number { - Err(Error::PageReferenceOutOfRange(page_number)) - } else { - Ok(page_number as PageNumber) - } - } +pub fn header_matches(actual: &[u8], expected: &[u8]) -> bool { + actual.len() >= expected.len() && &actual[0..expected.len()] == expected } -/// Represents a stream table at various stages of access -#[doc(hidden)] +#[cfg(feature = "alloc")] #[derive(Debug)] -enum StreamTable<'s> { - /// The MSF header gives us the size of the table in bytes, and the list of pages (usually one) - /// where we can find the list of pages that contain the stream table. - HeaderOnly { - size_in_bytes: usize, - stream_table_location_location: PageList, - }, - - /// Given the HeaderOnly information, we can do an initial read to get the actual location of - /// the stream table as a PageList. - TableFound { stream_table_location: PageList }, - - // Given the table location, we can access the stream table itself - Available { - stream_table_view: Box>, - }, -} - -fn view<'s>(source: &mut dyn Source<'s>, page_list: &PageList) -> Result>> { - // view it - let view = source.view(page_list.source_slices())?; - - // double check our Source - // if the Source didn't return the requested bits, that's an implementation bug, so - // assert instead of returning an error - assert_eq!(view.as_slice().len(), page_list.len()); - - // done - Ok(view) -} - -mod big { - use super::*; +pub struct Msf<'s, S>(Box + 's>); - pub const MAGIC: &[u8] = b"Microsoft C/C++ MSF 7.00\r\n\x1a\x44\x53\x00\x00\x00"; +#[cfg(feature = "alloc")] +impl<'s, S: Source<'s> + 's> Msf<'s, S> { - /// The PDB header as stored on disk. + /// Opens an MSF (Multi-Stream File) container from a source. /// - /// See the Microsoft code for reference: - #[repr(C)] - #[derive(Debug, Copy, Clone)] - struct RawHeader { - magic: [u8; 32], - page_size: u32, - free_page_map: u32, - pages_used: u32, - directory_size: u32, - _reserved: u32, - } - - impl<'t> TryFromCtx<'t, Endian> for RawHeader { - type Error = scroll::Error; - - fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { - let mut offset = 0; - let data = Self { - magic: { - let mut tmp = [0; 32]; - this.gread_inout_with(&mut offset, &mut tmp, le)?; - tmp - }, - page_size: this.gread_with(&mut offset, le)?, - free_page_map: this.gread_with(&mut offset, le)?, - pages_used: this.gread_with(&mut offset, le)?, - directory_size: this.gread_with(&mut offset, le)?, - _reserved: this.gread_with(&mut offset, le)?, - }; - Ok((data, offset)) - } - } - - #[derive(Debug)] - pub struct BigMSF<'s, S> { - header: Header, - source: S, - stream_table: StreamTable<'s>, - } - - impl<'s, S: Source<'s>> BigMSF<'s, S> { - pub fn new(source: S, header_view: Box>) -> Result> { - let mut buf = ParseBuffer::from(header_view.as_slice()); - let header: RawHeader = buf.parse()?; - - if header.magic != MAGIC { - return Err(Error::UnrecognizedFileFormat); - } - - if header.page_size.count_ones() != 1 - || header.page_size < 0x100 - || header.page_size > (128 * 0x10000) - { - return Err(Error::InvalidPageSize(header.page_size)); - } - - let header_object = Header { - page_size: header.page_size as usize, - maximum_valid_page_number: header.pages_used, - }; - - // calculate how many pages are needed to store the stream table - let size_of_stream_table_in_pages = - header_object.pages_needed_to_store(header.directory_size as usize); - - // now: how many pages are needed to store the list of pages that store the stream table? - // each page entry is a u32, so multiply by four - let size_of_stream_table_page_list_in_pages = - header_object.pages_needed_to_store(size_of_stream_table_in_pages * 4); - - // read the list of stream table page list pages, which immediately follow the header - // yes, this is a stupid level of indirection - let mut stream_table_page_list_page_list = PageList::new(header_object.page_size); - for _ in 0..size_of_stream_table_page_list_in_pages { - let n = buf.parse_u32()?; - stream_table_page_list_page_list.push(header_object.validate_page_number(n)?); - } - - // truncate the stream table location location to the correct size - stream_table_page_list_page_list.truncate(size_of_stream_table_in_pages * 4); - - Ok(BigMSF { - header: header_object, - source, - stream_table: StreamTable::HeaderOnly { - size_in_bytes: header.directory_size as usize, - stream_table_location_location: stream_table_page_list_page_list, - }, - }) - } - - fn find_stream_table(&mut self) -> Result<()> { - let mut new_stream_table: Option> = None; - - if let StreamTable::HeaderOnly { - size_in_bytes, - ref stream_table_location_location, - } = self.stream_table - { - // the header indicated we need to read size_in_pages page numbers from the - // specified PageList. - - // ask to view the location location - let location_location = view(&mut self.source, stream_table_location_location)?; - - // build a PageList - let mut page_list = PageList::new(self.header.page_size); - let mut buf = ParseBuffer::from(location_location.as_slice()); - while !buf.is_empty() { - let n = buf.parse_u32()?; - page_list.push(self.header.validate_page_number(n)?); + /// This function detects the MSF format version by reading the header and + /// returns the appropriate implementation (`BigMSF` or `SmallMSF`). + pub fn open(mut source: S) -> Result { + + // We don't yet know the page size, so we can't use a PageList here. + // Read a fixed probe large enough for both magics and both fixed + // headers. 512 is the minimum valid Small MSF page size, so anything + // shorter than this isn't a valid MSF file regardless of format. + const PROBE_LEN: usize = 512; + + let header_view = match source.view(&[SourceSlice { offset: 0, size: PROBE_LEN }]) { + Ok(view) => view, + Err(err) => { + if err.kind() == ErrorKind::UnexpectedEof { + return Err(Error::UnrecognizedFileFormat); + } else { + return Err(Error::IoError(err)); } - - page_list.truncate(size_in_bytes); - - // remember what we learned - new_stream_table = Some(StreamTable::TableFound { - stream_table_location: page_list, - }); } + }; - if let Some(st) = new_stream_table { - self.stream_table = st; - } - - Ok(()) + if header_matches(&header_view, big::MAGIC) { + let msf = big::BigMSF::new(source, header_view)?; + return Ok(Self(Box::new(msf))); } - fn make_stream_table_available(&mut self) -> Result<()> { - // do the initial read if we must - if let StreamTable::HeaderOnly { .. } = self.stream_table { - self.find_stream_table()?; - } - - // do we need to map the stream table itself? - let mut new_stream_table = None; - if let StreamTable::TableFound { - ref stream_table_location, - } = self.stream_table - { - // ask the source to view it - let stream_table_view = view(&mut self.source, stream_table_location)?; - new_stream_table = Some(StreamTable::Available { stream_table_view }); - } - - if let Some(st) = new_stream_table { - self.stream_table = st; - } + if header_matches(&header_view, small::MAGIC) { - // stream table is available - assert!(matches!(self.stream_table, StreamTable::Available { .. })); - - Ok(()) - } - - fn look_up_stream(&mut self, stream_number: u32) -> Result { - // ensure the stream table is available - self.make_stream_table_available()?; - - let header = self.header; - - // declare the things we're going to find - let bytes_in_stream: u32; - let page_list: PageList; - - if let StreamTable::Available { - ref stream_table_view, - } = self.stream_table - { - let stream_table_slice = stream_table_view.as_slice(); - let mut stream_table = ParseBuffer::from(stream_table_slice); - - // the stream table is structured as: - // stream_count - // 0..stream_count: size of stream in bytes (0xffffffff indicating "stream does not exist") - // stream 0: PageNumber - // stream 1: PageNumber, PageNumber - // stream 2: PageNumber, PageNumber, PageNumber, PageNumber, PageNumber - // stream 3: PageNumber, PageNumber, PageNumber, PageNumber - // (number of pages determined by number of bytes) - - let stream_count = stream_table.parse_u32()?; - - // check if we've already outworn our welcome - if stream_number >= stream_count { - return Err(Error::StreamNotFound(stream_number)); - } - - // we now have {stream_count} u32s describing the length of each stream - - // walk over the streams before the requested stream - // we need to pay attention to how big each one is, since their page numbers come - // before our page numbers in the stream table - let mut page_numbers_to_skip: usize = 0; - for _ in 0..stream_number { - let bytes = stream_table.parse_u32()?; - if bytes == u32::max_value() { - // stream is not present, ergo nothing to skip - } else { - page_numbers_to_skip += header.pages_needed_to_store(bytes as usize); - } - } - - // read our stream's size - bytes_in_stream = stream_table.parse_u32()?; - if bytes_in_stream == u32::max_value() { - return Err(Error::StreamNotFound(stream_number)); - } - let pages_in_stream = header.pages_needed_to_store(bytes_in_stream as usize); - - // skip the remaining streams' byte counts - let _ = stream_table.take((stream_count - stream_number - 1) as usize * 4)?; - - // skip the preceding streams' page numbers - let _ = stream_table.take(page_numbers_to_skip * 4)?; - - // we're now at the list of pages for our stream - // accumulate them into a PageList - let mut list = PageList::new(header.page_size); - for _ in 0..pages_in_stream { - let page_number = stream_table.parse_u32()?; - list.push(self.header.validate_page_number(page_number)?); - } - - // truncate to the size of the stream - list.truncate(bytes_in_stream as usize); - - page_list = list; - } else { - unreachable!(); - } - - // done! - Ok(page_list) + let msf = small::SmallMSF::new(source, header_view)?; + return Ok(Self(Box::new(msf))); } - } - - impl<'s, S: Source<'s>> Msf<'s, S> for BigMSF<'s, S> { - fn get(&mut self, stream_number: u32, limit: Option) -> Result> { - // look up the stream - let mut page_list = self.look_up_stream(stream_number)?; - - // apply any limits we have - if let Some(limit) = limit { - page_list.truncate(limit); - } - - // now that we know where this stream lives, we can view it - let view = view(&mut self.source, &page_list)?; - // pack it into a Stream - let stream = Stream { source_view: view }; - - Ok(stream) - } + Err(Error::UnrecognizedFileFormat) } -} - -mod small { - pub const MAGIC: &[u8] = b"Microsoft C/C++ program database 2.00\r\n\x1a\x4a\x47"; - // TODO: implement SmallMSF -} -/// Represents a single Stream within the multi-stream file. -#[derive(Debug)] -pub struct Stream<'s> { - source_view: Box>, -} - -impl<'s> Stream<'s> { + /// Returns the MSF format kind (Big or Small). #[inline] - pub(crate) fn parse_buffer(&self) -> ParseBuffer<'_> { - let slice = self.source_view.as_slice(); - ParseBuffer::from(slice) + pub fn kind(&self) -> MsfKind { + self.0.kind() } + /// Returns the total number of streams in this MSF, including nil streams. #[inline] - pub fn as_slice(&self) -> &[u8] { - self.source_view.as_slice() + pub fn stream_count(&mut self) -> Result { + self.0.stream_count() } -} - -impl Deref for Stream<'_> { - type Target = [u8]; + /// Returns the total number of streams in this MSF, including nil streams. #[inline] - fn deref(&self) -> &Self::Target { - self.as_slice() + pub fn stream_size(&mut self, stream_number: u32) -> Result> { + self.0.stream_size(stream_number) } -} -/// Provides access to a "multi-stream file", which is the container format used by PDBs. -pub trait Msf<'s, S>: fmt::Debug { - /// Accesses a stream by stream number, optionally restricted by a byte limit. - fn get(&mut self, stream_number: u32, limit: Option) -> Result>; -} - -fn header_matches(actual: &[u8], expected: &[u8]) -> bool { - actual.len() >= expected.len() && &actual[0..expected.len()] == expected -} - -pub fn open_msf<'s, S: Source<'s> + 's>(mut source: S) -> Result + 's>> { - // map the header - let mut header_location = PageList::new(4096); - header_location.push(0); - let header_view = match view(&mut source, &header_location) { - Ok(view) => view, - Err(e) => match e { - Error::IoError(x) => { - if x.kind() == std::io::ErrorKind::UnexpectedEof { - return Err(Error::UnrecognizedFileFormat); - } else { - return Err(Error::IoError(x)); - } - } - _ => return Err(e), - }, - }; - - // see if it's a BigMSF - if header_matches(header_view.as_slice(), big::MAGIC) { - // claimed! - let bigmsf = big::BigMSF::new(source, header_view)?; - return Ok(Box::new(bigmsf)); + /// Returns `true` if the stream exists and is not a nil stream. + #[inline] + pub fn has_stream(&mut self, stream_number: u32) -> Result { + self.0.has_stream(stream_number) } - if header_matches(header_view.as_slice(), small::MAGIC) { - // sorry - return Err(Error::UnimplementedFeature("small MSF file format")); + /// Retrieves a stream by number, optionally limited to a byte count. + #[inline] + pub fn get(&mut self, stream_number: u32, limit: Option) -> Result> { + self.0.get(stream_number, limit) } - - Err(Error::UnrecognizedFileFormat) } -#[cfg(test)] +#[cfg(all(test, feature = "alloc"))] mod tests { mod header { use crate::common::Error; - use crate::msf::open_msf; - use crate::msf::Header; + use crate::io::Cursor; + use crate::msf::{MSFHeader, Msf}; #[test] fn test_pages_needed_to_store() { - let h = Header { + let h = MSFHeader { page_size: 4096, maximum_valid_page_number: 15, }; @@ -437,7 +127,7 @@ mod tests { #[test] fn test_validate_page_number() { - let h = Header { + let h = MSFHeader { page_size: 4096, maximum_valid_page_number: 15, }; @@ -461,9 +151,9 @@ mod tests { #[test] fn test_small_file_unrecognized_file_format() { - let small_file = std::io::Cursor::new(b"\x7FELF"); + let small_file = Cursor::new(b"\x7FELF"); - match open_msf(small_file) { + match Msf::open(small_file) { Ok(_) => panic!("4 byte file should not parse as msf"), Err(e) => match e { Error::UnrecognizedFileFormat => (), diff --git a/src/msf/page_list.rs b/src/msf/page_list.rs index e5eace9..ac9632e 100644 --- a/src/msf/page_list.rs +++ b/src/msf/page_list.rs @@ -1,26 +1,37 @@ -// Copyright 2017 pdb Developers +// Copyright 2026 PDB Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + use crate::msf::PageNumber; use crate::source::SourceSlice; -/// Represents a list of `PageNumbers`, which are likely (but not certainly) sequential, and which -/// will be presented as a slice of `SourceSlice`s. +/// A list of page numbers that will be presented as a slice of `SourceSlice`s. +/// +/// `PageList` is used to track which pages in the MSF (Multi-Stream File) +/// container belong to a particular stream or data block. +#[cfg(feature = "alloc")] #[derive(Debug)] pub struct PageList { + /// Size of each page in bytes (e.g., 4096) page_size: usize, + /// The list of contiguous data ranges to read source_slices: Vec, + /// The last page added, used to detect sequential runs last_page: Option, + /// Whether this list has been truncated (no more pages can be added) truncated: bool, } +#[cfg(feature = "alloc")] impl PageList { /// Create a new PageList for a given page size. - pub fn new(page_size: usize) -> Self { + pub const fn new(page_size: usize) -> Self { Self { page_size, source_slices: Vec::new(), @@ -45,15 +56,23 @@ impl PageList { let last_slice = self.source_slices.last_mut().unwrap(); last_slice.size += self.page_size; } else { - self.source_slices.push(SourceSlice { + let slice = SourceSlice { offset: (self.page_size as u64) * u64::from(page), size: self.page_size, - }); + }; + self.source_slices.push(slice); } self.last_page = Some(page); } + /// Extend the PageList with multiple pages. + pub fn extend(&mut self, pages: impl IntoIterator) { + for page in pages { + self.push(page); + } + } + /// Truncate the `PageList` to request only a certain number of bytes, regardless of how many /// pages were pushed. Truncatation is optional, but it must be last; `push()` may not be /// called after `truncate()`. @@ -61,8 +80,8 @@ impl PageList { let mut bytes = bytes; let mut new_slices: Vec = Vec::new(); - for slice in &self.source_slices { - let mut slice: SourceSlice = *slice; + for slice in self.source_slices.iter().copied() { + let mut slice: SourceSlice = slice; if bytes > 0 { // we need something from this slice // restrict this slice to the number of bytes remaining @@ -70,13 +89,9 @@ impl PageList { slice.size = bytes; } - // keep it new_slices.push(slice); - - // subtract the number of bytes in this slice bytes -= slice.size; } else { - // we're done break; } } @@ -91,13 +106,14 @@ impl PageList { } /// Return a slice of SourceSlices. - pub fn source_slices(&self) -> &[SourceSlice] { + pub const fn source_slices(&self) -> &[SourceSlice] { self.source_slices.as_slice() } } -#[cfg(test)] +#[cfg(all(test, feature = "alloc"))] mod tests { + use alloc::vec; use crate::msf::page_list::*; #[test] @@ -176,15 +192,9 @@ mod tests { #[test] fn test_truncate() { let mut list = PageList::new(4096); - list.push(0); - list.push(1); - list.push(4); - list.push(5); - list.push(2); - list.push(2); + list.extend([0, 1, 4, 5, 2, 2]); assert_eq!(list.len(), 24576); - // truncation should do nothing when it's truncating more than is described list.truncate(25000); let expected = vec![ SourceSlice { @@ -207,7 +217,6 @@ mod tests { assert_eq!(list.source_slices(), expected.as_slice()); assert_eq!(list.len(), 24576); - // it's usually employed to reduce the size of the last slice... list.truncate(24000); let expected = vec![ SourceSlice { @@ -230,7 +239,6 @@ mod tests { assert_eq!(list.source_slices(), expected.as_slice()); assert_eq!(list.len(), 24000); - // ...but it should be able to lop off entire slices too list.truncate(10000); let expected = vec![ SourceSlice { @@ -260,7 +268,6 @@ mod tests { assert_eq!(list.source_slices(), expected.as_slice()); assert_eq!(list.len(), 10000); - // finally, we should be able to truncate the entire PageList down to nothing list.truncate(0); assert_eq!(list.source_slices().len(), 0); assert_eq!(list.len(), 0); diff --git a/src/msf/small.rs b/src/msf/small.rs new file mode 100644 index 0000000..ca290d5 --- /dev/null +++ b/src/msf/small.rs @@ -0,0 +1,247 @@ +use core::marker::PhantomData; +use scroll::ctx::TryFromCtx; +use scroll::{Endian, Pread, LE}; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +use crate::msf::MSFHeader; + +#[cfg(feature = "alloc")] +use crate::Source; + +#[cfg(feature = "alloc")] +use crate::msf::{MsfImpl, MsfKind}; + +use crate::{Error, ParseBuffer, Result}; + +#[cfg(feature = "alloc")] +use crate::SourceView; + +#[cfg(feature = "alloc")] +use crate::msf::page_list::PageList; + +#[cfg(feature = "alloc")] +use super::Stream; + +#[cfg(feature = "alloc")] +use super::Msf; + +pub const MAGIC: &[u8] = b"Microsoft C/C++ program database 2.00\r\n\x1a\x4a\x47\0\0"; + +/// Raw Small MSF header as stored on disk. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct RawHeader { + magic: [u8; 44], + block_size: u32, + free_list_block: u16, + total_alloc: u16, + toc_size: u32, // PDB_FILE.size + toc_unknown: u32, // PDB_FILE.unknown +} + +impl<'t> TryFromCtx<'t, Endian> for RawHeader { + type Error = scroll::Error; + + #[inline] + fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { + let mut offset = 0; + let data = Self { + magic: { + let mut tmp = [0; 44]; + this.gread_inout_with(&mut offset, &mut tmp, le)?; + tmp + }, + block_size: this.gread_with(&mut offset, le)?, + free_list_block: this.gread_with(&mut offset, le)?, + total_alloc: this.gread_with(&mut offset, le)?, + toc_size: this.gread_with(&mut offset, le)?, + toc_unknown: this.gread_with(&mut offset, le)?, + }; + Ok((data, offset)) + } +} + +/// Small MSF (version 2.00) container. +/// +/// This implementation handles the older MSF format with fixed 512-byte pages. +/// Directory pages are stored directly in the header at offset 44. +#[cfg(feature = "alloc")] +#[derive(Debug)] +pub struct SmallMSF<'s, S> { + /// Header information (page size, max page number) + header: MSFHeader, + /// Data source providing page access + source: S, + /// Directory page list pointing to the stream directory + directory: PageList, + /// Phantom data for lifetime + _phantom: PhantomData<&'s ()>, +} + +#[cfg(feature = "alloc")] +impl<'s, S: Source<'s>> SmallMSF<'s, S> { + + /// Creates a new SmallMSF instance by parsing the MSF header. + pub fn new(source: S, header_view: SourceView<'_>) -> Result { + let mut buf = ParseBuffer::from(header_view.as_slice()); + + let raw_header = buf.parse::()?; + + if raw_header.magic != MAGIC { + return Err(Error::UnrecognizedFileFormat); + } + + let page_size = raw_header.block_size as usize; + + if page_size == 0 || !page_size.is_power_of_two() { + return Err(Error::InvalidPageSize(page_size as u32)); + } + + let header = MSFHeader { + page_size, + maximum_valid_page_number: raw_header.total_alloc as u32, + }; + + let mut directory = PageList::new(page_size); + let pages_needed = header.pages_needed_to_store(raw_header.toc_size as usize); + + for _ in 0..pages_needed { + + let page = buf.parse_u16()?; + directory.push(header.validate_page_number(page as _)?); + } + + directory.truncate(raw_header.toc_size as usize); + + Ok(SmallMSF { + header, + source, + directory, + _phantom: PhantomData, + }) + } + + /// Looks up a stream by number and returns its page list. + fn look_up_stream(&mut self, stream_number: u32) -> Result { + let directory_view = self.source.view_pages(&self.directory)?; + let mut dir = ParseBuffer::from(directory_view.as_slice()); + let stream_count = dir.parse_u32()?; + + if stream_number >= stream_count { + return Err(Error::StreamNotFound(stream_number)); + } + + let mut stream_sizes = Vec::with_capacity(stream_count as usize); + for i in 0..stream_count { + let size = dir.parse_u32()?; + let unknown = dir.parse_u32()?; + stream_sizes.push(size); + } + + let block_list_start = dir.pos(); + let remaining = directory_view.as_slice().len() - block_list_start; + + if remaining > 0 { + let preview_len = core::cmp::min(32, remaining); + } + + let mut page_offset = 0; + for i in 0..stream_number { + let size = stream_sizes[i as usize]; + if size != u32::MAX { + let pages = self.header.pages_needed_to_store(size as usize); + page_offset += pages; + } + } + + let stream_size = stream_sizes[stream_number as usize]; + if stream_size == u32::MAX { + return Err(Error::StreamNotFound(stream_number)); + } + let page_numbers_to_read = self.header.pages_needed_to_store(stream_size as usize); + + let skip_bytes = page_offset * 2; + let _ = dir.take(skip_bytes)?; + + let mut list = PageList::new(self.header.page_size); + for i in 0..page_numbers_to_read { + let page = dir.parse_u16()?; + list.push(self.header.validate_page_number(page as u32)?); + } + + list.truncate(stream_size as usize); + + Ok(list) + } +} + +#[cfg(feature = "alloc")] +impl<'s, S: Source<'s>> MsfImpl<'s, S> for SmallMSF<'s, S> { + + #[inline] + fn get(&mut self, stream_number: u32, limit: Option) -> Result> { + + let mut page_list = self.look_up_stream(stream_number)?; + + if let Some(limit) = limit { + page_list.truncate(limit); + } + + let view = self.source.view_pages(&page_list)?; + Ok(Stream::new(view)) + } + + #[inline] + fn kind(&self) -> MsfKind { + MsfKind::Small + } + + #[inline] + fn stream_count(&mut self) -> Result { + let directory_view = self.source.view_pages(&self.directory)?; + let mut dir = ParseBuffer::from(directory_view.as_slice()); + dir.parse_u32() + } + + fn has_stream(&mut self, stream_number: u32) -> Result { + let directory_view = self.source.view_pages(&self.directory)?; + let mut dir = ParseBuffer::from(directory_view.as_slice()); + + let num_files = dir.parse_u32()?; + if stream_number >= num_files { + return Ok(false); + } + + // Skip preceding file entries (each is 8 bytes: size u32 + unknown u32) + let _ = dir.take(stream_number as usize * 8)?; + + let size = dir.parse_u32()?; + Ok(size != u32::MAX) + } + + #[inline] + fn stream_size(&mut self, stream_number: u32) -> Result> { + let directory_view = self.source.view_pages(&self.directory)?; + let mut dir = ParseBuffer::from(directory_view.as_slice()); + + let stream_count = dir.parse_u32()?; + if stream_number >= stream_count { + return Err(Error::StreamNotFound(stream_number)); + } + + // Small MSF entries are 8 bytes: size u32 + reserved u32. + let _ = dir.take(stream_number as usize * 8)?; + + let size = dir.parse_u32()?; + if size == u32::MAX { + Ok(None) + } else { + Ok(Some(size)) + } + } +} \ No newline at end of file diff --git a/src/msf/types.rs b/src/msf/types.rs new file mode 100644 index 0000000..77f1add --- /dev/null +++ b/src/msf/types.rs @@ -0,0 +1,172 @@ +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +use crate::common::*; + +#[cfg(feature = "alloc")] +use crate::msf::page_list::PageList; + +use crate::source::*; + +use core::fmt; +use core::ops::Deref; + +pub type PageNumber = u32; + +/// MSF file header information. +/// +/// Contains the page size and the maximum valid page number used to validate +/// page references in the stream table. +#[derive(Debug, Copy, Clone)] +pub struct MSFHeader { + /// Size of each page in bytes (must be power of two, >= 256) + pub(crate) page_size: usize, + /// The highest valid page number in this file + pub(crate) maximum_valid_page_number: PageNumber, +} + +impl MSFHeader { + /// Calculates how many pages are needed to store `bytes` of data. + pub const fn pages_needed_to_store(&self, bytes: usize) -> usize { + (bytes + (self.page_size - 1)) / self.page_size + } + + /// Validates that a page number is within the valid range. + /// + /// Page 0 is invalid (reserved) and page numbers must not exceed + /// `maximum_valid_page_number`. + pub fn validate_page_number(&self, page_number: u32) -> Result { + if page_number == 0 || page_number > self.maximum_valid_page_number { + Err(Error::PageReferenceOutOfRange(page_number)) + } else { + Ok(page_number as PageNumber) + } + } +} + +/// The state of stream table discovery in an MSF file. +/// +/// The stream table in an MSF (Multi-Stream File) is located through a two-level indirection: +/// +/// 1. **Header** → Tells us where to find the page list that points to the stream table +/// 2. **Page List** → Contains the actual pages of the stream table +/// 3. **Stream Table** → The final data structure mapping stream numbers to their pages +/// +/// This enum tracks which stage of discovery we're in, allowing lazy loading. +#[cfg(feature = "alloc")] +#[doc(hidden)] +#[derive(Debug)] +pub enum StreamTable<'s> { + /// We only know the size and the location of the location. + /// + /// The header gave us: + /// - `size_in_bytes`: How many bytes the stream table occupies + /// - `stream_table_location_location`: A `PageList` pointing to pages that contain + /// the actual page numbers of the stream table + /// + /// This is the initial state after parsing the MSF header. + HeaderOnly { + /// The total size of the stream table in bytes + size_in_bytes: usize, + /// A page list pointing to pages that contain the stream table's page numbers + stream_table_location_location: PageList, + }, + + /// We've located the stream table's page list. + /// + /// `stream_table_location` is a `PageList` that directly points to the pages + /// containing the stream table data. The stream table can now be read. + TableFound { + /// The page list containing the actual stream table data + stream_table_location: PageList, + }, + + /// The stream table data is loaded and available for reading. + /// + /// `stream_table_view` is a contiguous view of the stream table data, + /// ready to be parsed. + Available { + /// A contiguous view of the stream table data + stream_table_view: SourceView<'s>, + }, +} + +/// A stream within an MSF (Multi-Stream File) container. +/// +/// Streams are logical collections of data that span multiple pages in the MSF. +/// This type provides read-only access to the stream's raw bytes. +#[cfg(feature = "alloc")] +#[derive(Debug)] +pub struct Stream<'s>(SourceView<'s>); + +#[cfg(feature = "alloc")] +impl<'s> Stream<'s> { + /// Creates a new `Stream` from a contiguous view of the underlying source. + pub const fn new(view: SourceView<'s>) -> Self { + Self(view) + } + + /// Returns a `ParseBuffer` over this stream's bytes. + #[inline] + pub fn parse_buffer(&self) -> ParseBuffer<'_> { + let slice = self.0.as_slice(); + ParseBuffer::from(slice) + } + + /// Returns this stream's bytes as a slice. + #[inline] + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } +} + +#[cfg(feature = "alloc")] +impl Deref for Stream<'_> { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +/// Provides access to a "multi-stream file", which is the container format used by PDBs. +pub trait MsfImpl<'s, S>: fmt::Debug { + fn kind(&self) -> MsfKind; + + /// Accesses a stream by stream number, optionally restricted by a byte limit. + #[cfg(feature = "alloc")] + fn get(&mut self, stream_number: u32, limit: Option) -> Result>; + + /// Returns the total number of streams (including nil streams). + fn stream_count(&mut self) -> Result; + + /// Returns true if the stream exists and is not nil. + fn has_stream(&mut self, stream_number: u32) -> Result; + + /// Returns the size in bytes of the given stream, or `None` if the + /// stream is a nil stream. + /// + /// # Errors + /// + /// * `Error::StreamNotFound` if the stream number is out of range + fn stream_size(&mut self, stream_number: u32) -> Result>; +} + +/// MSF (Multi-Stream File) format version. +/// +/// The MSF container format has two major versions: +/// - **Small MSF**: Legacy format used by Visual Studio 6 and earlier +/// - **Big MSF**: Modern format used by Visual Studio 7+ (most PDB files) +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum MsfKind { + /// Legacy MSF format (version 2.00) with fixed 512-byte pages. + /// + /// This format is obsolete and rarely seen in practice. + Small, + + /// Modern MSF format (version 7.00) with variable page sizes. + /// + /// This is the format used by all modern PDB files. + Big, +} \ No newline at end of file diff --git a/src/noalloc/big.rs b/src/noalloc/big.rs new file mode 100644 index 0000000..ae18d7a --- /dev/null +++ b/src/noalloc/big.rs @@ -0,0 +1,94 @@ +use crate::noalloc::misc::*; +use crate::Result; + +#[derive(Debug)] +pub struct BigMsf<'a> { + dir: BigDirReader<'a>, + pub page_size: u32, + pub num_streams: u32, +} + +impl<'a> BigMsf<'a> { + + pub fn parse(data: &'a [u8]) -> Result { + if data.len() < BIG_HEADER_LEN { + return Err(crate::Error::UnexpectedEof); + } + + let page_size = read_u32_at(data, BIG_PAGE_SIZE_OFFSET)?; + if page_size < 0x100 || page_size > 128 * 0x10000 || !page_size.is_power_of_two() { + return Err(crate::Error::InvalidPageSize(page_size)); + } + + let max_page = read_u32_at(data, BIG_PAGES_USED_OFFSET)?; + let directory_size = read_u32_at(data, BIG_DIRECTORY_SIZE_OFFSET)?; + + if directory_size % 4 != 0 { + return Err(crate::Error::UnexpectedEof); + } + + let dir = BigDirReader { + data, + page_size, + directory_size, + max_page, + }; + + let num_streams = if directory_size >= 4 { + dir.read_u32(0)? + } else { + 0 + }; + + Ok(Self { + dir, + page_size, + num_streams, + }) + } + + pub fn stream_size(&self, stream: u32) -> Result> { + if stream >= self.num_streams { + return Err(crate::Error::StreamNotFound(stream)); + } + let size = self.dir.read_u32(4 + stream as usize * 4)?; + if size == u32::MAX { + Ok(None) + } else { + Ok(Some(size)) + } + } + + fn pages_offset_for(&self, stream: u32) -> Result { + let mut offset = 4 + self.num_streams as usize * 4; + for i in 0..stream { + let size = self.dir.read_u32(4 + i as usize * 4)?; + if size != u32::MAX { + offset += pages_needed_u32(size, self.page_size) as usize * 4; + } + } + Ok(offset) + } + + pub fn stream_chunks(&self, stream: u32) -> Result> { + let size = self + .stream_size(stream)? + .ok_or(crate::Error::StreamNotFound(stream))?; + + if size == 0 { + return Ok(StreamChunks::empty(self.dir.data)); + } + + Ok(StreamChunks { + data: self.dir.data, + page_size: self.page_size, + source: PageSource::Big { + dir: self.dir, + pages_offset: self.pages_offset_for(stream)?, + }, + total_pages: pages_needed_u32(size, self.page_size), + current: 0, + remaining: size, + }) + } +} diff --git a/src/noalloc/misc.rs b/src/noalloc/misc.rs new file mode 100644 index 0000000..7cf37c3 --- /dev/null +++ b/src/noalloc/misc.rs @@ -0,0 +1,172 @@ +use core::convert::TryInto; +use crate::Result; + +pub const BIG_HEADER_LEN: usize = 52; +pub const SMALL_BLOCK_SIZE_OFFSET: usize = 44; +pub const SMALL_TOTAL_ALLOC_OFFSET: usize = 50; +pub const SMALL_TOC_SIZE_OFFSET: usize = 52; +pub const SMALL_TOC_PAGES_OFFSET: usize = 60; +pub const BIG_PAGE_SIZE_OFFSET: usize = 32; +pub const BIG_PAGES_USED_OFFSET: usize = 40; +pub const BIG_DIRECTORY_SIZE_OFFSET: usize = 44; + +pub fn read_u32_at(data: &[u8], offset: usize) -> Result { + if offset + 4 > data.len() { + return Err(crate::Error::UnexpectedEof); + } + Ok(u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap())) +} + +pub fn read_u16_at(data: &[u8], offset: usize) -> Result { + if offset + 2 > data.len() { + return Err(crate::Error::UnexpectedEof); + } + Ok(u16::from_le_bytes(data[offset..offset + 2].try_into().unwrap())) +} + +pub const fn pages_needed_u32(size: u32, page_size: u32) -> u32 { + (size + page_size - 1) / page_size +} + + +#[derive(Debug, Clone, Copy)] +pub struct BigDirReader<'a> { + pub data: &'a [u8], + pub page_size: u32, + pub directory_size: u32, + pub max_page: u32, +} + +impl<'a> BigDirReader<'a> { + pub fn read_u32(&self, dir_offset: usize) -> Result { + if dir_offset + 4 > self.directory_size as usize { + return Err(crate::Error::UnexpectedEof); + } + + let page_size = self.page_size as usize; + let l2_page_index = dir_offset / page_size; + let within_l2 = dir_offset % page_size; + + let entries_per_l1 = page_size / 4; + let l1_page_index = l2_page_index / entries_per_l1; + let l1_entry_index = l2_page_index % entries_per_l1; + + let l1_page_num_offset = BIG_HEADER_LEN + l1_page_index * 4; + let l1_page_num = read_u32_at(self.data, l1_page_num_offset)?; + if l1_page_num == 0 || l1_page_num > self.max_page { + return Err(crate::Error::PageReferenceOutOfRange(l1_page_num)); + } + + let l1_page_start = l1_page_num as usize * page_size; + let l2_page_num_offset = l1_page_start + l1_entry_index * 4; + let l2_page_num = read_u32_at(self.data, l2_page_num_offset)?; + if l2_page_num == 0 || l2_page_num > self.max_page { + return Err(crate::Error::PageReferenceOutOfRange(l2_page_num)); + } + + let l2_page_start = l2_page_num as usize * page_size; + let value_offset = l2_page_start + within_l2; + read_u32_at(self.data, value_offset) + } +} + +#[derive(Debug, Clone, Copy)] +pub enum PageSource<'a> { + Small { + toc: &'a [u8], + pages_offset: usize, + }, + Big { + dir: BigDirReader<'a>, + pages_offset: usize, + }, +} + +impl<'a> PageSource<'a> { + fn read_page_number(&self, index: u32) -> Result { + match self { + PageSource::Small { toc, pages_offset } => { + let off = pages_offset + index as usize * 2; + if off + 2 > toc.len() { + return Err(crate::Error::UnexpectedEof); + } + Ok(u16::from_le_bytes(toc[off..off + 2].try_into().unwrap()) as u32) + } + PageSource::Big { dir, pages_offset } => { + let off = pages_offset + index as usize * 4; + dir.read_u32(off) + } + } + } +} + +#[derive(Debug)] +pub struct StreamChunks<'a> { + pub data: &'a [u8], + pub page_size: u32, + pub source: PageSource<'a>, + pub total_pages: u32, + pub current: u32, + pub remaining: u32, +} + +impl<'a> StreamChunks<'a> { + pub const fn empty(data: &'a [u8]) -> Self { + Self { + data, + page_size: 0, + source: PageSource::Small { + toc: &[], + pages_offset: 0, + }, + total_pages: 0, + current: 0, + remaining: 0, + } + } +} + +impl<'a> Iterator for StreamChunks<'a> { + type Item = Result<&'a [u8]>; + + fn next(&mut self) -> Option { + if self.current >= self.total_pages { + return None; + } + + let page = match self.source.read_page_number(self.current) { + Ok(p) => p, + Err(e) => { + self.current = self.total_pages; + return Some(Err(e)); + } + }; + + let mut run: u32 = 1; + while self.current + run < self.total_pages { + match self.source.read_page_number(self.current + run) { + Ok(next) if next == page + run => run += 1, + Ok(_) => break, + Err(e) => { + self.current = self.total_pages; + return Some(Err(e)); + } + } + } + + let start = page as usize * self.page_size as usize; + let run_bytes = (run * self.page_size).min(self.remaining); + let end = start + run_bytes as usize; + + if end > self.data.len() { + self.current = self.total_pages; + return Some(Err(crate::Error::PageReferenceOutOfRange(page))); + } + + self.remaining -= run_bytes; + self.current += run; + Some(Ok(&self.data[start..end])) + } +} + +impl core::iter::FusedIterator for StreamChunks<'_> {} \ No newline at end of file diff --git a/src/noalloc/mod.rs b/src/noalloc/mod.rs new file mode 100644 index 0000000..3f9e565 --- /dev/null +++ b/src/noalloc/mod.rs @@ -0,0 +1,196 @@ +use core::convert::TryInto; +use crate::{DBI_HEADER_LEN, DBI_STREAM, DBIHeader, MachineType, ModuleIter, ParseBuffer, Result, msf, noalloc::{big::BigMsf, misc::StreamChunks, small::SmallMsf}}; + +mod misc; +mod small; +mod big; + +#[derive(Debug)] +pub struct PDB<'a> { + msf: Msf<'a>, +} + +#[derive(Debug)] +pub enum Msf<'a> { + Small(SmallMsf<'a>), + Big(BigMsf<'a>), +} + +#[derive(Debug)] +pub struct DebugInformation { + header: DBIHeader, + header_len: usize, +} + +impl DebugInformation { + pub fn parse(data: &[u8]) -> Result { + let mut parse_buffer = ParseBuffer::from(data); + let header = DBIHeader::parse_buf(&mut parse_buffer)?; + let header_len = parse_buffer.pos(); + + Ok(Self { + header, + header_len + }) + } + + /// Returns a copy of the parsed DBI header. + pub const fn header(&self) -> DBIHeader { + self.header + } + + /// Returns this PDB's original `age`. + pub const fn age(&self) -> Option { + match self.header.age { + 0 => None, + age => Some(age), + } + } + + /// Returns the target's machine type (architecture). + pub fn machine_type(&self) -> Result { + Ok(self.header.machine_type.into()) + } + + /// Reads the DBI module list into `dst` and returns an iterator over the + /// modules it contains. + pub fn modules<'b>( + &self, + pdb: &PDB<'_>, + dst: &'b mut [u8], + ) -> Result> { + let want = self.header.module_list_size as usize; + if dst.len() < want { + return Err(crate::Error::UnexpectedEof); + } + + let n = pdb.read_stream_at( + DBI_STREAM, + self.header_len as u32, + &mut dst[..want], + )?; + + if n < want { + return Err(crate::Error::UnexpectedEof); + } + + Ok(ModuleIter::new(&dst[..want])) + } +} + +impl<'a> PDB<'a> { + pub fn open(data: &'a [u8]) -> Result { + if msf::header_matches(data, msf::small::MAGIC) { + return Ok(Self { + msf: Msf::Small(SmallMsf::parse(data)?), + }); + } + if msf::header_matches(data, msf::big::MAGIC) { + return Ok(Self { + msf: Msf::Big(BigMsf::parse(data)?), + }); + } + Err(crate::Error::UnrecognizedFileFormat) + } + + pub fn debug_information(&self) -> Result { + let mut buf = [0u8; DBI_HEADER_LEN]; + let n = self.read_stream(DBI_STREAM, &mut buf)?; + + if n < DBI_HEADER_LEN { + return Err(crate::Error::UnexpectedEof); + } + + DebugInformation::parse(&buf) + } + + pub fn msf_kind(&self) -> msf::MsfKind { + match self.msf { + Msf::Small(_) => msf::MsfKind::Small, + Msf::Big(_) => msf::MsfKind::Big, + } + } + + pub fn page_size(&self) -> u32 { + match &self.msf { + Msf::Small(m) => m.page_size, + Msf::Big(m) => m.page_size, + } + } + + pub fn stream_count(&self) -> u32 { + match &self.msf { + Msf::Small(m) => m.num_streams, + Msf::Big(m) => m.num_streams, + } + } + + pub fn stream_size(&self, stream: u32) -> Result> { + match &self.msf { + Msf::Small(m) => m.stream_size(stream), + Msf::Big(m) => m.stream_size(stream), + } + } + + pub fn stream_chunks(&self, stream: u32) -> Result> { + match &self.msf { + Msf::Small(m) => m.stream_chunks(stream), + Msf::Big(m) => m.stream_chunks(stream), + } + } + + pub fn read_stream(&self, stream: u32, dst: &mut [u8]) -> Result { + let mut written = 0; + for chunk in self.stream_chunks(stream)? { + let chunk = chunk?; + if written == dst.len() { + break; + } + let n = chunk.len().min(dst.len() - written); + dst[written..written + n].copy_from_slice(&chunk[..n]); + written += n; + } + Ok(written) + } + + /// Reads up to `dst.len()` bytes from `stream`, starting at byte `start` + /// within that stream. Returns the number of bytes written. + pub fn read_stream_at( + &self, + stream: u32, + start: u32, + dst: &mut [u8], + ) -> Result { + let mut written = 0usize; + let mut stream_pos: u32 = 0; + let mut remaining_start = start; + + for chunk in self.stream_chunks(stream)? { + let chunk = chunk?; + let chunk_len = chunk.len() as u32; + + // Skip chunks entirely before `start`. + if remaining_start >= chunk_len { + remaining_start -= chunk_len; + stream_pos += chunk_len; + continue; + } + + // Take the part of this chunk that's at or after `start`. + let chunk_start = remaining_start as usize; + let available = &chunk[chunk_start..]; + let take = available.len().min(dst.len() - written); + dst[written..written + take].copy_from_slice(&available[..take]); + written += take; + + if written == dst.len() { + break; + } + + stream_pos += chunk_len; + remaining_start = 0; + } + + Ok(written) + } +} diff --git a/src/noalloc/small.rs b/src/noalloc/small.rs new file mode 100644 index 0000000..1706b9c --- /dev/null +++ b/src/noalloc/small.rs @@ -0,0 +1,138 @@ +use core::convert::TryInto; + +use crate::noalloc::misc::*; +use crate::Result; + +#[derive(Debug)] +pub struct SmallMsf<'a> { + data: &'a [u8], + pub page_size: u32, + max_page: u32, + toc: &'a [u8], + pub num_streams: u32, +} + +impl<'a> SmallMsf<'a> { + const SMALL_ENTRY_SIZE: usize = 8; + + pub fn parse(data: &'a [u8]) -> Result { + if data.len() < SMALL_TOC_PAGES_OFFSET + 2 { + return Err(crate::Error::UnexpectedEof); + } + + let page_size = read_u32_at(data, SMALL_BLOCK_SIZE_OFFSET)?; + if page_size == 0 || !page_size.is_power_of_two() { + return Err(crate::Error::InvalidPageSize(page_size)); + } + + let max_page = read_u16_at(data, SMALL_TOTAL_ALLOC_OFFSET)? as u32; + let toc_size = read_u32_at(data, SMALL_TOC_SIZE_OFFSET)? as usize; + + let toc_pages_needed = pages_needed_u32(toc_size as u32, page_size); + + if toc_pages_needed == 0 { + return Ok(Self { + data, + page_size, + max_page, + toc: &[], + num_streams: 0, + }); + } + + if toc_pages_needed > 1 { + return Err(crate::Error::UnimplementedFeature( + "multi-page TOC", + )); + } + + let toc_page = read_u16_at(data, SMALL_TOC_PAGES_OFFSET)? as u32; + if toc_page == 0 || toc_page > max_page { + return Err(crate::Error::PageReferenceOutOfRange(toc_page)); + } + + let toc_start = toc_page as usize * page_size as usize; + let toc_end = toc_start + toc_size; + if toc_end > data.len() { + return Err(crate::Error::UnexpectedEof); + } + let toc = &data[toc_start..toc_end]; + + if toc.len() < 4 { + return Err(crate::Error::UnexpectedEof); + } + let num_streams = u32::from_le_bytes(toc[0..4].try_into().unwrap()); + + Ok(Self { + data, + page_size, + max_page, + toc, + num_streams, + }) + } + + pub fn stream_size(&self, stream: u32) -> Result> { + + if stream >= self.num_streams { + return Err(crate::Error::StreamNotFound(stream)); + } + + let off = 4 + stream as usize * Self::SMALL_ENTRY_SIZE; + + if off + 4 > self.toc.len() { + return Err(crate::Error::UnexpectedEof); + } + + let size = u32::from_le_bytes(self.toc[off..off + 4].try_into().unwrap()); + + if size == u32::MAX { + Ok(None) + } else { + Ok(Some(size)) + } + } + + fn pages_offset_for(&self, stream: u32) -> Result { + let mut offset = 4 + self.num_streams as usize * Self::SMALL_ENTRY_SIZE; + + for i in 0..stream { + let size_off = 4 + i as usize * Self::SMALL_ENTRY_SIZE; + + if size_off + 4 > self.toc.len() { + return Err(crate::Error::UnexpectedEof); + } + + let size = u32::from_le_bytes( + self.toc[size_off..size_off + 4].try_into().unwrap(), + ); + + if size != u32::MAX { + offset += pages_needed_u32(size, self.page_size) as usize * 2; + } + } + Ok(offset) + } + + pub fn stream_chunks(&self, stream: u32) -> Result> { + let size = self + .stream_size(stream)? + .ok_or(crate::Error::StreamNotFound(stream))?; + + if size == 0 { + return Ok(StreamChunks::empty(self.data)); + } + + Ok(StreamChunks { + data: self.data, + page_size: self.page_size, + source: PageSource::Small { + toc: self.toc, + pages_offset: self.pages_offset_for(stream)?, + }, + total_pages: pages_needed_u32(size, self.page_size), + current: 0, + remaining: size, + }) + } +} diff --git a/src/omap.rs b/src/omap.rs index 7b6326e..72e64a1 100644 --- a/src/omap.rs +++ b/src/omap.rs @@ -7,14 +7,21 @@ //! Utilities for translating addresses between PDB offsets and _Relative Virtual Addresses_ (RVAs). -use std::cmp::{self, Ordering}; -use std::fmt; -use std::iter::FusedIterator; -use std::mem; -use std::ops::Range; +use core::cmp::{self, Ordering}; +use core::fmt; +use core::iter::FusedIterator; +use core::mem; +use core::ops::Range; +use core::slice::Iter; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; use crate::common::*; + +#[cfg(feature = "alloc")] use crate::msf::Stream; + use crate::pe::ImageSectionHeader; /// A address translation record from an `OMAPTable`. @@ -118,14 +125,12 @@ impl Ord for OMAPRecord { /// binary search implementation seems appropriate. /// /// [module level documentation]: self -pub(crate) struct OMAPTable<'s> { - stream: Stream<'s>, -} +pub(crate) struct OMAPTable<'s>(Stream<'s>); impl<'s> OMAPTable<'s> { pub(crate) fn parse(stream: Stream<'s>) -> Result { match cast_aligned::(stream.as_slice()) { - Some(_) => Ok(OMAPTable { stream }), + Some(_) => Ok(OMAPTable(stream)), None => Err(Error::InvalidStreamLength("OMAP")), } } @@ -134,7 +139,7 @@ impl<'s> OMAPTable<'s> { #[inline] pub fn records(&self) -> &[OMAPRecord] { // alignment is checked during parsing, unwrap is safe. - cast_aligned(self.stream.as_slice()).unwrap() + cast_aligned(self.0.as_slice()).unwrap() } /// Look up `source_address` to yield a target address. @@ -193,7 +198,7 @@ impl fmt::Debug for OMAPTable<'_> { /// An iterator over mapped target ranges in an OMAP. pub(crate) struct RangeIter<'t> { /// Iterator over subsequent OMAP records. - records: std::slice::Iter<'t, OMAPRecord>, + records: Iter<'t, OMAPRecord>, /// The record that spans the current start address. record: OMAPRecord, /// The start address of the current subrange. @@ -387,6 +392,7 @@ impl FusedIterator for PdbInternalRvaRangeIter<'_> {} /// [Vulcan research project]: https://research.microsoft.com/pubs/69850/tr-2001-50.pdf /// [Microsoft Binary Technologies Projects]: https://microsoft.com/windows/cse/bit_projects.mspx /// [1997 reference material]: https://www.microsoft.com/msj/0597/hood0597.aspx +#[cfg(feature = "alloc")] #[derive(Debug, Default)] pub struct AddressMap<'s> { pub(crate) original_sections: Vec, @@ -395,6 +401,7 @@ pub struct AddressMap<'s> { pub(crate) original_to_transformed: Option>, } +#[cfg(feature = "alloc")] impl<'s> AddressMap<'s> { /// Resolves actual ranges in the executable's address space. /// @@ -443,6 +450,7 @@ fn get_virtual_address(sections: &[ImageSectionHeader], section: u16, offset: u3 .map(|section| section.virtual_address + offset) } +#[cfg(feature = "alloc")] impl Rva { /// Resolves a PDB-internal Relative Virtual Address. /// @@ -481,6 +489,7 @@ impl Rva { } } +#[cfg(feature = "alloc")] impl PdbInternalRva { /// Resolves an actual Relative Virtual Address in the executable's address space. pub fn to_rva(self, translator: &AddressMap<'_>) -> Option { @@ -511,6 +520,7 @@ impl PdbInternalRva { } } +#[cfg(feature = "alloc")] impl SectionOffset { /// Resolves an actual Relative Virtual Address in the executable's address space. pub fn to_rva(self, translator: &AddressMap<'_>) -> Option { @@ -546,6 +556,7 @@ impl SectionOffset { } } +#[cfg(feature = "alloc")] impl PdbInternalSectionOffset { /// Resolves an actual Relative Virtual Address in the executable's address space. pub fn to_rva(self, translator: &AddressMap<'_>) -> Option { @@ -573,9 +584,10 @@ impl PdbInternalSectionOffset { } } -#[cfg(test)] +#[cfg(all(test, feature = "alloc"))] mod tests { use super::*; + use alloc::vec; #[test] fn test_omap_record() { diff --git a/src/pdb.rs b/src/pdb.rs index b92f671..d8e12c6 100644 --- a/src/pdb.rs +++ b/src/pdb.rs @@ -1,40 +1,62 @@ -// Copyright 2017 pdb Developers +// Copyright 2026 PDB Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + use crate::common::*; + +use crate::constants::*; + +#[cfg(feature = "alloc")] use crate::dbi::{DBIExtraStreams, DBIHeader, DebugInformation, Module}; + +#[cfg(feature = "alloc")] use crate::framedata::FrameTable; + +#[cfg(feature = "alloc")] use crate::modi::ModuleInfo; -use crate::msf::{self, Msf, Stream}; + +#[cfg(feature = "alloc")] +use crate::msf::{self, Msf, MsfImpl, MsfKind, Stream}; + +#[cfg(feature = "alloc")] use crate::omap::{AddressMap, OMAPTable}; + +#[cfg(feature = "alloc")] use crate::pdbi::PDBInformation; + use crate::pe::ImageSectionHeader; + +#[cfg(feature = "alloc")] use crate::source::Source; + +#[cfg(feature = "alloc")] use crate::strings::StringTable; -use crate::symbol::SymbolTable; -use crate::tpi::{IdInformation, TypeInformation}; -// Some streams have a fixed stream index. -// http://llvm.org/docs/PDB/index.html +#[cfg(feature = "alloc")] +use crate::symbol::SymbolTable; -const PDB_STREAM: u32 = 1; -const TPI_STREAM: u32 = 2; -const DBI_STREAM: u32 = 3; -const IPI_STREAM: u32 = 4; +#[cfg(feature = "alloc")] +use crate::tpi::{IdInformation, TypeInformation}; /// `PDB` provides access to the data within a PDB file. /// /// A PDB file is internally a Multi-Stream File (MSF), composed of multiple independent /// (and usually discontiguous) data streams on-disk. `PDB` provides lazy access to these data /// structures, which means the `PDB` accessor methods usually cause disk accesses. +#[cfg(feature = "alloc")] #[derive(Debug)] -pub struct PDB<'s, S> { +pub struct PDB<'s, S: Source<'s>> { /// `msf` provides access to the underlying data streams - msf: Box + 's>, + msf: Msf<'s, S>, /// Memoize the `dbi::Header`, since it contains stream numbers we sometimes need dbi_header: Option, @@ -43,6 +65,7 @@ pub struct PDB<'s, S> { dbi_extra_streams: Option, } +#[cfg(feature = "alloc")] impl<'s, S: Source<'s> + 's> PDB<'s, S> { /// Create a new `PDB` for a `Source`. /// @@ -58,12 +81,67 @@ impl<'s, S: Source<'s> + 's> PDB<'s, S> { /// * `Error::PageReferenceOutOfRange`, `Error::InvalidPageSize` if the PDB file seems corrupt pub fn open(source: S) -> Result> { Ok(PDB { - msf: msf::open_msf(source)?, + msf: Msf::open(source)?, dbi_header: None, dbi_extra_streams: None, }) } + /// Opens a PDB file and eagerly parses the DBI stream. + /// + /// This is a convenience wrapper around [`Self::open`] that additionally + /// reads and parses the Debug Information (DBI) stream and caches the + /// [`DBIHeader`] and [`DBIExtraStreams`] immediately. + pub fn open_eager(source: S) -> Result> { + let mut msf = Msf::open(source)?; + let stream = msf.get(DBI_STREAM, None)?; + let (dbi_header, dbi_extra_streams) = match DebugInformation::parse(stream) { + Ok(debug_info) => { + let dbi_header = Some(debug_info.header()); + let dbi_extra_streams = DBIExtraStreams::new(&debug_info).ok(); + (dbi_header, dbi_extra_streams) + }, + Err(_) => (None, None), + }; + + Ok(PDB { + msf, + dbi_header, + dbi_extra_streams, + }) + } + + /// Returns the MSF format kind (Big or Small). + pub fn msf_kind(&self) -> MsfKind { + self.msf.kind() + } + + /// Returns the total number of streams in this PDB, including nil streams. + /// + /// Stream 0 is the old stream directory (unused in Big MSF). Streams 1-4 are + /// reserved by the PDB format itself (see [`PDB_STREAM`], [`TPI_STREAM`], + /// [`DBI_STREAM`], [`IPI_STREAM`]). Streams 5+ are named streams. + pub fn stream_count(&mut self) -> Result { + self.msf.stream_count() + } + + /// Returns `true` if the stream exists and is not a nil stream. + pub fn has_stream(&mut self, stream_number: u32) -> Result { + self.msf.has_stream(stream_number) + } + + /// Returns the size in bytes of the given stream, or `None` if the + /// stream is a nil stream. + /// + /// # Errors + /// + /// * `Error::StreamNotFound` if the PDB does not contain this stream + /// * `Error::IoError` if returned by the `Source` + /// * `Error::PageReferenceOutOfRange` if the PDB file seems corrupt + pub fn stream_size(&mut self, stream: u32) -> Result> { + self.msf.stream_size(stream) + } + /// Retrieve the `PDBInformation` for this PDB. /// /// The `PDBInformation` object contains the GUID and age fields that can be used to verify @@ -91,6 +169,7 @@ impl<'s, S: Source<'s> + 's> PDB<'s, S> { /// * `Error::PageReferenceOutOfRange` if the PDB file seems corrupt /// * `Error::InvalidTypeInformationHeader` if the type information stream header was not /// understood + #[cfg(feature = "alloc")] pub fn type_information(&mut self) -> Result> { let stream = self.msf.get(TPI_STREAM, None)?; TypeInformation::parse(stream) @@ -126,7 +205,6 @@ impl<'s, S: Source<'s> + 's> PDB<'s, S> { let stream = self.msf.get(DBI_STREAM, None)?; let debug_info = DebugInformation::parse(stream)?; - // Grab its header, since we need that for unrelated operations self.dbi_header = Some(debug_info.header()); Ok(debug_info) } @@ -137,7 +215,6 @@ impl<'s, S: Source<'s> + 's> PDB<'s, S> { return Ok(*h); } - // get just the first little bit of the DBI stream let stream = self.msf.get(DBI_STREAM, Some(1024))?; let header = DBIHeader::parse(stream)?; @@ -384,7 +461,7 @@ impl<'s, S: Source<'s> + 's> PDB<'s, S> { /// ``` pub fn address_map(&mut self) -> Result> { let sections = self.sections()?.unwrap_or_default(); - Ok(match self.original_sections()? { + let address_map = match self.original_sections()? { Some(original_sections) => { let omap_from_src = self.omap_from_src()?.ok_or(Error::AddressMapNotFound)?; let omap_to_src = self.omap_to_src()?.ok_or(Error::AddressMapNotFound)?; @@ -402,7 +479,8 @@ impl<'s, S: Source<'s> + 's> PDB<'s, S> { original_to_transformed: None, transformed_to_original: None, }, - }) + }; + Ok(address_map) } /// Retrieve the global string table of this PDB. @@ -443,6 +521,7 @@ impl<'s, S: Source<'s> + 's> PDB<'s, S> { /// * `Error::IoError` if returned by the `Source` /// * `Error::PageReferenceOutOfRange` if the PDB file seems corrupt /// * `Error::UnexpectedEof` if the string table ends prematurely + #[cfg(feature = "alloc")] pub fn string_table(&mut self) -> Result> { let stream = self.named_stream(b"/names")?; StringTable::parse(stream) @@ -483,6 +562,7 @@ impl<'s, S: Source<'s> + 's> PDB<'s, S> { /// * `Error::StreamNotFound` if the PDB does not contain the stream referred to /// * `Error::IoError` if returned by the `Source` /// * `Error::PageReferenceOutOfRange` if the PDB file seems corrupt + #[cfg(feature = "alloc")] pub fn named_stream(&mut self, name: &[u8]) -> Result> { let info = self.pdb_information()?; let names = info.stream_names()?; @@ -516,6 +596,7 @@ impl<'s, S: Source<'s> + 's> PDB<'s, S> { } } +#[cfg(feature = "alloc")] impl StreamIndex { /// Load the raw data of this stream from the PDB. /// diff --git a/src/pdbi.rs b/src/pdbi.rs index 542842a..c382098 100644 --- a/src/pdbi.rs +++ b/src/pdbi.rs @@ -1,19 +1,90 @@ -// Copyright 2017 pdb Developers +// Copyright 2026 PDB Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. -use std::convert::TryInto; -use std::mem; +//! PDB Information Stream parsing. +//! +//! The PDB information stream (stream 1) contains metadata about the PDB file, +//! including the GUID, age, and a map of named streams. +//! +//! # Stream Names Map Format +//! +//! The names map provides a mapping from stream names to stream indices. +//! It consists of a block of data comprising the names as null-terminated C strings, +//! followed by a map of stream indices to the offset of their names within the names block. +//! +//! The map itself is stored as: +//! - A 32-bit count of the number of entries +//! - A 32-bit value giving the number of bytes taken up by the entries +//! - Two sets: one for names that are present, and one for names that have been deleted +//! - The map entries, each a pair of 32-bit values: offset into names block and stream ID +//! +//! The two sets are each stored as a bit array, which consists of a 32-bit count, +//! followed by that many 32-bit words containing the bits in the array. +//! +//! ## References +//! +//! - [nmtni.h](https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/include/nmtni.h#L76) +//! - [map.h](https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/include/map.h#L474) +//! - [iset.h](https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/include/iset.h#L62) +//! - [array.h](https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/include/array.h#L209) + +use core::convert::TryInto; +use core::mem; +use core::slice::Iter; + +#[cfg(feature = "alloc")] +use alloc::vec; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; use uuid::Uuid; use crate::common::*; -use crate::dbi::HeaderVersion; + +#[cfg(feature = "alloc")] +use crate::dbi::PdbHeaderVersion; + +#[cfg(feature = "alloc")] use crate::msf::*; +/// A 32-bit timestamp written into the PDB info stream when the file was written. +/// +/// Microsoft's PDB writer sets this field to the result of `time()` at the +/// moment the file was created, so it usually holds a Unix timestamp (seconds +/// since 1970-01-01 UTC). It is not a magic value and it does not reliably +/// identify a specific PDB: two PDBs written in the same second collide, which +/// is part of why the GUID was introduced in VC7. +/// +/// Prefer matching a PDB to an image by GUID + age when both are present. Use +/// the signature only as a coarse sanity check, or when reading PDBs from VC6 +/// and earlier that predate the GUID field. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PDBSignature(u32); + +impl PDBSignature { + /// Creates a `PDBSignature` from the raw 32-bit value + pub const fn new(value: u32) -> Self { + Self(value) + } + + /// Decodes the signature as a UTC date-time. + #[cfg(feature = "chrono")] + pub const fn to_datetime(self) -> Option> { + chrono::DateTime::from_timestamp(self.0 as i64, 0) + } + + /// Formats the signature as an RFC 3339 string. + #[cfg(all(feature = "chrono", feature = "alloc"))] + pub fn to_rfc3339(self) -> Option { + self.to_datetime().map(|dt| dt.to_rfc3339()) + } +} + /// A PDB info stream header parsed from a stream. /// /// The [PDB information stream] contains the GUID and age fields that can be used to @@ -21,12 +92,17 @@ use crate::msf::*; /// with their stream indices. /// /// [PDB information stream]: http://llvm.org/docs/PDB/PdbStream.html +#[cfg(feature = "alloc")] #[derive(Debug)] pub struct PDBInformation<'s> { + stream: Stream<'s>, + /// The version of the PDB format in use. - pub version: HeaderVersion, + pub version: PdbHeaderVersion, + /// A 32-bit timestamp. - pub signature: u32, + pub signature: PDBSignature, + /// The number of times this PDB file has been written. /// /// This number is bumped by the linker and other tools every time the PDB is modified. It does @@ -36,42 +112,47 @@ pub struct PDBInformation<'s> { /// This PDB matches an image, if the `guid` values match and the PDB age is equal or higher /// than the image's age. pub age: u32, + /// A `Uuid` generated when this PDB file was created that should uniquely identify it. - pub guid: Uuid, + /// + /// `None` for PDBs predating VC7 (info stream version < `20000404`), + /// which do not include a GUID field. See [`PdbHeaderVersion::has_guid`]. + pub guid: Option, + /// The offset of the start of the stream name data within the stream. pub names_offset: usize, + /// The size of the stream name data, in bytes. pub names_size: usize, - stream: Stream<'s>, } +#[cfg(feature = "alloc")] impl<'s> PDBInformation<'s> { /// Parses a `PDBInformation` from raw stream data. pub(crate) fn parse(stream: Stream<'s>) -> Result { - let (version, signature, age, guid, names_size, names_offset) = { - let mut buf = stream.parse_buffer(); - let version = From::from(buf.parse_u32()?); - let signature = buf.parse_u32()?; - let age = buf.parse_u32()?; - let guid = Uuid::from_fields( - buf.parse_u32()?, - buf.parse_u16()?, - buf.parse_u16()?, - buf.take(8)?.try_into().unwrap(), - ); - let names_size = buf.parse_u32()? as usize; - let names_offset = buf.pos(); - (version, signature, age, guid, names_size, names_offset) + let mut buf = stream.parse_buffer(); + + let version = PdbHeaderVersion::from(buf.parse_u32()?); + let signature = PDBSignature(buf.parse_u32()?); + let age = buf.parse_u32()?; + + let guid = if version.has_guid() { + Some(Uuid::from_bytes_le(buf.take_array()?)) + } else { + None }; + let names_size = buf.parse_u32()? as usize; + let names_offset = buf.pos(); + Ok(PDBInformation { + stream, version, signature, age, guid, names_size, names_offset, - stream, }) } @@ -96,43 +177,24 @@ impl<'s> PDBInformation<'s> { /// # Ok(()) /// # } /// ``` + #[cfg(feature = "alloc")] pub fn stream_names(&self) -> Result> { - // The names map is part of the PDB info stream that provides a mapping from stream names to - // stream indicies. Its [format on disk](1) is somewhat complicated, consisting of a block of - // data comprising the names as null-terminated C strings, followed by a map of stream indices - // to the offset of their names within the names block. - // - // [The map itself](2) is stored as a 32-bit count of the number of entries, followed by a - // 32-bit value that gives the number of bytes taken up by the entries themselves, followed by - // two sets: one for names that are present in this PDB, and one for names that have been - // deleted, followed by the map entries, each of which is a pair of 32-bit values consisting of - // an offset into the names block and a stream ID. - // - // [The two sets](3) are each stored as a [bit array](4), which consists of a 32-bit count, and - // then that many 32-bit words containing the bits in the array. - // - // [1]: https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/include/nmtni.h#L76 - // [2]: https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/include/map.h#L474 - // [3]: https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/include/iset.h#L62 - // [4]: https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/include/array.h#L209 - + let mut names = vec![]; let mut buf = self.stream.parse_buffer(); - // Seek forward to the name map. buf.take(self.names_offset + self.names_size)?; let count = buf.parse_u32()?; - // We don't actually use most of these. + let _entries_size = buf.parse_u32()?; let ok_words = buf.parse_u32()?; let _ok_bits = buf.take(ok_words as usize * mem::size_of::())?; let deleted_words = buf.parse_u32()?; let _deleted_bits = buf.take(deleted_words as usize * mem::size_of::())?; - // Skip over the header here. let mut names_reader = self.stream.parse_buffer(); names_reader.take(self.names_offset)?; - // And take just the name data. + let names_buf = names_reader.take(self.names_size)?; for _ in 0..count { let name_offset = buf.parse_u32()? as usize; @@ -141,7 +203,32 @@ impl<'s> PDBInformation<'s> { names.push(StreamName { name, stream_id }); } - Ok(StreamNames { names }) + Ok(StreamNames(names.into())) + } + + /// Returns a lazy iterator over named streams + #[cfg(feature = "alloc")] + pub fn stream_names_iter(&self) -> Result> { + let mut buf = self.stream.parse_buffer(); + + buf.take(self.names_offset + self.names_size)?; + let count = buf.parse_u32()?; + let _entries_size = buf.parse_u32()?; + let ok_words = buf.parse_u32()?; + let _ok_bits = buf.take(ok_words as usize * mem::size_of::())?; + let deleted_words = buf.parse_u32()?; + let _deleted_bits = buf.take(deleted_words as usize * mem::size_of::())?; + + let mut names_reader = self.stream.parse_buffer(); + names_reader.take(self.names_offset)?; + let names_buf = names_reader.take(self.names_size)?; + + Ok(StreamNamesIter { + buf, + names_buf, + count, + index: 0, + }) } } @@ -154,33 +241,82 @@ pub struct StreamName<'n> { pub stream_id: StreamIndex, } +/// A lazy iterator over named streams in the PDB. +/// +/// This iterator parses stream names on-demand without allocating a Vec. +#[cfg(feature = "alloc")] +#[derive(Debug)] +pub struct StreamNamesIter<'s> { + /// The buffer containing the map entries + buf: ParseBuffer<'s>, + /// The buffer containing the name data + names_buf: &'s [u8], + /// Number of entries to iterate over + count: u32, + /// Current index + index: u32, +} + +#[cfg(feature = "alloc")] +impl<'s> StreamNamesIter<'s> { + /// Returns the next stream name, or `None` if all entries have been consumed. + /// + /// # Errors + /// + /// Returns an error if the data is malformed. + fn next_inner(&mut self) -> Result> { + let name_offset = self.buf.parse_u32()? as usize; + let stream_id = StreamIndex(self.buf.parse_u32()? as u16); + let name = ParseBuffer::from(&self.names_buf[name_offset..]).parse_cstring()?; + Ok(StreamName { name, stream_id }) + } +} + +#[cfg(feature = "alloc")] +impl<'s> Iterator for StreamNamesIter<'s> { + type Item = Result>; + + fn next(&mut self) -> Option { + if self.index >= self.count { + return None; + } + self.index += 1; + Some(self.next_inner()) + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = (self.count - self.index) as usize; + (remaining, Some(remaining)) + } +} + /// A list of named streams contained within the PDB file. /// /// Call [`StreamNames::iter`] to iterate over the names. The iterator produces [`StreamName`] /// objects. +#[cfg(feature = "alloc")] #[derive(Debug)] -pub struct StreamNames<'s> { - /// The list of streams and their names. - names: Vec>, -} +pub struct StreamNames<'s>(Box<[StreamName<'s>]>); /// An iterator over [`StreamName`]s. -pub type NameIter<'a, 'n> = std::slice::Iter<'a, StreamName<'n>>; +pub type NameIter<'a, 'n> = Iter<'a, StreamName<'n>>; +#[cfg(feature = "alloc")] impl<'s> StreamNames<'s> { /// Return an iterator over named streams and their stream indices. #[inline] pub fn iter(&self) -> NameIter<'_, 's> { - self.names.iter() + self.0.iter() } } +#[cfg(feature = "alloc")] impl<'a, 's> IntoIterator for &'a StreamNames<'s> { type Item = &'a StreamName<'s>; type IntoIter = NameIter<'a, 's>; #[inline] fn into_iter(self) -> Self::IntoIter { - self.names.iter() + self.0.iter() } } diff --git a/src/pe.rs b/src/pe.rs index d59cfc3..3c68f84 100644 --- a/src/pe.rs +++ b/src/pe.rs @@ -9,7 +9,7 @@ // PDBs contain PE section headers in one or two streams. `pdb::pe` is responsible for parsing them. -use std::fmt; +use core::fmt; use scroll::ctx::TryFromCtx; use scroll::Endian; @@ -346,16 +346,7 @@ impl ImageSectionHeader { let name_bytes = parse_buffer.take(8)?; Ok(Self { - name: [ - name_bytes[0], - name_bytes[1], - name_bytes[2], - name_bytes[3], - name_bytes[4], - name_bytes[5], - name_bytes[6], - name_bytes[7], - ], + name: core::array::from_fn(|i| name_bytes[i]), virtual_size: parse_buffer.parse_u32()?, virtual_address: parse_buffer.parse_u32()?, size_of_raw_data: parse_buffer.parse_u32()?, @@ -378,7 +369,7 @@ impl ImageSectionHeader { // The spec guarantees that the name is a proper UTF-8 string. // TODO: Look up long names from the string table. - std::str::from_utf8(&self.name[0..end]).unwrap_or("") + core::str::from_utf8(&self.name[0..end]).unwrap_or("") } } @@ -411,10 +402,12 @@ impl fmt::Debug for ImageSectionHeader { } } -#[cfg(test)] +#[cfg(all(test, feature = "alloc"))] mod tests { use super::*; - + use alloc::vec::Vec; + use alloc::vec; + #[test] fn test_section_characteristics() { let bytes: Vec = vec![0x40, 0x00, 0x00, 0xC8]; diff --git a/src/source.rs b/src/source.rs index 69dae13..b977126 100644 --- a/src/source.rs +++ b/src/source.rs @@ -1,12 +1,22 @@ -// Copyright 2017 pdb Developers +// Copyright 2026 PDB Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. -use std::fmt; -use std::io; +use core::{fmt, ops::Deref}; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +use crate::io::{IoError, Read, Seek, SeekFrom}; + +#[cfg(feature = "alloc")] +use crate::msf::PageList; /// Represents an offset + size of the source file. /// @@ -45,71 +55,174 @@ pub struct SourceSlice { /// /// PDB files are specified as always being a multiple of the page size, so `Source` implementations /// are free to e.g. map whole pages and return a sub-slice of the requested length. -/// +#[cfg(feature = "alloc")] pub trait Source<'s>: fmt::Debug { + /// Provides a contiguous view of the source file composed of the requested position(s). /// /// Note that the SourceView's as_slice() method cannot fail, so `view()` is the time to raise /// IO errors. - fn view(&mut self, slices: &[SourceSlice]) -> Result>, io::Error>; + fn view(&mut self, slices: &[SourceSlice]) -> Result, IoError>; + + /// Provides a contiguous view of the pages in a [`PageList`]. + /// + /// This is a convenience wrapper around [`Self::view`] that accepts a + /// [`PageList`] instead of a raw slice of [`SourceSlice`]s, and additionally + /// verifies that the returned view matches the expected length. + #[inline] + fn view_pages(&mut self, page_list: &PageList) -> crate::Result> { + + let view = self.view(page_list.source_slices())?; + + assert_eq!(view.as_slice().len(), page_list.len()); + + Ok(view) + } +} + +#[cfg(feature = "alloc")] +#[derive(Debug)] +pub struct SourceView<'s>(Box>); + +#[cfg(feature = "alloc")] +impl Deref for SourceView<'_> { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &Self::Target { + self.0.as_slice() + } +} + +#[cfg(feature = "alloc")] +impl<'s> SourceView<'s> { + + /// Returns a view to the raw data. + #[inline] + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } } /// An owned, droppable, read-only view of the source file which can be referenced as a byte slice. -pub trait SourceView<'s>: fmt::Debug { +pub trait SourceViewImpl<'s>: fmt::Debug { /// Returns a view to the raw data. fn as_slice(&self) -> &[u8]; } +/// A read-only view backed by an owned `Vec`. +#[cfg(feature = "alloc")] #[derive(Clone)] -struct ReadView { - bytes: Vec, +pub struct ReadView(Vec); + +#[cfg(feature = "alloc")] +impl ReadView { + /// Creates a new `ReadView` with the specified capacity. + pub fn with_capacity(len: usize) -> Self { + Self(Vec::with_capacity(len)) + } + + /// Returns a mutable reference to the underlying buffer. + #[inline] + pub fn as_mut_slice(&mut self) -> &mut [u8] { + self.0.as_mut_slice() + } + + /// Resizes the underlying buffer to the specified length. + #[inline] + pub fn resize(&mut self, len: usize, value: u8) { + self.0.resize(len, value); + } } +#[cfg(feature = "alloc")] impl fmt::Debug for ReadView { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ReadView({} bytes)", self.bytes.len()) + write!(f, "ReadView({} bytes)", self.0.len()) } } -impl SourceView<'_> for ReadView { +#[cfg(feature = "alloc")] +impl SourceViewImpl<'_> for ReadView { + #[inline] fn as_slice(&self) -> &[u8] { - self.bytes.as_slice() + self.0.as_slice() } } +#[derive(Debug, Clone)] +pub struct MemorySource<'a>(&'a [u8]); + +impl<'a> MemorySource<'a> { + pub const fn new(data: &'a [u8]) -> Self { + Self(data) + } +} + +#[cfg(feature = "alloc")] +impl<'a> Source<'a> for MemorySource<'a> { + + #[inline] + fn view(&mut self, slices: &[SourceSlice]) -> Result, IoError> { + let len = slices.iter().fold(0, |acc, s| acc + s.size); + let mut view = ReadView::with_capacity(len); + view.resize(len, 0); + + let bytes = view.as_mut_slice(); + let mut output_offset = 0; + for slice in slices { + let start = slice.offset as usize; + let end = start + slice.size; + + if end > self.0.len() { + return Err(IoError::new( + crate::io::ErrorKind::UnexpectedEof, + "source data too short", + )); + } + + bytes[output_offset..(output_offset + slice.size)] + .copy_from_slice(&self.0[start..end]); + output_offset += slice.size; + } + + Ok(SourceView(Box::new(view))) + } +} + +#[cfg(feature = "alloc")] impl<'s, T> Source<'s> for T where - T: io::Read + io::Seek + fmt::Debug + 's, + T: Read + Seek + fmt::Debug + 's, { - fn view(&mut self, slices: &[SourceSlice]) -> Result>, io::Error> { + fn view(&mut self, slices: &[SourceSlice]) -> Result, IoError> { let len = slices.iter().fold(0, |acc, s| acc + s.size); - let mut v = ReadView { - bytes: Vec::with_capacity(len), - }; - v.bytes.resize(len, 0); + let mut v = ReadView::with_capacity(len); + v.resize(len, 0); { - let bytes = v.bytes.as_mut_slice(); + let bytes = v.as_mut_slice(); let mut output_offset: usize = 0; for slice in slices { - self.seek(io::SeekFrom::Start(slice.offset))?; + self.seek(SeekFrom::Start(slice.offset))?; self.read_exact(&mut bytes[output_offset..(output_offset + slice.size)])?; output_offset += slice.size; } } - Ok(Box::new(v)) + Ok(SourceView(Box::new(v))) } } -#[cfg(test)] +#[cfg(all(test, feature = "alloc"))] mod tests { mod read_view { - use crate::source::*; - use std::io::Cursor; - use std::io::ErrorKind; - + use crate::io::{Cursor, ErrorKind}; + use crate::*; + use alloc::boxed::Box; + use alloc::vec; + #[test] fn test_basic_reading() { let mut data = vec![0; 4096]; @@ -124,7 +237,7 @@ mod tests { let view = source .view(source_slices.as_slice()) .expect("viewing must succeed"); - assert_eq!(&[0u8, 0, 42, 0], view.as_slice()); + assert_eq!(&[0u8, 0, 42, 0], &*view); } #[test] @@ -148,7 +261,7 @@ mod tests { let view = source .view(source_slices.as_slice()) .expect("viewing must succeed"); - assert_eq!(&[88u8, 0, 0, 42, 0], view.as_slice()); + assert_eq!(&[88u8, 0, 0, 42, 0], &*view); } #[test] @@ -176,7 +289,7 @@ mod tests { let view = source .view(source_slices.as_slice()) .expect("viewing must succeed"); - assert_eq!(&[88u8, 0, 0, 42, 0, 88], view.as_slice()); + assert_eq!(&[88u8, 0, 0, 42, 0, 88], &*view); } #[test] diff --git a/src/strings.rs b/src/strings.rs index 2a11044..9ed6cb3 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -1,8 +1,13 @@ -use std::borrow::Cow; +#[cfg(feature = "alloc")] +use alloc::borrow::Cow; +#[cfg(feature = "alloc")] +use fallible_iterator::FallibleIterator; use scroll::{ctx::TryFromCtx, Endian, Pread}; use crate::common::*; + +#[cfg(feature = "alloc")] use crate::msf::Stream; /// Magic bytes identifying the string name table. @@ -64,7 +69,7 @@ impl<'t> TryFromCtx<'t, Endian> for StringTableHeader { impl StringTableHeader { /// Start index of the names buffer in the string table stream. fn names_start(self) -> usize { - std::mem::size_of::() + core::mem::size_of::() } /// End index of the names buffer in the string table stream. @@ -80,6 +85,7 @@ impl StringTableHeader { /// The mapping from string to offset has not been implemented yet. /// /// Use [`PDB::string_table`](crate::PDB::string_table) to obtain an instance. +#[cfg(feature = "alloc")] #[derive(Debug)] pub struct StringTable<'s> { header: StringTableHeader, @@ -88,6 +94,7 @@ pub struct StringTable<'s> { stream: Stream<'s>, } +#[cfg(feature = "alloc")] impl<'s> StringTable<'s> { pub(crate) fn parse(stream: Stream<'s>) -> Result { let mut buf = stream.parse_buffer(); @@ -121,8 +128,22 @@ impl<'s> StringTable<'s> { stream, }) } + + /// Returns an iterator over all strings in the table. + /// + /// Each item is `(StringRef, RawString)`: the offset of the string within + /// the names buffer, and the string itself. + pub fn iter(&self) -> Result> { + let names = &self.stream.as_slice() + [self.header.names_start()..self.header.names_end()]; + Ok(StringTableIter { + buf: ParseBuffer::from(names), + offset: 0, + }) + } } +#[cfg(feature = "alloc")] impl<'s> StringTable<'s> { /// Resolves a string value from this string table. /// @@ -138,6 +159,7 @@ impl<'s> StringTable<'s> { } } +#[cfg(feature = "alloc")] impl StringRef { /// Resolves the raw string value of this reference. /// @@ -151,16 +173,51 @@ impl StringRef { /// /// This method errors if the offset is out of bounds of the string table. Use /// [`PDB::string_table`](crate::PDB::string_table) to obtain an instance of the string table. + #[cfg(feature = "alloc")] pub fn to_string_lossy<'s>(self, strings: &'s StringTable<'_>) -> Result> { strings.get(self).map(|r| r.to_string()) } } +/// A lazy iterator over the strings in a [`StringTable`]. +/// +/// The string table stores its names as a packed sequence of null-terminated +/// C-strings with no explicit count, so iteration walks the names buffer from +/// start to end. Strings are yielded in the order they appear, which is +/// usually (but not guaranteed to be) the order they were first interned. +/// +/// Each yielded item is paired with its [`StringRef`] offset, so callers can +/// hold a reference for later lookup instead of just the string value. +#[cfg(feature = "alloc")] +#[derive(Debug)] +pub struct StringTableIter<'s> { + /// Remaining bytes of the names buffer. + buf: ParseBuffer<'s>, + /// Offset of the next string, relative to the start of the names buffer. + offset: u32, +} + +#[cfg(feature = "alloc")] +impl<'s> FallibleIterator for StringTableIter<'s> { + type Item = (StringRef, RawString<'s>); + type Error = Error; + + fn next(&mut self) -> Result> { + if self.buf.is_empty() { + return Ok(None); + } + let start = self.offset; + let name = self.buf.parse_cstring()?; + self.offset += name.len() as u32 + 1; + Ok(Some((StringRef(start), name))) + } +} + #[cfg(test)] mod tests { use super::*; - use std::mem; + use core::mem; #[test] fn test_string_table_header() { diff --git a/src/symbol/annotations.rs b/src/symbol/annotations.rs index 508db4d..767140f 100644 --- a/src/symbol/annotations.rs +++ b/src/symbol/annotations.rs @@ -1,3 +1,10 @@ + +#[cfg(feature = "alloc")] +use alloc::vec; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + use crate::common::*; use crate::FallibleIterator; @@ -109,7 +116,7 @@ pub enum BinaryAnnotation { impl BinaryAnnotation { /// Does this annotation emit a line info? - pub fn emits_line_info(self) -> bool { + pub const fn emits_line_info(self) -> bool { matches!( self, BinaryAnnotation::ChangeCodeOffset(..) @@ -121,30 +128,29 @@ impl BinaryAnnotation { /// An iterator over binary annotations used by `S_INLINESITE`. #[derive(Clone, Debug, Default)] -pub struct BinaryAnnotationsIter<'t> { - buffer: ParseBuffer<'t>, -} +pub struct BinaryAnnotationsIter<'t>(ParseBuffer<'t>); impl<'t> BinaryAnnotationsIter<'t> { /// Parse a compact version of an unsigned integer. /// /// This implements `CVUncompressData`, which can decode numbers no larger than 0x1FFFFFFF. It /// seems that values compressed this way are only used for binary annotations at this point. + #[inline] fn uncompress_next(&mut self) -> Result { - let b1 = u32::from(self.buffer.parse::()?); + let b1 = u32::from(self.0.parse::()?); if (b1 & 0x80) == 0x00 { let value = b1; return Ok(value); } - let b2 = u32::from(self.buffer.parse::()?); + let b2 = u32::from(self.0.parse::()?); if (b1 & 0xc0) == 0x80 { let value = (b1 & 0x3f) << 8 | b2; return Ok(value); } - let b3 = u32::from(self.buffer.parse::()?); - let b4 = u32::from(self.buffer.parse::()?); + let b3 = u32::from(self.0.parse::()?); + let b4 = u32::from(self.0.parse::()?); if (b1 & 0xe0) == 0xc0 { let value = ((b1 & 0x1f) << 24) | (b2 << 16) | (b3 << 8) | b4; return Ok(value); @@ -155,7 +161,7 @@ impl<'t> BinaryAnnotationsIter<'t> { } /// Resembles `DecodeSignedInt32`. -fn decode_signed_operand(value: u32) -> i32 { +const fn decode_signed_operand(value: u32) -> i32 { if value & 1 != 0 { -((value >> 1) as i32) } else { @@ -168,7 +174,7 @@ impl<'t> FallibleIterator for BinaryAnnotationsIter<'t> { type Error = Error; fn next(&mut self) -> Result> { - if self.buffer.is_empty() { + if self.0.is_empty() { return Ok(None); } @@ -176,7 +182,7 @@ impl<'t> FallibleIterator for BinaryAnnotationsIter<'t> { let annotation = match BinaryAnnotationOpcode::parse(op)? { BinaryAnnotationOpcode::Eof => { // This makes the end of the stream - self.buffer = ParseBuffer::default(); + self.0 = ParseBuffer::default(); return Ok(None); } BinaryAnnotationOpcode::CodeOffset => { @@ -200,12 +206,8 @@ impl<'t> FallibleIterator for BinaryAnnotationsIter<'t> { BinaryAnnotationOpcode::ChangeLineEndDelta => { BinaryAnnotation::ChangeLineEndDelta(self.uncompress_next()?) } - BinaryAnnotationOpcode::ChangeRangeKind => { - BinaryAnnotation::ChangeRangeKind(self.uncompress_next()?) - } - BinaryAnnotationOpcode::ChangeColumnStart => { - BinaryAnnotation::ChangeColumnStart(self.uncompress_next()?) - } + BinaryAnnotationOpcode::ChangeRangeKind => BinaryAnnotation::ChangeRangeKind(self.uncompress_next()?), + BinaryAnnotationOpcode::ChangeColumnStart => BinaryAnnotation::ChangeColumnStart(self.uncompress_next()?), BinaryAnnotationOpcode::ChangeColumnEndDelta => BinaryAnnotation::ChangeColumnEndDelta( decode_signed_operand(self.uncompress_next()?), ), @@ -222,9 +224,7 @@ impl<'t> FallibleIterator for BinaryAnnotationsIter<'t> { self.uncompress_next()?, ) } - BinaryAnnotationOpcode::ChangeColumnEnd => { - BinaryAnnotation::ChangeColumnEnd(self.uncompress_next()?) - } + BinaryAnnotationOpcode::ChangeColumnEnd => BinaryAnnotation::ChangeColumnEnd(self.uncompress_next()?) }; Ok(Some(annotation)) @@ -238,75 +238,78 @@ impl<'t> FallibleIterator for BinaryAnnotationsIter<'t> { /// /// Binary annotations are primarily used as line programs for inline function calls. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] -pub struct BinaryAnnotations<'t> { - data: &'t [u8], -} +pub struct BinaryAnnotations<'t>(&'t [u8]); impl<'t> BinaryAnnotations<'t> { /// Creates a new instance of binary annotations. - pub(crate) fn new(data: &'t [u8]) -> Self { - BinaryAnnotations { data } + pub(crate) const fn new(data: &'t [u8]) -> Self { + BinaryAnnotations(data) } /// Iterates through binary annotations. pub fn iter(&self) -> BinaryAnnotationsIter<'t> { - BinaryAnnotationsIter { - buffer: ParseBuffer::from(self.data), - } + BinaryAnnotationsIter(ParseBuffer::from(self.0)) } } -#[test] -fn test_binary_annotation_iter() { - let inp = b"\x0b\x03\x06\n\x03\x08\x06\x06\x03-\x06\x08\x03\x07\x0br\x06\x06\x0c\x03\x07\x06\x0f\x0c\x06\x05\x00\x00"; - let annotations = BinaryAnnotations::new(inp) - .iter() - .collect::>() - .unwrap(); +#[cfg(all(test, feature = "alloc"))] +mod tests { + use fallible_iterator::FallibleIterator; + use crate::*; - assert_eq!( - annotations, - vec![ - BinaryAnnotation::ChangeCodeOffsetAndLineOffset(3, 0), - BinaryAnnotation::ChangeLineOffset(5), - BinaryAnnotation::ChangeCodeOffset(8), - BinaryAnnotation::ChangeLineOffset(3), - BinaryAnnotation::ChangeCodeOffset(45), - BinaryAnnotation::ChangeLineOffset(4), - BinaryAnnotation::ChangeCodeOffset(7), - BinaryAnnotation::ChangeCodeOffsetAndLineOffset(2, -3), - BinaryAnnotation::ChangeLineOffset(3), - BinaryAnnotation::ChangeCodeLengthAndCodeOffset(3, 7), - BinaryAnnotation::ChangeLineOffset(-7), - BinaryAnnotation::ChangeCodeLengthAndCodeOffset(6, 5) - ] - ); + #[test] + fn test_binary_annotation_iter() { + let inp = b"\x0b\x03\x06\n\x03\x08\x06\x06\x03-\x06\x08\x03\x07\x0br\x06\x06\x0c\x03\x07\x06\x0f\x0c\x06\x05\x00\x00"; + let annotations = BinaryAnnotations::new(inp) + .iter() + .collect::>() + .unwrap(); - let inp = b"\x03P\x06\x0e\x03\x0c\x06\x04\x032\x06\x06\x03T\x0b#\x0b\\\x0bC\x0b/\x06\x04\x0c-\t\x03;\x06\x1d\x0c\x05\x06\x00\x00"; - let annotations = BinaryAnnotations::new(inp) - .iter() - .collect::>() - .unwrap(); + assert_eq!( + annotations, + vec![ + BinaryAnnotation::ChangeCodeOffsetAndLineOffset(3, 0), + BinaryAnnotation::ChangeLineOffset(5), + BinaryAnnotation::ChangeCodeOffset(8), + BinaryAnnotation::ChangeLineOffset(3), + BinaryAnnotation::ChangeCodeOffset(45), + BinaryAnnotation::ChangeLineOffset(4), + BinaryAnnotation::ChangeCodeOffset(7), + BinaryAnnotation::ChangeCodeOffsetAndLineOffset(2, -3), + BinaryAnnotation::ChangeLineOffset(3), + BinaryAnnotation::ChangeCodeLengthAndCodeOffset(3, 7), + BinaryAnnotation::ChangeLineOffset(-7), + BinaryAnnotation::ChangeCodeLengthAndCodeOffset(6, 5) + ] + ); - assert_eq!( - annotations, - vec![ - BinaryAnnotation::ChangeCodeOffset(80), - BinaryAnnotation::ChangeLineOffset(7), - BinaryAnnotation::ChangeCodeOffset(12), - BinaryAnnotation::ChangeLineOffset(2), - BinaryAnnotation::ChangeCodeOffset(50), - BinaryAnnotation::ChangeLineOffset(3), - BinaryAnnotation::ChangeCodeOffset(84), - BinaryAnnotation::ChangeCodeOffsetAndLineOffset(3, 1), - BinaryAnnotation::ChangeCodeOffsetAndLineOffset(12, -2), - BinaryAnnotation::ChangeCodeOffsetAndLineOffset(3, 2), - BinaryAnnotation::ChangeCodeOffsetAndLineOffset(15, 1), - BinaryAnnotation::ChangeLineOffset(2), - BinaryAnnotation::ChangeCodeLengthAndCodeOffset(45, 9), - BinaryAnnotation::ChangeCodeOffset(59), - BinaryAnnotation::ChangeLineOffset(-14), - BinaryAnnotation::ChangeCodeLengthAndCodeOffset(5, 6), - ] - ); -} + let inp = b"\x03P\x06\x0e\x03\x0c\x06\x04\x032\x06\x06\x03T\x0b#\x0b\\\x0bC\x0b/\x06\x04\x0c-\t\x03;\x06\x1d\x0c\x05\x06\x00\x00"; + let annotations = BinaryAnnotations::new(inp) + .iter() + .collect::>() + .unwrap(); + + assert_eq!( + annotations, + vec![ + BinaryAnnotation::ChangeCodeOffset(80), + BinaryAnnotation::ChangeLineOffset(7), + BinaryAnnotation::ChangeCodeOffset(12), + BinaryAnnotation::ChangeLineOffset(2), + BinaryAnnotation::ChangeCodeOffset(50), + BinaryAnnotation::ChangeLineOffset(3), + BinaryAnnotation::ChangeCodeOffset(84), + BinaryAnnotation::ChangeCodeOffsetAndLineOffset(3, 1), + BinaryAnnotation::ChangeCodeOffsetAndLineOffset(12, -2), + BinaryAnnotation::ChangeCodeOffsetAndLineOffset(3, 2), + BinaryAnnotation::ChangeCodeOffsetAndLineOffset(15, 1), + BinaryAnnotation::ChangeLineOffset(2), + BinaryAnnotation::ChangeCodeLengthAndCodeOffset(45, 9), + BinaryAnnotation::ChangeCodeOffset(59), + BinaryAnnotation::ChangeLineOffset(-14), + BinaryAnnotation::ChangeCodeLengthAndCodeOffset(5, 6), + ] + ); + } + +} \ No newline at end of file diff --git a/src/symbol/constants.rs b/src/symbol/constants.rs index dd8f9ac..634b07e 100644 --- a/src/symbol/constants.rs +++ b/src/symbol/constants.rs @@ -1,4 +1,4 @@ -// Copyright 2017 pdb Developers +// Copyright 2026 PDB Developers // // Licensed under the Apache License, Version 2.0, or the MIT license or the MIT license , at your option. This file may not be -// copied, modified, or distributed except according to those terms. - -use std::fmt; - -use scroll::{ctx::TryFromCtx, Endian, Pread, LE}; - -use crate::common::*; -use crate::msf::*; -use crate::FallibleIterator; - +mod types; mod annotations; mod constants; -use self::constants::*; +pub use types::*; pub use self::constants::{CPUType, SourceLanguage}; pub use self::annotations::*; -/// The raw type discriminator for `Symbols`. -pub type SymbolKind = u16; - -/// Represents a symbol from the symbol table. -/// -/// A `Symbol` is represented internally as a `&[u8]`, and in general the bytes inside are not -/// inspected in any way before calling any of the accessor methods. -/// -/// To avoid copying, `Symbol`s exist as references to data owned by the parent `SymbolTable`. -/// Therefore, a `Symbol` may not outlive its parent `SymbolTable`. -#[derive(Copy, Clone, PartialEq)] -pub struct Symbol<'t> { - index: SymbolIndex, - data: &'t [u8], -} - -impl<'t> Symbol<'t> { - /// The index of this symbol in the containing symbol stream. - #[inline] - pub fn index(&self) -> SymbolIndex { - self.index - } - - /// Returns the kind of symbol identified by this Symbol. - #[inline] - pub fn raw_kind(&self) -> SymbolKind { - debug_assert!(self.data.len() >= 2); - self.data.pread_with(0, LE).unwrap_or_default() - } - - /// Returns the raw bytes of this symbol record, including the symbol type and extra data, but - /// not including the preceding symbol length indicator. - #[inline] - pub fn raw_bytes(&self) -> &'t [u8] { - self.data - } - - /// Parse the symbol into the `SymbolData` it contains. - #[inline] - pub fn parse(&self) -> Result> { - self.raw_bytes().pread_with(0, ()) - } - - /// Returns whether this symbol starts a scope. - /// - /// If `true`, this symbol has a `parent` and an `end` field, which contains the offset of the - /// corrsponding end symbol. - pub fn starts_scope(&self) -> bool { - matches!( - self.raw_kind(), - S_GPROC16 - | S_GPROC32 - | S_GPROC32_ST - | S_GPROCMIPS - | S_GPROCMIPS_ST - | S_GPROCIA64 - | S_GPROCIA64_ST - | S_LPROC16 - | S_LPROC32 - | S_LPROC32_ST - | S_LPROC32_DPC - | S_LPROCMIPS - | S_LPROCMIPS_ST - | S_LPROCIA64 - | S_LPROCIA64_ST - | S_LPROC32_DPC_ID - | S_GPROC32_ID - | S_GPROCMIPS_ID - | S_GPROCIA64_ID - | S_BLOCK16 - | S_BLOCK32 - | S_BLOCK32_ST - | S_WITH16 - | S_WITH32 - | S_WITH32_ST - | S_THUNK16 - | S_THUNK32 - | S_THUNK32_ST - | S_SEPCODE - | S_GMANPROC - | S_GMANPROC_ST - | S_LMANPROC - | S_LMANPROC_ST - | S_INLINESITE - | S_INLINESITE2 - ) - } - - /// Returns whether this symbol declares the end of a scope. - pub fn ends_scope(&self) -> bool { - matches!(self.raw_kind(), S_END | S_PROC_ID_END | S_INLINESITE_END) - } -} - -impl<'t> fmt::Debug for Symbol<'t> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Symbol{{ kind: 0x{:x} [{} bytes] }}", - self.raw_kind(), - self.data.len() - ) - } -} - -fn parse_symbol_name<'t>(buf: &mut ParseBuffer<'t>, kind: SymbolKind) -> Result> { - if kind < S_ST_MAX { - // Pascal-style name - buf.parse_u8_pascal_string() - } else { - // NUL-terminated name - buf.parse_cstring() - } -} - -fn parse_optional_name<'t>( - buf: &mut ParseBuffer<'t>, - kind: SymbolKind, -) -> Result>> { - if kind < S_ST_MAX { - // ST variants do not specify a name - Ok(None) - } else { - // NUL-terminated name - buf.parse_cstring().map(Some) - } -} - -fn parse_optional_index(buf: &mut ParseBuffer<'_>) -> Result> { - Ok(match buf.parse()? { - SymbolIndex(0) => None, - index => Some(index), - }) -} - -// data types are defined at: -// https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L3038 -// constants defined at: -// https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2735 -// decoding reference: -// https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/cvdump/dumpsym7.cpp#L264 - -/// Information parsed from a [`Symbol`] record. -#[non_exhaustive] -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum SymbolData<'t> { - /// End of a scope, such as a procedure. - ScopeEnd, - /// Name of the object file of this module. - ObjName(ObjNameSymbol<'t>), - /// A Register variable. - RegisterVariable(RegisterVariableSymbol<'t>), - /// A constant value. - Constant(ConstantSymbol<'t>), - /// A user defined type. - UserDefinedType(UserDefinedTypeSymbol<'t>), - /// A Register variable spanning multiple registers. - MultiRegisterVariable(MultiRegisterVariableSymbol<'t>), - /// Static data, such as a global variable. - Data(DataSymbol<'t>), - /// A public symbol with a mangled name. - Public(PublicSymbol<'t>), - /// A procedure, such as a function or method. - Procedure(ProcedureSymbol<'t>), - /// A thread local variable. - ThreadStorage(ThreadStorageSymbol<'t>), - /// Flags used to compile a module. - CompileFlags(CompileFlagsSymbol<'t>), - /// A using namespace directive. - UsingNamespace(UsingNamespaceSymbol<'t>), - /// Reference to a [`ProcedureSymbol`]. - ProcedureReference(ProcedureReferenceSymbol<'t>), - /// Reference to an imported variable. - DataReference(DataReferenceSymbol<'t>), - /// Reference to an annotation. - AnnotationReference(AnnotationReferenceSymbol<'t>), - /// Trampoline thunk. - Trampoline(TrampolineSymbol), - /// An exported symbol. - Export(ExportSymbol<'t>), - /// A local symbol in optimized code. - Local(LocalSymbol<'t>), - /// Reference to build information. - BuildInfo(BuildInfoSymbol), - /// The callsite of an inlined function. - InlineSite(InlineSiteSymbol<'t>), - /// End of an inline callsite. - InlineSiteEnd, - /// End of a procedure. - ProcedureEnd, - /// A label. - Label(LabelSymbol<'t>), - /// A block. - Block(BlockSymbol<'t>), - /// Data allocated relative to a register. - RegisterRelative(RegisterRelativeSymbol<'t>), - /// A thunk. - Thunk(ThunkSymbol<'t>), - /// A block of separated code. - SeparatedCode(SeparatedCodeSymbol), -} - -impl<'t> SymbolData<'t> { - /// Returns the name of this symbol if it has one. - pub fn name(&self) -> Option> { - match self { - Self::ScopeEnd => None, - Self::ObjName(data) => Some(data.name), - Self::RegisterVariable(_) => None, - Self::Constant(data) => Some(data.name), - Self::UserDefinedType(data) => Some(data.name), - Self::MultiRegisterVariable(_) => None, - Self::Data(data) => Some(data.name), - Self::Public(data) => Some(data.name), - Self::Procedure(data) => Some(data.name), - Self::ThreadStorage(data) => Some(data.name), - Self::CompileFlags(_) => None, - Self::UsingNamespace(data) => Some(data.name), - Self::ProcedureReference(data) => data.name, - Self::DataReference(data) => data.name, - Self::AnnotationReference(data) => Some(data.name), - Self::Trampoline(_) => None, - Self::Export(data) => Some(data.name), - Self::Local(data) => Some(data.name), - Self::InlineSite(_) => None, - Self::BuildInfo(_) => None, - Self::InlineSiteEnd => None, - Self::ProcedureEnd => None, - Self::Label(data) => Some(data.name), - Self::Block(data) => Some(data.name), - Self::RegisterRelative(data) => Some(data.name), - Self::Thunk(data) => Some(data.name), - Self::SeparatedCode(_) => None, - } - } -} - -impl<'t> TryFromCtx<'t> for SymbolData<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], _ctx: ()) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - let kind = buf.parse()?; - - let symbol = match kind { - S_END => SymbolData::ScopeEnd, - S_OBJNAME | S_OBJNAME_ST => SymbolData::ObjName(buf.parse_with(kind)?), - S_REGISTER | S_REGISTER_ST => SymbolData::RegisterVariable(buf.parse_with(kind)?), - S_CONSTANT | S_CONSTANT_ST | S_MANCONSTANT => { - SymbolData::Constant(buf.parse_with(kind)?) - } - S_UDT | S_UDT_ST | S_COBOLUDT | S_COBOLUDT_ST => { - SymbolData::UserDefinedType(buf.parse_with(kind)?) - } - S_MANYREG | S_MANYREG_ST | S_MANYREG2 | S_MANYREG2_ST => { - SymbolData::MultiRegisterVariable(buf.parse_with(kind)?) - } - S_LDATA32 | S_LDATA32_ST | S_GDATA32 | S_GDATA32_ST | S_LMANDATA | S_LMANDATA_ST - | S_GMANDATA | S_GMANDATA_ST => SymbolData::Data(buf.parse_with(kind)?), - S_PUB32 | S_PUB32_ST => SymbolData::Public(buf.parse_with(kind)?), - S_LPROC32 | S_LPROC32_ST | S_GPROC32 | S_GPROC32_ST | S_LPROC32_ID | S_GPROC32_ID - | S_LPROC32_DPC | S_LPROC32_DPC_ID => SymbolData::Procedure(buf.parse_with(kind)?), - S_LTHREAD32 | S_LTHREAD32_ST | S_GTHREAD32 | S_GTHREAD32_ST => { - SymbolData::ThreadStorage(buf.parse_with(kind)?) - } - S_COMPILE2 | S_COMPILE2_ST | S_COMPILE3 => { - SymbolData::CompileFlags(buf.parse_with(kind)?) - } - S_UNAMESPACE | S_UNAMESPACE_ST => SymbolData::UsingNamespace(buf.parse_with(kind)?), - S_PROCREF | S_PROCREF_ST | S_LPROCREF | S_LPROCREF_ST => { - SymbolData::ProcedureReference(buf.parse_with(kind)?) - } - S_TRAMPOLINE => Self::Trampoline(buf.parse_with(kind)?), - S_DATAREF | S_DATAREF_ST => SymbolData::DataReference(buf.parse_with(kind)?), - S_ANNOTATIONREF => SymbolData::AnnotationReference(buf.parse_with(kind)?), - S_EXPORT => SymbolData::Export(buf.parse_with(kind)?), - S_LOCAL => SymbolData::Local(buf.parse_with(kind)?), - S_BUILDINFO => SymbolData::BuildInfo(buf.parse_with(kind)?), - S_INLINESITE | S_INLINESITE2 => SymbolData::InlineSite(buf.parse_with(kind)?), - S_INLINESITE_END => SymbolData::InlineSiteEnd, - S_PROC_ID_END => SymbolData::ProcedureEnd, - S_LABEL32 | S_LABEL32_ST => SymbolData::Label(buf.parse_with(kind)?), - S_BLOCK32 | S_BLOCK32_ST => SymbolData::Block(buf.parse_with(kind)?), - S_REGREL32 => SymbolData::RegisterRelative(buf.parse_with(kind)?), - S_THUNK32 | S_THUNK32_ST => SymbolData::Thunk(buf.parse_with(kind)?), - S_SEPCODE => SymbolData::SeparatedCode(buf.parse_with(kind)?), - other => return Err(Error::UnimplementedSymbolKind(other)), - }; - - Ok((symbol, buf.pos())) - } -} - -/// A Register variable. -/// -/// Symbol kind `S_REGISTER`, or `S_REGISTER_ST` -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct RegisterVariableSymbol<'t> { - /// Identifier of the variable type. - pub type_index: TypeIndex, - /// The register this variable is stored in. - pub register: Register, - /// Name of the variable. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for RegisterVariableSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = RegisterVariableSymbol { - type_index: buf.parse()?, - register: buf.parse()?, - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// A Register variable spanning multiple registers. -/// -/// Symbol kind `S_MANYREG`, `S_MANYREG_ST`, `S_MANYREG2`, or `S_MANYREG2_ST`. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct MultiRegisterVariableSymbol<'t> { - /// Identifier of the variable type. - pub type_index: TypeIndex, - /// Most significant register first. - pub registers: Vec<(Register, RawString<'t>)>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for MultiRegisterVariableSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let type_index = buf.parse()?; - let count = match kind { - S_MANYREG2 | S_MANYREG2_ST => buf.parse::()?, - _ => u16::from(buf.parse::()?), - }; - - let mut registers = Vec::with_capacity(count as usize); - for _ in 0..count { - registers.push((buf.parse()?, parse_symbol_name(&mut buf, kind)?)); - } - - let symbol = MultiRegisterVariableSymbol { - type_index, - registers, - }; - - Ok((symbol, buf.pos())) - } -} - -// CV_PUBSYMFLAGS_e -const CVPSF_CODE: u32 = 0x1; -const CVPSF_FUNCTION: u32 = 0x2; -const CVPSF_MANAGED: u32 = 0x4; -const CVPSF_MSIL: u32 = 0x8; - -/// A public symbol with a mangled name. -/// -/// Symbol kind `S_PUB32`, or `S_PUB32_ST`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct PublicSymbol<'t> { - /// The public symbol refers to executable code. - pub code: bool, - /// The public symbol is a function. - pub function: bool, - /// The symbol is in managed code (native or IL). - pub managed: bool, - /// The symbol is managed IL code. - pub msil: bool, - /// Start offset of the symbol. - pub offset: PdbInternalSectionOffset, - /// Mangled name of the symbol. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for PublicSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let flags = buf.parse::()?; - let symbol = PublicSymbol { - code: flags & CVPSF_CODE != 0, - function: flags & CVPSF_FUNCTION != 0, - managed: flags & CVPSF_MANAGED != 0, - msil: flags & CVPSF_MSIL != 0, - offset: buf.parse()?, - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// Static data, such as a global variable. -/// -/// Symbol kinds: -/// - `S_LDATA32` and `S_LDATA32_ST` for local unmanaged data -/// - `S_GDATA32` and `S_GDATA32_ST` for global unmanaged data -/// - `S_LMANDATA32` and `S_LMANDATA32_ST` for local managed data -/// - `S_GMANDATA32` and `S_GMANDATA32_ST` for global managed data -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct DataSymbol<'t> { - /// Whether this data is global or local. - pub global: bool, - /// Whether this data is managed or unmanaged. - pub managed: bool, - /// Type identifier of the type of data. - pub type_index: TypeIndex, - /// Code offset of the start of the data region. - pub offset: PdbInternalSectionOffset, - /// Name of the data variable. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for DataSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = DataSymbol { - global: matches!(kind, S_GDATA32 | S_GDATA32_ST | S_GMANDATA | S_GMANDATA_ST), - managed: matches!( - kind, - S_LMANDATA | S_LMANDATA_ST | S_GMANDATA | S_GMANDATA_ST - ), - type_index: buf.parse()?, - offset: buf.parse()?, - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// Reference to an imported procedure. -/// -/// Symbol kind `S_PROCREF`, `S_PROCREF_ST`, `S_LPROCREF`, or `S_LPROCREF_ST`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct ProcedureReferenceSymbol<'t> { - /// Whether the referenced procedure is global or local. - pub global: bool, - /// SUC of the name. - pub sum_name: u32, - /// Symbol index of the referenced [`ProcedureSymbol`]. - /// - /// Note that this symbol might be located in a different module. - pub symbol_index: SymbolIndex, - /// Index of the module in [`DebugInformation::modules`](crate::DebugInformation::modules) - /// containing the actual symbol. - pub module: Option, - /// Name of the procedure reference. - pub name: Option>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for ProcedureReferenceSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = ProcedureReferenceSymbol { - global: matches!(kind, S_PROCREF | S_PROCREF_ST), - sum_name: buf.parse()?, - symbol_index: buf.parse()?, - module: buf.parse::()?.checked_sub(1).map(usize::from), - name: parse_optional_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// Reference to an imported variable. -/// -/// Symbol kind `S_DATAREF`, or `S_DATAREF_ST`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct DataReferenceSymbol<'t> { - /// SUC of the name. - pub sum_name: u32, - /// Symbol index of the referenced [`DataSymbol`]. - /// - /// Note that this symbol might be located in a different module. - pub symbol_index: SymbolIndex, - /// Index of the module in [`DebugInformation::modules`](crate::DebugInformation::modules) - /// containing the actual symbol. - pub module: Option, - /// Name of the data reference. - pub name: Option>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for DataReferenceSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = DataReferenceSymbol { - sum_name: buf.parse()?, - symbol_index: buf.parse()?, - module: buf.parse::()?.checked_sub(1).map(usize::from), - name: parse_optional_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// Reference to an annotation. -/// -/// Symbol kind `S_ANNOTATIONREF`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct AnnotationReferenceSymbol<'t> { - /// SUC of the name. - pub sum_name: u32, - /// Symbol index of the referenced symbol. - /// - /// Note that this symbol might be located in a different module. - pub symbol_index: SymbolIndex, - /// Index of the module in [`DebugInformation::modules`](crate::DebugInformation::modules) - /// containing the actual symbol. - pub module: Option, - /// Name of the annotation reference. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for AnnotationReferenceSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = AnnotationReferenceSymbol { - sum_name: buf.parse()?, - symbol_index: buf.parse()?, - module: buf.parse::()?.checked_sub(1).map(usize::from), - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// Subtype of [`TrampolineSymbol`]. -#[non_exhaustive] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum TrampolineType { - /// An incremental thunk. - Incremental, - /// Branch island thunk. - BranchIsland, - /// An unknown thunk type. - Unknown, -} - -/// Trampoline thunk. -/// -/// Symbol kind `S_TRAMPOLINE`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct TrampolineSymbol { - /// Trampoline symbol subtype. - pub tramp_type: TrampolineType, - /// Code size of the thunk. - pub size: u16, - /// Code offset of the thunk. - pub thunk: PdbInternalSectionOffset, - /// Code offset of the thunk target. - pub target: PdbInternalSectionOffset, -} - -impl TryFromCtx<'_, SymbolKind> for TrampolineSymbol { - type Error = Error; - - fn try_from_ctx(this: &'_ [u8], _kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let tramp_type = match buf.parse::()? { - 0x00 => TrampolineType::Incremental, - 0x01 => TrampolineType::BranchIsland, - _ => TrampolineType::Unknown, - }; - - let size = buf.parse()?; - let thunk_offset = buf.parse()?; - let target_offset = buf.parse()?; - let thunk_section = buf.parse()?; - let target_section = buf.parse()?; - - let symbol = Self { - tramp_type, - size, - thunk: PdbInternalSectionOffset::new(thunk_section, thunk_offset), - target: PdbInternalSectionOffset::new(target_section, target_offset), - }; - - Ok((symbol, buf.pos())) - } -} - -/// A constant value. -/// -/// Symbol kind `S_CONSTANT`, or `S_CONSTANT_ST`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct ConstantSymbol<'t> { - /// Whether this constant has metadata type information. - pub managed: bool, - /// The type of this constant or metadata token. - pub type_index: TypeIndex, - /// The value of this constant. - pub value: Variant, - /// Name of the constant. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for ConstantSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = ConstantSymbol { - managed: kind == S_MANCONSTANT, - type_index: buf.parse()?, - value: buf.parse()?, - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// A user defined type. -/// -/// Symbol kind `S_UDT`, or `S_UDT_ST`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct UserDefinedTypeSymbol<'t> { - /// Identifier of the type. - pub type_index: TypeIndex, - /// Name of the type. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for UserDefinedTypeSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = UserDefinedTypeSymbol { - type_index: buf.parse()?, - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// A thread local variable. -/// -/// Symbol kinds: -/// - `S_LTHREAD32`, `S_LTHREAD32_ST` for local thread storage. -/// - `S_GTHREAD32`, or `S_GTHREAD32_ST` for global thread storage. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct ThreadStorageSymbol<'t> { - /// Whether this is a global or local thread storage. - pub global: bool, - /// Identifier of the stored type. - pub type_index: TypeIndex, - /// Code offset of the thread local. - pub offset: PdbInternalSectionOffset, - /// Name of the thread local. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for ThreadStorageSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = ThreadStorageSymbol { - global: matches!(kind, S_GTHREAD32 | S_GTHREAD32_ST), - type_index: buf.parse()?, - offset: buf.parse()?, - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -// CV_PROCFLAGS: -const CV_PFLAG_NOFPO: u8 = 0x01; -const CV_PFLAG_INT: u8 = 0x02; -const CV_PFLAG_FAR: u8 = 0x04; -const CV_PFLAG_NEVER: u8 = 0x08; -const CV_PFLAG_NOTREACHED: u8 = 0x10; -const CV_PFLAG_CUST_CALL: u8 = 0x20; -const CV_PFLAG_NOINLINE: u8 = 0x40; -const CV_PFLAG_OPTDBGINFO: u8 = 0x80; - -/// Flags of a [`ProcedureSymbol`]. -#[non_exhaustive] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct ProcedureFlags { - /// Frame pointer is present (not omitted). - pub nofpo: bool, - /// Interrupt return. - pub int: bool, - /// Far return. - pub far: bool, - /// Procedure does not return. - pub never: bool, - /// Procedure is never called. - pub notreached: bool, - /// Custom calling convention. - pub cust_call: bool, - /// Marked as `noinline`. - pub noinline: bool, - /// Debug information for optimized code is present. - pub optdbginfo: bool, -} - -impl<'t> TryFromCtx<'t, Endian> for ProcedureFlags { - type Error = scroll::Error; - - fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { - let (value, size) = u8::try_from_ctx(this, le)?; - - let flags = Self { - nofpo: value & CV_PFLAG_NOFPO != 0, - int: value & CV_PFLAG_INT != 0, - far: value & CV_PFLAG_FAR != 0, - never: value & CV_PFLAG_NEVER != 0, - notreached: value & CV_PFLAG_NOTREACHED != 0, - cust_call: value & CV_PFLAG_CUST_CALL != 0, - noinline: value & CV_PFLAG_NOINLINE != 0, - optdbginfo: value & CV_PFLAG_OPTDBGINFO != 0, - }; - - Ok((flags, size)) - } -} - -/// A procedure, such as a function or method. -/// -/// Symbol kinds: -/// - `S_GPROC32`, `S_GPROC32_ST` for global procedures -/// - `S_LPROC32`, `S_LPROC32_ST` for local procedures -/// - `S_LPROC32_DPC` for DPC procedures -/// - `S_GPROC32_ID`, `S_LPROC32_ID`, `S_LPROC32_DPC_ID` for procedures referencing types from the -/// ID stream rather than the Type stream. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct ProcedureSymbol<'t> { - /// Whether this is a global or local procedure. - pub global: bool, - /// Indicates Deferred Procedure Calls (DPC). - pub dpc: bool, - /// The parent scope that this procedure is nested in. - pub parent: Option, - /// The end symbol of this procedure. - pub end: SymbolIndex, - /// The next procedure symbol. - pub next: Option, - /// The length of the code block covered by this procedure. - pub len: u32, - /// Start offset of the procedure's body code, which marks the end of the prologue. - pub dbg_start_offset: u32, - /// End offset of the procedure's body code, which marks the start of the epilogue. - pub dbg_end_offset: u32, - /// Identifier of the procedure type. - /// - /// The type contains the complete signature, including parameters, modifiers and the return - /// type. - pub type_index: TypeIndex, - /// Code offset of the start of this procedure. - pub offset: PdbInternalSectionOffset, - /// Detailed flags of this procedure. - pub flags: ProcedureFlags, - /// The full, demangled name of the procedure. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for ProcedureSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = ProcedureSymbol { - global: matches!(kind, S_GPROC32 | S_GPROC32_ST | S_GPROC32_ID), - dpc: matches!(kind, S_LPROC32_DPC | S_LPROC32_DPC_ID), - parent: parse_optional_index(&mut buf)?, - end: buf.parse()?, - next: parse_optional_index(&mut buf)?, - len: buf.parse()?, - dbg_start_offset: buf.parse()?, - dbg_end_offset: buf.parse()?, - type_index: buf.parse()?, - offset: buf.parse()?, - flags: buf.parse()?, - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// The callsite of an inlined function. -/// -/// Symbol kind `S_INLINESITE`, or `S_INLINESITE2`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct InlineSiteSymbol<'t> { - /// Index of the parent function. - /// - /// This might either be a [`ProcedureSymbol`] or another `InlineSiteSymbol`. - pub parent: Option, - /// The end symbol of this callsite. - pub end: SymbolIndex, - /// Identifier of the type describing the inline function. - pub inlinee: IdIndex, - /// The total number of invocations of the inline function. - pub invocations: Option, - /// Binary annotations containing the line program of this call site. - pub annotations: BinaryAnnotations<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for InlineSiteSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = InlineSiteSymbol { - parent: parse_optional_index(&mut buf)?, - end: buf.parse()?, - inlinee: buf.parse()?, - invocations: match kind { - S_INLINESITE2 => Some(buf.parse()?), - _ => None, - }, - annotations: BinaryAnnotations::new(buf.take(buf.len())?), - }; - - Ok((symbol, buf.pos())) - } -} - -/// Reference to build information. -/// -/// Symbol kind `S_BUILDINFO`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct BuildInfoSymbol { - /// Index of the build information record. - pub id: IdIndex, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for BuildInfoSymbol { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], _kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = Self { id: buf.parse()? }; - - Ok((symbol, buf.pos())) - } -} - -/// Name of the object file of this module. -/// -/// Symbol kind `S_OBJNAME`, or `S_OBJNAME_ST`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct ObjNameSymbol<'t> { - /// Signature. - pub signature: u32, - /// Path to the object file. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for ObjNameSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = ObjNameSymbol { - signature: buf.parse()?, - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// A version number refered to by `CompileFlagsSymbol`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct CompilerVersion { - /// The major version number. - pub major: u16, - /// The minor version number. - pub minor: u16, - /// The build (patch) version number. - pub build: u16, - /// The QFE (quick fix engineering) number. - pub qfe: Option, -} - -impl<'t> TryFromCtx<'t, bool> for CompilerVersion { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], has_qfe: bool) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let version = Self { - major: buf.parse()?, - minor: buf.parse()?, - build: buf.parse()?, - qfe: if has_qfe { Some(buf.parse()?) } else { None }, - }; - - Ok((version, buf.pos())) - } -} - -/// Compile flags declared in `CompileFlagsSymbol`. -#[non_exhaustive] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct CompileFlags { - /// Compiled for edit and continue. - pub edit_and_continue: bool, - /// Compiled without debugging info. - pub no_debug_info: bool, - /// Compiled with `LTCG`. - pub link_time_codegen: bool, - /// Compiled with `/bzalign`. - pub no_data_align: bool, - /// Managed code or data is present. - pub managed: bool, - /// Compiled with `/GS`. - pub security_checks: bool, - /// Compiled with `/hotpatch`. - pub hot_patch: bool, - /// Compiled with `CvtCIL`. - pub cvtcil: bool, - /// This is a MSIL .NET Module. - pub msil_module: bool, - /// Compiled with `/sdl`. - pub sdl: bool, - /// Compiled with `/ltcg:pgo` or `pgo:`. - pub pgo: bool, - /// This is a .exp module. - pub exp_module: bool, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for CompileFlags { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let is_compile3 = kind == S_COMPILE3; - - let raw = this.pread_with::(0, LE)?; - this.pread::(2)?; // unused - - let flags = Self { - edit_and_continue: raw & 1 != 0, - no_debug_info: (raw >> 1) & 1 != 0, - link_time_codegen: (raw >> 2) & 1 != 0, - no_data_align: (raw >> 3) & 1 != 0, - managed: (raw >> 4) & 1 != 0, - security_checks: (raw >> 5) & 1 != 0, - hot_patch: (raw >> 6) & 1 != 0, - cvtcil: (raw >> 7) & 1 != 0, - msil_module: (raw >> 8) & 1 != 0, - sdl: (raw >> 9) & 1 != 0 && is_compile3, - pgo: (raw >> 10) & 1 != 0 && is_compile3, - exp_module: (raw >> 11) & 1 != 0 && is_compile3, - }; - - Ok((flags, 3)) - } -} - -/// Flags used to compile a module. -/// -/// Symbol kind `S_COMPILE2`, `S_COMPILE2_ST`, or `S_COMPILE3`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct CompileFlagsSymbol<'t> { - /// The source code language. - pub language: SourceLanguage, - /// Compiler flags. - pub flags: CompileFlags, - /// Machine type of the compilation target. - pub cpu_type: CPUType, - /// Version of the compiler frontend. - pub frontend_version: CompilerVersion, - /// Version of the compiler backend. - pub backend_version: CompilerVersion, - /// Display name of the compiler. - pub version_string: RawString<'t>, - // TODO: Command block for S_COMPILE2? -} - -impl<'t> TryFromCtx<'t, SymbolKind> for CompileFlagsSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let has_qfe = kind == S_COMPILE3; - let symbol = CompileFlagsSymbol { - language: buf.parse()?, - flags: buf.parse_with(kind)?, - cpu_type: buf.parse()?, - frontend_version: buf.parse_with(has_qfe)?, - backend_version: buf.parse_with(has_qfe)?, - version_string: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// A using namespace directive. -/// -/// Symbol kind `S_UNAMESPACE`, or `S_UNAMESPACE_ST`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct UsingNamespaceSymbol<'t> { - /// The name of the imported namespace. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for UsingNamespaceSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = UsingNamespaceSymbol { - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -// CV_LVARFLAGS: -const CV_LVARFLAG_ISPARAM: u16 = 0x01; -const CV_LVARFLAG_ADDRTAKEN: u16 = 0x02; -const CV_LVARFLAG_COMPGENX: u16 = 0x04; -const CV_LVARFLAG_ISAGGREGATE: u16 = 0x08; -const CV_LVARFLAG_ISALIASED: u16 = 0x10; -const CV_LVARFLAG_ISALIAS: u16 = 0x20; -const CV_LVARFLAG_ISRETVALUE: u16 = 0x40; -const CV_LVARFLAG_ISOPTIMIZEDOUT: u16 = 0x80; -const CV_LVARFLAG_ISENREG_GLOB: u16 = 0x100; -const CV_LVARFLAG_ISENREG_STAT: u16 = 0x200; - -/// Flags for a [`LocalSymbol`]. -#[non_exhaustive] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct LocalVariableFlags { - /// Variable is a parameter. - pub isparam: bool, - /// Address is taken. - pub addrtaken: bool, - /// Variable is compiler generated. - pub compgenx: bool, - /// The symbol is splitted in temporaries, which are treated by compiler as independent - /// entities. - pub isaggregate: bool, - /// Variable has multiple simultaneous lifetimes. - pub isaliased: bool, - /// Represents one of the multiple simultaneous lifetimes. - pub isalias: bool, - /// Represents a function return value. - pub isretvalue: bool, - /// Variable has no lifetimes. - pub isoptimizedout: bool, - /// Variable is an enregistered global. - pub isenreg_glob: bool, - /// Variable is an enregistered static. - pub isenreg_stat: bool, -} - -impl<'t> TryFromCtx<'t, Endian> for LocalVariableFlags { - type Error = scroll::Error; - - fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { - let (value, size) = u16::try_from_ctx(this, le)?; - - let flags = Self { - isparam: value & CV_LVARFLAG_ISPARAM != 0, - addrtaken: value & CV_LVARFLAG_ADDRTAKEN != 0, - compgenx: value & CV_LVARFLAG_COMPGENX != 0, - isaggregate: value & CV_LVARFLAG_ISAGGREGATE != 0, - isaliased: value & CV_LVARFLAG_ISALIASED != 0, - isalias: value & CV_LVARFLAG_ISALIAS != 0, - isretvalue: value & CV_LVARFLAG_ISRETVALUE != 0, - isoptimizedout: value & CV_LVARFLAG_ISOPTIMIZEDOUT != 0, - isenreg_glob: value & CV_LVARFLAG_ISENREG_GLOB != 0, - isenreg_stat: value & CV_LVARFLAG_ISENREG_STAT != 0, - }; - - Ok((flags, size)) - } -} - -/// A local symbol in optimized code. -/// -/// Symbol kind `S_LOCAL`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct LocalSymbol<'t> { - /// The type of the symbol. - pub type_index: TypeIndex, - /// Flags for this symbol. - pub flags: LocalVariableFlags, - /// Name of the symbol. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for LocalSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = LocalSymbol { - type_index: buf.parse()?, - flags: buf.parse()?, - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -// https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L4456 -/// Flags of an [`ExportSymbol`]. -#[non_exhaustive] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct ExportSymbolFlags { - /// An exported constant. - pub constant: bool, - /// Exported data (e.g. a static variable). - pub data: bool, - /// A private symbol. - pub private: bool, - /// A symbol with no name. - pub no_name: bool, - /// Ordinal was explicitly assigned. - pub ordinal: bool, - /// This is a forwarder. - pub forwarder: bool, -} - -impl<'t> TryFromCtx<'t, Endian> for ExportSymbolFlags { - type Error = scroll::Error; - - fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { - let (value, size) = u16::try_from_ctx(this, le)?; - - let flags = Self { - constant: value & 0x01 != 0, - data: value & 0x02 != 0, - private: value & 0x04 != 0, - no_name: value & 0x08 != 0, - ordinal: value & 0x10 != 0, - forwarder: value & 0x20 != 0, - }; - - Ok((flags, size)) - } -} - -/// An exported symbol. -/// -/// Symbol kind `S_EXPORT`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct ExportSymbol<'t> { - /// Ordinal of the symbol. - pub ordinal: u16, - /// Flags declaring the type of the exported symbol. - pub flags: ExportSymbolFlags, - /// The name of the exported symbol. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for ExportSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = ExportSymbol { - ordinal: buf.parse()?, - flags: buf.parse()?, - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// A label symbol. -/// -/// Symbol kind `S_LABEL32`, `S_LABEL16`, or `S_LABEL32_ST`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct LabelSymbol<'t> { - /// Code offset of the start of this label. - pub offset: PdbInternalSectionOffset, - /// Detailed flags of this label. - pub flags: ProcedureFlags, - /// Name of the symbol. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for LabelSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = LabelSymbol { - offset: buf.parse()?, - flags: buf.parse()?, - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// A block symbol. -/// -/// Symbol kind `S_BLOCK32`, or `S_BLOCK32_ST`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct BlockSymbol<'t> { - /// The parent scope that this block is nested in. - pub parent: SymbolIndex, - /// The end symbol of this block. - pub end: SymbolIndex, - /// The length of the block. - pub len: u32, - /// Code offset of the start of this label. - pub offset: PdbInternalSectionOffset, - /// The block name. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for BlockSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = BlockSymbol { - parent: buf.parse()?, - end: buf.parse()?, - len: buf.parse()?, - offset: buf.parse()?, - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// A register relative symbol. -/// -/// The address of the variable is the value in the register + offset (e.g. %EBP + 8). -/// -/// Symbol kind `S_REGREL32`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct RegisterRelativeSymbol<'t> { - /// The variable offset. - pub offset: i32, - /// The type of the variable. - pub type_index: TypeIndex, - /// The register this variable address is relative to. - pub register: Register, - /// The variable name. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for RegisterRelativeSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let symbol = RegisterRelativeSymbol { - offset: buf.parse()?, - type_index: buf.parse()?, - register: buf.parse()?, - name: parse_symbol_name(&mut buf, kind)?, - }; - - Ok((symbol, buf.pos())) - } -} - -/// Thunk adjustor -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct ThunkAdjustor<'t> { - delta: u16, - target: RawString<'t>, -} - -/// A thunk kind -#[non_exhaustive] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ThunkKind<'t> { - /// Standard thunk - NoType, - /// "this" adjustor thunk with delta and target - Adjustor(ThunkAdjustor<'t>), - /// Virtual call thunk with table entry - VCall(u16), - /// pcode thunk - PCode, - /// thunk which loads the address to jump to via unknown means... - Load, - /// Unknown with ordinal value - Unknown(u8), -} - -/// A thunk symbol. -/// -/// Symbol kind `S_THUNK32`, or `S_THUNK32_ST`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct ThunkSymbol<'t> { - /// The parent scope that this thunk is nested in. - pub parent: Option, - /// The end symbol of this thunk. - pub end: SymbolIndex, - /// The next symbol. - pub next: Option, - /// Code offset of the start of this label. - pub offset: PdbInternalSectionOffset, - /// The length of the thunk. - pub len: u16, - /// The kind of the thunk. - pub kind: ThunkKind<'t>, - /// The thunk name. - pub name: RawString<'t>, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for ThunkSymbol<'t> { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let parent = parse_optional_index(&mut buf)?; - let end = buf.parse()?; - let next = parse_optional_index(&mut buf)?; - let offset = buf.parse()?; - let len = buf.parse()?; - let ord = buf.parse::()?; - let name = parse_symbol_name(&mut buf, kind)?; - - let kind = match ord { - 0 => ThunkKind::NoType, - 1 => ThunkKind::Adjustor(ThunkAdjustor { - delta: buf.parse::()?, - target: buf.parse_cstring()?, - }), - 2 => ThunkKind::VCall(buf.parse::()?), - 3 => ThunkKind::PCode, - 4 => ThunkKind::Load, - ord => ThunkKind::Unknown(ord), - }; - - let symbol = ThunkSymbol { - parent, - end, - next, - offset, - len, - kind, - name, - }; - - Ok((symbol, buf.pos())) - } -} - -// CV_SEPCODEFLAGS: -const CV_SEPCODEFLAG_IS_LEXICAL_SCOPE: u32 = 0x01; -const CV_SEPCODEFLAG_RETURNS_TO_PARENT: u32 = 0x02; - -/// Flags for a [`SeparatedCodeSymbol`]. -#[non_exhaustive] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct SeparatedCodeFlags { - /// S_SEPCODE doubles as lexical scope. - pub islexicalscope: bool, - /// code frag returns to parent. - pub returnstoparent: bool, -} - -impl<'t> TryFromCtx<'t, Endian> for SeparatedCodeFlags { - type Error = scroll::Error; - - fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { - let (value, size) = u32::try_from_ctx(this, le)?; - - let flags = Self { - islexicalscope: value & CV_SEPCODEFLAG_IS_LEXICAL_SCOPE != 0, - returnstoparent: value & CV_SEPCODEFLAG_RETURNS_TO_PARENT != 0, - }; - - Ok((flags, size)) - } -} - -/// A separated code symbol. -/// -/// Symbol kind `S_SEPCODE`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct SeparatedCodeSymbol { - /// The parent scope that this block is nested in. - pub parent: SymbolIndex, - /// The end symbol of this block. - pub end: SymbolIndex, - /// The length of the block. - pub len: u32, - /// Flags for this symbol - pub flags: SeparatedCodeFlags, - /// Code offset of the start of the separated code. - pub offset: PdbInternalSectionOffset, - /// Parent offset. - pub parent_offset: PdbInternalSectionOffset, -} - -impl<'t> TryFromCtx<'t, SymbolKind> for SeparatedCodeSymbol { - type Error = Error; - - fn try_from_ctx(this: &'t [u8], _: SymbolKind) -> Result<(Self, usize)> { - let mut buf = ParseBuffer::from(this); - - let parent = buf.parse()?; - let end = buf.parse()?; - let len = buf.parse()?; - let flags = buf.parse()?; - let offset = buf.parse()?; - let parent_offset = buf.parse()?; - let section = buf.parse()?; - let parent_section = buf.parse()?; - - let symbol = Self { - parent, - end, - len, - flags, - offset: PdbInternalSectionOffset { offset, section }, - parent_offset: PdbInternalSectionOffset { - offset: parent_offset, - section: parent_section, - }, - }; - - Ok((symbol, buf.pos())) - } -} - -/// PDB symbol tables contain names, locations, and metadata about functions, global/static data, -/// constants, data types, and more. -/// -/// The `SymbolTable` holds a `SourceView` referencing the symbol table inside the PDB file. All the -/// data structures returned by a `SymbolTable` refer to that buffer. -/// -/// # Example -/// -/// ``` -/// # use pdb::FallibleIterator; -/// # -/// # fn test() -> pdb::Result { -/// let file = std::fs::File::open("fixtures/self/foo.pdb")?; -/// let mut pdb = pdb::PDB::open(file)?; -/// -/// let symbol_table = pdb.global_symbols()?; -/// let address_map = pdb.address_map()?; -/// -/// # let mut count: usize = 0; -/// let mut symbols = symbol_table.iter(); -/// while let Some(symbol) = symbols.next()? { -/// match symbol.parse() { -/// Ok(pdb::SymbolData::Public(data)) if data.function => { -/// // we found the location of a function! -/// let rva = data.offset.to_rva(&address_map).unwrap_or_default(); -/// println!("{} is {}", rva, data.name); -/// # count += 1; -/// } -/// _ => {} -/// } -/// } -/// -/// # Ok(count) -/// # } -/// # assert!(test().expect("test") > 2000); -/// ``` -#[derive(Debug)] -pub struct SymbolTable<'s> { - stream: Stream<'s>, -} - -impl<'s> SymbolTable<'s> { - /// Parses a symbol table from raw stream data. - pub(crate) fn new(stream: Stream<'s>) -> Self { - SymbolTable { stream } - } - - /// Returns an iterator that can traverse the symbol table in sequential order. - pub fn iter(&self) -> SymbolIter<'_> { - SymbolIter::new(self.stream.parse_buffer()) - } - - /// Returns an iterator over symbols starting at the given index. - pub fn iter_at(&self, index: SymbolIndex) -> SymbolIter<'_> { - let mut iter = self.iter(); - iter.seek(index); - iter - } -} - -/// A `SymbolIter` iterates over a `SymbolTable`, producing `Symbol`s. -/// -/// Symbol tables are represented internally as a series of records, each of which have a length, a -/// type, and a type-specific field layout. Iteration performance is therefore similar to a linked -/// list. -#[derive(Debug)] -pub struct SymbolIter<'t> { - buf: ParseBuffer<'t>, -} - -impl<'t> SymbolIter<'t> { - pub(crate) fn new(buf: ParseBuffer<'t>) -> SymbolIter<'t> { - SymbolIter { buf } - } - - /// Move the iterator to the symbol referred to by `index`. - /// - /// This can be used to jump to the sibiling or parent of a symbol record. - pub fn seek(&mut self, index: SymbolIndex) { - self.buf.seek(index.0 as usize); - } - - /// Skip to the symbol referred to by `index`, returning the symbol. - /// - /// This can be used to jump to the sibiling or parent of a symbol record. Iteration continues - /// after that symbol. - /// - /// Note that the symbol may be located **before** the originating symbol, for instance when - /// jumping to the parent symbol. Take care not to enter an endless loop in this case. - pub fn skip_to(&mut self, index: SymbolIndex) -> Result>> { - self.seek(index); - self.next() - } -} - -impl<'t> FallibleIterator for SymbolIter<'t> { - type Item = Symbol<'t>; - type Error = Error; - - fn next(&mut self) -> Result> { - while !self.buf.is_empty() { - let index = SymbolIndex(self.buf.pos() as u32); - - // read the length of the next symbol - let symbol_length = self.buf.parse::()? as usize; - if symbol_length < 2 { - // this can't be correct - return Err(Error::SymbolTooShort); - } - - // grab the symbol itself - let data = self.buf.take(symbol_length)?; - let symbol = Symbol { index, data }; - - // skip over padding in the symbol table - match symbol.raw_kind() { - S_ALIGN | S_SKIP => continue, - _ => return Ok(Some(symbol)), - } - } - - Ok(None) - } -} - -#[cfg(test)] +#[cfg(all(test, feature = "alloc"))] mod tests { mod parsing { - use crate::symbol::*; + use crate::{*, symbol::{types::*, *}}; #[test] fn kind_0006() { @@ -2301,7 +684,8 @@ mod tests { } mod iterator { - use crate::symbol::*; + use fallible_iterator::FallibleIterator; + use crate::{*, symbol::{types::*, *}}; fn create_iter() -> SymbolIter<'static> { let data = &[ diff --git a/src/symbol/types.rs b/src/symbol/types.rs new file mode 100644 index 0000000..658e8a3 --- /dev/null +++ b/src/symbol/types.rs @@ -0,0 +1,1634 @@ +// Copyright 2026 PDB Developers +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use core::fmt; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +use scroll::{ctx::TryFromCtx, Endian, Pread, LE}; + +use crate::{BinaryAnnotations, common::*, symbol::constants::*}; + +#[cfg(feature = "alloc")] +use crate::msf::*; + +use crate::FallibleIterator; + +/// The raw type discriminator for `Symbols`. +pub type SymbolKind = u16; + +/// Represents a symbol from the symbol table. +/// +/// A `Symbol` is represented internally as a `&[u8]`, and in general the bytes inside are not +/// inspected in any way before calling any of the accessor methods. +/// +/// To avoid copying, `Symbol`s exist as references to data owned by the parent `SymbolTable`. +/// Therefore, a `Symbol` may not outlive its parent `SymbolTable`. +#[derive(Copy, Clone, PartialEq)] +pub struct Symbol<'t> { + pub(crate) index: SymbolIndex, + pub(crate) data: &'t [u8], +} + +impl<'t> Symbol<'t> { + /// The index of this symbol in the containing symbol stream. + #[inline] + pub fn index(&self) -> SymbolIndex { + self.index + } + + /// Returns the kind of symbol identified by this Symbol. + #[inline] + pub fn raw_kind(&self) -> SymbolKind { + debug_assert!(self.data.len() >= 2); + self.data.pread_with(0, LE).unwrap_or_default() + } + + /// Returns the raw bytes of this symbol record, including the symbol type and extra data, but + /// not including the preceding symbol length indicator. + #[inline] + pub fn raw_bytes(&self) -> &'t [u8] { + self.data + } + + /// Parse the symbol into the `SymbolData` it contains. + #[inline] + pub fn parse(&self) -> Result> { + self.raw_bytes().pread_with(0, ()) + } + + /// Returns whether this symbol starts a scope. + /// + /// If `true`, this symbol has a `parent` and an `end` field, which contains the offset of the + /// corrsponding end symbol. + pub fn starts_scope(&self) -> bool { + matches!( + self.raw_kind(), + S_GPROC16 + | S_GPROC32 + | S_GPROC32_ST + | S_GPROCMIPS + | S_GPROCMIPS_ST + | S_GPROCIA64 + | S_GPROCIA64_ST + | S_LPROC16 + | S_LPROC32 + | S_LPROC32_ST + | S_LPROC32_DPC + | S_LPROCMIPS + | S_LPROCMIPS_ST + | S_LPROCIA64 + | S_LPROCIA64_ST + | S_LPROC32_DPC_ID + | S_GPROC32_ID + | S_GPROCMIPS_ID + | S_GPROCIA64_ID + | S_BLOCK16 + | S_BLOCK32 + | S_BLOCK32_ST + | S_WITH16 + | S_WITH32 + | S_WITH32_ST + | S_THUNK16 + | S_THUNK32 + | S_THUNK32_ST + | S_SEPCODE + | S_GMANPROC + | S_GMANPROC_ST + | S_LMANPROC + | S_LMANPROC_ST + | S_INLINESITE + | S_INLINESITE2 + ) + } + + /// Returns whether this symbol declares the end of a scope. + pub fn ends_scope(&self) -> bool { + matches!(self.raw_kind(), S_END | S_PROC_ID_END | S_INLINESITE_END) + } +} + +impl<'t> fmt::Debug for Symbol<'t> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Symbol{{ kind: 0x{:x} [{} bytes] }}", + self.raw_kind(), + self.data.len() + ) + } +} + +fn parse_symbol_name<'t>(buf: &mut ParseBuffer<'t>, kind: SymbolKind) -> Result> { + if kind < S_ST_MAX { + // Pascal-style name + buf.parse_u8_pascal_string() + } else { + // NUL-terminated name + buf.parse_cstring() + } +} + +fn parse_optional_name<'t>( + buf: &mut ParseBuffer<'t>, + kind: SymbolKind, +) -> Result>> { + if kind < S_ST_MAX { + // ST variants do not specify a name + Ok(None) + } else { + // NUL-terminated name + buf.parse_cstring().map(Some) + } +} + +fn parse_optional_index(buf: &mut ParseBuffer<'_>) -> Result> { + Ok(match buf.parse()? { + SymbolIndex(0) => None, + index => Some(index), + }) +} + +// data types are defined at: +// https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L3038 +// constants defined at: +// https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2735 +// decoding reference: +// https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/cvdump/dumpsym7.cpp#L264 + +/// Information parsed from a [`Symbol`] record. +#[non_exhaustive] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum SymbolData<'t> { + /// End of a scope, such as a procedure. + ScopeEnd, + /// Name of the object file of this module. + ObjName(ObjNameSymbol<'t>), + /// A Register variable. + RegisterVariable(RegisterVariableSymbol<'t>), + /// A constant value. + Constant(ConstantSymbol<'t>), + /// A user defined type. + UserDefinedType(UserDefinedTypeSymbol<'t>), + #[cfg(feature = "alloc")] + /// A Register variable spanning multiple registers. + MultiRegisterVariable(MultiRegisterVariableSymbol<'t>), + /// Static data, such as a global variable. + Data(DataSymbol<'t>), + /// A public symbol with a mangled name. + Public(PublicSymbol<'t>), + /// A procedure, such as a function or method. + Procedure(ProcedureSymbol<'t>), + /// A thread local variable. + ThreadStorage(ThreadStorageSymbol<'t>), + /// Flags used to compile a module. + CompileFlags(CompileFlagsSymbol<'t>), + /// A using namespace directive. + UsingNamespace(UsingNamespaceSymbol<'t>), + /// Reference to a [`ProcedureSymbol`]. + ProcedureReference(ProcedureReferenceSymbol<'t>), + /// Reference to an imported variable. + DataReference(DataReferenceSymbol<'t>), + /// Reference to an annotation. + AnnotationReference(AnnotationReferenceSymbol<'t>), + /// Trampoline thunk. + Trampoline(TrampolineSymbol), + /// An exported symbol. + Export(ExportSymbol<'t>), + /// A local symbol in optimized code. + Local(LocalSymbol<'t>), + /// Reference to build information. + BuildInfo(BuildInfoSymbol), + /// The callsite of an inlined function. + InlineSite(InlineSiteSymbol<'t>), + /// End of an inline callsite. + InlineSiteEnd, + /// End of a procedure. + ProcedureEnd, + /// A label. + Label(LabelSymbol<'t>), + /// A block. + Block(BlockSymbol<'t>), + /// Data allocated relative to a register. + RegisterRelative(RegisterRelativeSymbol<'t>), + /// A thunk. + Thunk(ThunkSymbol<'t>), + /// A block of separated code. + SeparatedCode(SeparatedCodeSymbol), +} + +impl<'t> SymbolData<'t> { + /// Returns the name of this symbol if it has one. + pub fn name(&self) -> Option> { + match self { + Self::ScopeEnd => None, + Self::ObjName(data) => Some(data.name), + Self::RegisterVariable(_) => None, + Self::Constant(data) => Some(data.name), + Self::UserDefinedType(data) => Some(data.name), + #[cfg(feature = "alloc")] + Self::MultiRegisterVariable(_) => None, + Self::Data(data) => Some(data.name), + Self::Public(data) => Some(data.name), + Self::Procedure(data) => Some(data.name), + Self::ThreadStorage(data) => Some(data.name), + Self::CompileFlags(_) => None, + Self::UsingNamespace(data) => Some(data.name), + Self::ProcedureReference(data) => data.name, + Self::DataReference(data) => data.name, + Self::AnnotationReference(data) => Some(data.name), + Self::Trampoline(_) => None, + Self::Export(data) => Some(data.name), + Self::Local(data) => Some(data.name), + Self::InlineSite(_) => None, + Self::BuildInfo(_) => None, + Self::InlineSiteEnd => None, + Self::ProcedureEnd => None, + Self::Label(data) => Some(data.name), + Self::Block(data) => Some(data.name), + Self::RegisterRelative(data) => Some(data.name), + Self::Thunk(data) => Some(data.name), + Self::SeparatedCode(_) => None, + } + } +} + +impl<'t> TryFromCtx<'t> for SymbolData<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], _ctx: ()) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + let kind = buf.parse()?; + + let symbol = match kind { + S_END => SymbolData::ScopeEnd, + S_OBJNAME | S_OBJNAME_ST => SymbolData::ObjName(buf.parse_with(kind)?), + S_REGISTER | S_REGISTER_ST => SymbolData::RegisterVariable(buf.parse_with(kind)?), + S_CONSTANT | S_CONSTANT_ST | S_MANCONSTANT => { + SymbolData::Constant(buf.parse_with(kind)?) + } + S_UDT | S_UDT_ST | S_COBOLUDT | S_COBOLUDT_ST => { + SymbolData::UserDefinedType(buf.parse_with(kind)?) + } + S_MANYREG | S_MANYREG_ST | S_MANYREG2 | S_MANYREG2_ST => { + #[cfg(feature = "alloc")] + { + SymbolData::MultiRegisterVariable(buf.parse_with(kind)?) + } + #[cfg(not(feature = "alloc"))] + { + return Err(Error::UnimplementedFeature("MultiRegisterVariable requires alloc")); + } + } + S_LDATA32 | S_LDATA32_ST | S_GDATA32 | S_GDATA32_ST | S_LMANDATA | S_LMANDATA_ST + | S_GMANDATA | S_GMANDATA_ST => SymbolData::Data(buf.parse_with(kind)?), + S_PUB32 | S_PUB32_ST => SymbolData::Public(buf.parse_with(kind)?), + S_LPROC32 | S_LPROC32_ST | S_GPROC32 | S_GPROC32_ST | S_LPROC32_ID | S_GPROC32_ID + | S_LPROC32_DPC | S_LPROC32_DPC_ID => SymbolData::Procedure(buf.parse_with(kind)?), + S_LTHREAD32 | S_LTHREAD32_ST | S_GTHREAD32 | S_GTHREAD32_ST => { + SymbolData::ThreadStorage(buf.parse_with(kind)?) + } + S_COMPILE2 | S_COMPILE2_ST | S_COMPILE3 => { + SymbolData::CompileFlags(buf.parse_with(kind)?) + } + S_UNAMESPACE | S_UNAMESPACE_ST => SymbolData::UsingNamespace(buf.parse_with(kind)?), + S_PROCREF | S_PROCREF_ST | S_LPROCREF | S_LPROCREF_ST => { + SymbolData::ProcedureReference(buf.parse_with(kind)?) + } + S_TRAMPOLINE => Self::Trampoline(buf.parse_with(kind)?), + S_DATAREF | S_DATAREF_ST => SymbolData::DataReference(buf.parse_with(kind)?), + S_ANNOTATIONREF => SymbolData::AnnotationReference(buf.parse_with(kind)?), + S_EXPORT => SymbolData::Export(buf.parse_with(kind)?), + S_LOCAL => SymbolData::Local(buf.parse_with(kind)?), + S_BUILDINFO => SymbolData::BuildInfo(buf.parse_with(kind)?), + S_INLINESITE | S_INLINESITE2 => SymbolData::InlineSite(buf.parse_with(kind)?), + S_INLINESITE_END => SymbolData::InlineSiteEnd, + S_PROC_ID_END => SymbolData::ProcedureEnd, + S_LABEL32 | S_LABEL32_ST => SymbolData::Label(buf.parse_with(kind)?), + S_BLOCK32 | S_BLOCK32_ST => SymbolData::Block(buf.parse_with(kind)?), + S_REGREL32 => SymbolData::RegisterRelative(buf.parse_with(kind)?), + S_THUNK32 | S_THUNK32_ST => SymbolData::Thunk(buf.parse_with(kind)?), + S_SEPCODE => SymbolData::SeparatedCode(buf.parse_with(kind)?), + other => return Err(Error::UnimplementedSymbolKind(other)), + }; + + Ok((symbol, buf.pos())) + } +} + +/// A Register variable. +/// +/// Symbol kind `S_REGISTER`, or `S_REGISTER_ST` +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct RegisterVariableSymbol<'t> { + /// Identifier of the variable type. + pub type_index: TypeIndex, + /// The register this variable is stored in. + pub register: Register, + /// Name of the variable. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for RegisterVariableSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = RegisterVariableSymbol { + type_index: buf.parse()?, + register: buf.parse()?, + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// A Register variable spanning multiple registers. +/// +/// Symbol kind `S_MANYREG`, `S_MANYREG_ST`, `S_MANYREG2`, or `S_MANYREG2_ST`. +#[cfg(feature = "alloc")] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MultiRegisterVariableSymbol<'t> { + /// Identifier of the variable type. + pub type_index: TypeIndex, + /// Most significant register first. + pub registers: Vec<(Register, RawString<'t>)>, +} + +#[cfg(feature = "alloc")] +impl<'t> TryFromCtx<'t, SymbolKind> for MultiRegisterVariableSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let type_index = buf.parse()?; + let count = match kind { + S_MANYREG2 | S_MANYREG2_ST => buf.parse::()?, + _ => u16::from(buf.parse::()?), + }; + + let mut registers = Vec::with_capacity(count as usize); + for _ in 0..count { + registers.push((buf.parse()?, parse_symbol_name(&mut buf, kind)?)); + } + + let symbol = MultiRegisterVariableSymbol { + type_index, + registers, + }; + + Ok((symbol, buf.pos())) + } +} + +// CV_PUBSYMFLAGS_e +const CVPSF_CODE: u32 = 0x1; +const CVPSF_FUNCTION: u32 = 0x2; +const CVPSF_MANAGED: u32 = 0x4; +const CVPSF_MSIL: u32 = 0x8; + +/// A public symbol with a mangled name. +/// +/// Symbol kind `S_PUB32`, or `S_PUB32_ST`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct PublicSymbol<'t> { + /// The public symbol refers to executable code. + pub code: bool, + /// The public symbol is a function. + pub function: bool, + /// The symbol is in managed code (native or IL). + pub managed: bool, + /// The symbol is managed IL code. + pub msil: bool, + /// Start offset of the symbol. + pub offset: PdbInternalSectionOffset, + /// Mangled name of the symbol. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for PublicSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let flags = buf.parse::()?; + let symbol = PublicSymbol { + code: flags & CVPSF_CODE != 0, + function: flags & CVPSF_FUNCTION != 0, + managed: flags & CVPSF_MANAGED != 0, + msil: flags & CVPSF_MSIL != 0, + offset: buf.parse()?, + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// Static data, such as a global variable. +/// +/// Symbol kinds: +/// - `S_LDATA32` and `S_LDATA32_ST` for local unmanaged data +/// - `S_GDATA32` and `S_GDATA32_ST` for global unmanaged data +/// - `S_LMANDATA32` and `S_LMANDATA32_ST` for local managed data +/// - `S_GMANDATA32` and `S_GMANDATA32_ST` for global managed data +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct DataSymbol<'t> { + /// Whether this data is global or local. + pub global: bool, + /// Whether this data is managed or unmanaged. + pub managed: bool, + /// Type identifier of the type of data. + pub type_index: TypeIndex, + /// Code offset of the start of the data region. + pub offset: PdbInternalSectionOffset, + /// Name of the data variable. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for DataSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = DataSymbol { + global: matches!(kind, S_GDATA32 | S_GDATA32_ST | S_GMANDATA | S_GMANDATA_ST), + managed: matches!( + kind, + S_LMANDATA | S_LMANDATA_ST | S_GMANDATA | S_GMANDATA_ST + ), + type_index: buf.parse()?, + offset: buf.parse()?, + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// Reference to an imported procedure. +/// +/// Symbol kind `S_PROCREF`, `S_PROCREF_ST`, `S_LPROCREF`, or `S_LPROCREF_ST`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ProcedureReferenceSymbol<'t> { + /// Whether the referenced procedure is global or local. + pub global: bool, + /// SUC of the name. + pub sum_name: u32, + /// Symbol index of the referenced [`ProcedureSymbol`]. + /// + /// Note that this symbol might be located in a different module. + pub symbol_index: SymbolIndex, + /// Index of the module in [`DebugInformation::modules`](crate::DebugInformation::modules) + /// containing the actual symbol. + pub module: Option, + /// Name of the procedure reference. + pub name: Option>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for ProcedureReferenceSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = ProcedureReferenceSymbol { + global: matches!(kind, S_PROCREF | S_PROCREF_ST), + sum_name: buf.parse()?, + symbol_index: buf.parse()?, + module: buf.parse::()?.checked_sub(1).map(usize::from), + name: parse_optional_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// Reference to an imported variable. +/// +/// Symbol kind `S_DATAREF`, or `S_DATAREF_ST`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct DataReferenceSymbol<'t> { + /// SUC of the name. + pub sum_name: u32, + /// Symbol index of the referenced [`DataSymbol`]. + /// + /// Note that this symbol might be located in a different module. + pub symbol_index: SymbolIndex, + /// Index of the module in [`DebugInformation::modules`](crate::DebugInformation::modules) + /// containing the actual symbol. + pub module: Option, + /// Name of the data reference. + pub name: Option>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for DataReferenceSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = DataReferenceSymbol { + sum_name: buf.parse()?, + symbol_index: buf.parse()?, + module: buf.parse::()?.checked_sub(1).map(usize::from), + name: parse_optional_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// Reference to an annotation. +/// +/// Symbol kind `S_ANNOTATIONREF`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct AnnotationReferenceSymbol<'t> { + /// SUC of the name. + pub sum_name: u32, + /// Symbol index of the referenced symbol. + /// + /// Note that this symbol might be located in a different module. + pub symbol_index: SymbolIndex, + /// Index of the module in [`DebugInformation::modules`](crate::DebugInformation::modules) + /// containing the actual symbol. + pub module: Option, + /// Name of the annotation reference. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for AnnotationReferenceSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = AnnotationReferenceSymbol { + sum_name: buf.parse()?, + symbol_index: buf.parse()?, + module: buf.parse::()?.checked_sub(1).map(usize::from), + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// Subtype of [`TrampolineSymbol`]. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum TrampolineType { + /// An incremental thunk. + Incremental, + /// Branch island thunk. + BranchIsland, + /// An unknown thunk type. + Unknown, +} + +/// Trampoline thunk. +/// +/// Symbol kind `S_TRAMPOLINE`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct TrampolineSymbol { + /// Trampoline symbol subtype. + pub tramp_type: TrampolineType, + /// Code size of the thunk. + pub size: u16, + /// Code offset of the thunk. + pub thunk: PdbInternalSectionOffset, + /// Code offset of the thunk target. + pub target: PdbInternalSectionOffset, +} + +impl TryFromCtx<'_, SymbolKind> for TrampolineSymbol { + type Error = Error; + + fn try_from_ctx(this: &'_ [u8], _kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let tramp_type = match buf.parse::()? { + 0x00 => TrampolineType::Incremental, + 0x01 => TrampolineType::BranchIsland, + _ => TrampolineType::Unknown, + }; + + let size = buf.parse()?; + let thunk_offset = buf.parse()?; + let target_offset = buf.parse()?; + let thunk_section = buf.parse()?; + let target_section = buf.parse()?; + + let symbol = Self { + tramp_type, + size, + thunk: PdbInternalSectionOffset::new(thunk_section, thunk_offset), + target: PdbInternalSectionOffset::new(target_section, target_offset), + }; + + Ok((symbol, buf.pos())) + } +} + +/// A constant value. +/// +/// Symbol kind `S_CONSTANT`, or `S_CONSTANT_ST`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ConstantSymbol<'t> { + /// Whether this constant has metadata type information. + pub managed: bool, + /// The type of this constant or metadata token. + pub type_index: TypeIndex, + /// The value of this constant. + pub value: Variant, + /// Name of the constant. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for ConstantSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = ConstantSymbol { + managed: kind == S_MANCONSTANT, + type_index: buf.parse()?, + value: buf.parse()?, + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// A user defined type. +/// +/// Symbol kind `S_UDT`, or `S_UDT_ST`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct UserDefinedTypeSymbol<'t> { + /// Identifier of the type. + pub type_index: TypeIndex, + /// Name of the type. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for UserDefinedTypeSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = UserDefinedTypeSymbol { + type_index: buf.parse()?, + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// A thread local variable. +/// +/// Symbol kinds: +/// - `S_LTHREAD32`, `S_LTHREAD32_ST` for local thread storage. +/// - `S_GTHREAD32`, or `S_GTHREAD32_ST` for global thread storage. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ThreadStorageSymbol<'t> { + /// Whether this is a global or local thread storage. + pub global: bool, + /// Identifier of the stored type. + pub type_index: TypeIndex, + /// Code offset of the thread local. + pub offset: PdbInternalSectionOffset, + /// Name of the thread local. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for ThreadStorageSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = ThreadStorageSymbol { + global: matches!(kind, S_GTHREAD32 | S_GTHREAD32_ST), + type_index: buf.parse()?, + offset: buf.parse()?, + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +// CV_PROCFLAGS: +const CV_PFLAG_NOFPO: u8 = 0x01; +const CV_PFLAG_INT: u8 = 0x02; +const CV_PFLAG_FAR: u8 = 0x04; +const CV_PFLAG_NEVER: u8 = 0x08; +const CV_PFLAG_NOTREACHED: u8 = 0x10; +const CV_PFLAG_CUST_CALL: u8 = 0x20; +const CV_PFLAG_NOINLINE: u8 = 0x40; +const CV_PFLAG_OPTDBGINFO: u8 = 0x80; + +/// Flags of a [`ProcedureSymbol`]. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ProcedureFlags { + /// Frame pointer is present (not omitted). + pub nofpo: bool, + /// Interrupt return. + pub int: bool, + /// Far return. + pub far: bool, + /// Procedure does not return. + pub never: bool, + /// Procedure is never called. + pub notreached: bool, + /// Custom calling convention. + pub cust_call: bool, + /// Marked as `noinline`. + pub noinline: bool, + /// Debug information for optimized code is present. + pub optdbginfo: bool, +} + +impl<'t> TryFromCtx<'t, Endian> for ProcedureFlags { + type Error = scroll::Error; + + fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { + let (value, size) = u8::try_from_ctx(this, le)?; + + let flags = Self { + nofpo: value & CV_PFLAG_NOFPO != 0, + int: value & CV_PFLAG_INT != 0, + far: value & CV_PFLAG_FAR != 0, + never: value & CV_PFLAG_NEVER != 0, + notreached: value & CV_PFLAG_NOTREACHED != 0, + cust_call: value & CV_PFLAG_CUST_CALL != 0, + noinline: value & CV_PFLAG_NOINLINE != 0, + optdbginfo: value & CV_PFLAG_OPTDBGINFO != 0, + }; + + Ok((flags, size)) + } +} + +/// A procedure, such as a function or method. +/// +/// Symbol kinds: +/// - `S_GPROC32`, `S_GPROC32_ST` for global procedures +/// - `S_LPROC32`, `S_LPROC32_ST` for local procedures +/// - `S_LPROC32_DPC` for DPC procedures +/// - `S_GPROC32_ID`, `S_LPROC32_ID`, `S_LPROC32_DPC_ID` for procedures referencing types from the +/// ID stream rather than the Type stream. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ProcedureSymbol<'t> { + /// Whether this is a global or local procedure. + pub global: bool, + /// Indicates Deferred Procedure Calls (DPC). + pub dpc: bool, + /// The parent scope that this procedure is nested in. + pub parent: Option, + /// The end symbol of this procedure. + pub end: SymbolIndex, + /// The next procedure symbol. + pub next: Option, + /// The length of the code block covered by this procedure. + pub len: u32, + /// Start offset of the procedure's body code, which marks the end of the prologue. + pub dbg_start_offset: u32, + /// End offset of the procedure's body code, which marks the start of the epilogue. + pub dbg_end_offset: u32, + /// Identifier of the procedure type. + /// + /// The type contains the complete signature, including parameters, modifiers and the return + /// type. + pub type_index: TypeIndex, + /// Code offset of the start of this procedure. + pub offset: PdbInternalSectionOffset, + /// Detailed flags of this procedure. + pub flags: ProcedureFlags, + /// The full, demangled name of the procedure. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for ProcedureSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = ProcedureSymbol { + global: matches!(kind, S_GPROC32 | S_GPROC32_ST | S_GPROC32_ID), + dpc: matches!(kind, S_LPROC32_DPC | S_LPROC32_DPC_ID), + parent: parse_optional_index(&mut buf)?, + end: buf.parse()?, + next: parse_optional_index(&mut buf)?, + len: buf.parse()?, + dbg_start_offset: buf.parse()?, + dbg_end_offset: buf.parse()?, + type_index: buf.parse()?, + offset: buf.parse()?, + flags: buf.parse()?, + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// The callsite of an inlined function. +/// +/// Symbol kind `S_INLINESITE`, or `S_INLINESITE2`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct InlineSiteSymbol<'t> { + /// Index of the parent function. + /// + /// This might either be a [`ProcedureSymbol`] or another `InlineSiteSymbol`. + pub parent: Option, + /// The end symbol of this callsite. + pub end: SymbolIndex, + /// Identifier of the type describing the inline function. + pub inlinee: IdIndex, + /// The total number of invocations of the inline function. + pub invocations: Option, + /// Binary annotations containing the line program of this call site. + pub annotations: BinaryAnnotations<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for InlineSiteSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = InlineSiteSymbol { + parent: parse_optional_index(&mut buf)?, + end: buf.parse()?, + inlinee: buf.parse()?, + invocations: match kind { + S_INLINESITE2 => Some(buf.parse()?), + _ => None, + }, + annotations: BinaryAnnotations::new(buf.take(buf.len())?), + }; + + Ok((symbol, buf.pos())) + } +} + +/// Reference to build information. +/// +/// Symbol kind `S_BUILDINFO`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct BuildInfoSymbol { + /// Index of the build information record. + pub id: IdIndex, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for BuildInfoSymbol { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], _kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = Self { id: buf.parse()? }; + + Ok((symbol, buf.pos())) + } +} + +/// Name of the object file of this module. +/// +/// Symbol kind `S_OBJNAME`, or `S_OBJNAME_ST`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ObjNameSymbol<'t> { + /// Signature. + pub signature: u32, + /// Path to the object file. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for ObjNameSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = ObjNameSymbol { + signature: buf.parse()?, + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// A version number refered to by `CompileFlagsSymbol`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CompilerVersion { + /// The major version number. + pub major: u16, + /// The minor version number. + pub minor: u16, + /// The build (patch) version number. + pub build: u16, + /// The QFE (quick fix engineering) number. + pub qfe: Option, +} + +impl<'t> TryFromCtx<'t, bool> for CompilerVersion { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], has_qfe: bool) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let version = Self { + major: buf.parse()?, + minor: buf.parse()?, + build: buf.parse()?, + qfe: if has_qfe { Some(buf.parse()?) } else { None }, + }; + + Ok((version, buf.pos())) + } +} + +/// Compile flags declared in `CompileFlagsSymbol`. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CompileFlags { + /// Compiled for edit and continue. + pub edit_and_continue: bool, + /// Compiled without debugging info. + pub no_debug_info: bool, + /// Compiled with `LTCG`. + pub link_time_codegen: bool, + /// Compiled with `/bzalign`. + pub no_data_align: bool, + /// Managed code or data is present. + pub managed: bool, + /// Compiled with `/GS`. + pub security_checks: bool, + /// Compiled with `/hotpatch`. + pub hot_patch: bool, + /// Compiled with `CvtCIL`. + pub cvtcil: bool, + /// This is a MSIL .NET Module. + pub msil_module: bool, + /// Compiled with `/sdl`. + pub sdl: bool, + /// Compiled with `/ltcg:pgo` or `pgo:`. + pub pgo: bool, + /// This is a .exp module. + pub exp_module: bool, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for CompileFlags { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let is_compile3 = kind == S_COMPILE3; + + let raw = this.pread_with::(0, LE)?; + this.pread::(2)?; // unused + + let flags = Self { + edit_and_continue: raw & 1 != 0, + no_debug_info: (raw >> 1) & 1 != 0, + link_time_codegen: (raw >> 2) & 1 != 0, + no_data_align: (raw >> 3) & 1 != 0, + managed: (raw >> 4) & 1 != 0, + security_checks: (raw >> 5) & 1 != 0, + hot_patch: (raw >> 6) & 1 != 0, + cvtcil: (raw >> 7) & 1 != 0, + msil_module: (raw >> 8) & 1 != 0, + sdl: (raw >> 9) & 1 != 0 && is_compile3, + pgo: (raw >> 10) & 1 != 0 && is_compile3, + exp_module: (raw >> 11) & 1 != 0 && is_compile3, + }; + + Ok((flags, 3)) + } +} + +/// Flags used to compile a module. +/// +/// Symbol kind `S_COMPILE2`, `S_COMPILE2_ST`, or `S_COMPILE3`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CompileFlagsSymbol<'t> { + /// The source code language. + pub language: SourceLanguage, + /// Compiler flags. + pub flags: CompileFlags, + /// Machine type of the compilation target. + pub cpu_type: CPUType, + /// Version of the compiler frontend. + pub frontend_version: CompilerVersion, + /// Version of the compiler backend. + pub backend_version: CompilerVersion, + /// Display name of the compiler. + pub version_string: RawString<'t>, + // TODO: Command block for S_COMPILE2? +} + +impl<'t> TryFromCtx<'t, SymbolKind> for CompileFlagsSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let has_qfe = kind == S_COMPILE3; + let symbol = CompileFlagsSymbol { + language: buf.parse()?, + flags: buf.parse_with(kind)?, + cpu_type: buf.parse()?, + frontend_version: buf.parse_with(has_qfe)?, + backend_version: buf.parse_with(has_qfe)?, + version_string: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// A using namespace directive. +/// +/// Symbol kind `S_UNAMESPACE`, or `S_UNAMESPACE_ST`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct UsingNamespaceSymbol<'t> { + /// The name of the imported namespace. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for UsingNamespaceSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = UsingNamespaceSymbol { + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +// CV_LVARFLAGS: +const CV_LVARFLAG_ISPARAM: u16 = 0x01; +const CV_LVARFLAG_ADDRTAKEN: u16 = 0x02; +const CV_LVARFLAG_COMPGENX: u16 = 0x04; +const CV_LVARFLAG_ISAGGREGATE: u16 = 0x08; +const CV_LVARFLAG_ISALIASED: u16 = 0x10; +const CV_LVARFLAG_ISALIAS: u16 = 0x20; +const CV_LVARFLAG_ISRETVALUE: u16 = 0x40; +const CV_LVARFLAG_ISOPTIMIZEDOUT: u16 = 0x80; +const CV_LVARFLAG_ISENREG_GLOB: u16 = 0x100; +const CV_LVARFLAG_ISENREG_STAT: u16 = 0x200; + +/// Flags for a [`LocalSymbol`]. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct LocalVariableFlags { + /// Variable is a parameter. + pub isparam: bool, + /// Address is taken. + pub addrtaken: bool, + /// Variable is compiler generated. + pub compgenx: bool, + /// The symbol is splitted in temporaries, which are treated by compiler as independent + /// entities. + pub isaggregate: bool, + /// Variable has multiple simultaneous lifetimes. + pub isaliased: bool, + /// Represents one of the multiple simultaneous lifetimes. + pub isalias: bool, + /// Represents a function return value. + pub isretvalue: bool, + /// Variable has no lifetimes. + pub isoptimizedout: bool, + /// Variable is an enregistered global. + pub isenreg_glob: bool, + /// Variable is an enregistered static. + pub isenreg_stat: bool, +} + +impl<'t> TryFromCtx<'t, Endian> for LocalVariableFlags { + type Error = scroll::Error; + + fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { + let (value, size) = u16::try_from_ctx(this, le)?; + + let flags = Self { + isparam: value & CV_LVARFLAG_ISPARAM != 0, + addrtaken: value & CV_LVARFLAG_ADDRTAKEN != 0, + compgenx: value & CV_LVARFLAG_COMPGENX != 0, + isaggregate: value & CV_LVARFLAG_ISAGGREGATE != 0, + isaliased: value & CV_LVARFLAG_ISALIASED != 0, + isalias: value & CV_LVARFLAG_ISALIAS != 0, + isretvalue: value & CV_LVARFLAG_ISRETVALUE != 0, + isoptimizedout: value & CV_LVARFLAG_ISOPTIMIZEDOUT != 0, + isenreg_glob: value & CV_LVARFLAG_ISENREG_GLOB != 0, + isenreg_stat: value & CV_LVARFLAG_ISENREG_STAT != 0, + }; + + Ok((flags, size)) + } +} + +/// A local symbol in optimized code. +/// +/// Symbol kind `S_LOCAL`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct LocalSymbol<'t> { + /// The type of the symbol. + pub type_index: TypeIndex, + /// Flags for this symbol. + pub flags: LocalVariableFlags, + /// Name of the symbol. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for LocalSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = LocalSymbol { + type_index: buf.parse()?, + flags: buf.parse()?, + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +// https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L4456 +/// Flags of an [`ExportSymbol`]. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ExportSymbolFlags { + /// An exported constant. + pub constant: bool, + /// Exported data (e.g. a static variable). + pub data: bool, + /// A private symbol. + pub private: bool, + /// A symbol with no name. + pub no_name: bool, + /// Ordinal was explicitly assigned. + pub ordinal: bool, + /// This is a forwarder. + pub forwarder: bool, +} + +impl<'t> TryFromCtx<'t, Endian> for ExportSymbolFlags { + type Error = scroll::Error; + + fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { + let (value, size) = u16::try_from_ctx(this, le)?; + + let flags = Self { + constant: value & 0x01 != 0, + data: value & 0x02 != 0, + private: value & 0x04 != 0, + no_name: value & 0x08 != 0, + ordinal: value & 0x10 != 0, + forwarder: value & 0x20 != 0, + }; + + Ok((flags, size)) + } +} + +/// An exported symbol. +/// +/// Symbol kind `S_EXPORT`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ExportSymbol<'t> { + /// Ordinal of the symbol. + pub ordinal: u16, + /// Flags declaring the type of the exported symbol. + pub flags: ExportSymbolFlags, + /// The name of the exported symbol. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for ExportSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = ExportSymbol { + ordinal: buf.parse()?, + flags: buf.parse()?, + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// A label symbol. +/// +/// Symbol kind `S_LABEL32`, `S_LABEL16`, or `S_LABEL32_ST`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct LabelSymbol<'t> { + /// Code offset of the start of this label. + pub offset: PdbInternalSectionOffset, + /// Detailed flags of this label. + pub flags: ProcedureFlags, + /// Name of the symbol. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for LabelSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = LabelSymbol { + offset: buf.parse()?, + flags: buf.parse()?, + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// A block symbol. +/// +/// Symbol kind `S_BLOCK32`, or `S_BLOCK32_ST`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct BlockSymbol<'t> { + /// The parent scope that this block is nested in. + pub parent: SymbolIndex, + /// The end symbol of this block. + pub end: SymbolIndex, + /// The length of the block. + pub len: u32, + /// Code offset of the start of this label. + pub offset: PdbInternalSectionOffset, + /// The block name. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for BlockSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = BlockSymbol { + parent: buf.parse()?, + end: buf.parse()?, + len: buf.parse()?, + offset: buf.parse()?, + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// A register relative symbol. +/// +/// The address of the variable is the value in the register + offset (e.g. %EBP + 8). +/// +/// Symbol kind `S_REGREL32`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct RegisterRelativeSymbol<'t> { + /// The variable offset. + pub offset: i32, + /// The type of the variable. + pub type_index: TypeIndex, + /// The register this variable address is relative to. + pub register: Register, + /// The variable name. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for RegisterRelativeSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let symbol = RegisterRelativeSymbol { + offset: buf.parse()?, + type_index: buf.parse()?, + register: buf.parse()?, + name: parse_symbol_name(&mut buf, kind)?, + }; + + Ok((symbol, buf.pos())) + } +} + +/// Thunk adjustor +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ThunkAdjustor<'t> { + delta: u16, + target: RawString<'t>, +} + +/// A thunk kind +#[non_exhaustive] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ThunkKind<'t> { + /// Standard thunk + NoType, + /// "this" adjustor thunk with delta and target + Adjustor(ThunkAdjustor<'t>), + /// Virtual call thunk with table entry + VCall(u16), + /// pcode thunk + PCode, + /// thunk which loads the address to jump to via unknown means... + Load, + /// Unknown with ordinal value + Unknown(u8), +} + +/// A thunk symbol. +/// +/// Symbol kind `S_THUNK32`, or `S_THUNK32_ST`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ThunkSymbol<'t> { + /// The parent scope that this thunk is nested in. + pub parent: Option, + /// The end symbol of this thunk. + pub end: SymbolIndex, + /// The next symbol. + pub next: Option, + /// Code offset of the start of this label. + pub offset: PdbInternalSectionOffset, + /// The length of the thunk. + pub len: u16, + /// The kind of the thunk. + pub kind: ThunkKind<'t>, + /// The thunk name. + pub name: RawString<'t>, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for ThunkSymbol<'t> { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], kind: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let parent = parse_optional_index(&mut buf)?; + let end = buf.parse()?; + let next = parse_optional_index(&mut buf)?; + let offset = buf.parse()?; + let len = buf.parse()?; + let ord = buf.parse::()?; + let name = parse_symbol_name(&mut buf, kind)?; + + let kind = match ord { + 0 => ThunkKind::NoType, + 1 => ThunkKind::Adjustor(ThunkAdjustor { + delta: buf.parse::()?, + target: buf.parse_cstring()?, + }), + 2 => ThunkKind::VCall(buf.parse::()?), + 3 => ThunkKind::PCode, + 4 => ThunkKind::Load, + ord => ThunkKind::Unknown(ord), + }; + + let symbol = ThunkSymbol { + parent, + end, + next, + offset, + len, + kind, + name, + }; + + Ok((symbol, buf.pos())) + } +} + +// CV_SEPCODEFLAGS: +const CV_SEPCODEFLAG_IS_LEXICAL_SCOPE: u32 = 0x01; +const CV_SEPCODEFLAG_RETURNS_TO_PARENT: u32 = 0x02; + +/// Flags for a [`SeparatedCodeSymbol`]. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SeparatedCodeFlags { + /// S_SEPCODE doubles as lexical scope. + pub islexicalscope: bool, + /// code frag returns to parent. + pub returnstoparent: bool, +} + +impl<'t> TryFromCtx<'t, Endian> for SeparatedCodeFlags { + type Error = scroll::Error; + + fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { + let (value, size) = u32::try_from_ctx(this, le)?; + + let flags = Self { + islexicalscope: value & CV_SEPCODEFLAG_IS_LEXICAL_SCOPE != 0, + returnstoparent: value & CV_SEPCODEFLAG_RETURNS_TO_PARENT != 0, + }; + + Ok((flags, size)) + } +} + +/// A separated code symbol. +/// +/// Symbol kind `S_SEPCODE`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SeparatedCodeSymbol { + /// The parent scope that this block is nested in. + pub parent: SymbolIndex, + /// The end symbol of this block. + pub end: SymbolIndex, + /// The length of the block. + pub len: u32, + /// Flags for this symbol + pub flags: SeparatedCodeFlags, + /// Code offset of the start of the separated code. + pub offset: PdbInternalSectionOffset, + /// Parent offset. + pub parent_offset: PdbInternalSectionOffset, +} + +impl<'t> TryFromCtx<'t, SymbolKind> for SeparatedCodeSymbol { + type Error = Error; + + fn try_from_ctx(this: &'t [u8], _: SymbolKind) -> Result<(Self, usize)> { + let mut buf = ParseBuffer::from(this); + + let parent = buf.parse()?; + let end = buf.parse()?; + let len = buf.parse()?; + let flags = buf.parse()?; + let offset = buf.parse()?; + let parent_offset = buf.parse()?; + let section = buf.parse()?; + let parent_section = buf.parse()?; + + let symbol = Self { + parent, + end, + len, + flags, + offset: PdbInternalSectionOffset { offset, section }, + parent_offset: PdbInternalSectionOffset { + offset: parent_offset, + section: parent_section, + }, + }; + + Ok((symbol, buf.pos())) + } +} + +/// PDB symbol tables contain names, locations, and metadata about functions, global/static data, +/// constants, data types, and more. +/// +/// The `SymbolTable` holds a `SourceView` referencing the symbol table inside the PDB file. All the +/// data structures returned by a `SymbolTable` refer to that buffer. +/// +/// # Example +/// +/// ``` +/// # use pdb::FallibleIterator; +/// # +/// # fn test() -> pdb::Result { +/// let file = std::fs::File::open("fixtures/self/foo.pdb")?; +/// let mut pdb = pdb::PDB::open(file)?; +/// +/// let symbol_table = pdb.global_symbols()?; +/// let address_map = pdb.address_map()?; +/// +/// # let mut count: usize = 0; +/// let mut symbols = symbol_table.iter(); +/// while let Some(symbol) = symbols.next()? { +/// match symbol.parse() { +/// Ok(pdb::SymbolData::Public(data)) if data.function => { +/// // we found the location of a function! +/// let rva = data.offset.to_rva(&address_map).unwrap_or_default(); +/// println!("{} is {}", rva, data.name); +/// # count += 1; +/// } +/// _ => {} +/// } +/// } +/// +/// # Ok(count) +/// # } +/// # assert!(test().expect("test") > 2000); +/// ``` +#[cfg(feature = "alloc")] +#[derive(Debug)] +pub struct SymbolTable<'s>(Stream<'s>); + +#[cfg(feature = "alloc")] +impl<'s> SymbolTable<'s> { + /// Parses a symbol table from raw stream data. + pub(crate) fn new(stream: Stream<'s>) -> Self { + SymbolTable(stream) + } + + /// Returns an iterator that can traverse the symbol table in sequential order. + pub fn iter(&self) -> SymbolIter<'_> { + SymbolIter::new(self.0.parse_buffer()) + } + + /// Returns an iterator over symbols starting at the given index. + pub fn iter_at(&self, index: SymbolIndex) -> SymbolIter<'_> { + let mut iter = self.iter(); + iter.seek(index); + iter + } +} + +/// A `SymbolIter` iterates over a `SymbolTable`, producing `Symbol`s. +/// +/// Symbol tables are represented internally as a series of records, each of which have a length, a +/// type, and a type-specific field layout. Iteration performance is therefore similar to a linked +/// list. +#[derive(Debug)] +pub struct SymbolIter<'t> { + buf: ParseBuffer<'t>, +} + +impl<'t> SymbolIter<'t> { + pub(crate) fn new(buf: ParseBuffer<'t>) -> SymbolIter<'t> { + SymbolIter { buf } + } + + /// Move the iterator to the symbol referred to by `index`. + /// + /// This can be used to jump to the sibiling or parent of a symbol record. + pub fn seek(&mut self, index: SymbolIndex) { + self.buf.seek(index.0 as usize); + } + + /// Skip to the symbol referred to by `index`, returning the symbol. + /// + /// This can be used to jump to the sibiling or parent of a symbol record. Iteration continues + /// after that symbol. + /// + /// Note that the symbol may be located **before** the originating symbol, for instance when + /// jumping to the parent symbol. Take care not to enter an endless loop in this case. + pub fn skip_to(&mut self, index: SymbolIndex) -> Result>> { + self.seek(index); + self.next() + } +} + +impl<'t> FallibleIterator for SymbolIter<'t> { + type Item = Symbol<'t>; + type Error = Error; + + fn next(&mut self) -> Result> { + while !self.buf.is_empty() { + let index = SymbolIndex(self.buf.pos() as u32); + + // read the length of the next symbol + let symbol_length = self.buf.parse::()? as usize; + if symbol_length < 2 { + // this can't be correct + return Err(Error::SymbolTooShort); + } + + // grab the symbol itself + let data = self.buf.take(symbol_length)?; + let symbol = Symbol { index, data }; + + // skip over padding in the symbol table + match symbol.raw_kind() { + S_ALIGN | S_SKIP => continue, + _ => return Ok(Some(symbol)), + } + } + + Ok(None) + } +} \ No newline at end of file diff --git a/src/tpi/constants.rs b/src/tpi/constants.rs index 4033ab0..c20f81e 100644 --- a/src/tpi/constants.rs +++ b/src/tpi/constants.rs @@ -1,4 +1,4 @@ -// Copyright 2017 pdb Developers +// Copyright 2026 PDB Developers // // Licensed under the Apache License, Version 2.0, or the MIT license or the MIT license { + /// A built-in primitive type (int, float, char, etc.) Primitive(PrimitiveType), + + /// A class, struct, or interface type Class(ClassType<'t>), + + /// A non-static data member of a class/struct/union Member(MemberType<'t>), + + /// A member function (method) of a class MemberFunction(MemberFunctionType), + + /// A set of overloaded methods sharing the same name OverloadedMethod(OverloadedMethodType<'t>), + + /// A single method of a class Method(MethodType<'t>), + + /// A static data member of a class StaticMember(StaticMemberType<'t>), + + /// A nested type definition within another type Nested(NestedType<'t>), + + /// A base class from which another class derives BaseClass(BaseClassType), + + /// A virtual base class VirtualBaseClass(VirtualBaseClassType), + + /// A pointer to a virtual function table VirtualFunctionTablePointer(VirtualFunctionTablePointerType), + + /// A procedure/function type (signature only) Procedure(ProcedureType), + + /// A pointer type Pointer(PointerType), + + /// A type with modifiers (const, volatile, unaligned) Modifier(ModifierType), + + /// An enumeration type Enumeration(EnumerationType<'t>), + + /// An enumerator value within an enumeration Enumerate(EnumerateType<'t>), + + /// An array type Array(ArrayType), + + /// A union type Union(UnionType<'t>), + + /// A bitfield type Bitfield(BitfieldType), + + /// A list of fields/class members FieldList(FieldList<'t>), + + /// A list of arguments for a function ArgumentList(ArgumentList), + + /// A list of methods for a class MethodList(MethodList), } impl<'t> TypeData<'t> { + /// Return the name of this TypeData, if any - pub fn name(&self) -> Option> { + pub const fn name(&self) -> Option> { let name = match self { Self::Class(ClassType { ref name, .. }) | Self::Member(MemberType { ref name, .. }) @@ -56,1027 +108,332 @@ impl<'t> TypeData<'t> { Some(*name) } -} -/// Parse a type out of a `ParseBuffer`. -pub(crate) fn parse_type_data<'t>(buf: &mut ParseBuffer<'t>) -> Result> { - let leaf = buf.parse_u16()?; + /// Parse a type out of a `ParseBuffer`. + pub(crate) fn parse(buf: &mut ParseBuffer<'t>) -> Result { + let leaf = buf.parse_u16()?; + + match leaf { + // Basic types + // ----------- + + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1631-L1642 + LF_CLASS | LF_CLASS_ST | LF_STRUCTURE | LF_STRUCTURE_ST | LF_INTERFACE => Ok(TypeData::Class(ClassType::parse(buf, leaf)?)), + + // https://github.com/microsoft/microsoft-pdb/issues/50#issuecomment-737890766 + LF_STRUCTURE19 => { + let mut class = ClassType { + kind: ClassKind::Struct, + properties: TypeProperties::new(buf.parse_u32()? as u16), + fields: parse_optional_type_index(buf)?, + derived_from: parse_optional_type_index(buf)?, + vtable_shape: parse_optional_type_index(buf)?, + count: buf.parse_u16()?, + size: parse_unsigned(buf)?, + name: parse_string(leaf, buf)?, + unique_name: None, + }; + + if class.properties.has_unique_name() { + class.unique_name = Some(parse_string(leaf, buf)?); + } - match leaf { - // Basic types - // ----------- + Ok(TypeData::Class(class)) + } - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1631-L1642 - LF_CLASS | LF_CLASS_ST | LF_STRUCTURE | LF_STRUCTURE_ST | LF_INTERFACE => { - let mut class = ClassType { - kind: match leaf { - LF_CLASS | LF_CLASS_ST => ClassKind::Class, - LF_STRUCTURE | LF_STRUCTURE_ST => ClassKind::Struct, - LF_INTERFACE => ClassKind::Interface, - _ => unreachable!(), - }, - count: buf.parse_u16()?, - properties: TypeProperties(buf.parse_u16()?), - fields: parse_optional_type_index(buf)?, - derived_from: parse_optional_type_index(buf)?, - vtable_shape: parse_optional_type_index(buf)?, - size: parse_unsigned(buf)?, + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2580-L2586 + LF_MEMBER | LF_MEMBER_ST => Ok(TypeData::Member(MemberType { + attributes: FieldAttributes::new(buf.parse_u16()?), + field_type: buf.parse()?, + offset: parse_unsigned(buf)?, name: parse_string(leaf, buf)?, - unique_name: None, - }; + })), - if class.properties.has_unique_name() { - class.unique_name = Some(parse_string(leaf, buf)?); - } + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2699-L2714 + LF_NESTTYPE | LF_NESTTYPE_ST | LF_NESTTYPEEX | LF_NESTTYPEEX_ST => { + // These structs differ in their use of the first 16 bits + let raw_attr = match leaf { + LF_NESTTYPEEX | LF_NESTTYPEEX_ST => buf.parse_u16()?, + _ => { + // discard padding + buf.parse_u16()?; + // assume zero + 0 + } + }; - Ok(TypeData::Class(class)) - } + Ok(TypeData::Nested(NestedType { + attributes: FieldAttributes::new(raw_attr), + nested_type: buf.parse()?, + name: parse_string(leaf, buf)?, + })) + } - // https://github.com/microsoft/microsoft-pdb/issues/50#issuecomment-737890766 - LF_STRUCTURE19 => { - let mut class = ClassType { - kind: ClassKind::Struct, - properties: TypeProperties(buf.parse_u32()? as u16), - fields: parse_optional_type_index(buf)?, - derived_from: parse_optional_type_index(buf)?, - vtable_shape: parse_optional_type_index(buf)?, + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1801-L1811 + LF_MFUNCTION => Ok(TypeData::MemberFunction(MemberFunctionType { + return_type: buf.parse()?, + class_type: buf.parse()?, + this_pointer_type: parse_optional_type_index(buf)?, + attributes: FunctionAttributes::new(buf.parse_u16()?), + parameter_count: buf.parse_u16()?, + argument_list: buf.parse()?, + this_adjustment: buf.parse_u32()?, + })), + + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2650-L2655 + LF_METHOD | LF_METHOD_ST => Ok(TypeData::OverloadedMethod(OverloadedMethodType { count: buf.parse_u16()?, - size: parse_unsigned(buf)?, + method_list: buf.parse()?, name: parse_string(leaf, buf)?, - unique_name: None, - }; + })), - if class.properties.has_unique_name() { - class.unique_name = Some(parse_string(leaf, buf)?); + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2671-L2678 + LF_ONEMETHOD | LF_ONEMETHOD_ST => { + let attr = FieldAttributes::new(buf.parse_u16()?); + Ok(TypeData::Method(MethodType { + attributes: attr, + method_type: buf.parse()?, + vtable_offset: if attr.is_intro_virtual() { + Some(buf.parse_u32()?) + } else { + // yes, this is variable length + None + }, + name: parse_string(leaf, buf)?, + })) } - Ok(TypeData::Class(class)) - } - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2580-L2586 - LF_MEMBER | LF_MEMBER_ST => Ok(TypeData::Member(MemberType { - attributes: FieldAttributes(buf.parse_u16()?), - field_type: buf.parse()?, - offset: parse_unsigned(buf)?, - name: parse_string(leaf, buf)?, - })), - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2699-L2714 - LF_NESTTYPE | LF_NESTTYPE_ST | LF_NESTTYPEEX | LF_NESTTYPEEX_ST => { - // These structs differ in their use of the first 16 bits - let raw_attr = match leaf { - LF_NESTTYPEEX | LF_NESTTYPEEX_ST => buf.parse_u16()?, - _ => { - // discard padding - buf.parse_u16()?; - // assume zero - 0 - } - }; + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2499-L2505 + LF_BCLASS | LF_BINTERFACE => Ok(TypeData::BaseClass(BaseClassType { + kind: match leaf { + LF_BCLASS => ClassKind::Class, + LF_BINTERFACE => ClassKind::Interface, + _ => unreachable!(), + }, + attributes: FieldAttributes::new(buf.parse_u16()?), + base_class: buf.parse()?, + offset: parse_unsigned(buf)? as u32, + })), + + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2615-L2619 + LF_VFUNCTAB => { + // padding is supposed to be zero always, but… let's not check + buf.parse_u16()?; + Ok(TypeData::VirtualFunctionTablePointer( + VirtualFunctionTablePointerType { + table: buf.parse()?, + }, + )) + } - Ok(TypeData::Nested(NestedType { - attributes: FieldAttributes(raw_attr), - nested_type: buf.parse()?, + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2599-L2604 + LF_STMEMBER | LF_STMEMBER_ST => Ok(TypeData::StaticMember(StaticMemberType { + attributes: FieldAttributes::new(buf.parse_u16()?), + field_type: buf.parse()?, name: parse_string(leaf, buf)?, - })) - } + })), - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1801-L1811 - LF_MFUNCTION => Ok(TypeData::MemberFunction(MemberFunctionType { - return_type: buf.parse()?, - class_type: buf.parse()?, - this_pointer_type: parse_optional_type_index(buf)?, - attributes: FunctionAttributes(buf.parse_u16()?), - parameter_count: buf.parse_u16()?, - argument_list: buf.parse()?, - this_adjustment: buf.parse_u32()?, - })), + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1469-L1506 + LF_POINTER => { + let underlying_type = buf.parse()?; + let attributes = PointerAttributes::new(buf.parse()?); - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2650-L2655 - LF_METHOD | LF_METHOD_ST => Ok(TypeData::OverloadedMethod(OverloadedMethodType { - count: buf.parse_u16()?, - method_list: buf.parse()?, - name: parse_string(leaf, buf)?, - })), - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2671-L2678 - LF_ONEMETHOD | LF_ONEMETHOD_ST => { - let attr = FieldAttributes(buf.parse_u16()?); - Ok(TypeData::Method(MethodType { - attributes: attr, - method_type: buf.parse()?, - vtable_offset: if attr.is_intro_virtual() { - Some(buf.parse_u32()?) + let containing_class = if attributes.pointer_to_member() { + Some(buf.parse()?) } else { - // yes, this is variable length None - }, - name: parse_string(leaf, buf)?, - })) - } - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2499-L2505 - LF_BCLASS | LF_BINTERFACE => Ok(TypeData::BaseClass(BaseClassType { - kind: match leaf { - LF_BCLASS => ClassKind::Class, - LF_BINTERFACE => ClassKind::Interface, - _ => unreachable!(), - }, - attributes: FieldAttributes(buf.parse_u16()?), - base_class: buf.parse()?, - offset: parse_unsigned(buf)? as u32, - })), - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2615-L2619 - LF_VFUNCTAB => { - // padding is supposed to be zero always, but… let's not check - buf.parse_u16()?; - Ok(TypeData::VirtualFunctionTablePointer( - VirtualFunctionTablePointerType { - table: buf.parse()?, - }, - )) - } - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2599-L2604 - LF_STMEMBER | LF_STMEMBER_ST => Ok(TypeData::StaticMember(StaticMemberType { - attributes: FieldAttributes(buf.parse_u16()?), - field_type: buf.parse()?, - name: parse_string(leaf, buf)?, - })), - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1469-L1506 - LF_POINTER => { - let underlying_type = buf.parse()?; - let attributes = PointerAttributes(buf.parse()?); - - let containing_class = if attributes.pointer_to_member() { - Some(buf.parse()?) - } else { - None - }; - - Ok(TypeData::Pointer(PointerType { - underlying_type, - attributes, - containing_class, - })) - } - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1775-L1782 - LF_PROCEDURE => Ok(TypeData::Procedure(ProcedureType { - return_type: parse_optional_type_index(buf)?, - attributes: FunctionAttributes(buf.parse_u16()?), - parameter_count: buf.parse_u16()?, - argument_list: buf.parse()?, - })), - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1460-L1464 - LF_MODIFIER => { - let type_index = buf.parse()?; - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1090-L1095 - let flags = buf.parse_u16()?; - - Ok(TypeData::Modifier(ModifierType { - underlying_type: type_index, - constant: (flags & 0x01) != 0, - volatile: (flags & 0x02) != 0, - unaligned: (flags & 0x04) != 0, - })) - } + }; - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1752-L1759 - LF_ENUM | LF_ENUM_ST => { - let mut enumeration = EnumerationType { - count: buf.parse_u16()?, - properties: TypeProperties(buf.parse_u16()?), - underlying_type: buf.parse()?, - fields: buf.parse()?, - name: parse_string(leaf, buf)?, - unique_name: None, - }; - - if enumeration.properties.has_unique_name() { - enumeration.unique_name = Some(parse_string(leaf, buf)?); + Ok(TypeData::Pointer(PointerType { + underlying_type, + attributes, + containing_class, + })) } - Ok(TypeData::Enumeration(enumeration)) - } - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2683-L2688 - LF_ENUMERATE | LF_ENUMERATE_ST => Ok(TypeData::Enumerate(EnumerateType { - attributes: FieldAttributes(buf.parse_u16()?), - value: buf.parse()?, - name: parse_string(leaf, buf)?, - })), - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1564-L1579 - LF_ARRAY | LF_ARRAY_ST | LF_STRIDED_ARRAY => { - let element_type = buf.parse()?; - let indexing_type = buf.parse()?; - let stride: Option = if leaf == LF_STRIDED_ARRAY { - Some(buf.parse_u32()?) - } else { - None - }; - - let mut dimensions: Vec = Vec::new(); - - loop { - let dim = parse_unsigned(buf)?; - if dim > u64::from(u32::max_value()) { - return Err(Error::UnimplementedFeature("u64 array sizes")); - } - dimensions.push(dim as u32); + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1775-L1782 + LF_PROCEDURE => Ok(TypeData::Procedure(ProcedureType { + return_type: parse_optional_type_index(buf)?, + attributes: FunctionAttributes::new(buf.parse_u16()?), + parameter_count: buf.parse_u16()?, + argument_list: buf.parse()?, + })), + + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1460-L1464 + LF_MODIFIER => { + let type_index = buf.parse()?; + + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1090-L1095 + let flags = buf.parse_u16()?; + + Ok(TypeData::Modifier(ModifierType { + underlying_type: type_index, + constant: (flags & 0x01) != 0, + volatile: (flags & 0x02) != 0, + unaligned: (flags & 0x04) != 0, + })) + } - if buf.is_empty() { - // shouldn't run out here - return Err(Error::UnexpectedEof); + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1752-L1759 + LF_ENUM | LF_ENUM_ST => { + let mut enumeration = EnumerationType { + count: buf.parse_u16()?, + properties: TypeProperties::new(buf.parse_u16()?), + underlying_type: buf.parse()?, + fields: buf.parse()?, + name: parse_string(leaf, buf)?, + unique_name: None, + }; + + if enumeration.properties.has_unique_name() { + enumeration.unique_name = Some(parse_string(leaf, buf)?); } - if buf.peek_u8()? == 0x00 { - // end of dimensions - buf.parse_u8()?; - break; - } + Ok(TypeData::Enumeration(enumeration)) } - // eat any padding - parse_padding(buf)?; - - //println!("array: {:x}", buf); - //println!("dimensions: {:?}", dimensions); - - assert!(buf.is_empty()); - - Ok(TypeData::Array(ArrayType { - element_type, - indexing_type, - stride, - dimensions, - })) - } - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1657-L1664 - LF_UNION | LF_UNION_ST => { - let mut union = UnionType { - count: buf.parse_u16()?, - properties: TypeProperties(buf.parse_u16()?), - fields: buf.parse()?, - size: parse_unsigned(buf)?, + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2683-L2688 + LF_ENUMERATE | LF_ENUMERATE_ST => Ok(TypeData::Enumerate(EnumerateType { + attributes: FieldAttributes::new(buf.parse_u16()?), + value: buf.parse()?, name: parse_string(leaf, buf)?, - unique_name: None, - }; - - if union.properties.has_unique_name() { - union.unique_name = Some(parse_string(leaf, buf)?); - } - - Ok(TypeData::Union(union)) - } - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2164-L2170 - LF_BITFIELD => Ok(TypeData::Bitfield(BitfieldType { - underlying_type: buf.parse()?, - length: buf.parse_u8()?, - position: buf.parse_u8()?, - })), - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1819-L1823 - LF_VTSHAPE => { - // TODO - Err(Error::UnimplementedTypeKind(leaf)) - } + })), - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1825-L1837 - LF_VFTABLE => { - // TODO - Err(Error::UnimplementedTypeKind(leaf)) - } - - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2521-L2528 - LF_VBCLASS | LF_IVBCLASS => Ok(TypeData::VirtualBaseClass(VirtualBaseClassType { - direct: leaf == LF_VBCLASS, - attributes: FieldAttributes(buf.parse_u16()?), - base_class: buf.parse()?, - base_pointer: buf.parse()?, - base_pointer_offset: parse_unsigned(buf)? as u32, - virtual_base_offset: parse_unsigned(buf)? as u32, - })), - - // List types - // ---------- + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1564-L1579 + LF_ARRAY | LF_ARRAY_ST | LF_STRIDED_ARRAY => { + let element_type = buf.parse()?; + let indexing_type = buf.parse()?; + let stride: Option = if leaf == LF_STRIDED_ARRAY { + Some(buf.parse_u32()?) + } else { + None + }; - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2112-L2115 - LF_FIELDLIST => { - let mut fields: Vec> = Vec::new(); - let mut continuation: Option = None; + let mut dimensions: Vec = Vec::new(); - while !buf.is_empty() { - match buf.peek_u16()? { - LF_INDEX => { - // continuation record - // eat the leaf value - buf.parse_u16()?; + loop { + let dim = parse_unsigned(buf)?; + if dim > u64::from(u32::MAX) { + return Err(Error::UnimplementedFeature("u64 array sizes")); + } + dimensions.push(dim as u32); - // parse the TypeIndex where we continue - continuation = Some(buf.parse()?); + if buf.is_empty() { + // shouldn't run out here + return Err(Error::UnexpectedEof); } - _ => { - // other type - // recurse because recursion is endless fun because [STACK OVERFLOW] - fields.push(parse_type_data(buf)?); + + if buf.peek_u8()? == 0x00 { + // end of dimensions + buf.parse_u8()?; + break; } } - // consume any padding parse_padding(buf)?; - } - Ok(TypeData::FieldList(FieldList { - fields, - continuation, - })) - } + assert!(buf.is_empty()); - LF_ARGLIST => { - let count = buf.parse_u32()?; - let mut arglist: Vec = Vec::with_capacity(count as usize); - for _ in 0..count { - arglist.push(buf.parse()?); + Ok(TypeData::Array(ArrayType { + element_type, + indexing_type, + stride, + dimensions, + })) } - Ok(TypeData::ArgumentList(ArgumentList { arguments: arglist })) - } - - LF_METHODLIST => { - let mut methods: Vec = Vec::new(); - while !buf.is_empty() { - // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2131-L2136 - let attr = FieldAttributes(buf.parse_u16()?); - buf.parse_u16()?; // padding + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1657-L1664 + LF_UNION | LF_UNION_ST => { + let mut union = UnionType { + count: buf.parse_u16()?, + properties: TypeProperties::new(buf.parse_u16()?), + fields: buf.parse()?, + size: parse_unsigned(buf)?, + name: parse_string(leaf, buf)?, + unique_name: None, + }; + + if union.properties.has_unique_name() { + union.unique_name = Some(parse_string(leaf, buf)?); + } - methods.push(MethodListEntry { - attributes: attr, - method_type: buf.parse()?, - vtable_offset: if attr.is_intro_virtual() { - Some(buf.parse_u32()?) - } else { - None - }, - }); + Ok(TypeData::Union(union)) } - Ok(TypeData::MethodList(MethodList { methods })) - } - - _ => Err(Error::UnimplementedTypeKind(leaf)), - } -} - -#[inline] -fn parse_optional_type_index(buf: &mut ParseBuffer<'_>) -> Result> { - let index = buf.parse()?; - if index == TypeIndex(0) || index == TypeIndex(0xffff) { - Ok(None) - } else { - Ok(Some(index)) - } -} - -#[inline] -fn parse_string<'t>(leaf: u16, buf: &mut ParseBuffer<'t>) -> Result> { - if leaf > LF_ST_MAX { - buf.parse_cstring() - } else { - buf.parse_u8_pascal_string() - } -} - -#[inline] -fn parse_padding(buf: &mut ParseBuffer<'_>) -> Result<()> { - while !buf.is_empty() && buf.peek_u8()? >= 0xf0 { - let padding = buf.parse_u8()?; - if padding > 0xf0 { - // low four bits indicate amount of padding - // (don't ask me what 0xf0 means, then) - buf.take((padding & 0x0f) as usize - 1)?; - } - } - Ok(()) -} - -// https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/pdbdump/pdbdump.cpp#L2417-L2456 -fn parse_unsigned(buf: &mut ParseBuffer<'_>) -> Result { - let leaf = buf.parse_u16()?; - if leaf < LF_NUMERIC { - // the u16 directly encodes a value - return Ok(u64::from(leaf)); - } - - match leaf { - LF_CHAR => Ok(u64::from(buf.parse_u8()?)), - LF_USHORT => Ok(u64::from(buf.parse_u16()?)), - LF_ULONG => Ok(u64::from(buf.parse_u32()?)), - LF_UQUADWORD => Ok(buf.parse_u64()?), - _ => { - if cfg!(debug_assertions) { - unreachable!(); - } else { - Err(Error::UnexpectedNumericPrefix(leaf)) + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2164-L2170 + LF_BITFIELD => Ok(TypeData::Bitfield(BitfieldType { + underlying_type: buf.parse()?, + length: buf.parse_u8()?, + position: buf.parse_u8()?, + })), + + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1819-L1823 + LF_VTSHAPE => { + // TODO + Err(Error::UnimplementedTypeKind(leaf)) } - } - } -} - -/* -typedef struct CV_prop_t { -unsigned short packed :1; // true if structure is packed -unsigned short ctor :1; // true if constructors or destructors present -unsigned short ovlops :1; // true if overloaded operators present -unsigned short isnested :1; // true if this is a nested class -unsigned short cnested :1; // true if this class contains nested types -unsigned short opassign :1; // true if overloaded assignment (=) -unsigned short opcast :1; // true if casting methods -unsigned short fwdref :1; // true if forward reference (incomplete defn) -unsigned short scoped :1; // scoped definition -unsigned short hasuniquename :1; // true if there is a decorated name following the regular name -unsigned short sealed :1; // true if class cannot be used as a base class -unsigned short hfa :2; // CV_HFA_e -unsigned short intrinsic :1; // true if class is an intrinsic type (e.g. __m128d) -unsigned short mocom :2; // CV_MOCOM_UDT_e -} CV_prop_t; -*/ -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct TypeProperties(u16); -impl TypeProperties { - /// Indicates if a type is packed via `#pragma pack` or similar. - pub fn packed(self) -> bool { - self.0 & 0x0001 != 0 - } - - /// Indicates if a type has constructors or destructors. - pub fn constructors(self) -> bool { - self.0 & 0x0002 != 0 - } - - /// Indicates if a type has any overloaded operators. - pub fn overloaded_operators(self) -> bool { - self.0 & 0x0004 != 0 - } - /// Indicates if a type is a nested type, e.g. a `union` defined inside a `class`. - pub fn is_nested_type(self) -> bool { - self.0 & 0x0008 != 0 - } - - /// Indicates if a type contains nested types. - pub fn contains_nested_types(self) -> bool { - self.0 & 0x0010 != 0 - } - - /// Indicates if a class has overloaded the assignment operator. - pub fn overloaded_assignment(self) -> bool { - self.0 & 0x0020 != 0 - } - pub fn overloaded_casting(self) -> bool { - self.0 & 0x0040 != 0 - } - - /// Indicates if a type is a forward reference, i.e. an incomplete Type that serves as a - /// placeholder until a complete Type can be built. This is necessary for e.g. self-referential - /// data structures, but other more common declaration/definition idioms can cause forward - /// references too. - pub fn forward_reference(self) -> bool { - self.0 & 0x0080 != 0 - } - - pub fn scoped_definition(self) -> bool { - self.0 & 0x0100 != 0 - } - pub fn has_unique_name(self) -> bool { - self.0 & 0x0200 != 0 - } - pub fn sealed(self) -> bool { - self.0 & 0x0400 != 0 - } - pub fn hfa(self) -> u8 { - ((self.0 & 0x1800) >> 11) as u8 - } - pub fn intrinsic_type(self) -> bool { - self.0 & 0x1000 != 0 - } - pub fn mocom(self) -> u8 { - ((self.0 & 0x6000) >> 14) as u8 - } -} - -/* -typedef struct CV_fldattr_t { - unsigned short access :2; // access protection CV_access_t - unsigned short mprop :3; // method properties CV_methodprop_t - unsigned short pseudo :1; // compiler generated fcn and does not exist - unsigned short noinherit :1; // true if class cannot be inherited - unsigned short noconstruct :1; // true if class cannot be constructed - unsigned short compgenx :1; // compiler generated fcn and does exist - unsigned short sealed :1; // true if method cannot be overridden - unsigned short unused :6; // unused -} CV_fldattr_t; - -typedef enum CV_methodprop_e { - CV_MTvanilla = 0x00, - CV_MTvirtual = 0x01, - CV_MTstatic = 0x02, - CV_MTfriend = 0x03, - CV_MTintro = 0x04, - CV_MTpurevirt = 0x05, - CV_MTpureintro = 0x06 -} CV_methodprop_e; - -*/ -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct FieldAttributes(u16); -impl FieldAttributes { - #[inline] - pub fn access(self) -> u8 { - (self.0 & 0x0003) as u8 - } - #[inline] - fn method_properties(self) -> u8 { - ((self.0 & 0x001c) >> 2) as u8 - } - - #[inline] - pub fn is_static(self) -> bool { - self.method_properties() == 0x02 - } - - #[inline] - pub fn is_virtual(self) -> bool { - self.method_properties() == 0x01 - } - - #[inline] - pub fn is_pure_virtual(self) -> bool { - self.method_properties() == 0x05 - } - - #[inline] - pub fn is_intro_virtual(self) -> bool { - matches!(self.method_properties(), 0x04 | 0x06) - } - - // TODO -} - -#[allow(unused)] -#[repr(u8)] -enum Access { - None = 0x00, - Private = 0x01, - Protected = 0x02, - Public = 0x03, -} - -// CV_call_t and CV_funcattr_t are always found back to back -// Treat them as a combined u16 -/* -typedef struct CV_funcattr_t { - unsigned char cxxreturnudt :1; // true if C++ style ReturnUDT - unsigned char ctor :1; // true if func is an instance constructor - unsigned char ctorvbase :1; // true if func is an instance constructor of a class with virtual bases - unsigned char unused :5; // unused -} CV_funcattr_t; -*/ -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct FunctionAttributes(u16); -impl FunctionAttributes { - pub fn calling_convention(self) -> u8 { - (self.0 & 0xff) as u8 - } - pub fn cxx_return_udt(self) -> bool { - (self.0 & 0x0100) > 0 - } - pub fn is_constructor(self) -> bool { - (self.0 & 0x0200) > 0 - } - pub fn is_constructor_with_virtual_bases(self) -> bool { - (self.0 & 0x0400) > 0 - } -} - -/// The kind of a `PointerType`. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum PointerKind { - /// 16 bit pointer. - Near16, - /// 16:16 far pointer. - Far16, - /// 16:16 huge pointer. - Huge16, - /// Based on segment. - BaseSeg, - /// Based on value of base. - BaseVal, - /// Based on segment value of base. - BaseSegVal, - /// Based on address of base. - BaseAddr, - /// Based on segment address of base. - BaseSegAddr, - /// Based on type. - BaseType, - /// Based on self. - BaseSelf, - /// 32-bit pointer. - Near32, - /// 48-bit 16:32 pointer. - Far32, - /// 64-bit pointer. - Ptr64, -} - -/// The mode of a `PointerType`. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum PointerMode { - /// A regular pointer. - Pointer, - /// L-Value reference. - LValueReference, - /// Pointer to data member. - Member, - /// Pointer to member function. - MemberFunction, - /// R-Value reference. - RValueReference, -} - -/* -struct lfPointerAttr { - unsigned long ptrtype :5; // ordinal specifying pointer type (CV_ptrtype_e) - unsigned long ptrmode :3; // ordinal specifying pointer mode (CV_ptrmode_e) - unsigned long isflat32 :1; // true if 0:32 pointer - unsigned long isvolatile :1; // TRUE if volatile pointer - unsigned long isconst :1; // TRUE if const pointer - unsigned long isunaligned :1; // TRUE if unaligned pointer - unsigned long isrestrict :1; // TRUE if restricted pointer (allow agressive opts) - unsigned long size :6; // size of pointer (in bytes) - unsigned long ismocom :1; // TRUE if it is a MoCOM pointer (^ or %) - unsigned long islref :1; // TRUE if it is this pointer of member function with & ref-qualifier - unsigned long isrref :1; // TRUE if it is this pointer of member function with && ref-qualifier - unsigned long unused :10;// pad out to 32-bits for following cv_typ_t's -} attr; -*/ - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct PointerAttributes(u32); - -impl PointerAttributes { - /// Indicates the type of pointer. - pub fn pointer_kind(self) -> PointerKind { - match self.0 & 0x1f { - 0x00 => PointerKind::Near16, - 0x01 => PointerKind::Far16, - 0x02 => PointerKind::Huge16, - 0x03 => PointerKind::BaseSeg, - 0x04 => PointerKind::BaseVal, - 0x05 => PointerKind::BaseSegVal, - 0x06 => PointerKind::BaseAddr, - 0x07 => PointerKind::BaseSegAddr, - 0x08 => PointerKind::BaseType, - 0x09 => PointerKind::BaseSelf, - 0x0a => PointerKind::Near32, - 0x0b => PointerKind::Far32, - 0x0c => PointerKind::Ptr64, - _ => unreachable!(), - } - } - - /// Returns the mode of this pointer. - pub fn pointer_mode(self) -> PointerMode { - match (self.0 >> 5) & 0x7 { - 0x00 => PointerMode::Pointer, - 0x01 => PointerMode::LValueReference, - 0x02 => PointerMode::Member, - 0x03 => PointerMode::MemberFunction, - 0x04 => PointerMode::RValueReference, - _ => unreachable!(), - } - } - - /// Returns `true` if this points to a member (either data or function). - pub fn pointer_to_member(self) -> bool { - matches!( - self.pointer_mode(), - PointerMode::Member | PointerMode::MemberFunction - ) - } - - /// Returns `true` if this is a flat `0:32` pointer. - pub fn is_flat_32(self) -> bool { - (self.0 & 0x100) != 0 - } - - /// Returns `true` if this pointer is `volatile`. - pub fn is_volatile(self) -> bool { - (self.0 & 0x200) != 0 - } + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1825-L1837 + LF_VFTABLE => { + // TODO + Err(Error::UnimplementedTypeKind(leaf)) + } - /// Returns `true` if this pointer is `const`. - pub fn is_const(self) -> bool { - (self.0 & 0x400) != 0 - } + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2521-L2528 + LF_VBCLASS | LF_IVBCLASS => Ok(TypeData::VirtualBaseClass(VirtualBaseClassType { + direct: leaf == LF_VBCLASS, + attributes: FieldAttributes::new(buf.parse_u16()?), + base_class: buf.parse()?, + base_pointer: buf.parse()?, + base_pointer_offset: parse_unsigned(buf)? as u32, + virtual_base_offset: parse_unsigned(buf)? as u32, + })), + + // List types + // ---------- + + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2112-L2115 + LF_FIELDLIST => { + let mut fields: Vec> = Vec::new(); + let mut continuation: Option = None; + + loop { + if buf.is_empty() { + break; + } - /// Returns `true` if this pointer is unaligned. - pub fn is_unaligned(self) -> bool { - (self.0 & 0x800) != 0 - } + match buf.peek_u16()? { + LF_INDEX => { + // continuation record + // eat the leaf value + buf.parse_u16()?; + + // parse the TypeIndex where we continue + continuation = Some(buf.parse()?); + } + _ => { + fields.push(Self::parse(buf)?); + } + } - /// Returns `true` if this pointer is restricted (allow aggressive opts). - pub fn is_restrict(self) -> bool { - (self.0 & 0x1000) != 0 - } + // consume any padding + parse_padding(buf)?; + } - /// Is this a C++ reference, as opposed to a C pointer? - pub fn is_reference(self) -> bool { - matches!( - self.pointer_mode(), - PointerMode::LValueReference | PointerMode::RValueReference - ) - } + Ok(TypeData::FieldList(FieldList { + fields, + continuation, + })) + } - /// The size of the pointer in bytes. - pub fn size(self) -> u8 { - let size = ((self.0 >> 13) & 0x3f) as u8; - if size != 0 { - return size; - } + LF_ARGLIST => Ok(TypeData::ArgumentList(ArgumentList::parse(buf)?)), + LF_METHODLIST => Ok(TypeData::MethodList(MethodList::parse(buf)?)), - match self.pointer_kind() { - PointerKind::Near32 | PointerKind::Far32 => 4, - PointerKind::Ptr64 => 8, - _ => 0, + _ => Err(Error::UnimplementedTypeKind(leaf)), } } - - /// Returns `true` if this is a MoCOM pointer (`^` or `%`). - pub fn is_mocom(self) -> bool { - (self.0 & 0x40000) != 0 - } -} - -/// The information parsed from a type record with kind -/// `LF_CLASS`, `LF_CLASS_ST`, `LF_STRUCTURE`, `LF_STRUCTURE_ST` or `LF_INTERFACE`. -// https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1631 -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ClassType<'t> { - pub kind: ClassKind, - - /// Count of number of elements in this class - pub count: u16, - pub properties: TypeProperties, - - /// Type index which describes the fields of this class - pub fields: Option, - - /// Type index which describes the class from which this class is derived, if any - pub derived_from: Option, - - /// Type index which describes the shape of the vtable for this class, if any - pub vtable_shape: Option, - - pub size: u64, - - /// Display name of the class including type parameters. - pub name: RawString<'t>, - - /// Mangled name, if present. - pub unique_name: Option>, -} - -/// Used by `ClassType` to distinguish class-like concepts. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum ClassKind { - Class, - Struct, - Interface, -} - -/// The information parsed from a type record with kind `LF_MEMBER` or `LF_MEMBER_ST`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MemberType<'t> { - pub attributes: FieldAttributes, - pub field_type: TypeIndex, - pub offset: u64, - pub name: RawString<'t>, -} - -/// The information parsed from a type record with kind `LF_MFUNCTION`. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct MemberFunctionType { - pub return_type: TypeIndex, - pub class_type: TypeIndex, - pub this_pointer_type: Option, - pub attributes: FunctionAttributes, - pub parameter_count: u16, - pub argument_list: TypeIndex, - pub this_adjustment: u32, -} - -/// The information parsed from a type record with kind `LF_METHOD` or `LF_METHOD_ST`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct OverloadedMethodType<'t> { - pub count: u16, - pub method_list: TypeIndex, - pub name: RawString<'t>, -} - -/// The information parsed from a type record with kind `LF_ONEMETHOD` or `LF_ONEMETHOD_ST`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MethodType<'t> { - pub attributes: FieldAttributes, - pub method_type: TypeIndex, - pub vtable_offset: Option, - pub name: RawString<'t>, -} - -/// The information parsed from a type record with kind `LF_STMEMBER` or `LF_STMEMBER_ST`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct StaticMemberType<'t> { - pub attributes: FieldAttributes, - pub field_type: TypeIndex, - pub name: RawString<'t>, -} - -/// The information parsed from a type record with kind -/// `LF_NESTTYPE`, `LF_NESTTYPE_ST`, `LF_NESTTYPEEX`, or `LF_NESTTYPEEX_ST`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct NestedType<'t> { - pub attributes: FieldAttributes, - pub nested_type: TypeIndex, - pub name: RawString<'t>, -} - -/// The information parsed from a type record with kind `LF_BCLASS` or `LF_BINTERFACE`. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct BaseClassType { - pub kind: ClassKind, - pub attributes: FieldAttributes, - pub base_class: TypeIndex, - - /// Describes the offset of the base class within the class - pub offset: u32, -} - -/// The information parsed from a type record with kind `LF_VBCLASS` or `LF_IVBCLASS`. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct VirtualBaseClassType { - pub direct: bool, - pub attributes: FieldAttributes, - pub base_class: TypeIndex, - pub base_pointer: TypeIndex, - - pub base_pointer_offset: u32, - pub virtual_base_offset: u32, -} - -/// The information parsed from a type record with kind `LF_VFUNCTAB`. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct VirtualFunctionTablePointerType { - pub table: TypeIndex, -} - -/// The information parsed from a type record with kind `LF_PROCEDURE`. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct ProcedureType { - pub return_type: Option, - pub attributes: FunctionAttributes, - pub parameter_count: u16, - pub argument_list: TypeIndex, -} - -/// The information parsed from a type record with kind `LF_POINTER`. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct PointerType { - pub underlying_type: TypeIndex, - pub attributes: PointerAttributes, - pub containing_class: Option, -} - -/// The information parsed from a type record with kind `LF_MODIFIER`. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct ModifierType { - pub underlying_type: TypeIndex, - pub constant: bool, - pub volatile: bool, - pub unaligned: bool, } -/// The information parsed from a type record with kind `LF_ENUM` or `LF_ENUM_ST`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EnumerationType<'t> { - pub count: u16, - pub properties: TypeProperties, - pub underlying_type: TypeIndex, - pub fields: TypeIndex, - pub name: RawString<'t>, - pub unique_name: Option>, -} - -/// The information parsed from a type record with kind `LF_ENUMERATE` or `LF_ENUMERATE_ST`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EnumerateType<'t> { - pub attributes: FieldAttributes, - pub value: Variant, - pub name: RawString<'t>, -} - -/// The information parsed from a type record with kind -/// `LF_ARRAY`, `LF_ARRAY_ST` or `LF_STRIDED_ARRAY`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ArrayType { - pub element_type: TypeIndex, - pub indexing_type: TypeIndex, - pub stride: Option, - - /// Contains array dimensions as specified in the PDB. This is not what you expect: - /// - /// * Dimensions are specified in terms of byte sizes, not element counts. - /// * Multidimensional arrays aggregate the lower dimensions into the sizes of the higher - /// dimensions. - /// - /// Thus a `float[4][4]` has `dimensions: [16, 64]`. Determining array dimensions in terms - /// of element counts requires determining the size of the `element_type` and iteratively - /// dividing. - pub dimensions: Vec, -} - -/// The information parsed from a type record with kind `LF_UNION` or `LF_UNION_ST`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct UnionType<'t> { - pub count: u16, - pub properties: TypeProperties, - pub fields: TypeIndex, - pub size: u64, - pub name: RawString<'t>, - pub unique_name: Option>, -} - -/// The information parsed from a type record with kind `LF_BITFIELD`. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct BitfieldType { - pub underlying_type: TypeIndex, - pub length: u8, - pub position: u8, -} - -/// The information parsed from a type record with kind `LF_FIELDLIST`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct FieldList<'t> { - pub fields: Vec>, - - /// Sometimes fields can't all fit in a single FieldList, in which case the FieldList - /// refers to another FieldList in a chain. - pub continuation: Option, -} - -/// The information parsed from a type record with kind `LF_ARGLIST`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ArgumentList { - pub arguments: Vec, -} - -/// The information parsed from a type record with kind `LF_METHODLIST`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MethodList { - pub methods: Vec, -} - -/// An entry in a `MethodList`. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct MethodListEntry { - pub attributes: FieldAttributes, - pub method_type: TypeIndex, - pub vtable_offset: Option, -} - -/* -// arrays: -ParseBuf::from("\x03\x15\xa0\xdc\x0b\x00\x23\x00\x00\x00\x40\x00\x00\xf1").as_bytes(), -ParseBuf::from("\x03\x15\xa0\xdc\x0b\x00\x23\x00\x00\x00\x50\x00\x00\xf1").as_bytes(), -ParseBuf::from("\x03\x15\xa9\x12\x00\x00\x23\x00\x00\x00\x50\x02\x00\xf1").as_bytes(), -ParseBuf::from("\x03\x15\xac\x12\x00\x00\x23\x00\x00\x00\x6c\x00\x00\xf1").as_bytes(), -ParseBuf::from("\x03\x15\x14\x10\x00\x00\x23\x00\x00\x00\x80\x00\x00\xf1").as_bytes(), -ParseBuf::from("\x03\x15\x75\x00\x00\x00\x23\x00\x00\x00\x28\x00\x00\xf1").as_bytes(), -ParseBuf::from("\x03\x15\x14\x10\x00\x00\x23\x00\x00\x00\x70\x0e\x00\xf1").as_bytes(), -ParseBuf::from("\x03\x15\x31\x14\x00\x00\x23\x00\x00\x00\x04\x02\x00\xf1").as_bytes(), -ParseBuf::from("\x03\x15\x31\x14\x00\x00\x23\x00\x00\x00\x0e\x03\x00\xf1").as_bytes(), -ParseBuf::from("\x03\x15\x77\x13\x00\x00\x23\x00\x00\x00\x02\x80\xbd\xda\x00\xf3\xf2\xf1").as_bytes(), -ParseBuf::from("\x03\x15\xb7\x16\x00\x00\x23\x00\x00\x00\x28\x00\x00\xf1").as_bytes(), -ParseBuf::from("\x03\x15\x14\x10\x00\x00\x23\x00\x00\x00\x55\x00\x00\xf1").as_bytes(), -*/ - #[test] fn kind_1609() { let data = &[ @@ -1085,11 +442,11 @@ fn kind_1609() { ][..]; assert_eq!( - parse_type_data(&mut ParseBuffer::from(data)).expect("parse"), + TypeData::parse(&mut ParseBuffer::from(data)).expect("parse"), TypeData::Class(ClassType { kind: ClassKind::Struct, count: 2, - properties: TypeProperties(512), + properties: TypeProperties::new(512), fields: Some(TypeIndex(0x1016)), derived_from: None, vtable_shape: None, diff --git a/src/tpi/header.rs b/src/tpi/header.rs index 28a0a56..bb17d84 100644 --- a/src/tpi/header.rs +++ b/src/tpi/header.rs @@ -1,40 +1,71 @@ -// Copyright 2017 pdb Developers +// Copyright 2026 PDB Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. -use crate::common::*; +use crate::{PdbHeaderVersion, common::*}; -// OFFCB: +/// A slice descriptor referencing a portion of a stream. +/// +/// Contains an offset and size pair used to locate data within the TPI stream. +/// The offset is stored as a signed 32-bit value (matching the original PDB +/// implementation's "long" type), while the size is unsigned. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct Slice { - pub offset: i32, // technically a "long", but... 32 bits for life? + /// Offset into the stream (signed 32-bit, from the original PDB code) + pub offset: i32, + /// Size of the slice in bytes pub size: u32, } -// HDR: -// https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/dbi/tpi.h#L45 +impl Slice { + pub const fn default() -> Self { + Self { offset: 0, size: 0 } + } +} + +/// TPI (Type Provider Index) stream header. +/// +/// Contains metadata about the type information stream, including version +/// information, type index ranges, and hash table layout for fast type lookup. +/// +/// # Reference +/// +/// Based on the Microsoft PDB implementation: +/// #[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct Header { +pub struct TPIHeader { + /// TPI stream version number pub version: u32, + /// Size of this header in bytes pub header_size: u32, + /// First valid type index pub minimum_index: u32, + /// Last valid type index pub maximum_index: u32, + /// Size of the global type record buffer pub gprec_size: u32, + /// Stream containing the type hash table pub tpi_hash_stream: u16, + /// Stream containing hash table padding pub tpi_hash_pad_stream: u16, + /// Size of hash keys in bytes pub hash_key_size: u32, + /// Number of buckets in the hash table pub hash_bucket_size: u32, + /// Hash values array pub hash_values: Slice, + /// Type index offset table pub ti_off: Slice, - pub hash_adj: Slice, // "offcb of hash head list, maps (hashval,ti), where ti is the head of the hashval chain." + /// Hash chain heads: maps (hash value, type index) to the head of each hash chain + pub hash_adj: Slice, } -impl Header { - pub(crate) fn empty() -> Self { - let empty_slice = Slice { offset: 0, size: 0 }; +impl TPIHeader { + pub(crate) const fn empty() -> Self { + let empty_slice = Slice::default(); Self { version: 0, @@ -62,8 +93,19 @@ impl Header { return Ok(Self::empty()); } + let version: u32 = buf.parse()?; + + // Reject streams whose first 4 bytes aren't a recognized PDB stream version. + // This catches things like VC6's stream 4 (a hash table, not an IPI stream) + // which would otherwise be mis-parsed as a TPI header. + if matches!(PdbHeaderVersion::from(version), PdbHeaderVersion::OtherValue(_)) { + return Err(Error::InvalidTypeInformationHeader( + "unrecognized type information stream version", + )); + } + let header = Self { - version: buf.parse()?, + version, header_size: buf.parse()?, minimum_index: buf.parse()?, maximum_index: buf.parse()?, @@ -86,8 +128,6 @@ impl Header { }, }; - // we read 56 bytes - // make sure that's okay let bytes_read = buf.pos() as u32; if header.header_size < bytes_read { return Err(Error::InvalidTypeInformationHeader( @@ -99,10 +139,8 @@ impl Header { )); } - // consume anything else the header says belongs to the header buf.take((header.header_size - bytes_read) as usize)?; - // do some final validations if header.minimum_index < 4096 { return Err(Error::InvalidTypeInformationHeader( "minimum type index is < 4096", @@ -114,7 +152,6 @@ impl Header { )); } - // success Ok(header) } } diff --git a/src/tpi/id.rs b/src/tpi/id.rs index a70036d..bb65e40 100644 --- a/src/tpi/id.rs +++ b/src/tpi/id.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + use scroll::ctx::TryFromCtx; use crate::common::*; diff --git a/src/tpi/mod.rs b/src/tpi/mod.rs index e807342..7a84ea5 100644 --- a/src/tpi/mod.rs +++ b/src/tpi/mod.rs @@ -1,29 +1,51 @@ -// Copyright 2017 pdb Developers +// Copyright 2026 PDB Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. -use std::fmt; -use std::marker::PhantomData; -use std::result; +use core::fmt; +use core::marker::PhantomData; +use core::result; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; use crate::common::*; + +#[cfg(feature = "alloc")] use crate::msf::Stream; + use crate::FallibleIterator; pub(crate) mod constants; + +#[cfg(feature = "alloc")] mod data; + +mod utils; +mod types; mod header; + +#[cfg(feature = "alloc")] mod id; + mod primitive; +pub use self::types::*; + use self::header::*; + +#[cfg(feature = "alloc")] use self::primitive::type_data_for_primitive; +#[cfg(feature = "alloc")] pub use self::data::*; + +#[cfg(feature = "alloc")] pub use self::id::*; + pub use self::primitive::{Indirection, PrimitiveKind, PrimitiveType}; /// Zero-copy access to a PDB type or id stream. @@ -124,13 +146,15 @@ pub use self::primitive::{Indirection, PrimitiveKind, PrimitiveType}; /// # } /// # assert!(test().expect("test") > 8000); /// ``` +#[cfg(feature = "alloc")] #[derive(Debug)] pub struct ItemInformation<'s, I> { stream: Stream<'s>, - header: Header, + header: TPIHeader, _ph: PhantomData<&'s I>, } +#[cfg(feature = "alloc")] impl<'s, I> ItemInformation<'s, I> where I: ItemIndex, @@ -138,7 +162,7 @@ where /// Parses `TypeInformation` from raw stream data. pub(crate) fn parse(stream: Stream<'s>) -> Result { let mut buf = stream.parse_buffer(); - let header = Header::parse(&mut buf)?; + let header = TPIHeader::parse(&mut buf)?; let _ph = PhantomData; Ok(Self { stream, @@ -218,24 +242,34 @@ where /// Returns this item's index. /// /// Depending on the stream, either a [`TypeIndex`] or [`IdIndex`]. - pub fn index(&self) -> I { + pub const fn index(&self) -> I { self.index } /// Returns the the binary data length in the on-disk format. /// /// Items are prefixed by a 16-bit length number, which is not included in this length. - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.data.len() } /// Returns whether this items's data is empty. /// /// Items are prefixed by a 16-bit length number, which is not included in this operation. - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.data.is_empty() } + /// Returns the parsed leaf kind of this item. + /// + /// This is a typed view of [`Self::raw_kind`]. Unrecognized leaves are + /// preserved as [`LeafKind::Unknown`] rather than being discarded, so this + /// method never fails. + #[inline] + pub fn kind(&self) -> LeafKind { + LeafKind::from(self.raw_kind()) + } + /// Returns the identifier of the kind of data stored by this this `Item`. /// /// As a special case, if this is a primitive [`Type`], this function will return `0xffff`. @@ -309,6 +343,7 @@ where /// A `shift` of 2 or 3 is likely appropriate for most workloads. 500K items would require 1 MB or /// 500 KB of memory respectively, and lookups -- though indirect -- would still usually need only /// one or two 64-byte cache lines. +#[cfg(feature = "alloc")] #[derive(Debug)] pub struct ItemFinder<'t, I> { buffer: ParseBuffer<'t>, @@ -319,6 +354,7 @@ pub struct ItemFinder<'t, I> { _ph: PhantomData<&'t I>, } +#[cfg(feature = "alloc")] impl<'t, I> ItemFinder<'t, I> where I: ItemIndex, @@ -381,7 +417,7 @@ where let (vec_index, iteration_count) = self.resolve(iterator.index); if iteration_count == 0 && vec_index == self.positions.len() { let pos = iterator.buf.pos(); - assert!(pos < u32::max_value() as usize); + assert!(pos < u32::MAX as usize); self.positions.push(pos as u32); } } @@ -459,22 +495,17 @@ where return Ok(None); } - // read the length of the next type let length = self.buf.parse_u16()? as usize; - // validate if length < 2 { // this can't be correct return Err(Error::TypeTooShort); } - // grab the type itself let type_buf = self.buf.take(length)?; let index = self.index; - self.index += 1; - // Done Ok(Some(Item { index: I::from(index), data: type_buf, @@ -486,12 +517,14 @@ where /// /// This stream exposes types, the variants of which are enumerated by [`TypeData`]. See /// [`ItemInformation`] for more information on accessing types. +#[cfg(feature = "alloc")] pub type TypeInformation<'s> = ItemInformation<'s, TypeIndex>; /// In-memory index for efficient random-access of [`Type`]s by index. /// /// `TypeFinder` can be obtained via [`TypeInformation::finder`](ItemInformation::finder). See /// [`ItemFinder`] for more information. +#[cfg(feature = "alloc")] pub type TypeFinder<'t> = ItemFinder<'t, TypeIndex>; /// An iterator over [`Type`]s returned by [`TypeInformation::iter`](ItemInformation::iter). @@ -500,6 +533,7 @@ pub type TypeIter<'t> = ItemIter<'t, TypeIndex>; /// Information on a primitive type, class, or procedure. pub type Type<'t> = Item<'t, TypeIndex>; +#[cfg(feature = "alloc")] impl<'t> Item<'t, TypeIndex> { /// Parse this `Type` into `TypeData`. /// @@ -514,7 +548,7 @@ impl<'t> Item<'t, TypeIndex> { type_data_for_primitive(self.index) } else { let mut buf = ParseBuffer::from(self.data); - parse_type_data(&mut buf) + TypeData::parse(&mut buf) } } } @@ -523,12 +557,14 @@ impl<'t> Item<'t, TypeIndex> { /// /// This stream exposes types, the variants of which are enumerated by [`IdData`]. See /// [`ItemInformation`] for more information on accessing types. +#[cfg(feature = "alloc")] pub type IdInformation<'s> = ItemInformation<'s, IdIndex>; /// In-memory index for efficient random-access of [`Id`]s by index. /// /// `IdFinder` can be obtained via [`IdInformation::finder`](ItemInformation::finder). See /// [`ItemFinder`] for more information. +#[cfg(feature = "alloc")] pub type IdFinder<'t> = ItemFinder<'t, IdIndex>; /// An iterator over [`Id`]s returned by [`IdInformation::iter`](ItemInformation::iter). @@ -537,6 +573,7 @@ pub type IdIter<'t> = ItemIter<'t, IdIndex>; /// Information on an inline function, build infos or source references. pub type Id<'t> = Item<'t, IdIndex>; +#[cfg(feature = "alloc")] impl<'t> Item<'t, IdIndex> { /// Parse this `Id` into `IdData`. /// diff --git a/src/tpi/primitive.rs b/src/tpi/primitive.rs index da45e56..297614a 100644 --- a/src/tpi/primitive.rs +++ b/src/tpi/primitive.rs @@ -1,4 +1,4 @@ -// Copyright 2017 pdb Developers +// Copyright 2026 PDB Developers // // Licensed under the Apache License, Version 2.0, or the MIT license Result> { // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L326-L750 diff --git a/src/tpi/types.rs b/src/tpi/types.rs new file mode 100644 index 0000000..e7dcfdd --- /dev/null +++ b/src/tpi/types.rs @@ -0,0 +1,1157 @@ +use core::ops::Deref; + +use crate::{tpi::{constants::*, utils::*}, *}; + +/* +typedef struct CV_prop_t { +unsigned short packed :1; // true if structure is packed +unsigned short ctor :1; // true if constructors or destructors present +unsigned short ovlops :1; // true if overloaded operators present +unsigned short isnested :1; // true if this is a nested class +unsigned short cnested :1; // true if this class contains nested types +unsigned short opassign :1; // true if overloaded assignment (=) +unsigned short opcast :1; // true if casting methods +unsigned short fwdref :1; // true if forward reference (incomplete defn) +unsigned short scoped :1; // scoped definition +unsigned short hasuniquename :1; // true if there is a decorated name following the regular name +unsigned short sealed :1; // true if class cannot be used as a base class +unsigned short hfa :2; // CV_HFA_e +unsigned short intrinsic :1; // true if class is an intrinsic type (e.g. __m128d) +unsigned short mocom :2; // CV_MOCOM_UDT_e +} CV_prop_t; +*/ +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct TypeProperties(u16); + +impl TypeProperties { + pub const fn new(value: u16) -> Self { + Self(value) + } + + /// Indicates if a type is packed via `#pragma pack` or similar. + pub const fn packed(self) -> bool { + self.0 & 0x0001 != 0 + } + + /// Indicates if a type has constructors or destructors. + pub const fn constructors(self) -> bool { + self.0 & 0x0002 != 0 + } + + /// Indicates if a type has any overloaded operators. + pub const fn overloaded_operators(self) -> bool { + self.0 & 0x0004 != 0 + } + + /// Indicates if a type is a nested type, e.g. a `union` defined inside a `class`. + pub const fn is_nested_type(self) -> bool { + self.0 & 0x0008 != 0 + } + + /// Indicates if a type contains nested types. + pub const fn contains_nested_types(self) -> bool { + self.0 & 0x0010 != 0 + } + + /// Indicates if a class has overloaded the assignment operator. + pub const fn overloaded_assignment(self) -> bool { + self.0 & 0x0020 != 0 + } + + pub const fn overloaded_casting(self) -> bool { + self.0 & 0x0040 != 0 + } + + /// Indicates if a type is a forward reference, i.e. an incomplete Type that serves as a + /// placeholder until a complete Type can be built. This is necessary for e.g. self-referential + /// data structures, but other more common declaration/definition idioms can cause forward + /// references too. + pub const fn forward_reference(self) -> bool { + self.0 & 0x0080 != 0 + } + + pub const fn scoped_definition(self) -> bool { + self.0 & 0x0100 != 0 + } + + pub const fn has_unique_name(self) -> bool { + self.0 & 0x0200 != 0 + } + + pub const fn sealed(self) -> bool { + self.0 & 0x0400 != 0 + } + + pub const fn hfa(self) -> u8 { + ((self.0 & 0x1800) >> 11) as u8 + } + + pub const fn intrinsic_type(self) -> bool { + self.0 & 0x1000 != 0 + } + + pub const fn mocom(self) -> u8 { + ((self.0 & 0x6000) >> 14) as u8 + } +} + +/// Attribute bits for a class member (field or method). +/// +/// This corresponds to the `CV_fldattr_t` bitfield used throughout CodeView +/// type records. It packs an access specifier, a method-property code, and +/// several boolean flags into a single `u16`. +/// +/// # Bit layout +/// +/// ```text +/// 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +/// └──── unused ────┘ se cg nc ni ps │ mprop │ access +/// ``` +/// +/// | Field | Bits | Meaning | +/// |-------|------|---------| +/// | `access` | 0-1 | Access specifier (see [`FieldAccess`]) | +/// | `mprop` | 2-4 | Method property (see [`FieldMethodProperty`]) | +/// | `pseudo` | 5 | Compiler-generated function that does not exist | +/// | `noinherit` | 6 | Class cannot be inherited from | +/// | `noconstruct` | 7 | Class cannot be constructed | +/// | `compgenx` | 8 | Compiler-generated function that *does* exist | +/// | `sealed` | 9 | Method cannot be overridden | +/// | `unused` | 10-15 | Reserved | +/// +/// The raw field bits are only meaningful in context: for a data member, +/// only `access` and the flag bits apply; for a method, `mprop` is +/// meaningful as well. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct FieldAttributes(u16); + +impl FieldAttributes { + + pub const fn new(value: u16) -> Self { + Self(value) + } + + pub const fn access(self) -> u8 { + (self.0 & 0x0003) as u8 + } + + #[inline] + const fn method_properties(self) -> u8 { + ((self.0 & 0x001c) >> 2) as u8 + } + + #[inline] + pub const fn is_static(self) -> bool { + self.method_properties() == 0x02 + } + + #[inline] + pub const fn is_virtual(self) -> bool { + self.method_properties() == 0x01 + } + + #[inline] + pub const fn is_pure_virtual(self) -> bool { + self.method_properties() == 0x05 + } + + #[inline] + pub const fn is_intro_virtual(self) -> bool { + matches!(self.method_properties(), 0x04 | 0x06) + } + + // TODO +} + +#[allow(unused)] +#[repr(u8)] +enum Access { + None = 0x00, + Private = 0x01, + Protected = 0x02, + Public = 0x03, +} + +// CV_call_t and CV_funcattr_t are always found back to back +// Treat them as a combined u16 +/* +typedef struct CV_funcattr_t { + unsigned char cxxreturnudt :1; // true if C++ style ReturnUDT + unsigned char ctor :1; // true if func is an instance constructor + unsigned char ctorvbase :1; // true if func is an instance constructor of a class with virtual bases + unsigned char unused :5; // unused +} CV_funcattr_t; +*/ +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct FunctionAttributes(u16); + +impl FunctionAttributes { + + pub const fn new(value: u16) -> Self { + Self(value) + } + + pub const fn calling_convention(self) -> u8 { + (self.0 & 0xff) as u8 + } + + pub const fn cxx_return_udt(self) -> bool { + (self.0 & 0x0100) > 0 + } + + pub const fn is_constructor(self) -> bool { + (self.0 & 0x0200) > 0 + } + + pub const fn is_constructor_with_virtual_bases(self) -> bool { + (self.0 & 0x0400) > 0 + } +} + +/// The kind of a `PointerType`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PointerKind { + /// 16 bit pointer. + Near16, + /// 16:16 far pointer. + Far16, + /// 16:16 huge pointer. + Huge16, + /// Based on segment. + BaseSeg, + /// Based on value of base. + BaseVal, + /// Based on segment value of base. + BaseSegVal, + /// Based on address of base. + BaseAddr, + /// Based on segment address of base. + BaseSegAddr, + /// Based on type. + BaseType, + /// Based on self. + BaseSelf, + /// 32-bit pointer. + Near32, + /// 48-bit 16:32 pointer. + Far32, + /// 64-bit pointer. + Ptr64, +} + +/// The mode of a `PointerType`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PointerMode { + /// A regular pointer. + Pointer, + /// L-Value reference. + LValueReference, + /// Pointer to data member. + Member, + /// Pointer to member function. + MemberFunction, + /// R-Value reference. + RValueReference, +} + +/// Attribute bits for a pointer type, parsed from an `LF_POINTER` record. +/// +/// This corresponds to the `lfPointerAttr` bitfield in CodeView. It packs a +/// pointer kind, a pointer mode, several qualifier flags, and the pointer's +/// size into a single `u32`. +/// +/// # Bit layout +/// +/// ```text +/// 31 22 21 20 19 18 17 16 15 6 5 3 2 0 +/// └── unused (10) ───┘ lr lf mm ur cn vl f │ size │ ptrmode │ ptrtype +/// ``` +/// +/// | Bits | Field | Meaning | +/// |------|-------|---------| +/// | 0-4 | `ptrtype` | Pointer kind (near, far, based, etc.) — see [`PointerKind`] | +/// | 5-7 | `ptrmode` | How the pointer is used (regular, reference, member) — see [`PointerMode`] | +/// | 8 | `isflat32` | 0:32 flat pointer | +/// | 9 | `isvolatile` | `volatile` qualifier | +/// | 10 | `isconst` | `const` qualifier | +/// | 11 | `isunaligned` | Unaligned pointer | +/// | 12 | `isrestrict` | `__restrict` (allows aggressive optimizations) | +/// | 13-18 | `size` | Pointer size in bytes (0 means "use the kind's natural size") | +/// | 19 | `ismocom` | MoCOM pointer (`^` or `%`) | +/// | 20 | `islref` | `&` ref-qualifier on a member function's `this` pointer | +/// | 21 | `isrref` | `&&` ref-qualifier on a member function's `this` pointer | +/// | 22-31 | unused | Padding, so the record is 32-bit aligned | +/// +/// # Size semantics +/// +/// The `size` field is only 6 bits, so it can only express values up to 63. +/// Historically, linkers wrote the actual byte size here when it fit. For +/// modern targets where the natural size is implied by the pointer kind +/// (near = 4, far = 4, 64-bit = 8), the field is often left as 0 and callers +/// must fall back on [`PointerAttributes::size`], which handles both cases. +/// +/// # References +/// +/// - [cvinfo.h, `lfPointerAttr`](https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1480) +/// - [LLVM: CodeView Pointer Records](https://llvm.org/docs/CodeView/CodeView.html) +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PointerAttributes(u32); + +impl PointerAttributes { + + pub const fn new(value: u32) -> Self { + Self(value) + } + + /// Indicates the type of pointer. + pub const fn pointer_kind(self) -> PointerKind { + match self.0 & 0x1f { + 0x00 => PointerKind::Near16, + 0x01 => PointerKind::Far16, + 0x02 => PointerKind::Huge16, + 0x03 => PointerKind::BaseSeg, + 0x04 => PointerKind::BaseVal, + 0x05 => PointerKind::BaseSegVal, + 0x06 => PointerKind::BaseAddr, + 0x07 => PointerKind::BaseSegAddr, + 0x08 => PointerKind::BaseType, + 0x09 => PointerKind::BaseSelf, + 0x0a => PointerKind::Near32, + 0x0b => PointerKind::Far32, + 0x0c => PointerKind::Ptr64, + _ => unreachable!(), + } + } + + /// Returns the mode of this pointer. + pub const fn pointer_mode(self) -> PointerMode { + match (self.0 >> 5) & 0x7 { + 0x00 => PointerMode::Pointer, + 0x01 => PointerMode::LValueReference, + 0x02 => PointerMode::Member, + 0x03 => PointerMode::MemberFunction, + 0x04 => PointerMode::RValueReference, + _ => unreachable!(), + } + } + + /// Returns `true` if this points to a member (either data or function). + pub const fn pointer_to_member(self) -> bool { + matches!( + self.pointer_mode(), + PointerMode::Member | PointerMode::MemberFunction + ) + } + + /// Returns `true` if this is a flat `0:32` pointer. + pub const fn is_flat_32(self) -> bool { + (self.0 & 0x100) != 0 + } + + /// Returns `true` if this pointer is `volatile`. + pub const fn is_volatile(self) -> bool { + (self.0 & 0x200) != 0 + } + + /// Returns `true` if this pointer is `const`. + pub const fn is_const(self) -> bool { + (self.0 & 0x400) != 0 + } + + /// Returns `true` if this pointer is unaligned. + pub fn is_unaligned(self) -> bool { + (self.0 & 0x800) != 0 + } + + /// Returns `true` if this pointer is restricted (allow aggressive opts). + pub const fn is_restrict(self) -> bool { + (self.0 & 0x1000) != 0 + } + + /// Is this a C++ reference, as opposed to a C pointer? + pub const fn is_reference(self) -> bool { + matches!( + self.pointer_mode(), + PointerMode::LValueReference | PointerMode::RValueReference + ) + } + + /// The size of the pointer in bytes. + pub const fn size(self) -> u8 { + let size = ((self.0 >> 13) & 0x3f) as u8; + if size != 0 { + return size; + } + + match self.pointer_kind() { + PointerKind::Near32 | PointerKind::Far32 => 4, + PointerKind::Ptr64 => 8, + _ => 0, + } + } + + /// Returns `true` if this is a MoCOM pointer (`^` or `%`). + pub const fn is_mocom(self) -> bool { + (self.0 & 0x40000) != 0 + } +} + +/// A class, struct, or interface type parsed from a `LF_CLASS`, `LF_CLASS_ST`, +/// `LF_STRUCTURE`, `LF_STRUCTURE_ST`, or `LF_INTERFACE` record. +/// +/// These five leaf kinds all describe the same underlying shape — a +/// user-defined type with members, an optional base class, and an optional +/// vtable — and differ only in how the source language declared them +/// (`class` vs `struct` vs COM `interface`) and how the name was encoded +/// (regular vs "ST" / "structured" variants). +/// +/// # References +/// +/// - [cvinfo.h, LF_CLASS and friends](https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L1631) +/// - [LLVM: CodeView Type Records — Classes](https://llvm.org/docs/CodeView/CodeView.html) +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ClassType<'t> { + /// Whether this type was declared as a `class`, `struct`, or `interface`. + /// + /// This is derived from the leaf kind, not from any field in the record + /// itself; the on-disk format is otherwise identical for all three. + pub kind: ClassKind, + + /// Number of top-level elements in this class. + /// + /// This includes all entries in the associated field list: data members, + /// methods, static members, nested types, and base classes. It does *not* + /// include members inherited from base classes. If the field list is + /// chained via [`FieldList::continuation`](crate::FieldList::continuation), + /// this count covers the total across all chained lists. + pub count: u16, + + /// Bitfield of properties describing the class. + /// + /// Use the accessors on [`TypeProperties`] to read individual flags + /// (packed, scoped, sealed, has unique name, etc.) rather than inspecting + /// the raw value. + pub properties: TypeProperties, + + /// Type index of the `LF_FIELDLIST` describing this class's members. + /// + /// `None` for forward declarations and for classes with no members. + pub fields: Option, + + /// Type index of the base class this class derives from, if any. + /// + /// This points at another `ClassType` record (or a modifier wrapping one). + /// For multiple inheritance, only one base is recorded here; additional + /// bases are represented as [`BaseClassType`](crate::BaseClassType) + /// entries inside the field list. + pub derived_from: Option, + + /// Type index describing the layout of this class's vtable, if any. + /// + /// Only present for polymorphic classes. The referenced record is + /// typically an `LF_VTSHAPE` describing the vtable slot kinds (near, + /// far, this-adjusting, etc.). You rarely need this unless you are + /// reconstructing the vtable layout. + pub vtable_shape: Option, + + /// Total size of this class in bytes, including base classes and padding. + /// + /// This is `0` for forward declarations and for classes whose size the + /// compiler did not record. + pub size: u64, + + /// Display name of the class, including any template parameters. + /// + /// This is the name as written in source, e.g. `MyClass` or + /// `std::vector`. It may not be valid UTF-8; use + /// [`RawString::to_string`] to decode it lossily. + pub name: RawString<'t>, + + /// Mangled name of the class, if present. + /// + /// This is the linker-level name (e.g. `.?AVMyClass@@` on MSVC) used for + /// symbol resolution. It is present only when the + /// [`TypeProperties::has_unique_name`] flag is set on + /// [`properties`](Self::properties). + pub unique_name: Option>, +} + +impl<'t> ClassType<'t> { + pub fn parse(buf: &mut ParseBuffer<'t>, leaf: u16) -> Result { + let mut class = Self { + kind: match leaf { + LF_CLASS | LF_CLASS_ST => ClassKind::Class, + LF_STRUCTURE | LF_STRUCTURE_ST => ClassKind::Struct, + LF_INTERFACE => ClassKind::Interface, + _ => unreachable!(), + }, + count: buf.parse_u16()?, + properties: TypeProperties::new(buf.parse_u16()?), + fields: parse_optional_type_index(buf)?, + derived_from: parse_optional_type_index(buf)?, + vtable_shape: parse_optional_type_index(buf)?, + size: parse_unsigned(buf)?, + name: parse_string(leaf, buf)?, + unique_name: None, + }; + + if class.properties.has_unique_name() { + class.unique_name = Some(parse_string(leaf, buf)?); + } + + Ok(class) + } +} + +/// Used by `ClassType` to distinguish class-like concepts. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ClassKind { + Class, + Struct, + Interface, +} + +/// The information parsed from a type record with kind `LF_MEMBER` or `LF_MEMBER_ST`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MemberType<'t> { + pub attributes: FieldAttributes, + pub field_type: TypeIndex, + pub offset: u64, + pub name: RawString<'t>, +} + +/// The information parsed from a type record with kind `LF_MFUNCTION`. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct MemberFunctionType { + pub return_type: TypeIndex, + pub class_type: TypeIndex, + pub this_pointer_type: Option, + pub attributes: FunctionAttributes, + pub parameter_count: u16, + pub argument_list: TypeIndex, + pub this_adjustment: u32, +} + +/// The information parsed from a type record with kind `LF_METHOD` or `LF_METHOD_ST`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OverloadedMethodType<'t> { + pub count: u16, + pub method_list: TypeIndex, + pub name: RawString<'t>, +} + +/// The information parsed from a type record with kind `LF_ONEMETHOD` or `LF_ONEMETHOD_ST`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MethodType<'t> { + pub attributes: FieldAttributes, + pub method_type: TypeIndex, + pub vtable_offset: Option, + pub name: RawString<'t>, +} + +/// The information parsed from a type record with kind `LF_STMEMBER` or `LF_STMEMBER_ST`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StaticMemberType<'t> { + pub attributes: FieldAttributes, + pub field_type: TypeIndex, + pub name: RawString<'t>, +} + +/// The information parsed from a type record with kind +/// `LF_NESTTYPE`, `LF_NESTTYPE_ST`, `LF_NESTTYPEEX`, or `LF_NESTTYPEEX_ST`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NestedType<'t> { + pub attributes: FieldAttributes, + pub nested_type: TypeIndex, + pub name: RawString<'t>, +} + +/// The information parsed from a type record with kind `LF_BCLASS` or `LF_BINTERFACE`. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct BaseClassType { + pub kind: ClassKind, + pub attributes: FieldAttributes, + pub base_class: TypeIndex, + + /// Describes the offset of the base class within the class + pub offset: u32, +} + +/// The information parsed from a type record with kind `LF_VBCLASS` or `LF_IVBCLASS`. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct VirtualBaseClassType { + pub direct: bool, + pub attributes: FieldAttributes, + pub base_class: TypeIndex, + pub base_pointer: TypeIndex, + + pub base_pointer_offset: u32, + pub virtual_base_offset: u32, +} + +/// The information parsed from a type record with kind `LF_VFUNCTAB`. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct VirtualFunctionTablePointerType { + pub table: TypeIndex, +} + +/// The information parsed from a type record with kind `LF_PROCEDURE`. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ProcedureType { + pub return_type: Option, + pub attributes: FunctionAttributes, + pub parameter_count: u16, + pub argument_list: TypeIndex, +} + +/// The information parsed from a type record with kind `LF_POINTER`. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct PointerType { + pub underlying_type: TypeIndex, + pub attributes: PointerAttributes, + pub containing_class: Option, +} + +/// The information parsed from a type record with kind `LF_MODIFIER`. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ModifierType { + pub underlying_type: TypeIndex, + pub constant: bool, + pub volatile: bool, + pub unaligned: bool, +} + +/// The information parsed from a type record with kind `LF_ENUM` or `LF_ENUM_ST`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EnumerationType<'t> { + pub count: u16, + pub properties: TypeProperties, + pub underlying_type: TypeIndex, + pub fields: TypeIndex, + pub name: RawString<'t>, + pub unique_name: Option>, +} + +/// The information parsed from a type record with kind `LF_ENUMERATE` or `LF_ENUMERATE_ST`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EnumerateType<'t> { + pub attributes: FieldAttributes, + pub value: Variant, + pub name: RawString<'t>, +} + +/// The information parsed from a type record with kind +/// `LF_ARRAY`, `LF_ARRAY_ST` or `LF_STRIDED_ARRAY`. +#[cfg(feature = "alloc")] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ArrayType { + pub element_type: TypeIndex, + pub indexing_type: TypeIndex, + pub stride: Option, + + /// Contains array dimensions as specified in the PDB. This is not what you expect: + /// + /// * Dimensions are specified in terms of byte sizes, not element counts. + /// * Multidimensional arrays aggregate the lower dimensions into the sizes of the higher + /// dimensions. + /// + /// Thus a `float[4][4]` has `dimensions: [16, 64]`. Determining array dimensions in terms + /// of element counts requires determining the size of the `element_type` and iteratively + /// dividing. + pub dimensions: Vec, +} + +/// The information parsed from a type record with kind `LF_UNION` or `LF_UNION_ST`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UnionType<'t> { + pub count: u16, + pub properties: TypeProperties, + pub fields: TypeIndex, + pub size: u64, + pub name: RawString<'t>, + pub unique_name: Option>, +} + +/// The information parsed from a type record with kind `LF_BITFIELD`. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct BitfieldType { + pub underlying_type: TypeIndex, + pub length: u8, + pub position: u8, +} + +#[cfg(feature = "alloc")] +/// The information parsed from a type record with kind `LF_FIELDLIST`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FieldList<'t> { + pub fields: Vec>, + + /// Sometimes fields can't all fit in a single FieldList, in which case the FieldList + /// refers to another FieldList in a chain. + pub continuation: Option, +} + +/// The information parsed from a type record with kind `LF_ARGLIST`. +#[cfg(feature = "alloc")] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ArgumentList(Vec); + +#[cfg(feature = "alloc")] +impl Deref for ArgumentList { + type Target = [TypeIndex]; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(feature = "alloc")] +impl ArgumentList { + pub const fn new(data: Vec) -> Self { + Self(data) + } + + pub fn parse(buf: &mut ParseBuffer<'_>) -> Result { + let count = buf.parse_u32()?; + let mut arglist: Vec = Vec::with_capacity(count as usize); + + for _ in 0..count { + arglist.push(buf.parse()?); + } + + Ok(Self(arglist)) + } +} + + +/// The information parsed from a type record with kind `LF_METHODLIST`. +#[cfg(feature = "alloc")] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MethodList(Vec); + +#[cfg(feature = "alloc")] +impl Deref for MethodList { + type Target = [MethodListEntry]; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(feature = "alloc")] +impl MethodList { + pub const fn new(data: Vec) -> Self { + Self(data) + } + + pub fn parse(buf: &mut ParseBuffer<'_>) -> Result { + let mut methods: Vec = Vec::new(); + + while !buf.is_empty() { + // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L2131-L2136 + let attr = FieldAttributes(buf.parse_u16()?); + buf.parse_u16()?; // padding + + let entry = MethodListEntry { + attributes: attr, + method_type: buf.parse()?, + vtable_offset: if attr.is_intro_virtual() { + Some(buf.parse_u32()?) + } else { + None + }, + }; + methods.push(entry); + } + + Ok(Self(methods)) + } +} + +/// An entry in a `MethodList`. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct MethodListEntry { + pub attributes: FieldAttributes, + pub method_type: TypeIndex, + pub vtable_offset: Option, +} + +/* +// arrays: +ParseBuf::from("\x03\x15\xa0\xdc\x0b\x00\x23\x00\x00\x00\x40\x00\x00\xf1").as_bytes(), +ParseBuf::from("\x03\x15\xa0\xdc\x0b\x00\x23\x00\x00\x00\x50\x00\x00\xf1").as_bytes(), +ParseBuf::from("\x03\x15\xa9\x12\x00\x00\x23\x00\x00\x00\x50\x02\x00\xf1").as_bytes(), +ParseBuf::from("\x03\x15\xac\x12\x00\x00\x23\x00\x00\x00\x6c\x00\x00\xf1").as_bytes(), +ParseBuf::from("\x03\x15\x14\x10\x00\x00\x23\x00\x00\x00\x80\x00\x00\xf1").as_bytes(), +ParseBuf::from("\x03\x15\x75\x00\x00\x00\x23\x00\x00\x00\x28\x00\x00\xf1").as_bytes(), +ParseBuf::from("\x03\x15\x14\x10\x00\x00\x23\x00\x00\x00\x70\x0e\x00\xf1").as_bytes(), +ParseBuf::from("\x03\x15\x31\x14\x00\x00\x23\x00\x00\x00\x04\x02\x00\xf1").as_bytes(), +ParseBuf::from("\x03\x15\x31\x14\x00\x00\x23\x00\x00\x00\x0e\x03\x00\xf1").as_bytes(), +ParseBuf::from("\x03\x15\x77\x13\x00\x00\x23\x00\x00\x00\x02\x80\xbd\xda\x00\xf3\xf2\xf1").as_bytes(), +ParseBuf::from("\x03\x15\xb7\x16\x00\x00\x23\x00\x00\x00\x28\x00\x00\xf1").as_bytes(), +ParseBuf::from("\x03\x15\x14\x10\x00\x00\x23\x00\x00\x00\x55\x00\x00\xf1").as_bytes(), +*/ + + +macro_rules! define_leaf_kinds { + ($($variant:ident = $value:literal => $name:literal),* $(,)?) => { + /// The CodeView leaf kind of a type or id record. + /// + /// Every TPI and IPI record starts with a `u16` leaf kind that + /// determines how the rest of the record is laid out. This enum + /// maps the leaf values recognized by CodeView to named variants, + /// preserving anything unrecognized as [`LeafKind::Unknown`]. + /// + /// # Legacy vs. modern leaves + /// + /// CodeView accumulated duplicate leaves over its lifetime. VC5 and + /// earlier emitted 16-bit-type-index variants, distinguished by a + /// suffix like `LF_MODIFIER_16t`. Modern compilers emit the + /// 32-bit-type-index variants, whose values have the `0x1000` bit set + /// (e.g. `LF_MODIFIER`). Both sets appear here. A few kinds + /// (`LF_VTSHAPE`, `LF_COBOL1`, `LF_LABEL`, `LF_NULL`, `LF_NOTTRAN`, + /// `LF_ENDPRECOMP`, `LF_REFSYM`) appear in only one form and are shared + /// between the two eras. + /// + /// # Unknown leaves + /// + /// The enum is `#[non_exhaustive]` and includes an + /// [`LeafKind::Unknown`] variant so that new or unrecognized leaves + /// can be carried through the parser without loss. Use + /// [`LeafKind::raw`] to recover the original `u16`. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + #[non_exhaustive] + pub enum LeafKind { + $($variant,)* + /// A leaf kind not recognized by this version of the crate. + Unknown(u16), + } + + impl LeafKind { + /// Returns the raw `u16` value of this leaf kind as it appears on + /// disk. + pub const fn raw(self) -> u16 { + match self { + $(Self::$variant => $value,)* + Self::Unknown(v) => v, + } + } + } + + impl From for LeafKind { + fn from(v: u16) -> Self { + match v { + $($value => Self::$variant,)* + other => Self::Unknown(other), + } + } + } + + impl From for u16 { + fn from(kind: LeafKind) -> Self { + kind.raw() + } + } + + impl core::fmt::Display for LeafKind { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + $(Self::$variant => f.write_str($name),)* + Self::Unknown(v) => write!(f, "LF_UNKNOWN(0x{v:04x})"), + } + } + } + }; +} + +define_leaf_kinds! { + // ===== Legacy 16-bit-type-index leaves (VC5 and earlier) ===== + Modifier16t = 0x0001 => "LF_MODIFIER_16t", + Pointer16t = 0x0002 => "LF_POINTER_16t", + Array16t = 0x0003 => "LF_ARRAY_16t", + Class16t = 0x0004 => "LF_CLASS_16t", + Structure16t = 0x0005 => "LF_STRUCTURE_16t", + Union16t = 0x0006 => "LF_UNION_16t", + Enum16t = 0x0007 => "LF_ENUM_16t", + Procedure16t = 0x0008 => "LF_PROCEDURE_16t", + Mfunction16t = 0x0009 => "LF_MFUNCTION_16t", + Vtshape = 0x000a => "LF_VTSHAPE", + Cobol016t = 0x000b => "LF_COBOL0_16t", + Cobol1 = 0x000c => "LF_COBOL1", + Barray16t = 0x000d => "LF_BARRAY_16t", + Label = 0x000e => "LF_LABEL", + Null = 0x000f => "LF_NULL", + Nottran = 0x0010 => "LF_NOTTRAN", + Dimarray16t = 0x0011 => "LF_DIMARRAY_16t", + Vftpath16t = 0x0012 => "LF_VFTPATH_16t", + Precomp16t = 0x0013 => "LF_PRECOMP_16t", + Endprecomp = 0x0014 => "LF_ENDPRECOMP", + Oem16t = 0x0015 => "LF_OEM_16t", + TypeserverSt = 0x0016 => "LF_TYPESERVER_ST", + + Skip16t = 0x0200 => "LF_SKIP_16t", + Arglist16t = 0x0201 => "LF_ARGLIST_16t", + Defarg16t = 0x0202 => "LF_DEFARG_16t", + List = 0x0203 => "LF_LIST", + Fieldlist16t = 0x0204 => "LF_FIELDLIST_16t", + Derived16t = 0x0205 => "LF_DERIVED_16t", + Bitfield16t = 0x0206 => "LF_BITFIELD_16t", + Methodlist16t = 0x0207 => "LF_METHODLIST_16t", + Dimconu16t = 0x0208 => "LF_DIMCONU_16t", + Dimconlu16t = 0x0209 => "LF_DIMCONLU_16t", + Dimvaru16t = 0x020a => "LF_DIMVARU_16t", + Dimvarlu16t = 0x020b => "LF_DIMVARLU_16t", + Refsym = 0x020c => "LF_REFSYM", + + Bclass16t = 0x0400 => "LF_BCLASS_16t", + Vbclass16t = 0x0401 => "LF_VBCLASS_16t", + Ivbclass16t = 0x0402 => "LF_IVBCLASS_16t", + EnumerateSt = 0x0403 => "LF_ENUMERATE_ST", + Friendfcn16t = 0x0404 => "LF_FRIENDFCN_16t", + Index16t = 0x0405 => "LF_INDEX_16t", + Member16t = 0x0406 => "LF_MEMBER_16t", + Stmember16t = 0x0407 => "LF_STMEMBER_16t", + Method16t = 0x0408 => "LF_METHOD_16t", + Nesttype16t = 0x0409 => "LF_NESTTYPE_16t", + Vfunctab16t = 0x040a => "LF_VFUNCTAB_16t", + Friendcls16t = 0x040b => "LF_FRIENDCLS_16t", + Onemethod16t = 0x040c => "LF_ONEMETHOD_16t", + Vfuncoff16t = 0x040d => "LF_VFUNCOFF_16t", + + // ===== Modern 32-bit-type-index leaves ===== + Modifier = 0x1001 => "LF_MODIFIER", + Pointer = 0x1002 => "LF_POINTER", + ArraySt = 0x1003 => "LF_ARRAY_ST", + ClassSt = 0x1004 => "LF_CLASS_ST", + StructureSt = 0x1005 => "LF_STRUCTURE_ST", + UnionSt = 0x1006 => "LF_UNION_ST", + EnumSt = 0x1007 => "LF_ENUM_ST", + Procedure = 0x1008 => "LF_PROCEDURE", + Mfunction = 0x1009 => "LF_MFUNCTION", + Cobol0 = 0x100a => "LF_COBOL0", + Barray = 0x100b => "LF_BARRAY", + DimarraySt = 0x100c => "LF_DIMARRAY_ST", + Vftpath = 0x100d => "LF_VFTPATH", + PrecompSt = 0x100e => "LF_PRECOMP_ST", + Oem = 0x100f => "LF_OEM", + AliasSt = 0x1010 => "LF_ALIAS_ST", + Oem2 = 0x1011 => "LF_OEM2", + + Skip = 0x1200 => "LF_SKIP", + Arglist = 0x1201 => "LF_ARGLIST", + DefargSt = 0x1202 => "LF_DEFARG_ST", + Fieldlist = 0x1203 => "LF_FIELDLIST", + Derived = 0x1204 => "LF_DERIVED", + Bitfield = 0x1205 => "LF_BITFIELD", + Methodlist = 0x1206 => "LF_METHODLIST", + Dimconu = 0x1207 => "LF_DIMCONU", + Dimconlu = 0x1208 => "LF_DIMCONLU", + Dimvaru = 0x1209 => "LF_DIMVARU", + Dimvarlu = 0x120a => "LF_DIMVARLU", + + Bclass = 0x1400 => "LF_BCLASS", + Vbclass = 0x1401 => "LF_VBCLASS", + Ivbclass = 0x1402 => "LF_IVBCLASS", + FriendfcnSt = 0x1403 => "LF_FRIENDFCN_ST", + Index = 0x1404 => "LF_INDEX", + MemberSt = 0x1405 => "LF_MEMBER_ST", + StmemberSt = 0x1406 => "LF_STMEMBER_ST", + MethodSt = 0x1407 => "LF_METHOD_ST", + NesttypeSt = 0x1408 => "LF_NESTTYPE_ST", + Vfunctab = 0x1409 => "LF_VFUNCTAB", + Friendcls = 0x140a => "LF_FRIENDCLS", + OnemethodSt = 0x140b => "LF_ONEMETHOD_ST", + Vfuncoff = 0x140c => "LF_VFUNCOFF", + NesttypeexSt = 0x140d => "LF_NESTTYPEEX_ST", + MembermodifySt = 0x140e => "LF_MEMBERMODIFY_ST", + ManagedSt = 0x140f => "LF_MANAGED_ST", + + // ===== Modern leaves with SZ (null-terminated) names ===== + Typeserver = 0x1501 => "LF_TYPESERVER", + Enumerate = 0x1502 => "LF_ENUMERATE", + Array = 0x1503 => "LF_ARRAY", + Class = 0x1504 => "LF_CLASS", + Structure = 0x1505 => "LF_STRUCTURE", + Union = 0x1506 => "LF_UNION", + Enum = 0x1507 => "LF_ENUM", + Dimarray = 0x1508 => "LF_DIMARRAY", + Precomp = 0x1509 => "LF_PRECOMP", + Alias = 0x150a => "LF_ALIAS", + Defarg = 0x150b => "LF_DEFARG", + Friendfcn = 0x150c => "LF_FRIENDFCN", + Member = 0x150d => "LF_MEMBER", + Stmember = 0x150e => "LF_STMEMBER", + Method = 0x150f => "LF_METHOD", + Nesttype = 0x1510 => "LF_NESTTYPE", + Onemethod = 0x1511 => "LF_ONEMETHOD", + Nesttypeex = 0x1512 => "LF_NESTTYPEEX", + Membermodify = 0x1513 => "LF_MEMBERMODIFY", + Managed = 0x1514 => "LF_MANAGED", + Typeserver2 = 0x1515 => "LF_TYPESERVER2", + StridedArray = 0x1516 => "LF_STRIDED_ARRAY", + Hlsl = 0x1517 => "LF_HLSL", + ModifierEx = 0x1518 => "LF_MODIFIER_EX", + Interface = 0x1519 => "LF_INTERFACE", + Binterface = 0x151a => "LF_BINTERFACE", + Vector = 0x151b => "LF_VECTOR", + Matrix = 0x151c => "LF_MATRIX", + Vftable = 0x151d => "LF_VFTABLE", + + // ===== IPI (id) stream leaves ===== + FuncId = 0x1601 => "LF_FUNC_ID", + MfuncId = 0x1602 => "LF_MFUNC_ID", + Buildinfo = 0x1603 => "LF_BUILDINFO", + SubstrList = 0x1604 => "LF_SUBSTR_LIST", + StringId = 0x1605 => "LF_STRING_ID", + UdtSrcLine = 0x1606 => "LF_UDT_SRC_LINE", + UdtModSrcLine = 0x1607 => "LF_UDT_MOD_SRC_LINE", + Structure19 = 0x1609 => "LF_STRUCTURE19", +} + +impl LeafKind { + /// Returns `true` if this leaf kind is only emitted by VC5 and earlier. + /// + /// Modern compilers emit the corresponding 32-bit-type-index variants + /// ([`Self::Modifier`], [`Self::Pointer`], [`Self::Class`], etc.) instead. + /// The shared leaves that appear in both eras (`Vtshape`, `Cobol1`, + /// `Label`, `Null`, `Nottran`, `Endprecomp`, `Refsym`) return `false`. + pub const fn is_legacy(self) -> bool { + match self { + Self::Modifier16t + | Self::Pointer16t + | Self::Array16t + | Self::Class16t + | Self::Structure16t + | Self::Union16t + | Self::Enum16t + | Self::Procedure16t + | Self::Mfunction16t + | Self::Cobol016t + | Self::Barray16t + | Self::Dimarray16t + | Self::Vftpath16t + | Self::Precomp16t + | Self::Oem16t + | Self::Skip16t + | Self::Arglist16t + | Self::Defarg16t + | Self::Fieldlist16t + | Self::Derived16t + | Self::Bitfield16t + | Self::Methodlist16t + | Self::Dimconu16t + | Self::Dimconlu16t + | Self::Dimvaru16t + | Self::Dimvarlu16t + | Self::Bclass16t + | Self::Vbclass16t + | Self::Ivbclass16t + | Self::Friendfcn16t + | Self::Index16t + | Self::Member16t + | Self::Stmember16t + | Self::Method16t + | Self::Nesttype16t + | Self::Vfunctab16t + | Self::Friendcls16t + | Self::Onemethod16t + | Self::Vfuncoff16t => true, + Self::Vtshape + | Self::Cobol1 + | Self::Label + | Self::Null + | Self::Nottran + | Self::Endprecomp + | Self::Refsym + | Self::TypeserverSt + | Self::List + | Self::EnumerateSt => false, + Self::Modifier + | Self::Pointer + | Self::ArraySt + | Self::ClassSt + | Self::StructureSt + | Self::UnionSt + | Self::EnumSt + | Self::Procedure + | Self::Mfunction + | Self::Cobol0 + | Self::Barray + | Self::DimarraySt + | Self::Vftpath + | Self::PrecompSt + | Self::Oem + | Self::AliasSt + | Self::Oem2 + | Self::Skip + | Self::Arglist + | Self::DefargSt + | Self::Fieldlist + | Self::Derived + | Self::Bitfield + | Self::Methodlist + | Self::Dimconu + | Self::Dimconlu + | Self::Dimvaru + | Self::Dimvarlu + | Self::Bclass + | Self::Vbclass + | Self::Ivbclass + | Self::FriendfcnSt + | Self::Index + | Self::MemberSt + | Self::StmemberSt + | Self::MethodSt + | Self::NesttypeSt + | Self::Vfunctab + | Self::Friendcls + | Self::OnemethodSt + | Self::Vfuncoff + | Self::NesttypeexSt + | Self::MembermodifySt + | Self::ManagedSt + | Self::Typeserver + | Self::Enumerate + | Self::Array + | Self::Class + | Self::Structure + | Self::Union + | Self::Enum + | Self::Dimarray + | Self::Precomp + | Self::Alias + | Self::Defarg + | Self::Friendfcn + | Self::Member + | Self::Stmember + | Self::Method + | Self::Nesttype + | Self::Onemethod + | Self::Nesttypeex + | Self::Membermodify + | Self::Managed + | Self::Typeserver2 + | Self::StridedArray + | Self::Hlsl + | Self::ModifierEx + | Self::Interface + | Self::Binterface + | Self::Vector + | Self::Matrix + | Self::Vftable + | Self::FuncId + | Self::MfuncId + | Self::Buildinfo + | Self::SubstrList + | Self::StringId + | Self::UdtSrcLine + | Self::UdtModSrcLine + | Self::Structure19 => false, + // #[non_exhaustive] doesn't affect same-crate matches, but the + // compiler still requires an arm for Unknown. + Self::Unknown(_) => false, + } + } +} diff --git a/src/tpi/utils.rs b/src/tpi/utils.rs new file mode 100644 index 0000000..8754389 --- /dev/null +++ b/src/tpi/utils.rs @@ -0,0 +1,57 @@ +use crate::{*, tpi::constants::*}; + +#[inline] +pub fn parse_optional_type_index(buf: &mut ParseBuffer<'_>) -> Result> { + let index = buf.parse()?; + if index == TypeIndex(0) || index == TypeIndex(0xffff) { + Ok(None) + } else { + Ok(Some(index)) + } +} + +#[inline] +pub fn parse_string<'t>(leaf: u16, buf: &mut ParseBuffer<'t>) -> Result> { + if leaf > LF_ST_MAX { + buf.parse_cstring() + } else { + buf.parse_u8_pascal_string() + } +} + +#[inline] +pub fn parse_padding(buf: &mut ParseBuffer<'_>) -> Result<()> { + while !buf.is_empty() && buf.peek_u8()? >= 0xf0 { + let padding = buf.parse_u8()?; + if padding > 0xf0 { + // low four bits indicate amount of padding + // (don't ask me what 0xf0 means, then) + buf.take((padding & 0x0f) as usize - 1)?; + } + } + Ok(()) +} + + +// https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/pdbdump/pdbdump.cpp#L2417-L2456 +pub fn parse_unsigned(buf: &mut ParseBuffer<'_>) -> Result { + let leaf = buf.parse_u16()?; + if leaf < LF_NUMERIC { + // the u16 directly encodes a value + return Ok(u64::from(leaf)); + } + + match leaf { + LF_CHAR => Ok(u64::from(buf.parse_u8()?)), + LF_USHORT => Ok(u64::from(buf.parse_u16()?)), + LF_ULONG => Ok(u64::from(buf.parse_u32()?)), + LF_UQUADWORD => Ok(buf.parse_u64()?), + _ => { + if cfg!(debug_assertions) { + unreachable!(); + } else { + Err(Error::UnexpectedNumericPrefix(leaf)) + } + } + } +} diff --git a/tests/debug_information.rs b/tests/debug_information.rs index c3d91b3..9af9c3f 100644 --- a/tests/debug_information.rs +++ b/tests/debug_information.rs @@ -1,12 +1,16 @@ +use pdb::Result; + #[test] -fn pdb_info() { - let file = std::fs::File::open("fixtures/self/foo.pdb").expect("opening file"); +fn should_parse_big_msf_pdb_debug_info() -> Result<()> { + let file = std::fs::File::open("fixtures/cpp/foo.pdb")?; - let mut pdb = pdb::PDB::open(file).expect("opening pdb"); - let pdb_info = pdb.debug_information().expect("pdb information"); + let mut pdb = pdb::PDB::open(file)?; + let pdb_info = pdb.debug_information()?; assert_eq!( - pdb_info.machine_type().expect("machien type"), + pdb_info.machine_type()?, pdb::MachineType::Amd64 ); + + Ok(()) } diff --git a/tests/id_information.rs b/tests/id_information.rs index b9ba907..1f2c503 100644 --- a/tests/id_information.rs +++ b/tests/id_information.rs @@ -1,29 +1,28 @@ //! Tests that IdInformation works on files where the IPI is missing (empty stream). -use pdb::{FallibleIterator, IdIndex, PDB}; +use pdb::{FallibleIterator, Result, IdIndex, PDB}; -fn open_file() -> std::fs::File { +fn open_file() -> Result { let path = "fixtures/symbol_server/0ea7c70545374958ad3307514bdfc8642-wntdll.pdb"; - std::fs::File::open(path).expect("missing fixtures, please run scripts/download from the root") + Ok(std::fs::File::open(path)?) } #[test] -fn test_missing_ipi() { - let mut pdb = PDB::open(open_file()).expect("opening pdb"); +fn test_missing_ipi() -> Result<()> { + let mut pdb = PDB::open(open_file()?)?; - let id_information = pdb.id_information().expect("get id information"); + let id_information = pdb.id_information()?; - // Check ItemInformation API assert_eq!(id_information.len(), 0); assert!(id_information.is_empty()); - // Check ItemIter API let mut iter = id_information.iter(); assert!(iter.next().expect("iter empty IPI").is_none()); - // Check ItemFinder API let finder = id_information.finder(); assert_eq!(finder.max_index(), IdIndex(0)); finder.find(IdIndex(0)).expect_err("find index"); finder.find(IdIndex(4097)).expect_err("find index"); + + Ok(()) } diff --git a/tests/no_alloc.rs b/tests/no_alloc.rs new file mode 100644 index 0000000..b030ab7 --- /dev/null +++ b/tests/no_alloc.rs @@ -0,0 +1,48 @@ +use pdb::{DbiVersion, FallibleIterator, MachineType, MsfKind, Result, StreamIndex, noalloc}; + +/// Parses `big_msf_rust_fixture.pdb`, a Big MSF file, through the +/// no-alloc reader. +/// +/// Exercises the Big MSF container path (three-level directory indirection, +/// 32-bit page numbers, per-page chunk iteration), the DBI header parse, +/// and the module list. The module list is read into a caller-supplied +/// buffer, since the noalloc reader cannot allocate one itself. +#[test] +fn should_parse_pdb_without_alloc() -> Result<()> { + + let path = r#"C:\repos\pdb\fixtures\rust\basic\target\debug\big_msf_rust_fixture.pdb"#; + let data = std::fs::read(path)?; + let pdb = noalloc::PDB::open(&data)?; + + assert_eq!(pdb.msf_kind(), MsfKind::Big); + assert_eq!(pdb.page_size(), 4096); + assert_eq!(pdb.stream_count(), 160); + + // Stream 0 should always be readable without error. + assert_eq!(pdb.stream_size(0)?.unwrap(), 48); + let mut buf = vec![0u8; 48 as usize]; + assert_eq!(pdb.read_stream(0, &mut buf)?, 48 as usize); + let debug = pdb.debug_information()?; + + assert_eq!(debug.machine_type()?, MachineType::Amd64); + assert_eq!(debug.header().version, DbiVersion::V70); + assert_eq!(debug.header().symbol_records_stream, StreamIndex(151)); + assert_eq!(debug.header().gs_symbols_stream, StreamIndex(149)); + assert_eq!(debug.header().ps_symbols_stream, StreamIndex(150)); + assert_eq!(debug.header().section_map_size, 124); + assert_eq!(debug.header().file_info_size, 66056); + assert_eq!(debug.header().flags, 0); + assert_eq!(debug.header().module_list_size, 44320); + assert!(debug.header().module_list_size > 100); + + let mut buf = [0u8; 44320]; + let modules = debug.modules(&pdb, &mut buf)?; + assert!(modules.count()? > 100); + + let mut modules = debug.modules(&pdb, &mut buf)?; + let module = modules.next().unwrap().unwrap(); + + assert!(module.module_name_bytes().len() > 0); + + Ok(()) +} \ No newline at end of file diff --git a/tests/pdb_information.rs b/tests/pdb_information.rs index f059465..ca2f716 100644 --- a/tests/pdb_information.rs +++ b/tests/pdb_information.rs @@ -1,14 +1,19 @@ -#[test] -fn pdb_info() { - let file = std::fs::File::open("fixtures/self/foo.pdb").expect("opening file"); +use pdb::{PDBSignature, Result}; +use uuid::Uuid; - let mut pdb = pdb::PDB::open(file).expect("opening pdb"); - let pdb_info = pdb.pdb_information().expect("pdb information"); +#[test] +fn pdb_info() -> Result<()> { + let file = std::fs::File::open("fixtures/self/foo.pdb")?; + let mut pdb = pdb::PDB::open(file)?; + let pdb_info = pdb.pdb_information()?; assert_eq!(pdb_info.age, 2); assert_eq!( - pdb_info.guid, - "2B3C3FA5-5A2E-44B8-8BBA-C3300FF69F62".parse().unwrap(), + pdb_info.guid.unwrap(), + "2B3C3FA5-5A2E-44B8-8BBA-C3300FF69F62".parse::().unwrap(), ); - assert_eq!(pdb_info.signature, 0x587B_A621); + + assert_eq!(pdb_info.signature.to_rfc3339().unwrap(), "2017-01-15T16:41:05+00:00"); + + Ok(()) } diff --git a/tests/pdb_lines.rs b/tests/pdb_lines.rs index d1f7ff9..b0bb6f6 100644 --- a/tests/pdb_lines.rs +++ b/tests/pdb_lines.rs @@ -1,36 +1,44 @@ -use pdb::{FallibleIterator, Rva, PDB}; +use std::io::Error; + +use pdb::{FallibleIterator, Rva, PDB, Result}; #[test] -fn test_module_lines() { - let file = std::fs::File::open("fixtures/self/foo.pdb").expect("opening file"); - let mut pdb = PDB::open(file).expect("parse pdb"); +fn test_module_lines() -> Result<()> { + let file = std::fs::File::open("fixtures/self/foo.pdb")?; + let mut pdb = PDB::open(file)?; + + let address_map = pdb.address_map()?; + let string_table = pdb.string_table()?; - let address_map = pdb.address_map().expect("address map"); - let string_table = pdb.string_table().expect("string table"); + let dbi = pdb.debug_information()?; + let mut modules = dbi.modules()?; + let module = modules.next()? + .ok_or(Error::new(std::io::ErrorKind::InvalidData, "invalid module"))?; - let dbi = pdb.debug_information().expect("dbi"); - let mut modules = dbi.modules().expect("modules"); - let module = modules.next().expect("parse module").expect("no module"); let module_info = pdb .module_info(&module) .expect("parse module info") .expect("module info"); - let line_program = module_info.line_program().expect("line program"); + let line_program = module_info.line_program()?; let mut lines = line_program.lines(); - let line_info = lines.next().expect("parse line info").expect("no lines"); + let line_info = lines.next().expect("parse line info") + .ok_or(Error::new(std::io::ErrorKind::InvalidData, "invalid line_info"))?; + + let rva = line_info.offset.to_rva(&address_map) + .ok_or(Error::new(std::io::ErrorKind::InvalidData, "invalid rva"))?; - let rva = line_info.offset.to_rva(&address_map).expect("line rva"); let file_info = line_program - .get_file_info(line_info.file_index) - .expect("file info"); + .get_file_info(line_info.file_index)?; + let file_name = file_info .name - .to_string_lossy(&string_table) - .expect("file name"); + .to_string_lossy(&string_table)?; assert_eq!(line_info.line_start, 29); assert_eq!(line_info.column_start, None); assert_eq!(rva, Rva(0x64f0)); assert_eq!(file_name, "c:\\users\\user\\desktop\\self\\foo.cpp"); + + Ok(()) } diff --git a/tests/small.rs b/tests/small.rs new file mode 100644 index 0000000..8cfc8e5 --- /dev/null +++ b/tests/small.rs @@ -0,0 +1,81 @@ +use std::io::Cursor; +use pdb::{FallibleIterator, MsfKind, PdbHeaderVersion, Result}; + +/// Parses `fixtures/small1.pdb`, a Small MSF (VC6-era) file, through the +/// alloc-enabled `PDB` reader. +/// +/// Exercises the Small MSF container path end to end: header detection, +/// MSF metadata, the PDB info stream (which predates the GUID field and +/// reports `guid = None`), the named-stream map, the string table, and the +/// TPI stream. The DBI stream is nil in this fixture and is not parsed. +#[test] +fn should_parse_small_pdb() -> Result<()> { + let path = r#"C:\repos\pdb\fixtures\small1.pdb"#; + let data = std::fs::read(path).unwrap(); + + let cursor = Cursor::new(data.as_slice()); + let mut pdb = pdb::PDB::open(cursor)?; + assert_eq!(pdb.msf_kind(), crate::MsfKind::Small); + assert_eq!(pdb.stream_count()?, 6); + + let pdb_info = pdb.pdb_information()?; + assert_eq!(pdb_info.age, 13); + assert_eq!(pdb_info.guid, None); + assert_eq!(pdb_info.names_offset, 16); + assert_eq!(pdb_info.names_size, 7); + assert_eq!(pdb_info.signature.to_rfc3339().unwrap(), "2005-05-01T14:24:21+00:00"); + assert_eq!(pdb_info.version, PdbHeaderVersion::Vc98); + + let mut stream_names = pdb_info.stream_names_iter()?; + let stream_name = stream_names.next().unwrap()?; + assert_eq!(stream_name.name.as_bytes(), b"/names"); + + let string_table = pdb.string_table()?; + let mut iter = string_table.iter()?; + let (ref_str, raw_str,) = iter.next()?.unwrap(); + assert_eq!(ref_str.0, 0); + assert_eq!(raw_str.as_bytes(), b""); + + let stream = pdb.raw_stream(pdb::StreamIndex(3))?.unwrap(); + assert_eq!(stream.len(), 0); + + let type_info = pdb.type_information()?; + + let mut iter = type_info.iter(); + + while let Some(item_type) = iter.next()? { + println!("{} {}", item_type.len(), item_type.kind()); + + let parsed = item_type.parse().unwrap(); + } + + // let stream = pdb.raw_stream(pdb::StreamIndex(2))?.unwrap(); + + // let bytes = stream.as_slice(); + // for (i, chunk) in bytes.chunks(16).enumerate() { + // print!("STREAM2 {:04x}:", i * 16); + // for b in chunk { print!(" {:02x}", b); } + // println!(); + // } + + Ok(()) +} + +#[test] +fn small_stream_sizes_match_toc() -> Result<()> { + let data = std::fs::read(r#"C:\repos\pdb\fixtures\small1.pdb"#).unwrap(); + let cursor = Cursor::new(data.as_slice()); + let mut pdb = pdb::PDB::open(cursor)?; + + assert_eq!(pdb.msf_kind(), MsfKind::Small); + assert_eq!(pdb.stream_count()?, 6); + + assert_eq!(pdb.stream_size(0)?, Some(62)); + assert_eq!(pdb.stream_size(1)?, Some(55)); + assert_eq!(pdb.stream_size(2)?, Some(1104)); + assert_eq!(pdb.stream_size(3)?, Some(0)); + assert_eq!(pdb.stream_size(4)?, Some(132)); + assert_eq!(pdb.stream_size(5)?, Some(25)); + + Ok(()) +} \ No newline at end of file diff --git a/tests/symbol_table.rs b/tests/symbol_table.rs index 5616db4..fee9b89 100644 --- a/tests/symbol_table.rs +++ b/tests/symbol_table.rs @@ -1,10 +1,10 @@ use std::collections::hash_map::{Entry, HashMap}; -use pdb::FallibleIterator; +use pdb::{Result, FallibleIterator}; -fn setup(func: F) +fn setup(func: F) -> Result<()> where - F: FnOnce(&pdb::SymbolTable<'_>, bool), + F: FnOnce(&pdb::SymbolTable<'_>, bool) -> Result<()>, { let (file, is_fixture) = if let Ok(filename) = std::env::var("PDB_FILE") { (std::fs::File::open(filename).expect("opening file"), false) @@ -18,15 +18,16 @@ where let mut pdb = pdb::PDB::open(file).expect("opening pdb"); let symbol_table = pdb.global_symbols().expect("global symbols"); - func(&symbol_table, is_fixture); + func(&symbol_table, is_fixture)?; + + Ok(()) } #[test] -fn count_symbols() { - setup(|global_symbols, is_fixture| { +fn count_symbols() -> Result<()> { + setup(|global_symbols: &pdb::SymbolTable<'_>, is_fixture: bool| { let mut map: HashMap = HashMap::new(); - // walk the symbol table let mut iter = global_symbols.iter(); while let Some(sym) = iter.next().expect("next symbol") { let kind = sym.raw_kind(); @@ -67,44 +68,34 @@ fn count_symbols() { assert!(*map.get(&0x110e).expect("0x110e") >= 3000); assert!(*map.get(&0x1125).expect("0x1125") >= 2000); assert!(*map.get(&0x1127).expect("0x1127") >= 500); - }) + + Ok(()) + })?; + + Ok(()) } #[test] -fn find_symbols() { - setup(|global_symbols, is_fixture| { - // can't do much if we don't know which PDB we're using +fn find_symbols() -> Result<()> { + setup(|global_symbols: &pdb::SymbolTable<'_>, is_fixture: bool| { if !is_fixture { - return; + return Ok(()); } let mut map: HashMap<&[u8], Option>> = HashMap::new(); - // look for: - // main(), defined in the program map.insert(b"main", None); - - // malloc(), defined in libc map.insert(b"memcpy", None); - - // HeapAlloc(), defined... somewhere map.insert(b"HeapAlloc", None); - - // Baz::static_f_public(), except MSVC-mangled map.insert(b"?static_f_public@Baz@@SAXXZ", None); - // walk the symbol table let mut iter = global_symbols.iter(); while let Some(sym) = iter.next().expect("next symbol") { - // ensure we can parse all the symbols, even though we only want a few + let data = sym.parse().expect("symbol parsing"); - - // get symbol name let name = data.name().unwrap_or_default(); if let Entry::Occupied(mut e) = map.entry(name.as_bytes()) { - // this is a symbol we wanted to find - // store our data e.insert(Some(data)); } } @@ -119,5 +110,9 @@ fn find_symbols() { } } } - }) + + Ok(()) + })?; + + Ok(()) } diff --git a/tests/type_information.rs b/tests/type_information.rs index 4d5f57a..ff57a4b 100644 --- a/tests/type_information.rs +++ b/tests/type_information.rs @@ -1,27 +1,28 @@ use std::collections::HashMap; -use pdb::FallibleIterator; +use pdb::{FallibleIterator, Result}; -fn setup(func: F) +fn setup(func: F) -> Result<()> where - F: FnOnce(&pdb::TypeInformation<'_>), + F: FnOnce(&pdb::TypeInformation<'_>) -> Result<()>, { let file = if let Ok(filename) = std::env::var("PDB_FILE") { std::fs::File::open(filename) } else { std::fs::File::open("fixtures/self/foo.pdb") - } - .expect("opening file"); + }?; - let mut pdb = pdb::PDB::open(file).expect("opening pdb"); - let type_information = pdb.type_information().expect("type information"); + let mut pdb = pdb::PDB::open(file)?; + let type_information = pdb.type_information()?; - func(&type_information); + func(&type_information)?; + + Ok(()) } #[test] -fn iteration() { - setup(|type_information| { +fn iteration() -> Result<()> { + setup(|type_information| -> Result<()> { let len = type_information.len(); let mut count: usize = 0; @@ -34,60 +35,55 @@ fn iteration() { } assert_eq!(len, count); - }); + Ok(()) + })?; + + Ok(()) } #[test] -fn type_finder() { - setup(|type_information| { +fn type_finder() -> Result<()> { + setup(|type_information| -> Result<()> { let mut type_finder = type_information.finder(); let mut map: HashMap> = HashMap::new(); assert_eq!(type_finder.max_index().0 >> 3, 4096 >> 3); - // iterate over all the types let mut iter = type_information.iter(); while let Some(typ) = iter.next().expect("next type") { assert_eq!(type_finder.max_index().0 >> 3, typ.index().0 >> 3); - - // update the type finder type_finder.update(&iter); - - // record this type in our map map.insert(typ.index(), typ); } - // iterate over the map -- which is randomized -- making sure the type finder finds identical types for (index, typ) in map.iter() { let found = type_finder.find(*index).expect("find"); assert_eq!(*typ, found); } - }) + + Ok(()) + })?; + + Ok(()) } #[test] -fn find_classes() { - setup(|type_information| { +fn find_classes() -> Result<()> { + setup(|type_information| -> Result<()> { let mut type_finder = type_information.finder(); - // iterate over all the types let mut iter = type_information.iter(); while let Some(typ) = iter.next().expect("next type") { - // update the type finder type_finder.update(&iter); - // parse the type record match typ.parse() { Ok(pdb::TypeData::Class(pdb::ClassType { name, fields: Some(fields), .. })) => { - // this Type describes a class-like type with fields println!("class {} (type {}):", name, typ.index()); - // fields is presently a TypeIndex - // find and parse the list of fields match type_finder.find(fields).expect("find fields").parse() { Ok(pdb::TypeData::FieldList(list)) => { for field in list.fields { @@ -109,7 +105,6 @@ fn find_classes() { Ok(pdb::TypeData::Enumeration(data)) => { println!("enum {} (type {}):", data.name, data.fields); - // fields is presently a TypeIndex match type_finder.find(data.fields).expect("find fields").parse() { Ok(pdb::TypeData::FieldList(list)) => { for field in list.fields { @@ -132,15 +127,12 @@ fn find_classes() { // ignore, since we find these by class } Ok(_) => { - //println!("type: {:?}", data); + // ignore other types } Err(pdb::Error::UnimplementedTypeKind(kind)) => { println!("unimplemented: 0x{:04x}", kind); - // TODO: parse everything - // ignore for now } Err(e) => { - // other parse error println!( "other parse error on type {} (raw type {:04x}): {}", typ.index(), @@ -152,8 +144,10 @@ fn find_classes() { } } - // hooah! - }) + Ok(()) + })?; + + Ok(()) } /*