├── .codecov.yml ├── .craft.yml ├── .editorconfig ├── .github └── workflows │ ├── ci.yml │ ├── enforce-license-compliance.yml │ ├── release.yml │ └── weekly.yml ├── .gitignore ├── .semgrepignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── pdb2hpp.rs ├── pdb_framedata.rs ├── pdb_lines.rs ├── pdb_symbols.rs └── stream_names.rs ├── fixtures ├── self │ ├── Makefile │ ├── README.md │ ├── foo.cpp │ ├── foo.exe │ └── foo.pdb └── symbol_server │ └── README.md ├── scripts ├── bump-version.sh ├── download └── download.bat ├── src ├── common.rs ├── dbi.rs ├── framedata.rs ├── lib.rs ├── modi │ ├── c13.rs │ ├── constants.rs │ └── mod.rs ├── msf │ ├── mod.rs │ └── page_list.rs ├── omap.rs ├── pdb.rs ├── pdbi.rs ├── pe.rs ├── source.rs ├── strings.rs ├── symbol │ ├── annotations.rs │ ├── constants.rs │ └── mod.rs └── tpi │ ├── constants.rs │ ├── data.rs │ ├── header.rs │ ├── id.rs │ ├── mod.rs │ └── primitive.rs └── tests ├── debug_information.rs ├── id_information.rs ├── modi_symbol_depth.rs ├── omap_address_translation.rs ├── pdb_information.rs ├── pdb_lines.rs ├── symbol_table.rs └── type_information.rs /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | informational: true 6 | patch: false 7 | 8 | comment: 9 | layout: "diff" 10 | require_changes: true 11 | -------------------------------------------------------------------------------- /.craft.yml: -------------------------------------------------------------------------------- 1 | minVersion: "1.8.1" 2 | github: 3 | owner: getsentry 4 | repo: pdb 5 | changelogPolicy: auto 6 | 7 | statusProvider: 8 | name: github 9 | artifactProvider: 10 | name: none 11 | 12 | preReleaseCommand: bash scripts/bump-version.sh 13 | targets: 14 | - name: github 15 | - name: crates 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | 9 | [Makefile] 10 | indent_style = tab 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - "release/**" 8 | pull_request: 9 | 10 | env: 11 | RUSTFLAGS: -Dwarnings 12 | 13 | jobs: 14 | lints: 15 | name: Style/Linting 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - run: rustup toolchain install stable --profile minimal --component rustfmt --component clippy --no-self-update 22 | - uses: Swatinem/rust-cache@v2 23 | 24 | - run: cargo fmt --all -- --check 25 | - run: cargo clippy --all-features --workspace --tests --examples -- -D clippy::all 26 | 27 | doc-comments: 28 | name: Documentation 29 | runs-on: ubuntu-latest 30 | env: 31 | RUSTDOCFLAGS: -Dwarnings 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - run: rustup toolchain install stable --profile minimal --component rust-docs --no-self-update 36 | - uses: Swatinem/rust-cache@v2 37 | 38 | - run: cargo doc --workspace --all-features --document-private-items --no-deps 39 | 40 | test: 41 | strategy: 42 | fail-fast: false 43 | matrix: 44 | os: [ubuntu-latest, windows-latest, macos-latest] 45 | 46 | name: Tests on ${{ matrix.os }} 47 | runs-on: ${{ matrix.os }} 48 | 49 | steps: 50 | - uses: actions/checkout@v4 51 | 52 | - run: rustup toolchain install stable --profile minimal --no-self-update 53 | - uses: Swatinem/rust-cache@v2 54 | 55 | - run: scripts/download 56 | 57 | - run: cargo test --workspace --all-features --all-targets 58 | - run: cargo test --workspace --all-features --doc 59 | 60 | codecov: 61 | name: Code Coverage 62 | runs-on: ubuntu-latest 63 | 64 | steps: 65 | - uses: actions/checkout@v4 66 | 67 | - run: rustup toolchain install stable --profile minimal --component llvm-tools-preview --no-self-update 68 | - uses: Swatinem/rust-cache@v2 69 | - uses: taiki-e/install-action@cargo-llvm-cov 70 | 71 | - run: scripts/download 72 | 73 | - run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info 74 | 75 | - uses: codecov/codecov-action@v3 76 | with: 77 | files: lcov.info 78 | -------------------------------------------------------------------------------- /.github/workflows/enforce-license-compliance.yml: -------------------------------------------------------------------------------- 1 | name: Enforce License Compliance 2 | 3 | on: 4 | push: 5 | branches: [master, release/*] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | enforce-license-compliance: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: "Enforce License Compliance" 14 | uses: getsentry/action-enforce-license-compliance@main 15 | with: 16 | fossa_api_key: ${{ secrets.FOSSA_API_KEY }} 17 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: Version to release 7 | required: false 8 | force: 9 | description: Force a release even when there are release-blockers (optional) 10 | required: false 11 | merge_target: 12 | description: Target branch to merge into. Uses the default branch as a fallback (optional) 13 | required: false 14 | jobs: 15 | release: 16 | runs-on: ubuntu-latest 17 | name: "Release a new version" 18 | steps: 19 | - name: Get auth token 20 | id: token 21 | uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 22 | with: 23 | app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} 24 | private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} 25 | - uses: actions/checkout@v4 26 | with: 27 | # Fetch all commits so we can determine previous version 28 | fetch-depth: 0 29 | token: ${{ steps.token.outputs.token }} 30 | - name: Prepare release 31 | uses: getsentry/action-prepare-release@v1 32 | env: 33 | GITHUB_TOKEN: ${{ steps.token.outputs.token }} 34 | with: 35 | version: ${{ github.event.inputs.version }} 36 | force: ${{ github.event.inputs.force }} 37 | -------------------------------------------------------------------------------- /.github/workflows/weekly.yml: -------------------------------------------------------------------------------- 1 | name: Weekly CI 2 | 3 | on: 4 | schedule: 5 | - cron: "14 3 * * 5" # every friday at 03:14 6 | workflow_dispatch: 7 | 8 | env: 9 | RUSTFLAGS: -Dwarnings 10 | 11 | jobs: 12 | weekly-ci: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | rust: [nightly, beta] 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - run: | 24 | rustup toolchain install ${{ matrix.rust }} --profile minimal --component clippy --no-self-update 25 | rustup default ${{ matrix.rust }} 26 | 27 | - uses: arduino/setup-protoc@v3 28 | 29 | - run: cargo clippy --all-features --workspace --tests --examples -- -D clippy::all 30 | - run: cargo test --workspace --all-features --all-targets 31 | - run: cargo test --workspace --all-features --doc 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.iml 3 | .idea 4 | .DS_Store 5 | 6 | fixtures/symbol_server/* 7 | -------------------------------------------------------------------------------- /.semgrepignore: -------------------------------------------------------------------------------- 1 | # Ignore git items 2 | .git/ 3 | .gitignore 4 | :include .gitignore 5 | 6 | examples/ 7 | tests/ 8 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "fallible-iterator" 7 | version = "0.2.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 10 | 11 | [[package]] 12 | name = "getopts" 13 | version = "0.2.21" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 16 | dependencies = [ 17 | "unicode-width", 18 | ] 19 | 20 | [[package]] 21 | name = "pdb" 22 | version = "0.8.0" 23 | dependencies = [ 24 | "fallible-iterator", 25 | "getopts", 26 | "scroll", 27 | "uuid", 28 | ] 29 | 30 | [[package]] 31 | name = "scroll" 32 | version = "0.11.0" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" 35 | 36 | [[package]] 37 | name = "unicode-width" 38 | version = "0.1.5" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 41 | 42 | [[package]] 43 | name = "uuid" 44 | version = "1.0.0" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0" 47 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pdb" 3 | version = "0.8.0" 4 | description = "A parser for Microsoft PDB (Program Database) debugging information" 5 | repository = "https://github.com/willglynn/pdb" 6 | authors = ["Jan Michael Auer ", "Will Glynn "] 7 | readme = "README.md" 8 | license = "MIT OR Apache-2.0" 9 | edition = "2018" 10 | exclude = [ 11 | "fixtures/*", 12 | "scripts/*", 13 | ] 14 | 15 | [dependencies] 16 | fallible-iterator = "0.2.0" 17 | scroll = "0.11.0" 18 | uuid = "1.0.0" 19 | 20 | [dev-dependencies] 21 | # for examples/ 22 | getopts = "0.2.21" 23 | 24 | [package.metadata.release] 25 | pre-release-commit-message = "Release {{version}}" 26 | tag-name = "{{version}}" 27 | tag-message = "Release {{version}}" 28 | dev-version = false 29 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 pdb Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `pdb` 2 | === 3 | 4 | [![](https://img.shields.io/crates/v/pdb.svg)](https://crates.io/crates/pdb) [![](https://docs.rs/pdb/badge.svg)](https://docs.rs/pdb/) ![Build Status](https://github.com/willglynn/pdb/actions/workflows/ci.yml/badge.svg) 5 | 6 | This is a Rust library that parses Microsoft PDB (Program Database) files. 7 | These files contain debugging information produced by most compilers that 8 | target Windows, including information about symbols, types, modules, and so on. 9 | 10 | The PDB format is not documented per sé, but Microsoft has [published 11 | information](https://github.com/Microsoft/microsoft-pdb) in the form of C++ 12 | code relating to its use. The PDB format is full of... history, including 13 | support for debugging 16-bit executables, COBOL user-defined types, and myriad 14 | other features. `pdb` does not understand everything about the PDB format, 15 | but it does cover enough to be useful for typical programs compiled today. 16 | 17 | [Documentation on docs.rs](https://docs.rs/pdb/). 18 | 19 | Design 20 | --- 21 | 22 | `pdb`'s design objectives are similar to 23 | [`gimli`](https://github.com/gimli-rs/gimli): 24 | 25 | * `pdb` works with the original data as it's formatted on-disk as long as 26 | possible. 27 | 28 | * `pdb` parses only what you ask. 29 | 30 | * `pdb` can read PDBs anywhere. There's no dependency on Windows, on the 31 | [DIA SDK](https://msdn.microsoft.com/en-us/library/x93ctkx8.aspx), or on 32 | the target's native byte ordering. 33 | 34 | Usage Example 35 | --- 36 | 37 | ```rust 38 | use pdb::FallibleIterator; 39 | use std::fs::File; 40 | 41 | fn main() -> pdb::Result<()> { 42 | let file = File::open("fixtures/self/foo.pdb")?; 43 | let mut pdb = pdb::PDB::open(file)?; 44 | 45 | let symbol_table = pdb.global_symbols()?; 46 | let address_map = pdb.address_map()?; 47 | 48 | let mut symbols = symbol_table.iter(); 49 | while let Some(symbol) = symbols.next()? { 50 | match symbol.parse() { 51 | Ok(pdb::SymbolData::Public(data)) if data.function => { 52 | // we found the location of a function! 53 | let rva = data.offset.to_rva(&address_map).unwrap_or_default 54 | println!("{} is {}", rva, data.name); 55 | } 56 | _ => {} 57 | } 58 | } 59 | 60 | Ok(()) 61 | } 62 | ``` 63 | 64 | Example Programs 65 | --- 66 | 67 | Run with `cargo run --release --example `: 68 | 69 | * [`pdb_symbols`](examples/pdb_symbols.rs) is a toy program that prints the name and location of every function and 70 | data value defined in the symbol table. 71 | 72 | * [`pdb2hpp`](examples/pdb2hpp.rs) is a somewhat larger program that prints an approximation of a C++ header file for 73 | a requested type given only a PDB. 74 | 75 | * [`pdb_lines`](examples/pdb_lines.rs) outputs line number information for every symbol in every module contained in 76 | a PDB. 77 | 78 | Real-world examples: 79 | 80 | * [`mstange/pdb-addr2line`](https://github.com/mstange/pdb-addr2line) resolves addresses to function names, and to file name and line number information, with the help of a PDB file. Inline stacks are supported. 81 | 82 | * [`getsentry/symbolic`](https://github.com/getsentry/symbolic) is a high-level symbolication library supporting most common debug file formats, demangling, and more. 83 | 84 | License 85 | --- 86 | 87 | Licensed under either of 88 | 89 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 90 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 91 | 92 | at your option. 93 | 94 | Contribution 95 | --- 96 | 97 | Unless you explicitly state otherwise, any contribution intentionally submitted 98 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 99 | additional terms or conditions. 100 | -------------------------------------------------------------------------------- /examples/pdb_framedata.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use getopts::Options; 4 | use pdb::FallibleIterator; 5 | 6 | fn print_usage(program: &str, opts: Options) { 7 | let brief = format!("Usage: {} input.pdb", program); 8 | print!("{}", opts.usage(&brief)); 9 | } 10 | 11 | fn dump_framedata(filename: &str) -> pdb::Result<()> { 12 | let file = std::fs::File::open(filename)?; 13 | let mut pdb = pdb::PDB::open(file)?; 14 | 15 | let string_table = pdb.string_table()?; 16 | 17 | println!("Frame data:"); 18 | println!("Address Blk Size Locals Params StkMax Prolog SavedReg SEH C++EH Start BP Type Program"); 19 | println!(); 20 | 21 | let frame_table = pdb.frame_table()?; 22 | let mut frames = frame_table.iter(); 23 | while let Some(data) = frames.next()? { 24 | let program_string = match data.program { 25 | Some(prog_ref) => prog_ref.to_string_lossy(&string_table)?, 26 | None => Default::default(), 27 | }; 28 | 29 | println!( 30 | "{} {:8x} {:8x} {:8x} {:8x} {:8x} {:8x} {} {} {} {} {:5} {}", 31 | data.code_start, 32 | data.code_size, 33 | data.locals_size, 34 | data.params_size, 35 | data.max_stack_size.unwrap_or(0), 36 | data.prolog_size, 37 | data.saved_regs_size, 38 | if data.has_structured_eh { 'Y' } else { 'N' }, 39 | if data.has_cpp_eh { 'Y' } else { 'N' }, 40 | if data.is_function_start { 'Y' } else { 'N' }, 41 | if data.uses_base_pointer { 'Y' } else { 'N' }, 42 | data.ty, 43 | program_string, 44 | ); 45 | } 46 | 47 | Ok(()) 48 | } 49 | 50 | fn main() { 51 | let args: Vec = env::args().collect(); 52 | let program = args[0].clone(); 53 | 54 | let mut opts = Options::new(); 55 | opts.optflag("h", "help", "print this help menu"); 56 | let matches = match opts.parse(&args[1..]) { 57 | Ok(m) => m, 58 | Err(f) => panic!("{}", f.to_string()), 59 | }; 60 | 61 | let filename = if matches.free.len() == 1 { 62 | &matches.free[0] 63 | } else { 64 | print_usage(&program, opts); 65 | return; 66 | }; 67 | 68 | match dump_framedata(filename) { 69 | Ok(_) => (), 70 | Err(e) => eprintln!("error dumping PDB: {}", e), 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /examples/pdb_lines.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::io::Write; 3 | 4 | use getopts::Options; 5 | 6 | use pdb::{FallibleIterator, SymbolData, PDB}; 7 | 8 | fn dump_pdb(filename: &str) -> pdb::Result<()> { 9 | let file = std::fs::File::open(filename)?; 10 | let mut pdb = PDB::open(file)?; 11 | 12 | let address_map = pdb.address_map()?; 13 | let string_table = pdb.string_table()?; 14 | 15 | println!("Module private symbols:"); 16 | let dbi = pdb.debug_information()?; 17 | let mut modules = dbi.modules()?; 18 | while let Some(module) = modules.next()? { 19 | println!(); 20 | println!("Module: {}", module.module_name()); 21 | 22 | let info = match pdb.module_info(&module)? { 23 | Some(info) => info, 24 | None => { 25 | println!(" no module info"); 26 | continue; 27 | } 28 | }; 29 | 30 | let program = info.line_program()?; 31 | let mut symbols = info.symbols()?; 32 | 33 | while let Some(symbol) = symbols.next()? { 34 | if let Ok(SymbolData::Procedure(proc)) = symbol.parse() { 35 | let sign = if proc.global { "+" } else { "-" }; 36 | println!("{} {}", sign, proc.name); 37 | 38 | let mut lines = program.lines_for_symbol(proc.offset); 39 | while let Some(line_info) = lines.next()? { 40 | let rva = line_info.offset.to_rva(&address_map).expect("invalid rva"); 41 | let file_info = program.get_file_info(line_info.file_index)?; 42 | let file_name = file_info.name.to_string_lossy(&string_table)?; 43 | println!(" {} {}:{}", rva, file_name, line_info.line_start); 44 | } 45 | } 46 | } 47 | } 48 | 49 | Ok(()) 50 | } 51 | 52 | fn main() { 53 | let args: Vec = env::args().collect(); 54 | 55 | let mut opts = Options::new(); 56 | opts.optflag("h", "help", "print this help menu"); 57 | let matches = match opts.parse(&args[1..]) { 58 | Ok(m) => m, 59 | Err(f) => panic!("{}", f.to_string()), 60 | }; 61 | 62 | let filename = if matches.free.len() == 1 { 63 | &matches.free[0] 64 | } else { 65 | //print_usage(&program, opts); 66 | println!("specify path to a PDB"); 67 | return; 68 | }; 69 | 70 | match dump_pdb(filename) { 71 | Ok(_) => {} 72 | Err(e) => { 73 | writeln!(&mut std::io::stderr(), "error dumping PDB: {}", e).expect("stderr write"); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /examples/pdb_symbols.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use getopts::Options; 4 | use pdb::{FallibleIterator, PdbInternalSectionOffset}; 5 | 6 | fn print_usage(program: &str, opts: Options) { 7 | let brief = format!("Usage: {} input.pdb", program); 8 | print!("{}", opts.usage(&brief)); 9 | } 10 | 11 | fn print_row(offset: PdbInternalSectionOffset, kind: &str, name: pdb::RawString<'_>) { 12 | println!( 13 | "{:x}\t{:x}\t{}\t{}", 14 | offset.section, offset.offset, kind, name 15 | ); 16 | } 17 | 18 | fn print_symbol(symbol: &pdb::Symbol<'_>) -> pdb::Result<()> { 19 | match symbol.parse()? { 20 | pdb::SymbolData::Public(data) => { 21 | print_row(data.offset, "function", data.name); 22 | } 23 | pdb::SymbolData::Data(data) => { 24 | print_row(data.offset, "data", data.name); 25 | } 26 | pdb::SymbolData::Procedure(data) => { 27 | print_row(data.offset, "function", data.name); 28 | } 29 | _ => { 30 | // ignore everything else 31 | } 32 | } 33 | 34 | Ok(()) 35 | } 36 | 37 | fn walk_symbols(mut symbols: pdb::SymbolIter<'_>) -> pdb::Result<()> { 38 | println!("segment\toffset\tkind\tname"); 39 | 40 | while let Some(symbol) = symbols.next()? { 41 | match print_symbol(&symbol) { 42 | Ok(_) => (), 43 | Err(e) => eprintln!("error printing symbol {:?}: {}", symbol, e), 44 | } 45 | } 46 | 47 | Ok(()) 48 | } 49 | 50 | fn dump_pdb(filename: &str) -> pdb::Result<()> { 51 | let file = std::fs::File::open(filename)?; 52 | let mut pdb = pdb::PDB::open(file)?; 53 | let symbol_table = pdb.global_symbols()?; 54 | println!("Global symbols:"); 55 | walk_symbols(symbol_table.iter())?; 56 | 57 | println!("Module private symbols:"); 58 | let dbi = pdb.debug_information()?; 59 | let mut modules = dbi.modules()?; 60 | while let Some(module) = modules.next()? { 61 | println!("Module: {}", module.object_file_name()); 62 | let info = match pdb.module_info(&module)? { 63 | Some(info) => info, 64 | None => { 65 | println!(" no module info"); 66 | continue; 67 | } 68 | }; 69 | 70 | walk_symbols(info.symbols()?)?; 71 | } 72 | Ok(()) 73 | } 74 | 75 | fn main() { 76 | let args: Vec = env::args().collect(); 77 | let program = args[0].clone(); 78 | 79 | let mut opts = Options::new(); 80 | opts.optflag("h", "help", "print this help menu"); 81 | let matches = match opts.parse(&args[1..]) { 82 | Ok(m) => m, 83 | Err(f) => panic!("{}", f.to_string()), 84 | }; 85 | 86 | let filename = if matches.free.len() == 1 { 87 | &matches.free[0] 88 | } else { 89 | print_usage(&program, opts); 90 | return; 91 | }; 92 | 93 | match dump_pdb(filename) { 94 | Ok(_) => (), 95 | Err(e) => eprintln!("error dumping PDB: {}", e), 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /examples/stream_names.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsStr; 2 | 3 | fn dump_stream_names(filename: &OsStr) -> pdb::Result<()> { 4 | let file = std::fs::File::open(filename)?; 5 | let mut pdb = pdb::PDB::open(file)?; 6 | let info = pdb.pdb_information()?; 7 | let names = info.stream_names()?; 8 | println!("index, name"); 9 | for name in &names { 10 | let stream = pdb.raw_stream(name.stream_id)?.expect("named stream"); 11 | println!("{:5}, {} {} bytes", name.stream_id, name.name, stream.len()); 12 | } 13 | Ok(()) 14 | } 15 | 16 | fn main() { 17 | let filename = std::env::args_os().nth(1).expect("Missing PDB filename"); 18 | 19 | match dump_stream_names(&filename) { 20 | Ok(_) => (), 21 | Err(e) => eprintln!("error dumping PDB: {}", e), 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /fixtures/self/Makefile: -------------------------------------------------------------------------------- 1 | # This is an NMAKE Makefile 2 | 3 | all: clean foo.exe 4 | 5 | clean: 6 | del *.obj *.pdb *.exe 7 | 8 | # compiler: 9 | # /Od Disables optimization 10 | # /GR Enables run-time type information (RTTI) 11 | # /Zi Produces a program database (PDB) that contains type information and symbolic debugging information for use with the debugger. The symbolic debugging information includes the names and types of variables, as well as functions and line numbers. 12 | # linker: 13 | # /DEBUG Creates debugging information 14 | foo.exe: foo.cpp 15 | cl /Od /GR /Zi foo.cpp /link /debug:full /out:foo.exe 16 | del *.obj *.ilk 17 | -------------------------------------------------------------------------------- /fixtures/self/README.md: -------------------------------------------------------------------------------- 1 | Fixtures 2 | === 3 | 4 | This folder contains a toy program, an `NMAKE` Makefile, and the output produced by Visual Studio 2015 when compiling 5 | this program. 6 | 7 | `foo.pdb` is used by various tests. Bundling it as part of the repository allows tests to run on platforms where Visual 8 | Studio is not available. 9 | -------------------------------------------------------------------------------- /fixtures/self/foo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Define a struct 4 | typedef struct bar { 5 | int this_is_an_int; 6 | char * this_is_a_char_pointer; 7 | const char * this_is_a_const_char_pointer; 8 | bool bools_are_so_hot_right_now; 9 | } bar_t; 10 | 11 | // Define a class 12 | class Baz { 13 | public: 14 | int m_public; 15 | float f_public(); 16 | static void static_f_public(); 17 | 18 | Baz(int construtor_arg); 19 | 20 | protected: 21 | int m_protected; 22 | void f_protected(); 23 | static void static_f_protected(); 24 | 25 | private: 26 | int m_private; 27 | }; 28 | 29 | float Baz::f_public() { return 42; } 30 | void Baz::static_f_public() {} 31 | Baz::Baz(int constructor_arg) {} 32 | void Baz::f_protected() {} 33 | void Baz::static_f_protected() {} 34 | 35 | // Define an enum 36 | enum Quxx { 37 | Lorem, 38 | Ipsum, 39 | Dolor, 40 | Sit = 0x100, 41 | Amet 42 | }; 43 | 44 | // Entry point 45 | int main(int argc, char ** argv) { 46 | printf("Hello, world!\n"); 47 | } 48 | -------------------------------------------------------------------------------- /fixtures/self/foo.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/pdb/65c5b6d5c38c5f84225bfb3bc5365ea4097c8adf/fixtures/self/foo.exe -------------------------------------------------------------------------------- /fixtures/self/foo.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsentry/pdb/65c5b6d5c38c5f84225bfb3bc5365ea4097c8adf/fixtures/self/foo.pdb -------------------------------------------------------------------------------- /fixtures/symbol_server/README.md: -------------------------------------------------------------------------------- 1 | # `fixtures/symbol_server/` 2 | 3 | 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. 4 | 5 | Before running tests, please run `scripts/download` from the project root folder (where Cargo.lock is located) to seed the files. 6 | -------------------------------------------------------------------------------- /scripts/bump-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eux 3 | 4 | if [ "$(uname -s)" != "Linux" ]; then 5 | echo "Please use the GitHub Action." 6 | exit 1 7 | fi 8 | 9 | SCRIPT_DIR="$( dirname "$0" )" 10 | cd $SCRIPT_DIR/.. 11 | 12 | OLD_VERSION="${1}" 13 | NEW_VERSION="${2}" 14 | 15 | echo "Current version: $OLD_VERSION" 16 | echo "Bumping version: $NEW_VERSION" 17 | 18 | function replace() { 19 | ! grep "$2" $3 20 | perl -i -pe "s/$1/$2/g" $3 21 | grep "$2" $3 # verify that replacement was successful 22 | } 23 | 24 | replace "^version = \"[0-9.]+\"" "version = \"$NEW_VERSION\"" Cargo.toml 25 | cargo metadata --format-version 1 > /dev/null # update `Cargo.lock` 26 | -------------------------------------------------------------------------------- /scripts/download: -------------------------------------------------------------------------------- 1 | curl -L -o "fixtures/symbol_server/0ea7c70545374958ad3307514bdfc8642-wntdll.pdb" https://msdl.microsoft.com/download/symbols/wntdll.pdb/0ea7c70545374958ad3307514bdfc8642/wntdll.pdb 2 | curl -L -o "fixtures/symbol_server/3844dbb920174967be7aa4a2c20430fa2-ntkrnlmp.pdb" https://msdl.microsoft.com/download/symbols/ntkrnlmp.pdb/3844dbb920174967be7aa4a2c20430fa2/ntkrnlmp.pdb 3 | -------------------------------------------------------------------------------- /scripts/download.bat: -------------------------------------------------------------------------------- 1 | powershell -Command "Invoke-WebRequest https://msdl.microsoft.com/download/symbols/wntdll.pdb/0ea7c70545374958ad3307514bdfc8642/wntdll.pdb -OutFile fixtures/symbol_server/0ea7c70545374958ad3307514bdfc8642-wntdll.pdb" 2 | powershell -Command "Invoke-WebRequest https://msdl.microsoft.com/download/symbols/ntkrnlmp.pdb/3844dbb920174967be7aa4a2c20430fa2/ntkrnlmp.pdb -OutFile fixtures/symbol_server/3844dbb920174967be7aa4a2c20430fa2-ntkrnlmp.pdb" 3 | -------------------------------------------------------------------------------- /src/framedata.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 pdb Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | //! Facilities for parsing legacy FPO and FrameData streams. 9 | 10 | use std::cmp::Ordering; 11 | use std::fmt; 12 | 13 | use crate::common::*; 14 | use crate::msf::Stream; 15 | use crate::FallibleIterator; 16 | 17 | /// A compiler specific frame type. 18 | /// 19 | /// This frame type is used by the old FPO data and has been superseeded by program strings. Its 20 | /// values are originally specified in [`enum StackFrameTypeEnum`]. 21 | /// 22 | /// [`enum StackFrameTypeEnum`]: https://docs.microsoft.com/en-us/visualstudio/debugger/debug-interface-access/stackframetypeenum?view=vs-2017 23 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 24 | #[repr(u8)] 25 | pub enum FrameType { 26 | /// Frame which does not have any debug info. 27 | Unknown = 0xff, 28 | 29 | /// Frame pointer omitted, FPO info available. 30 | FPO = 0, 31 | 32 | /// Kernel Trap frame. 33 | Trap = 1, 34 | 35 | /// Kernel Trap frame. 36 | TSS = 2, 37 | 38 | /// Standard EBP stackframe. 39 | Standard = 3, 40 | 41 | /// Frame pointer omitted, FrameData info available. 42 | FrameData = 4, 43 | } 44 | 45 | impl fmt::Display for FrameType { 46 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 47 | match self { 48 | Self::Unknown => write!(f, "unknown"), 49 | Self::FPO => write!(f, "fpo"), 50 | Self::Trap => write!(f, "trap"), 51 | Self::TSS => write!(f, "tss"), 52 | Self::Standard => write!(f, "std"), 53 | Self::FrameData => write!(f, "fdata"), 54 | } 55 | } 56 | } 57 | 58 | /// New frame data format. 59 | /// 60 | /// This format is used in the `DEBUG_S_FRAMEDATA` subsection in C13 module information, as well as 61 | /// in the `dbgFRAMEDATA` stream defined in the optional debug header. Effectively, all recent PDBs 62 | /// contain frame infos in this format. 63 | /// 64 | /// The definition corresponds to [`struct tagFRAMEDATA`]. 65 | /// 66 | /// ```c 67 | /// struct tagFRAMEDATA { 68 | /// unsigned long ulRvaStart; 69 | /// unsigned long cbBlock; 70 | /// unsigned long cbLocals; 71 | /// unsigned long cbParams; 72 | /// unsigned long cbStkMax; 73 | /// unsigned long frameFunc; 74 | /// unsigned short cbProlog; 75 | /// unsigned short cbSavedRegs; 76 | /// 77 | /// unsigned long fHasSEH : 1; 78 | /// unsigned long fHasEH : 1; 79 | /// unsigned long fIsFunctionStart : 1; 80 | /// unsigned long reserved : 29; 81 | /// }; 82 | /// ``` 83 | /// 84 | /// [`struct tagFRAMEDATA`]: https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L4635 85 | #[repr(C)] 86 | struct NewFrameData { 87 | code_start: u32, 88 | code_size: u32, 89 | locals_size: u32, 90 | params_size: u32, 91 | max_stack_size: u32, 92 | frame_func: u32, 93 | prolog_size: u16, 94 | saved_regs_size: u16, 95 | flags: u32, 96 | } 97 | 98 | impl NewFrameData { 99 | pub fn code_start(&self) -> PdbInternalRva { 100 | PdbInternalRva(u32::from_le(self.code_start)) 101 | } 102 | 103 | pub fn code_size(&self) -> u32 { 104 | u32::from_le(self.code_size) 105 | } 106 | 107 | pub fn locals_size(&self) -> u32 { 108 | u32::from_le(self.locals_size) 109 | } 110 | 111 | pub fn params_size(&self) -> u32 { 112 | u32::from_le(self.params_size) 113 | } 114 | 115 | pub fn max_stack_size(&self) -> u32 { 116 | u32::from_le(self.max_stack_size) 117 | } 118 | 119 | pub fn frame_func(&self) -> StringRef { 120 | StringRef(u32::from_le(self.frame_func)) 121 | } 122 | 123 | pub fn prolog_size(&self) -> u16 { 124 | u16::from_le(self.prolog_size) 125 | } 126 | 127 | pub fn saved_regs_size(&self) -> u16 { 128 | u16::from_le(self.saved_regs_size) 129 | } 130 | 131 | pub fn has_seh(&self) -> bool { 132 | self.flags() & 1 != 0 133 | } 134 | 135 | pub fn has_eh(&self) -> bool { 136 | self.flags() & 2 != 0 137 | } 138 | 139 | pub fn is_function_start(&self) -> bool { 140 | self.flags() & 4 != 0 141 | } 142 | 143 | fn flags(&self) -> u32 { 144 | u32::from_le(self.flags) 145 | } 146 | } 147 | 148 | impl fmt::Debug for NewFrameData { 149 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 150 | f.debug_struct("NewFrameData") 151 | .field("code_start", &self.code_start()) 152 | .field("code_size", &self.code_size()) 153 | .field("locals_size", &self.locals_size()) 154 | .field("params_size", &self.params_size()) 155 | .field("max_stack_size", &self.max_stack_size()) 156 | .field("frame_func", &self.frame_func()) 157 | .field("prolog_size", &self.prolog_size()) 158 | .field("saved_regs_size", &self.saved_regs_size()) 159 | .field("has_seh", &self.has_seh()) 160 | .field("has_eh", &self.has_eh()) 161 | .field("is_function_start", &self.is_function_start()) 162 | .finish() 163 | } 164 | } 165 | 166 | /// Initial structure used for describing stack frames. 167 | /// 168 | /// This structure corresponds to [`struct _FPO_DATA`] in the PE/COFF spec. It was used to describe 169 | /// the layout of stack frames in the `dbgFPO` stream defined in the optional debug header. Since, 170 | /// it has been superseeded by the `tagFRAMEDATA` structure (see [`NewFrameData`]). 171 | /// 172 | /// Even if the newer FrameData stream is present, a PDB might still contain an additional FPO 173 | /// stream. This is due to the fact that the linker simply copies over the stream. As a result, both 174 | /// stream might describe the same RVA. 175 | /// 176 | /// [`struct _FPO_DATA`]: https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#debug-type 177 | /// 178 | /// ```c 179 | /// typedef struct _FPO_DATA { 180 | /// DWORD ulOffStart; // offset 1st byte of function code 181 | /// DWORD cbProcSize; // # bytes in function 182 | /// DWORD cdwLocals; // # bytes in locals/4 183 | /// WORD cdwParams; // # bytes in params/4 184 | /// 185 | /// WORD cbProlog : 8; // # bytes in prolog 186 | /// WORD cbRegs : 3; // # regs saved 187 | /// WORD fHasSEH : 1; // TRUE if SEH in func 188 | /// WORD fUseBP : 1; // TRUE if EBP has been allocated 189 | /// WORD reserved : 1; // reserved for future use 190 | /// WORD cbFrame : 2; // frame type 191 | /// } FPO_DATA; 192 | /// ``` 193 | #[repr(C)] 194 | struct OldFrameData { 195 | code_start: u32, 196 | code_size: u32, 197 | locals_size: u32, 198 | params_size: u16, 199 | attributes: u16, 200 | } 201 | 202 | impl OldFrameData { 203 | pub fn code_start(&self) -> PdbInternalRva { 204 | PdbInternalRva(u32::from_le(self.code_start)) 205 | } 206 | 207 | pub fn code_size(&self) -> u32 { 208 | u32::from_le(self.code_size) 209 | } 210 | 211 | pub fn locals_size(&self) -> u32 { 212 | u32::from_le(self.locals_size) 213 | } 214 | 215 | pub fn params_size(&self) -> u16 { 216 | u16::from_le(self.params_size) 217 | } 218 | 219 | pub fn prolog_size(&self) -> u16 { 220 | self.attributes() & 0xf 221 | } 222 | 223 | pub fn saved_regs_size(&self) -> u16 { 224 | (self.attributes() >> 8) & 0x7 225 | } 226 | 227 | pub fn has_seh(&self) -> bool { 228 | self.attributes() & 0x200 != 0 229 | } 230 | 231 | pub fn uses_base_pointer(&self) -> bool { 232 | self.attributes() & 0x400 != 0 233 | } 234 | 235 | pub fn frame_type(&self) -> FrameType { 236 | match self.attributes() >> 14 { 237 | 0x00 => FrameType::FPO, 238 | 0x01 => FrameType::Trap, 239 | 0x02 => FrameType::TSS, 240 | 0x03 => FrameType::Standard, 241 | 0x04 => FrameType::FrameData, 242 | _ => FrameType::Unknown, 243 | } 244 | } 245 | 246 | fn attributes(&self) -> u16 { 247 | u16::from_le(self.attributes) 248 | } 249 | } 250 | 251 | impl fmt::Debug for OldFrameData { 252 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 253 | f.debug_struct("OldFrameData") 254 | .field("code_start", &self.code_start()) 255 | .field("code_size", &self.code_size()) 256 | .field("locals_size", &self.locals_size()) 257 | .field("params_size", &self.params_size()) 258 | .field("prolog_size", &self.prolog_size()) 259 | .field("saved_regs_size", &self.saved_regs_size()) 260 | .field("has_seh", &self.has_seh()) 261 | .field("uses_base_pointer", &self.uses_base_pointer()) 262 | .field("frame_type", &self.frame_type()) 263 | .finish() 264 | } 265 | } 266 | 267 | /// Frame data for a code block. 268 | #[derive(Clone, Debug)] 269 | pub struct FrameData { 270 | /// Compiler-specific frame type. 271 | pub ty: FrameType, 272 | 273 | /// Relative virtual address of the start of the code block. 274 | /// 275 | /// Note that this address is internal to the PDB. To convert this to an actual [`Rva`], use 276 | /// [`PdbInternalRva::to_rva`]. 277 | pub code_start: PdbInternalRva, 278 | 279 | /// Size of the code block covered by this frame data in bytes. 280 | pub code_size: u32, 281 | 282 | /// Size of local variables pushed on the stack in bytes. 283 | pub locals_size: u32, 284 | 285 | /// Size of parameters pushed on the stack in bytes. 286 | pub params_size: u32, 287 | 288 | /// Number of bytes of prologue code in the block. 289 | pub prolog_size: u16, 290 | 291 | /// Size of saved registers pushed on the stack in bytes. 292 | pub saved_regs_size: u16, 293 | 294 | /// The maximum number of bytes pushed on the stack. 295 | pub max_stack_size: Option, 296 | 297 | /// Indicates that structured exception handling is in effect. 298 | pub has_structured_eh: bool, 299 | 300 | /// Indicates that C++ exception handling is in effect. 301 | pub has_cpp_eh: bool, 302 | 303 | /// Indicates that this frame is the start of a function. 304 | pub is_function_start: bool, 305 | 306 | /// Indicates that this function uses the EBP register. 307 | pub uses_base_pointer: bool, 308 | 309 | /// A program string allowing to reconstruct register values for this frame. 310 | /// 311 | /// The program string is a sequence of macros that is interpreted in order to establish the 312 | /// prologue. For example, a typical stack frame might use the program string `"$T0 $ebp = $eip 313 | /// $T0 4 + ^ = $ebp $T0 ^ = $esp $T0 8 + ="`. The format is reverse polish notation, where the 314 | /// operators follow the operands. `T0` represents a temporary variable on the stack. 315 | /// 316 | /// Note that the program string is specific to the CPU and to the calling convention set up for 317 | /// the function represented by the current stack frame. 318 | pub program: Option, 319 | } 320 | 321 | impl From<&'_ OldFrameData> for FrameData { 322 | fn from(data: &OldFrameData) -> Self { 323 | Self { 324 | ty: data.frame_type(), 325 | code_start: data.code_start(), 326 | code_size: data.code_size(), 327 | prolog_size: data.prolog_size(), 328 | locals_size: data.locals_size() * 4, 329 | params_size: u32::from(data.params_size()) * 4, 330 | saved_regs_size: data.saved_regs_size() * 4, 331 | max_stack_size: None, 332 | has_structured_eh: data.has_seh(), 333 | has_cpp_eh: false, 334 | is_function_start: false, 335 | uses_base_pointer: data.uses_base_pointer(), 336 | program: None, 337 | } 338 | } 339 | } 340 | 341 | impl From<&'_ NewFrameData> for FrameData { 342 | fn from(data: &NewFrameData) -> Self { 343 | Self { 344 | ty: FrameType::FrameData, 345 | code_start: data.code_start(), 346 | code_size: data.code_size(), 347 | prolog_size: data.prolog_size(), 348 | locals_size: data.locals_size(), 349 | params_size: data.params_size(), 350 | saved_regs_size: data.saved_regs_size(), 351 | max_stack_size: Some(data.max_stack_size()), 352 | has_structured_eh: data.has_seh(), 353 | has_cpp_eh: data.has_eh(), 354 | is_function_start: data.is_function_start(), 355 | uses_base_pointer: false, 356 | program: Some(data.frame_func()), 357 | } 358 | } 359 | } 360 | 361 | /// Iterator over entries in a [`FrameTable`]. 362 | #[derive(Debug, Default)] 363 | pub struct FrameDataIter<'t> { 364 | old_frames: &'t [OldFrameData], 365 | new_frames: &'t [NewFrameData], 366 | old_index: usize, 367 | new_index: usize, 368 | } 369 | 370 | impl FallibleIterator for FrameDataIter<'_> { 371 | type Item = FrameData; 372 | type Error = Error; 373 | 374 | fn next(&mut self) -> Result> { 375 | let old_opt = self.old_frames.get(self.old_index); 376 | let new_opt = self.new_frames.get(self.new_index); 377 | 378 | Ok(Some(match (old_opt, new_opt) { 379 | (Some(old_frame), Some(new_frame)) => { 380 | match new_frame.code_start().cmp(&old_frame.code_start()) { 381 | Ordering::Less => { 382 | self.new_index += 1; 383 | new_frame.into() 384 | } 385 | Ordering::Equal => { 386 | self.new_index += 1; 387 | self.old_index += 1; 388 | new_frame.into() 389 | } 390 | Ordering::Greater => { 391 | self.old_index += 1; 392 | old_frame.into() 393 | } 394 | } 395 | } 396 | (Some(old_frame), None) => { 397 | self.old_index += 1; 398 | old_frame.into() 399 | } 400 | (None, Some(new_frame)) => { 401 | self.new_index += 1; 402 | new_frame.into() 403 | } 404 | (None, None) => return Ok(None), 405 | })) 406 | } 407 | } 408 | 409 | /// An object that spans a code range. 410 | trait AddrRange { 411 | /// The start RVA of the block. 412 | fn start(&self) -> PdbInternalRva; 413 | 414 | /// The size of the block in bytes. 415 | fn size(&self) -> u32; 416 | 417 | /// The non-inclusive end of the block. 418 | #[inline] 419 | fn end(&self) -> PdbInternalRva { 420 | self.start() + self.size() 421 | } 422 | 423 | /// Returns whether this item includes the given Rva. 424 | #[inline] 425 | fn contains(&self, rva: PdbInternalRva) -> bool { 426 | rva >= self.start() && rva < self.end() 427 | } 428 | } 429 | 430 | impl AddrRange for OldFrameData { 431 | fn start(&self) -> PdbInternalRva { 432 | self.code_start() 433 | } 434 | 435 | fn size(&self) -> u32 { 436 | self.code_size() 437 | } 438 | } 439 | 440 | impl AddrRange for NewFrameData { 441 | fn start(&self) -> PdbInternalRva { 442 | self.code_start() 443 | } 444 | 445 | fn size(&self) -> u32 { 446 | self.code_size() 447 | } 448 | } 449 | 450 | /// Searches for a frame data entry covering the given `PdbInternalRva`. 451 | fn binary_search_by_rva(frames: &[R], rva: PdbInternalRva) -> usize { 452 | match frames.binary_search_by_key(&rva, |f| f.start()) { 453 | Ok(index) => index, 454 | Err(index) => { 455 | if index > 0 && frames[index - 1].contains(rva) { 456 | index - 1 457 | } else { 458 | index 459 | } 460 | } 461 | } 462 | } 463 | 464 | /// Describes stack frame layout of functions. 465 | /// 466 | /// The table contains [`FrameData`] entries ordered by [`PdbInternalRva`]. Each entry describes a 467 | /// range of instructions starting at `code_rva` for `code_size` bytes. 468 | /// 469 | /// A procedure/function might be described by multiple entries, with the first one declaring 470 | /// `is_function_start`. To retrieve frame information for a specific function, use 471 | /// [`FrameTable::iter_at_rva`]. 472 | /// 473 | /// Not every function in the image file must have frame data defined for it. Those functions that 474 | /// do not have frame data are assumed to have normal stack frames. 475 | /// 476 | /// # Example 477 | /// 478 | /// ```rust 479 | /// # use pdb::{PDB, Rva, FallibleIterator}; 480 | /// # 481 | /// # fn test() -> pdb::Result<()> { 482 | /// # let source = std::fs::File::open("fixtures/self/foo.pdb")?; 483 | /// let mut pdb = PDB::open(source)?; 484 | /// 485 | /// // Read the frame table once and reuse it 486 | /// let frame_table = pdb.frame_table()?; 487 | /// let mut frames = frame_table.iter(); 488 | /// 489 | /// // Iterate frame data in RVA order 490 | /// while let Some(frame) = frames.next()? { 491 | /// println!("{:#?}", frame); 492 | /// } 493 | /// # Ok(()) 494 | /// # } 495 | /// # test().unwrap() 496 | /// ``` 497 | pub struct FrameTable<'s> { 498 | old_stream: Option>, 499 | new_stream: Option>, 500 | } 501 | 502 | impl<'s> FrameTable<'s> { 503 | /// Parses frame data from raw streams. 504 | pub(crate) fn parse( 505 | old_stream: Option>, 506 | new_stream: Option>, 507 | ) -> Result { 508 | if let Some(ref stream) = old_stream { 509 | if cast_aligned::(stream.as_slice()).is_none() { 510 | return Err(Error::InvalidStreamLength("FrameData")); 511 | } 512 | } 513 | 514 | if let Some(ref stream) = new_stream { 515 | if cast_aligned::(stream.as_slice()).is_none() { 516 | return Err(Error::InvalidStreamLength("FPO")); 517 | } 518 | } 519 | 520 | Ok(FrameTable { 521 | old_stream, 522 | new_stream, 523 | }) 524 | } 525 | 526 | /// Returns an iterator over all frame data in this table, ordered by `code_rva`. 527 | pub fn iter(&self) -> FrameDataIter<'_> { 528 | FrameDataIter { 529 | old_frames: self.old_frames(), 530 | new_frames: self.new_frames(), 531 | old_index: 0, 532 | new_index: 0, 533 | } 534 | } 535 | 536 | /// Returns an iterator over frame data starting at the given `PdbInternalRva`. 537 | /// 538 | /// The first item returned by this iterator covers the given RVA. If the address is not a 539 | /// direct start of a function or block, this is the closest element preceding the block. If no 540 | /// frame data covers the given RVA, the iterator starts at the first item **after** the RVA. 541 | /// Therefore, check for the desired RVA range when iterating frame data. 542 | /// 543 | /// To obtain a `PdbInternalRva`, use [`PdbInternalSectionOffset::to_internal_rva`] or 544 | /// [`Rva::to_internal_rva`]. 545 | pub fn iter_at_rva(&self, rva: PdbInternalRva) -> FrameDataIter<'_> { 546 | let old_frames = self.old_frames(); 547 | let old_index = binary_search_by_rva(old_frames, rva); 548 | 549 | let new_frames = self.new_frames(); 550 | let new_index = binary_search_by_rva(new_frames, rva); 551 | 552 | FrameDataIter { 553 | old_frames, 554 | new_frames, 555 | old_index, 556 | new_index, 557 | } 558 | } 559 | 560 | /// Indicates whether any frame data is available. 561 | pub fn is_empty(&self) -> bool { 562 | self.new_frames().is_empty() && self.old_frames().is_empty() 563 | } 564 | 565 | fn old_frames(&self) -> &[OldFrameData] { 566 | match self.old_stream { 567 | // alignment checked during parsing 568 | Some(ref stream) => cast_aligned(stream.as_slice()).unwrap(), 569 | None => &[], 570 | } 571 | } 572 | 573 | fn new_frames(&self) -> &[NewFrameData] { 574 | match self.new_stream { 575 | // alignment checked during parsing 576 | Some(ref stream) => cast_aligned(stream.as_slice()).unwrap(), 577 | None => &[], 578 | } 579 | } 580 | } 581 | 582 | #[cfg(test)] 583 | mod tests { 584 | use super::*; 585 | 586 | use std::mem; 587 | 588 | #[test] 589 | fn test_new_frame_data() { 590 | assert_eq!(mem::size_of::(), 32); 591 | assert_eq!(mem::align_of::(), 4); 592 | } 593 | 594 | #[test] 595 | fn test_old_frame_data() { 596 | assert_eq!(mem::size_of::(), 16); 597 | assert_eq!(mem::align_of::(), 4); 598 | } 599 | } 600 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 pdb Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | //! The `pdb` create parses Microsoft PDB (Program Database) files. PDB files contain debugging 9 | //! information produced by most compilers that target Windows, including information about symbols, 10 | //! types, modules, and so on. 11 | //! 12 | //! # Usage 13 | //! 14 | //! PDB files are accessed via the [`pdb::PDB`] object. 15 | //! 16 | //! # Example 17 | //! 18 | //! ``` 19 | //! # use pdb::FallibleIterator; 20 | //! # 21 | //! # fn test() -> pdb::Result { 22 | //! let file = std::fs::File::open("fixtures/self/foo.pdb")?; 23 | //! let mut pdb = pdb::PDB::open(file)?; 24 | //! 25 | //! let symbol_table = pdb.global_symbols()?; 26 | //! let address_map = pdb.address_map()?; 27 | //! 28 | //! # let mut count: usize = 0; 29 | //! let mut symbols = symbol_table.iter(); 30 | //! while let Some(symbol) = symbols.next()? { 31 | //! match symbol.parse() { 32 | //! Ok(pdb::SymbolData::Public(data)) if data.function => { 33 | //! // we found the location of a function! 34 | //! let rva = data.offset.to_rva(&address_map).unwrap_or_default(); 35 | //! println!("{} is {}", rva, data.name); 36 | //! # count += 1; 37 | //! } 38 | //! _ => {} 39 | //! } 40 | //! } 41 | //! 42 | //! # Ok(count) 43 | //! # } 44 | //! # assert!(test().expect("test") > 2000); 45 | //! ``` 46 | 47 | #![warn(missing_docs)] 48 | 49 | // modules 50 | mod common; 51 | mod dbi; 52 | mod framedata; 53 | mod modi; 54 | mod msf; 55 | mod omap; 56 | mod pdb; 57 | mod pdbi; 58 | mod pe; 59 | mod source; 60 | mod strings; 61 | mod symbol; 62 | mod tpi; 63 | 64 | // exports 65 | pub use crate::common::*; 66 | pub use crate::dbi::*; 67 | pub use crate::framedata::*; 68 | pub use crate::modi::*; 69 | pub use crate::omap::*; 70 | pub use crate::pdb::*; 71 | pub use crate::pdbi::*; 72 | pub use crate::pe::*; 73 | pub use crate::source::*; 74 | pub use crate::strings::*; 75 | pub use crate::symbol::*; 76 | pub use crate::tpi::*; 77 | 78 | // re-export FallibleIterator for convenience 79 | #[doc(no_inline)] 80 | pub use fallible_iterator::FallibleIterator; 81 | -------------------------------------------------------------------------------- /src/modi/constants.rs: -------------------------------------------------------------------------------- 1 | //! Constants for all versions of the module info stream. 2 | #![allow(unused)] 3 | 4 | /// First explicit signature. 5 | pub const CV_SIGNATURE_C7: u32 = 1; 6 | /// Signature indicating a C11 (VC 5.x) module info stream. Uses 32-bit types. 7 | pub const CV_SIGNATURE_C11: u32 = 2; 8 | /// Signature indicating a C13 (VC 7.x) module info stream. Uses zero terminated names. 9 | pub const CV_SIGNATURE_C13: u32 = 4; 10 | 11 | /// Debug subsection kind for empty subsections. Should be skipped. 12 | pub const DEBUG_S_IGNORE: u32 = 0x8000_0000; 13 | /// Flag indicating that column information is present. 14 | pub const CV_LINES_HAVE_COLUMNS: u16 = 0x1; 15 | 16 | /// Flag indicating the default format of `DEBUG_S_INLINEELINEINFO` 17 | pub const CV_INLINEE_SOURCE_LINE_SIGNATURE: u32 = 0x0; 18 | /// Flag indicating the extended format of `DEBUG_S_INLINEELINEINFO` 19 | pub const CV_INLINEE_SOURCE_LINE_SIGNATURE_EX: u32 = 0x1; 20 | -------------------------------------------------------------------------------- /src/modi/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::common::*; 4 | use crate::dbi::Module; 5 | use crate::msf::Stream; 6 | use crate::symbol::SymbolIter; 7 | use crate::FallibleIterator; 8 | 9 | mod c13; 10 | mod constants; 11 | 12 | pub use c13::{ 13 | CrossModuleExportIter, CrossModuleExports, CrossModuleImports, Inlinee, InlineeIterator, 14 | InlineeLineIterator, 15 | }; 16 | 17 | #[derive(Clone, Copy, Debug)] 18 | enum LinesSize { 19 | C11(usize), 20 | C13(usize), 21 | } 22 | 23 | /// This struct contains data about a single module from its module info stream. 24 | /// 25 | /// The module info stream is where private symbols and line info is stored. 26 | pub struct ModuleInfo<'s> { 27 | stream: Stream<'s>, 28 | symbols_size: usize, 29 | lines_size: LinesSize, 30 | } 31 | 32 | impl<'s> ModuleInfo<'s> { 33 | /// Parses a `ModuleInfo` from it's Module info stream data. 34 | pub(crate) fn parse(stream: Stream<'s>, module: &Module<'_>) -> Self { 35 | let info = module.info(); 36 | 37 | let lines_size = if info.lines_size > 0 { 38 | LinesSize::C11(info.lines_size as usize) 39 | } else { 40 | LinesSize::C13(info.c13_lines_size as usize) 41 | }; 42 | 43 | let symbols_size = info.symbols_size as usize; 44 | ModuleInfo { 45 | stream, 46 | symbols_size, 47 | lines_size, 48 | } 49 | } 50 | 51 | fn lines_data(&self, size: usize) -> &[u8] { 52 | let start = self.symbols_size; 53 | &self.stream[start..start + size] 54 | } 55 | 56 | /// Get an iterator over the all symbols in this module. 57 | pub fn symbols(&self) -> Result> { 58 | let mut buf = self.stream.parse_buffer(); 59 | buf.truncate(self.symbols_size)?; 60 | if self.symbols_size > 0 { 61 | let sig = buf.parse_u32()?; 62 | if sig != constants::CV_SIGNATURE_C13 { 63 | return Err(Error::UnimplementedFeature( 64 | "Unsupported symbol data format", 65 | )); 66 | } 67 | } 68 | Ok(SymbolIter::new(buf)) 69 | } 70 | 71 | /// Get an iterator over symbols starting at the given index. 72 | pub fn symbols_at(&self, index: SymbolIndex) -> Result> { 73 | let mut iter = self.symbols()?; 74 | iter.seek(index); 75 | Ok(iter) 76 | } 77 | 78 | /// Returns a line program that gives access to file and line information in this module. 79 | pub fn line_program(&self) -> Result> { 80 | let inner = match self.lines_size { 81 | LinesSize::C11(_size) => return Err(Error::UnimplementedFeature("C11 line programs")), 82 | LinesSize::C13(size) => { 83 | LineProgramInner::C13(c13::LineProgram::parse(self.lines_data(size))?) 84 | } 85 | }; 86 | 87 | Ok(LineProgram { inner }) 88 | } 89 | 90 | /// Returns an iterator over all inlinees in this module. 91 | /// 92 | /// Inlinees are not guaranteed to be sorted. When requiring random access by `ItemId`, collect 93 | /// them into a mapping structure rather than reiterating multiple times. 94 | pub fn inlinees(&self) -> Result> { 95 | Ok(match self.lines_size { 96 | // C11 does not contain inlinee information. 97 | LinesSize::C11(_size) => Default::default(), 98 | LinesSize::C13(size) => InlineeIterator::parse(self.lines_data(size))?, 99 | }) 100 | } 101 | 102 | /// Returns a table of exports declared by this module. 103 | pub fn exports(&self) -> Result { 104 | Ok(match self.lines_size { 105 | // C11 does not have cross module exports. 106 | LinesSize::C11(_size) => Default::default(), 107 | LinesSize::C13(size) => CrossModuleExports::parse(self.lines_data(size))?, 108 | }) 109 | } 110 | 111 | /// Returns a table of imports of this module. 112 | pub fn imports(&self) -> Result> { 113 | Ok(match self.lines_size { 114 | // C11 does not have cross module imports. 115 | LinesSize::C11(_size) => Default::default(), 116 | LinesSize::C13(size) => CrossModuleImports::parse(self.lines_data(size))?, 117 | }) 118 | } 119 | } 120 | 121 | /// Checksum of a source file's contents. 122 | #[derive(Clone, Debug)] 123 | #[allow(missing_docs)] 124 | pub enum FileChecksum<'a> { 125 | None, 126 | Md5(&'a [u8]), 127 | Sha1(&'a [u8]), 128 | Sha256(&'a [u8]), 129 | } 130 | 131 | impl PartialEq for FileChecksum<'_> { 132 | fn eq(&self, other: &Self) -> bool { 133 | // Manual implementation to allow for None != None. 134 | match (self, other) { 135 | (&FileChecksum::Md5(lhs), &FileChecksum::Md5(rhs)) => lhs == rhs, 136 | (&FileChecksum::Sha1(lhs), &FileChecksum::Sha1(rhs)) => lhs == rhs, 137 | (&FileChecksum::Sha256(lhs), &FileChecksum::Sha256(rhs)) => lhs == rhs, 138 | _ => false, 139 | } 140 | } 141 | } 142 | 143 | /// Information record on a source file. 144 | #[derive(Clone, Debug, PartialEq)] 145 | pub struct FileInfo<'a> { 146 | /// Reference to the file name in the [`StringTable`](crate::StringTable). 147 | pub name: StringRef, 148 | 149 | /// Checksum of the file contents. 150 | pub checksum: FileChecksum<'a>, 151 | } 152 | 153 | /// The kind of source construct a line info is referring to. 154 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 155 | pub enum LineInfoKind { 156 | /// A source code expression. 157 | Expression, 158 | /// A source code statement. 159 | Statement, 160 | } 161 | 162 | impl Default for LineInfoKind { 163 | fn default() -> Self { 164 | Self::Statement 165 | } 166 | } 167 | 168 | /// Mapping of a source code offset to a source file location. 169 | /// 170 | /// A line entry is always valid up to the subsequent entry. 171 | #[derive(Clone, Debug, PartialEq)] 172 | pub struct LineInfo { 173 | /// Source code offset. 174 | pub offset: PdbInternalSectionOffset, 175 | /// The optional length of the code. 176 | pub length: Option, 177 | /// Index of the source file in this module. 178 | pub file_index: FileIndex, 179 | /// Line number of the start of the covered range. 180 | pub line_start: u32, 181 | /// Line number of the end of the covered range. 182 | pub line_end: u32, 183 | /// Column number of the start of the covered range. 184 | /// 185 | /// This value is only present if column information is provided by the PDB. Even then, it is 186 | /// often zero. 187 | pub column_start: Option, 188 | /// Column number of the end of the covered range. 189 | /// 190 | /// This value is only present if column information is provided by the PDB. Even then, it is 191 | /// often zero. 192 | pub column_end: Option, 193 | /// Kind of this line information. 194 | pub kind: LineInfoKind, 195 | } 196 | 197 | impl LineInfo { 198 | pub(crate) fn set_end(&mut self, end_offset: PdbInternalSectionOffset) { 199 | // This uses PartialOrd which only compares if the section is equal 200 | debug_assert!(self.offset <= end_offset); 201 | 202 | if self.offset <= end_offset { 203 | let length = end_offset.offset - self.offset.offset; 204 | if self.length.map_or(true, |l| l > length) { 205 | self.length = Some(length); 206 | } 207 | } 208 | } 209 | } 210 | 211 | enum LineProgramInner<'a> { 212 | C13(c13::LineProgram<'a>), 213 | } 214 | 215 | /// The `LineProgram` provides access to source line information for a module and its procedures. 216 | pub struct LineProgram<'a> { 217 | inner: LineProgramInner<'a>, 218 | } 219 | 220 | impl<'a> LineProgram<'a> { 221 | /// Returns an iterator over all line information records of this module. 222 | /// 223 | /// Note that line records are not guaranteed to be ordered by source code offset. If a 224 | /// monotonic order by `PdbInternalSectionOffset` or `Rva` is required, the lines have to be 225 | /// sorted manually. 226 | pub fn lines(&self) -> LineIterator<'_> { 227 | match self.inner { 228 | LineProgramInner::C13(ref inner) => LineIterator { 229 | inner: LineIteratorInner::C13(inner.lines()), 230 | }, 231 | } 232 | } 233 | 234 | /// Returns an iterator over all file records of this module. 235 | pub fn files(&self) -> FileIterator<'a> { 236 | match self.inner { 237 | LineProgramInner::C13(ref inner) => FileIterator { 238 | inner: FileIteratorInner::C13(inner.files()), 239 | }, 240 | } 241 | } 242 | 243 | /// Returns an iterator over line records for a symbol at the given section offset. 244 | /// 245 | /// This may return line records before the start offset of the symbol. When using ASM, 246 | /// specifically MASM, symbol records may specify a range that is smaller than the actual 247 | /// code generated for this function. `lines_for_symbol` returns all line records covering this 248 | /// function, potentially exceeding this range. 249 | /// 250 | /// Note that line records are not guaranteed to be ordered by source code offset. If a 251 | /// monotonic order by `PdbInternalSectionOffset` or `Rva` is required, the lines have to be 252 | /// sorted manually. 253 | pub fn lines_for_symbol(&self, offset: PdbInternalSectionOffset) -> LineIterator<'_> { 254 | match self.inner { 255 | LineProgramInner::C13(ref inner) => LineIterator { 256 | inner: LineIteratorInner::C13(inner.lines_for_symbol(offset)), 257 | }, 258 | } 259 | } 260 | 261 | /// Looks up file information for the specified file. 262 | pub fn get_file_info(&self, offset: FileIndex) -> Result> { 263 | match self.inner { 264 | LineProgramInner::C13(ref inner) => inner.get_file_info(offset), 265 | } 266 | } 267 | } 268 | 269 | #[derive(Clone, Debug)] 270 | enum LineIteratorInner<'a> { 271 | C13(c13::LineIterator<'a>), 272 | } 273 | 274 | /// An iterator over line information records in a module. 275 | #[derive(Clone, Debug)] 276 | pub struct LineIterator<'a> { 277 | inner: LineIteratorInner<'a>, 278 | } 279 | 280 | impl Default for LineIterator<'_> { 281 | fn default() -> Self { 282 | LineIterator { 283 | inner: LineIteratorInner::C13(Default::default()), 284 | } 285 | } 286 | } 287 | 288 | impl<'a> FallibleIterator for LineIterator<'a> { 289 | type Item = LineInfo; 290 | type Error = Error; 291 | 292 | fn next(&mut self) -> Result> { 293 | match self.inner { 294 | LineIteratorInner::C13(ref mut inner) => inner.next(), 295 | } 296 | } 297 | } 298 | 299 | #[derive(Clone, Debug)] 300 | enum FileIteratorInner<'a> { 301 | C13(c13::FileIterator<'a>), 302 | } 303 | 304 | /// An iterator over file records in a module. 305 | #[derive(Clone, Debug)] 306 | pub struct FileIterator<'a> { 307 | inner: FileIteratorInner<'a>, 308 | } 309 | 310 | impl Default for FileIterator<'_> { 311 | fn default() -> Self { 312 | FileIterator { 313 | inner: FileIteratorInner::C13(Default::default()), 314 | } 315 | } 316 | } 317 | 318 | impl<'a> FallibleIterator for FileIterator<'a> { 319 | type Item = FileInfo<'a>; 320 | type Error = Error; 321 | 322 | fn next(&mut self) -> Result> { 323 | match self.inner { 324 | FileIteratorInner::C13(ref mut inner) => inner.next(), 325 | } 326 | } 327 | } 328 | 329 | /// Named reference to a [`Module`]. 330 | /// 331 | /// The name stored in the [`StringTable`](crate::StringTable) corresponds to the name of the module 332 | /// as returned by [`Module::module_name`]. 333 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 334 | pub struct ModuleRef(pub StringRef); 335 | 336 | impl fmt::Display for ModuleRef { 337 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 338 | self.0.fmt(f) 339 | } 340 | } 341 | 342 | /// Reference to a local type or id in another module. 343 | /// 344 | /// See [`ItemIndex::is_cross_module`] for more information. 345 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 346 | pub struct CrossModuleRef(pub ModuleRef, pub Local); 347 | 348 | /// A cross module export that can either be a `Type` or an `Id`. 349 | /// 350 | /// Other modules may reference this item using its local ID by declaring it in the cross module 351 | /// imports subsection. 352 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 353 | pub enum CrossModuleExport { 354 | /// A cross module export of a [`Type`](crate::Type). 355 | Type(Local, TypeIndex), 356 | /// A cross module export of an [`Id`](crate::Id). 357 | Id(Local, IdIndex), 358 | } 359 | -------------------------------------------------------------------------------- /src/msf/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 pdb Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | use std::fmt; 9 | use std::ops::Deref; 10 | 11 | use scroll::{ctx::TryFromCtx, Endian, Pread}; 12 | 13 | use crate::common::*; 14 | use crate::source::*; 15 | 16 | mod page_list; 17 | use self::page_list::PageList; 18 | 19 | type PageNumber = u32; 20 | 21 | #[derive(Debug, Copy, Clone)] 22 | struct Header { 23 | page_size: usize, 24 | maximum_valid_page_number: PageNumber, 25 | } 26 | 27 | impl Header { 28 | fn pages_needed_to_store(&self, bytes: usize) -> usize { 29 | (bytes + (self.page_size - 1)) / self.page_size 30 | } 31 | 32 | fn validate_page_number(&self, page_number: u32) -> Result { 33 | if page_number == 0 || page_number > self.maximum_valid_page_number { 34 | Err(Error::PageReferenceOutOfRange(page_number)) 35 | } else { 36 | Ok(page_number as PageNumber) 37 | } 38 | } 39 | } 40 | 41 | /// Represents a stream table at various stages of access 42 | #[doc(hidden)] 43 | #[derive(Debug)] 44 | enum StreamTable<'s> { 45 | /// The MSF header gives us the size of the table in bytes, and the list of pages (usually one) 46 | /// where we can find the list of pages that contain the stream table. 47 | HeaderOnly { 48 | size_in_bytes: usize, 49 | stream_table_location_location: PageList, 50 | }, 51 | 52 | /// Given the HeaderOnly information, we can do an initial read to get the actual location of 53 | /// the stream table as a PageList. 54 | TableFound { stream_table_location: PageList }, 55 | 56 | // Given the table location, we can access the stream table itself 57 | Available { 58 | stream_table_view: Box>, 59 | }, 60 | } 61 | 62 | fn view<'s>(source: &mut dyn Source<'s>, page_list: &PageList) -> Result>> { 63 | // view it 64 | let view = source.view(page_list.source_slices())?; 65 | 66 | // double check our Source 67 | // if the Source didn't return the requested bits, that's an implementation bug, so 68 | // assert instead of returning an error 69 | assert_eq!(view.as_slice().len(), page_list.len()); 70 | 71 | // done 72 | Ok(view) 73 | } 74 | 75 | mod big { 76 | use super::*; 77 | 78 | pub const MAGIC: &[u8] = b"Microsoft C/C++ MSF 7.00\r\n\x1a\x44\x53\x00\x00\x00"; 79 | 80 | /// The PDB header as stored on disk. 81 | /// 82 | /// See the Microsoft code for reference: 83 | #[repr(C)] 84 | #[derive(Debug, Copy, Clone)] 85 | struct RawHeader { 86 | magic: [u8; 32], 87 | page_size: u32, 88 | free_page_map: u32, 89 | pages_used: u32, 90 | directory_size: u32, 91 | _reserved: u32, 92 | } 93 | 94 | impl<'t> TryFromCtx<'t, Endian> for RawHeader { 95 | type Error = scroll::Error; 96 | 97 | fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { 98 | let mut offset = 0; 99 | let data = Self { 100 | magic: { 101 | let mut tmp = [0; 32]; 102 | this.gread_inout_with(&mut offset, &mut tmp, le)?; 103 | tmp 104 | }, 105 | page_size: this.gread_with(&mut offset, le)?, 106 | free_page_map: this.gread_with(&mut offset, le)?, 107 | pages_used: this.gread_with(&mut offset, le)?, 108 | directory_size: this.gread_with(&mut offset, le)?, 109 | _reserved: this.gread_with(&mut offset, le)?, 110 | }; 111 | Ok((data, offset)) 112 | } 113 | } 114 | 115 | #[derive(Debug)] 116 | pub struct BigMSF<'s, S> { 117 | header: Header, 118 | source: S, 119 | stream_table: StreamTable<'s>, 120 | } 121 | 122 | impl<'s, S: Source<'s>> BigMSF<'s, S> { 123 | pub fn new(source: S, header_view: Box>) -> Result> { 124 | let mut buf = ParseBuffer::from(header_view.as_slice()); 125 | let header: RawHeader = buf.parse()?; 126 | 127 | if header.magic != MAGIC { 128 | return Err(Error::UnrecognizedFileFormat); 129 | } 130 | 131 | if header.page_size.count_ones() != 1 132 | || header.page_size < 0x100 133 | || header.page_size > (128 * 0x10000) 134 | { 135 | return Err(Error::InvalidPageSize(header.page_size)); 136 | } 137 | 138 | let header_object = Header { 139 | page_size: header.page_size as usize, 140 | maximum_valid_page_number: header.pages_used, 141 | }; 142 | 143 | // calculate how many pages are needed to store the stream table 144 | let size_of_stream_table_in_pages = 145 | header_object.pages_needed_to_store(header.directory_size as usize); 146 | 147 | // now: how many pages are needed to store the list of pages that store the stream table? 148 | // each page entry is a u32, so multiply by four 149 | let size_of_stream_table_page_list_in_pages = 150 | header_object.pages_needed_to_store(size_of_stream_table_in_pages * 4); 151 | 152 | // read the list of stream table page list pages, which immediately follow the header 153 | // yes, this is a stupid level of indirection 154 | let mut stream_table_page_list_page_list = PageList::new(header_object.page_size); 155 | for _ in 0..size_of_stream_table_page_list_in_pages { 156 | let n = buf.parse_u32()?; 157 | stream_table_page_list_page_list.push(header_object.validate_page_number(n)?); 158 | } 159 | 160 | // truncate the stream table location location to the correct size 161 | stream_table_page_list_page_list.truncate(size_of_stream_table_in_pages * 4); 162 | 163 | Ok(BigMSF { 164 | header: header_object, 165 | source, 166 | stream_table: StreamTable::HeaderOnly { 167 | size_in_bytes: header.directory_size as usize, 168 | stream_table_location_location: stream_table_page_list_page_list, 169 | }, 170 | }) 171 | } 172 | 173 | fn find_stream_table(&mut self) -> Result<()> { 174 | let mut new_stream_table: Option> = None; 175 | 176 | if let StreamTable::HeaderOnly { 177 | size_in_bytes, 178 | ref stream_table_location_location, 179 | } = self.stream_table 180 | { 181 | // the header indicated we need to read size_in_pages page numbers from the 182 | // specified PageList. 183 | 184 | // ask to view the location location 185 | let location_location = view(&mut self.source, stream_table_location_location)?; 186 | 187 | // build a PageList 188 | let mut page_list = PageList::new(self.header.page_size); 189 | let mut buf = ParseBuffer::from(location_location.as_slice()); 190 | while !buf.is_empty() { 191 | let n = buf.parse_u32()?; 192 | page_list.push(self.header.validate_page_number(n)?); 193 | } 194 | 195 | page_list.truncate(size_in_bytes); 196 | 197 | // remember what we learned 198 | new_stream_table = Some(StreamTable::TableFound { 199 | stream_table_location: page_list, 200 | }); 201 | } 202 | 203 | if let Some(st) = new_stream_table { 204 | self.stream_table = st; 205 | } 206 | 207 | Ok(()) 208 | } 209 | 210 | fn make_stream_table_available(&mut self) -> Result<()> { 211 | // do the initial read if we must 212 | if let StreamTable::HeaderOnly { .. } = self.stream_table { 213 | self.find_stream_table()?; 214 | } 215 | 216 | // do we need to map the stream table itself? 217 | let mut new_stream_table = None; 218 | if let StreamTable::TableFound { 219 | ref stream_table_location, 220 | } = self.stream_table 221 | { 222 | // ask the source to view it 223 | let stream_table_view = view(&mut self.source, stream_table_location)?; 224 | new_stream_table = Some(StreamTable::Available { stream_table_view }); 225 | } 226 | 227 | if let Some(st) = new_stream_table { 228 | self.stream_table = st; 229 | } 230 | 231 | // stream table is available 232 | assert!(matches!(self.stream_table, StreamTable::Available { .. })); 233 | 234 | Ok(()) 235 | } 236 | 237 | fn look_up_stream(&mut self, stream_number: u32) -> Result { 238 | // ensure the stream table is available 239 | self.make_stream_table_available()?; 240 | 241 | let header = self.header; 242 | 243 | // declare the things we're going to find 244 | let bytes_in_stream: u32; 245 | let page_list: PageList; 246 | 247 | if let StreamTable::Available { 248 | ref stream_table_view, 249 | } = self.stream_table 250 | { 251 | let stream_table_slice = stream_table_view.as_slice(); 252 | let mut stream_table = ParseBuffer::from(stream_table_slice); 253 | 254 | // the stream table is structured as: 255 | // stream_count 256 | // 0..stream_count: size of stream in bytes (0xffffffff indicating "stream does not exist") 257 | // stream 0: PageNumber 258 | // stream 1: PageNumber, PageNumber 259 | // stream 2: PageNumber, PageNumber, PageNumber, PageNumber, PageNumber 260 | // stream 3: PageNumber, PageNumber, PageNumber, PageNumber 261 | // (number of pages determined by number of bytes) 262 | 263 | let stream_count = stream_table.parse_u32()?; 264 | 265 | // check if we've already outworn our welcome 266 | if stream_number >= stream_count { 267 | return Err(Error::StreamNotFound(stream_number)); 268 | } 269 | 270 | // we now have {stream_count} u32s describing the length of each stream 271 | 272 | // walk over the streams before the requested stream 273 | // we need to pay attention to how big each one is, since their page numbers come 274 | // before our page numbers in the stream table 275 | let mut page_numbers_to_skip: usize = 0; 276 | for _ in 0..stream_number { 277 | let bytes = stream_table.parse_u32()?; 278 | if bytes == u32::max_value() { 279 | // stream is not present, ergo nothing to skip 280 | } else { 281 | page_numbers_to_skip += header.pages_needed_to_store(bytes as usize); 282 | } 283 | } 284 | 285 | // read our stream's size 286 | bytes_in_stream = stream_table.parse_u32()?; 287 | if bytes_in_stream == u32::max_value() { 288 | return Err(Error::StreamNotFound(stream_number)); 289 | } 290 | let pages_in_stream = header.pages_needed_to_store(bytes_in_stream as usize); 291 | 292 | // skip the remaining streams' byte counts 293 | let _ = stream_table.take((stream_count - stream_number - 1) as usize * 4)?; 294 | 295 | // skip the preceding streams' page numbers 296 | let _ = stream_table.take(page_numbers_to_skip * 4)?; 297 | 298 | // we're now at the list of pages for our stream 299 | // accumulate them into a PageList 300 | let mut list = PageList::new(header.page_size); 301 | for _ in 0..pages_in_stream { 302 | let page_number = stream_table.parse_u32()?; 303 | list.push(self.header.validate_page_number(page_number)?); 304 | } 305 | 306 | // truncate to the size of the stream 307 | list.truncate(bytes_in_stream as usize); 308 | 309 | page_list = list; 310 | } else { 311 | unreachable!(); 312 | } 313 | 314 | // done! 315 | Ok(page_list) 316 | } 317 | } 318 | 319 | impl<'s, S: Source<'s>> Msf<'s, S> for BigMSF<'s, S> { 320 | fn get(&mut self, stream_number: u32, limit: Option) -> Result> { 321 | // look up the stream 322 | let mut page_list = self.look_up_stream(stream_number)?; 323 | 324 | // apply any limits we have 325 | if let Some(limit) = limit { 326 | page_list.truncate(limit); 327 | } 328 | 329 | // now that we know where this stream lives, we can view it 330 | let view = view(&mut self.source, &page_list)?; 331 | 332 | // pack it into a Stream 333 | let stream = Stream { source_view: view }; 334 | 335 | Ok(stream) 336 | } 337 | } 338 | } 339 | 340 | mod small { 341 | pub const MAGIC: &[u8] = b"Microsoft C/C++ program database 2.00\r\n\x1a\x4a\x47"; 342 | // TODO: implement SmallMSF 343 | } 344 | 345 | /// Represents a single Stream within the multi-stream file. 346 | #[derive(Debug)] 347 | pub struct Stream<'s> { 348 | source_view: Box>, 349 | } 350 | 351 | impl<'s> Stream<'s> { 352 | #[inline] 353 | pub(crate) fn parse_buffer(&self) -> ParseBuffer<'_> { 354 | let slice = self.source_view.as_slice(); 355 | ParseBuffer::from(slice) 356 | } 357 | 358 | #[inline] 359 | pub fn as_slice(&self) -> &[u8] { 360 | self.source_view.as_slice() 361 | } 362 | } 363 | 364 | impl Deref for Stream<'_> { 365 | type Target = [u8]; 366 | 367 | #[inline] 368 | fn deref(&self) -> &Self::Target { 369 | self.as_slice() 370 | } 371 | } 372 | 373 | /// Provides access to a "multi-stream file", which is the container format used by PDBs. 374 | pub trait Msf<'s, S>: fmt::Debug { 375 | /// Accesses a stream by stream number, optionally restricted by a byte limit. 376 | fn get(&mut self, stream_number: u32, limit: Option) -> Result>; 377 | } 378 | 379 | fn header_matches(actual: &[u8], expected: &[u8]) -> bool { 380 | actual.len() >= expected.len() && &actual[0..expected.len()] == expected 381 | } 382 | 383 | pub fn open_msf<'s, S: Source<'s> + 's>(mut source: S) -> Result + 's>> { 384 | // map the header 385 | let mut header_location = PageList::new(4096); 386 | header_location.push(0); 387 | let header_view = match view(&mut source, &header_location) { 388 | Ok(view) => view, 389 | Err(e) => match e { 390 | Error::IoError(x) => { 391 | if x.kind() == std::io::ErrorKind::UnexpectedEof { 392 | return Err(Error::UnrecognizedFileFormat); 393 | } else { 394 | return Err(Error::IoError(x)); 395 | } 396 | } 397 | _ => return Err(e), 398 | }, 399 | }; 400 | 401 | // see if it's a BigMSF 402 | if header_matches(header_view.as_slice(), big::MAGIC) { 403 | // claimed! 404 | let bigmsf = big::BigMSF::new(source, header_view)?; 405 | return Ok(Box::new(bigmsf)); 406 | } 407 | 408 | if header_matches(header_view.as_slice(), small::MAGIC) { 409 | // sorry 410 | return Err(Error::UnimplementedFeature("small MSF file format")); 411 | } 412 | 413 | Err(Error::UnrecognizedFileFormat) 414 | } 415 | 416 | #[cfg(test)] 417 | mod tests { 418 | mod header { 419 | use crate::common::Error; 420 | use crate::msf::open_msf; 421 | use crate::msf::Header; 422 | 423 | #[test] 424 | fn test_pages_needed_to_store() { 425 | let h = Header { 426 | page_size: 4096, 427 | maximum_valid_page_number: 15, 428 | }; 429 | assert_eq!(h.pages_needed_to_store(0), 0); 430 | assert_eq!(h.pages_needed_to_store(1), 1); 431 | assert_eq!(h.pages_needed_to_store(1024), 1); 432 | assert_eq!(h.pages_needed_to_store(2048), 1); 433 | assert_eq!(h.pages_needed_to_store(4095), 1); 434 | assert_eq!(h.pages_needed_to_store(4096), 1); 435 | assert_eq!(h.pages_needed_to_store(4097), 2); 436 | } 437 | 438 | #[test] 439 | fn test_validate_page_number() { 440 | let h = Header { 441 | page_size: 4096, 442 | maximum_valid_page_number: 15, 443 | }; 444 | assert!(matches!( 445 | h.validate_page_number(0), 446 | Err(Error::PageReferenceOutOfRange(0)) 447 | )); 448 | assert!(matches!(h.validate_page_number(1), Ok(1))); 449 | assert!(matches!(h.validate_page_number(2), Ok(2))); 450 | assert!(matches!(h.validate_page_number(14), Ok(14))); 451 | assert!(matches!(h.validate_page_number(15), Ok(15))); 452 | assert!(matches!( 453 | h.validate_page_number(16), 454 | Err(Error::PageReferenceOutOfRange(16)) 455 | )); 456 | assert!(matches!( 457 | h.validate_page_number(17), 458 | Err(Error::PageReferenceOutOfRange(17)) 459 | )); 460 | } 461 | 462 | #[test] 463 | fn test_small_file_unrecognized_file_format() { 464 | let small_file = std::io::Cursor::new(b"\x7FELF"); 465 | 466 | match open_msf(small_file) { 467 | Ok(_) => panic!("4 byte file should not parse as msf"), 468 | Err(e) => match e { 469 | Error::UnrecognizedFileFormat => (), 470 | _ => panic!("4 byte file should parse as unrecognized file format"), 471 | }, 472 | }; 473 | } 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /src/msf/page_list.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 pdb Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | use crate::msf::PageNumber; 9 | use crate::source::SourceSlice; 10 | 11 | /// Represents a list of `PageNumbers`, which are likely (but not certainly) sequential, and which 12 | /// will be presented as a slice of `SourceSlice`s. 13 | #[derive(Debug)] 14 | pub struct PageList { 15 | page_size: usize, 16 | source_slices: Vec, 17 | last_page: Option, 18 | truncated: bool, 19 | } 20 | 21 | impl PageList { 22 | /// Create a new PageList for a given page size. 23 | pub fn new(page_size: usize) -> Self { 24 | Self { 25 | page_size, 26 | source_slices: Vec::new(), 27 | last_page: None, 28 | truncated: false, 29 | } 30 | } 31 | 32 | /// Add a page to the PageList. If this page number is sequential with the previous page number, 33 | /// it will be combined into the previous `SourceSlice` for efficiency. 34 | pub fn push(&mut self, page: PageNumber) { 35 | assert!(!self.truncated); 36 | 37 | let is_continuous = match self.last_page { 38 | Some(n) => n.checked_add(1) == Some(page), 39 | None => false, 40 | }; 41 | 42 | if is_continuous { 43 | // extend by one page 44 | debug_assert!(!self.source_slices.is_empty()); 45 | let last_slice = self.source_slices.last_mut().unwrap(); 46 | last_slice.size += self.page_size; 47 | } else { 48 | self.source_slices.push(SourceSlice { 49 | offset: (self.page_size as u64) * u64::from(page), 50 | size: self.page_size, 51 | }); 52 | } 53 | 54 | self.last_page = Some(page); 55 | } 56 | 57 | /// Truncate the `PageList` to request only a certain number of bytes, regardless of how many 58 | /// pages were pushed. Truncatation is optional, but it must be last; `push()` may not be 59 | /// called after `truncate()`. 60 | pub fn truncate(&mut self, bytes: usize) { 61 | let mut bytes = bytes; 62 | let mut new_slices: Vec = Vec::new(); 63 | 64 | for slice in &self.source_slices { 65 | let mut slice: SourceSlice = *slice; 66 | if bytes > 0 { 67 | // we need something from this slice 68 | // restrict this slice to the number of bytes remaining 69 | if slice.size > bytes { 70 | slice.size = bytes; 71 | } 72 | 73 | // keep it 74 | new_slices.push(slice); 75 | 76 | // subtract the number of bytes in this slice 77 | bytes -= slice.size; 78 | } else { 79 | // we're done 80 | break; 81 | } 82 | } 83 | 84 | self.source_slices = new_slices; 85 | self.truncated = true; 86 | } 87 | 88 | /// Return the total length of this PageList. 89 | pub fn len(&self) -> usize { 90 | self.source_slices.iter().fold(0, |acc, s| acc + s.size) 91 | } 92 | 93 | /// Return a slice of SourceSlices. 94 | pub fn source_slices(&self) -> &[SourceSlice] { 95 | self.source_slices.as_slice() 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use crate::msf::page_list::*; 102 | 103 | #[test] 104 | fn test_push() { 105 | let mut list = PageList::new(4096); 106 | 107 | // PageList should coalesce sequential pages 108 | list.push(0); 109 | list.push(1); 110 | let expected = vec![SourceSlice { 111 | offset: 0, 112 | size: 8192, 113 | }]; 114 | assert_eq!(list.source_slices(), expected.as_slice()); 115 | assert_eq!(list.len(), 8192); 116 | 117 | // PageList should handle nonsequential runs too 118 | list.push(4); 119 | list.push(5); 120 | let expected = vec![ 121 | SourceSlice { 122 | offset: 0, 123 | size: 8192, 124 | }, 125 | SourceSlice { 126 | offset: 16384, 127 | size: 8192, 128 | }, 129 | ]; 130 | assert_eq!(list.source_slices(), expected.as_slice()); 131 | assert_eq!(list.len(), 16384); 132 | 133 | // ...including nonsequential runs that go backwards 134 | list.push(2); 135 | let expected = vec![ 136 | SourceSlice { 137 | offset: 0, 138 | size: 8192, 139 | }, 140 | SourceSlice { 141 | offset: 16384, 142 | size: 8192, 143 | }, 144 | SourceSlice { 145 | offset: 8192, 146 | size: 4096, 147 | }, 148 | ]; 149 | assert_eq!(list.source_slices(), expected.as_slice()); 150 | assert_eq!(list.len(), 20480); 151 | 152 | // ...and runs that repeat themselves 153 | list.push(2); 154 | let expected = vec![ 155 | SourceSlice { 156 | offset: 0, 157 | size: 8192, 158 | }, 159 | SourceSlice { 160 | offset: 16384, 161 | size: 8192, 162 | }, 163 | SourceSlice { 164 | offset: 8192, 165 | size: 4096, 166 | }, 167 | SourceSlice { 168 | offset: 8192, 169 | size: 4096, 170 | }, 171 | ]; 172 | assert_eq!(list.source_slices(), expected.as_slice()); 173 | assert_eq!(list.len(), 24576); 174 | } 175 | 176 | #[test] 177 | fn test_truncate() { 178 | let mut list = PageList::new(4096); 179 | list.push(0); 180 | list.push(1); 181 | list.push(4); 182 | list.push(5); 183 | list.push(2); 184 | list.push(2); 185 | assert_eq!(list.len(), 24576); 186 | 187 | // truncation should do nothing when it's truncating more than is described 188 | list.truncate(25000); 189 | let expected = vec![ 190 | SourceSlice { 191 | offset: 0, 192 | size: 8192, 193 | }, 194 | SourceSlice { 195 | offset: 16384, 196 | size: 8192, 197 | }, 198 | SourceSlice { 199 | offset: 8192, 200 | size: 4096, 201 | }, 202 | SourceSlice { 203 | offset: 8192, 204 | size: 4096, 205 | }, 206 | ]; 207 | assert_eq!(list.source_slices(), expected.as_slice()); 208 | assert_eq!(list.len(), 24576); 209 | 210 | // it's usually employed to reduce the size of the last slice... 211 | list.truncate(24000); 212 | let expected = vec![ 213 | SourceSlice { 214 | offset: 0, 215 | size: 8192, 216 | }, 217 | SourceSlice { 218 | offset: 16384, 219 | size: 8192, 220 | }, 221 | SourceSlice { 222 | offset: 8192, 223 | size: 4096, 224 | }, 225 | SourceSlice { 226 | offset: 8192, 227 | size: 3520, 228 | }, 229 | ]; 230 | assert_eq!(list.source_slices(), expected.as_slice()); 231 | assert_eq!(list.len(), 24000); 232 | 233 | // ...but it should be able to lop off entire slices too 234 | list.truncate(10000); 235 | let expected = vec![ 236 | SourceSlice { 237 | offset: 0, 238 | size: 8192, 239 | }, 240 | SourceSlice { 241 | offset: 16384, 242 | size: 1808, 243 | }, 244 | ]; 245 | assert_eq!(list.source_slices(), expected.as_slice()); 246 | assert_eq!(list.len(), 10000); 247 | 248 | // and again, it shouldn't do anything if we re-truncate to a larger size 249 | list.truncate(12000); 250 | let expected = vec![ 251 | SourceSlice { 252 | offset: 0, 253 | size: 8192, 254 | }, 255 | SourceSlice { 256 | offset: 16384, 257 | size: 1808, 258 | }, 259 | ]; 260 | assert_eq!(list.source_slices(), expected.as_slice()); 261 | assert_eq!(list.len(), 10000); 262 | 263 | // finally, we should be able to truncate the entire PageList down to nothing 264 | list.truncate(0); 265 | assert_eq!(list.source_slices().len(), 0); 266 | assert_eq!(list.len(), 0); 267 | } 268 | 269 | #[test] 270 | #[should_panic] 271 | fn test_push_after_truncate() { 272 | // push after truncate isn't permitted 273 | let mut list = PageList::new(4096); 274 | list.push(5); 275 | list.truncate(2000); 276 | // so far so good 277 | 278 | // bam! 279 | list.push(6); 280 | } 281 | 282 | #[test] 283 | fn push_overflow() { 284 | let mut list = PageList::new(4096); 285 | list.push(u32::MAX); 286 | list.push(u32::MAX); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/pdbi.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 pdb Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | use std::convert::TryInto; 9 | use std::mem; 10 | 11 | use uuid::Uuid; 12 | 13 | use crate::common::*; 14 | use crate::dbi::HeaderVersion; 15 | use crate::msf::*; 16 | 17 | /// A PDB info stream header parsed from a stream. 18 | /// 19 | /// The [PDB information stream] contains the GUID and age fields that can be used to 20 | /// verify that a PDB file matches a specific binary, as well a list of named PDB streams 21 | /// with their stream indices. 22 | /// 23 | /// [PDB information stream]: http://llvm.org/docs/PDB/PdbStream.html 24 | #[derive(Debug)] 25 | pub struct PDBInformation<'s> { 26 | /// The version of the PDB format in use. 27 | pub version: HeaderVersion, 28 | /// A 32-bit timestamp. 29 | pub signature: u32, 30 | /// The number of times this PDB file has been written. 31 | /// 32 | /// This number is bumped by the linker and other tools every time the PDB is modified. It does 33 | /// not necessarily correspond to the age declared in the image. Consider using 34 | /// [`DebugInformation::age`](crate::DebugInformation::age) for a better match. 35 | /// 36 | /// This PDB matches an image, if the `guid` values match and the PDB age is equal or higher 37 | /// than the image's age. 38 | pub age: u32, 39 | /// A `Uuid` generated when this PDB file was created that should uniquely identify it. 40 | pub guid: Uuid, 41 | /// The offset of the start of the stream name data within the stream. 42 | pub names_offset: usize, 43 | /// The size of the stream name data, in bytes. 44 | pub names_size: usize, 45 | stream: Stream<'s>, 46 | } 47 | 48 | impl<'s> PDBInformation<'s> { 49 | /// Parses a `PDBInformation` from raw stream data. 50 | pub(crate) fn parse(stream: Stream<'s>) -> Result { 51 | let (version, signature, age, guid, names_size, names_offset) = { 52 | let mut buf = stream.parse_buffer(); 53 | let version = From::from(buf.parse_u32()?); 54 | let signature = buf.parse_u32()?; 55 | let age = buf.parse_u32()?; 56 | let guid = Uuid::from_fields( 57 | buf.parse_u32()?, 58 | buf.parse_u16()?, 59 | buf.parse_u16()?, 60 | buf.take(8)?.try_into().unwrap(), 61 | ); 62 | let names_size = buf.parse_u32()? as usize; 63 | let names_offset = buf.pos(); 64 | (version, signature, age, guid, names_size, names_offset) 65 | }; 66 | 67 | Ok(PDBInformation { 68 | version, 69 | signature, 70 | age, 71 | guid, 72 | names_size, 73 | names_offset, 74 | stream, 75 | }) 76 | } 77 | 78 | /// Get a `StreamNames` object that can be used to iterate over named streams contained 79 | /// within the PDB file. 80 | /// 81 | /// This can be used to look up certain PDB streams by name. 82 | /// 83 | /// # Example 84 | /// 85 | /// ``` 86 | /// # use pdb::FallibleIterator; 87 | /// # 88 | /// # fn test() -> pdb::Result<()> { 89 | /// let file = std::fs::File::open("fixtures/self/foo.pdb")?; 90 | /// let mut pdb = pdb::PDB::open(file)?; 91 | /// let info = pdb.pdb_information()?; 92 | /// let names = info.stream_names()?; 93 | /// let mut v: Vec<_> = names.iter().map(|n| n.name.to_string()).collect(); 94 | /// v.sort(); 95 | /// assert_eq!(&v, &["mystream", "/LinkInfo", "/names", "/src/headerblock"]); 96 | /// # Ok(()) 97 | /// # } 98 | /// ``` 99 | pub fn stream_names(&self) -> Result> { 100 | // The names map is part of the PDB info stream that provides a mapping from stream names to 101 | // stream indicies. Its [format on disk](1) is somewhat complicated, consisting of a block of 102 | // data comprising the names as null-terminated C strings, followed by a map of stream indices 103 | // to the offset of their names within the names block. 104 | // 105 | // [The map itself](2) is stored as a 32-bit count of the number of entries, followed by a 106 | // 32-bit value that gives the number of bytes taken up by the entries themselves, followed by 107 | // two sets: one for names that are present in this PDB, and one for names that have been 108 | // deleted, followed by the map entries, each of which is a pair of 32-bit values consisting of 109 | // an offset into the names block and a stream ID. 110 | // 111 | // [The two sets](3) are each stored as a [bit array](4), which consists of a 32-bit count, and 112 | // then that many 32-bit words containing the bits in the array. 113 | // 114 | // [1]: https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/include/nmtni.h#L76 115 | // [2]: https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/include/map.h#L474 116 | // [3]: https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/include/iset.h#L62 117 | // [4]: https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/include/array.h#L209 118 | 119 | let mut names = vec![]; 120 | let mut buf = self.stream.parse_buffer(); 121 | 122 | // Seek forward to the name map. 123 | buf.take(self.names_offset + self.names_size)?; 124 | let count = buf.parse_u32()?; 125 | // We don't actually use most of these. 126 | let _entries_size = buf.parse_u32()?; 127 | let ok_words = buf.parse_u32()?; 128 | let _ok_bits = buf.take(ok_words as usize * mem::size_of::())?; 129 | let deleted_words = buf.parse_u32()?; 130 | let _deleted_bits = buf.take(deleted_words as usize * mem::size_of::())?; 131 | 132 | // Skip over the header here. 133 | let mut names_reader = self.stream.parse_buffer(); 134 | names_reader.take(self.names_offset)?; 135 | // And take just the name data. 136 | let names_buf = names_reader.take(self.names_size)?; 137 | for _ in 0..count { 138 | let name_offset = buf.parse_u32()? as usize; 139 | let stream_id = StreamIndex(buf.parse_u32()? as u16); 140 | let name = ParseBuffer::from(&names_buf[name_offset..]).parse_cstring()?; 141 | names.push(StreamName { name, stream_id }); 142 | } 143 | 144 | Ok(StreamNames { names }) 145 | } 146 | } 147 | 148 | /// A named stream contained within the PDB file. 149 | #[derive(Debug)] 150 | pub struct StreamName<'n> { 151 | /// The stream's name. 152 | pub name: RawString<'n>, 153 | /// The index of this stream. 154 | pub stream_id: StreamIndex, 155 | } 156 | 157 | /// A list of named streams contained within the PDB file. 158 | /// 159 | /// Call [`StreamNames::iter`] to iterate over the names. The iterator produces [`StreamName`] 160 | /// objects. 161 | #[derive(Debug)] 162 | pub struct StreamNames<'s> { 163 | /// The list of streams and their names. 164 | names: Vec>, 165 | } 166 | 167 | /// An iterator over [`StreamName`]s. 168 | pub type NameIter<'a, 'n> = std::slice::Iter<'a, StreamName<'n>>; 169 | 170 | impl<'s> StreamNames<'s> { 171 | /// Return an iterator over named streams and their stream indices. 172 | #[inline] 173 | pub fn iter(&self) -> NameIter<'_, 's> { 174 | self.names.iter() 175 | } 176 | } 177 | 178 | impl<'a, 's> IntoIterator for &'a StreamNames<'s> { 179 | type Item = &'a StreamName<'s>; 180 | type IntoIter = NameIter<'a, 's>; 181 | 182 | #[inline] 183 | fn into_iter(self) -> Self::IntoIter { 184 | self.names.iter() 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/pe.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 pdb Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | //! Definitions for PE headers contained in PDBs. 9 | 10 | // PDBs contain PE section headers in one or two streams. `pdb::pe` is responsible for parsing them. 11 | 12 | use std::fmt; 13 | 14 | use scroll::ctx::TryFromCtx; 15 | use scroll::Endian; 16 | 17 | use crate::common::*; 18 | 19 | /// The section should not be padded to the next boundary. This flag is 20 | /// obsolete and is replaced by `IMAGE_SCN_ALIGN_1BYTES`. 21 | const IMAGE_SCN_TYPE_NO_PAD: u32 = 0x00000008; 22 | /// The section contains executable code. 23 | const IMAGE_SCN_CNT_CODE: u32 = 0x00000020; 24 | /// The section contains initialized data. 25 | const IMAGE_SCN_CNT_INITIALIZED_DATA: u32 = 0x00000040; 26 | /// The section contains uninitialized data. 27 | const IMAGE_SCN_CNT_UNINITIALIZED_DATA: u32 = 0x00000080; 28 | /// Reserved. 29 | const IMAGE_SCN_LNK_OTHER: u32 = 0x00000100; 30 | /// The section contains comments or other information. This is valid only for object files. 31 | const IMAGE_SCN_LNK_INFO: u32 = 0x00000200; 32 | /// The section will not become part of the image. This is valid only for object files. 33 | const IMAGE_SCN_LNK_REMOVE: u32 = 0x00000800; 34 | /// The section contains COMDAT data. This is valid only for object files. 35 | const IMAGE_SCN_LNK_COMDAT: u32 = 0x00001000; 36 | /// Reset speculative exceptions handling bits in the TLB entries for this section. 37 | const IMAGE_SCN_NO_DEFER_SPEC_EXC: u32 = 0x00004000; 38 | /// The section contains data referenced through the global pointer. 39 | const IMAGE_SCN_GPREL: u32 = 0x00008000; 40 | /// Reserved. 41 | const IMAGE_SCN_MEM_PURGEABLE: u32 = 0x00020000; 42 | /// Reserved. 43 | const IMAGE_SCN_MEM_LOCKED: u32 = 0x00040000; 44 | /// Reserved. 45 | const IMAGE_SCN_MEM_PRELOAD: u32 = 0x00080000; 46 | /// Align data on a 1-byte boundary. This is valid only for object files. 47 | const IMAGE_SCN_ALIGN_1BYTES: u32 = 0x00100000; 48 | /// Align data on a 2-byte boundary. This is valid only for object files. 49 | const IMAGE_SCN_ALIGN_2BYTES: u32 = 0x00200000; 50 | /// Align data on a 4-byte boundary. This is valid only for object files. 51 | const IMAGE_SCN_ALIGN_4BYTES: u32 = 0x00300000; 52 | /// Align data on a 8-byte boundary. This is valid only for object files. 53 | const IMAGE_SCN_ALIGN_8BYTES: u32 = 0x00400000; 54 | /// Align data on a 16-byte boundary. This is valid only for object files. 55 | const IMAGE_SCN_ALIGN_16BYTES: u32 = 0x00500000; 56 | /// Align data on a 32-byte boundary. This is valid only for object files. 57 | const IMAGE_SCN_ALIGN_32BYTES: u32 = 0x00600000; 58 | /// Align data on a 64-byte boundary. This is valid only for object files. 59 | const IMAGE_SCN_ALIGN_64BYTES: u32 = 0x00700000; 60 | /// Align data on a 128-byte boundary. This is valid only for object files. 61 | const IMAGE_SCN_ALIGN_128BYTES: u32 = 0x00800000; 62 | /// Align data on a 256-byte boundary. This is valid only for object files. 63 | const IMAGE_SCN_ALIGN_256BYTES: u32 = 0x00900000; 64 | /// Align data on a 512-byte boundary. This is valid only for object files. 65 | const IMAGE_SCN_ALIGN_512BYTES: u32 = 0x00A00000; 66 | /// Align data on a 1024-byte boundary. This is valid only for object files. 67 | const IMAGE_SCN_ALIGN_1024BYTES: u32 = 0x00B00000; 68 | /// Align data on a 2048-byte boundary. This is valid only for object files. 69 | const IMAGE_SCN_ALIGN_2048BYTES: u32 = 0x00C00000; 70 | /// Align data on a 4096-byte boundary. This is valid only for object files. 71 | const IMAGE_SCN_ALIGN_4096BYTES: u32 = 0x00D00000; 72 | /// Align data on a 8192-byte boundary. This is valid only for object files. 73 | const IMAGE_SCN_ALIGN_8192BYTES: u32 = 0x00E00000; 74 | /// The section contains extended relocations. The count of relocations for the 75 | /// section exceeds the 16 bits that is reserved for it in the section header. 76 | /// If the `number_of_relocations` field in the section header is `0xffff`, the 77 | /// actual relocation count is stored in the `virtual_address` field of the first 78 | /// relocation. It is an error if `IMAGE_SCN_LNK_NRELOC_OVFL` is set and there 79 | /// are fewer than `0xffff` relocations in the section. 80 | const IMAGE_SCN_LNK_NRELOC_OVFL: u32 = 0x01000000; 81 | /// The section can be discarded as needed. 82 | const IMAGE_SCN_MEM_DISCARDABLE: u32 = 0x02000000; 83 | /// The section cannot be cached. 84 | const IMAGE_SCN_MEM_NOT_CACHED: u32 = 0x04000000; 85 | /// The section cannot be paged. 86 | const IMAGE_SCN_MEM_NOT_PAGED: u32 = 0x08000000; 87 | /// The section can be shared in memory. 88 | const IMAGE_SCN_MEM_SHARED: u32 = 0x10000000; 89 | /// The section can be executed as code. 90 | const IMAGE_SCN_MEM_EXECUTE: u32 = 0x20000000; 91 | /// The section can be read. 92 | const IMAGE_SCN_MEM_READ: u32 = 0x40000000; 93 | /// The section can be written to. 94 | const IMAGE_SCN_MEM_WRITE: u32 = 0x80000000; 95 | 96 | /// Characteristic flags of an [`ImageSectionHeader`]. 97 | /// 98 | /// These are defined by Microsoft as [`IMAGE_SCN_`] constants. 99 | /// 100 | /// [`IMAGE_SCN_`]: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_section_header 101 | #[derive(Clone, Copy, Eq, Default, PartialEq)] 102 | pub struct SectionCharacteristics(pub u32); 103 | 104 | impl SectionCharacteristics { 105 | /// The section contains executable code. 106 | pub fn executable(self) -> bool { 107 | (self.0 & IMAGE_SCN_CNT_CODE) > 0 108 | } 109 | 110 | /// The section contains initialized data. 111 | pub fn initialized_data(self) -> bool { 112 | (self.0 & IMAGE_SCN_CNT_INITIALIZED_DATA) > 0 113 | } 114 | 115 | /// The section contains uninitialized data. 116 | pub fn uninitialized_data(self) -> bool { 117 | (self.0 & IMAGE_SCN_CNT_UNINITIALIZED_DATA) > 0 118 | } 119 | 120 | /// Reserved. 121 | pub fn other(self) -> bool { 122 | (self.0 & IMAGE_SCN_LNK_OTHER) > 0 123 | } 124 | 125 | /// The section contains comments or other information. This is valid only for object files. 126 | pub fn info(self) -> bool { 127 | (self.0 & IMAGE_SCN_LNK_INFO) > 0 128 | } 129 | 130 | /// The section will not become part of the image. This is valid only for object files. 131 | pub fn remove(self) -> bool { 132 | (self.0 & IMAGE_SCN_LNK_REMOVE) > 0 133 | } 134 | 135 | /// The section contains COMDAT data. This is valid only for object files. 136 | pub fn comdat(self) -> bool { 137 | (self.0 & IMAGE_SCN_LNK_COMDAT) > 0 138 | } 139 | 140 | /// Reset speculative exceptions handling bits in the TLB entries for this section. 141 | pub fn defer_speculative_exceptions(self) -> bool { 142 | (self.0 & IMAGE_SCN_NO_DEFER_SPEC_EXC) > 0 143 | } 144 | 145 | /// The section contains data referenced through the global pointer. 146 | pub fn global_pointer_relative(self) -> bool { 147 | (self.0 & IMAGE_SCN_GPREL) > 0 148 | } 149 | 150 | /// Reserved. 151 | pub fn purgeable(self) -> bool { 152 | (self.0 & IMAGE_SCN_MEM_PURGEABLE) > 0 153 | } 154 | 155 | /// Reserved. 156 | pub fn locked(self) -> bool { 157 | (self.0 & IMAGE_SCN_MEM_LOCKED) > 0 158 | } 159 | 160 | /// Reserved. 161 | pub fn preload(self) -> bool { 162 | (self.0 & IMAGE_SCN_MEM_PRELOAD) > 0 163 | } 164 | 165 | /// Alignment for section data. 166 | /// 167 | /// This is valid only for object files. Returns `Some` if alignment is specified, and `None` if 168 | /// no alignment is specified. An alignment of `Some(1)` means that the section should not be 169 | /// padded to a boundary. 170 | pub fn alignment(self) -> Option { 171 | // Mask covering all align values and IMAGE_SCN_TYPE_NO_PAD. 172 | match self.0 & 0x00F00008 { 173 | self::IMAGE_SCN_ALIGN_1BYTES => Some(1), 174 | self::IMAGE_SCN_ALIGN_2BYTES => Some(2), 175 | self::IMAGE_SCN_ALIGN_4BYTES => Some(4), 176 | self::IMAGE_SCN_ALIGN_8BYTES => Some(8), 177 | self::IMAGE_SCN_ALIGN_16BYTES => Some(16), 178 | self::IMAGE_SCN_ALIGN_32BYTES => Some(32), 179 | self::IMAGE_SCN_ALIGN_64BYTES => Some(64), 180 | self::IMAGE_SCN_ALIGN_128BYTES => Some(128), 181 | self::IMAGE_SCN_ALIGN_256BYTES => Some(256), 182 | self::IMAGE_SCN_ALIGN_512BYTES => Some(512), 183 | self::IMAGE_SCN_ALIGN_1024BYTES => Some(1024), 184 | self::IMAGE_SCN_ALIGN_2048BYTES => Some(2048), 185 | self::IMAGE_SCN_ALIGN_4096BYTES => Some(4096), 186 | self::IMAGE_SCN_ALIGN_8192BYTES => Some(8192), 187 | self::IMAGE_SCN_TYPE_NO_PAD => Some(1), 188 | _ => None, 189 | } 190 | } 191 | 192 | /// The section contains extended relocations. 193 | /// 194 | /// The count of relocations for the section exceeds the 16 bits that is reserved for it in the 195 | /// section header. If the [`number_of_relocations`](ImageSectionHeader::number_of_relocations) 196 | /// field in the section header is `0xffff`, the actual relocation count is stored in the 197 | /// `virtual_address` field of the first relocation. It is an error if this flag is set and 198 | /// there are fewer than `0xffff` relocations in the section. 199 | pub fn lnk_nreloc_ovfl(self) -> bool { 200 | (self.0 & IMAGE_SCN_LNK_NRELOC_OVFL) > 0 201 | } 202 | 203 | /// The section can be discarded as needed. 204 | pub fn discardable(self) -> bool { 205 | (self.0 & IMAGE_SCN_MEM_DISCARDABLE) > 0 206 | } 207 | 208 | /// The section cannot be cached. 209 | pub fn not_cached(self) -> bool { 210 | (self.0 & IMAGE_SCN_MEM_NOT_CACHED) > 0 211 | } 212 | 213 | /// The section cannot be paged. 214 | pub fn not_paged(self) -> bool { 215 | (self.0 & IMAGE_SCN_MEM_NOT_PAGED) > 0 216 | } 217 | 218 | /// The section can be shared in memory. 219 | pub fn shared(self) -> bool { 220 | (self.0 & IMAGE_SCN_MEM_SHARED) > 0 221 | } 222 | 223 | /// The section can be executed as code. 224 | pub fn execute(self) -> bool { 225 | (self.0 & IMAGE_SCN_MEM_EXECUTE) > 0 226 | } 227 | 228 | /// The section can be read. 229 | pub fn read(self) -> bool { 230 | (self.0 & IMAGE_SCN_MEM_READ) > 0 231 | } 232 | 233 | /// The section can be written to. 234 | pub fn write(self) -> bool { 235 | (self.0 & IMAGE_SCN_MEM_WRITE) > 0 236 | } 237 | } 238 | 239 | impl fmt::Debug for SectionCharacteristics { 240 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 241 | if f.alternate() { 242 | f.debug_struct("ImageCharacteristics") 243 | .field("executable", &self.executable()) 244 | .field("initialized_data", &self.initialized_data()) 245 | .field("uninitialized_data", &self.uninitialized_data()) 246 | .field("info", &self.info()) 247 | .field("remove", &self.remove()) 248 | .field("comdat", &self.comdat()) 249 | .field( 250 | "defer_speculative_exceptions", 251 | &self.defer_speculative_exceptions(), 252 | ) 253 | .field("global_pointer_relative", &self.global_pointer_relative()) 254 | .field("purgeable", &self.purgeable()) 255 | .field("locked", &self.locked()) 256 | .field("preload", &self.preload()) 257 | .field("alignment", &self.alignment()) 258 | .field("lnk_nreloc_ovfl", &self.lnk_nreloc_ovfl()) 259 | .field("discardable", &self.discardable()) 260 | .field("not_cached", &self.not_cached()) 261 | .field("not_paged", &self.not_paged()) 262 | .field("shared", &self.shared()) 263 | .field("execute", &self.execute()) 264 | .field("read", &self.read()) 265 | .field("write", &self.write()) 266 | .finish() 267 | } else { 268 | f.debug_tuple("ImageCharacteristics") 269 | .field(&format_args!("{:#x}", self.0)) 270 | .finish() 271 | } 272 | } 273 | } 274 | 275 | impl<'t> TryFromCtx<'t, Endian> for SectionCharacteristics { 276 | type Error = scroll::Error; 277 | 278 | fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { 279 | let (value, size) = u32::try_from_ctx(this, le)?; 280 | Ok((SectionCharacteristics(value), size)) 281 | } 282 | } 283 | 284 | /// A PE `IMAGE_SECTION_HEADER`, as described in [the Microsoft documentation](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680341(v=vs.85).aspx). 285 | #[derive(Copy, Clone, Default, PartialEq, Eq)] 286 | pub struct ImageSectionHeader { 287 | /// An 8-byte, null-padded UTF-8 string. There is no terminating null character if the string is 288 | /// exactly eight characters long. For longer names, this member contains a forward slash (`/`) 289 | /// followed by an ASCII representation of a decimal number that is an offset into the string 290 | /// table. Executable images do not use a string table and do not support section names longer 291 | /// than eight characters. 292 | pub name: [u8; 8], 293 | 294 | /// The total size of the section when loaded into memory, in bytes. If this value is greater 295 | /// than the [`size_of_raw_data`](Self::size_of_raw_data) member, the section is filled with 296 | /// zeroes. This field is valid only for executable images and should be set to `0` for object 297 | /// files. 298 | /// 299 | /// In object files, this field would be replaced with the physical file address. Such headers 300 | /// are never embedded in PDBs. 301 | pub virtual_size: u32, 302 | 303 | /// The address of the first byte of the section when loaded into memory, relative to the image 304 | /// base. For object files, this is the address of the first byte before relocation is applied. 305 | pub virtual_address: u32, 306 | 307 | /// The size of the initialized data on disk, in bytes. This value must be a multiple of the 308 | /// `FileAlignment` member of the [`IMAGE_OPTIONAL_HEADER`] structure. If this value is less than 309 | /// the [`virtual_size`](Self::virtual_size) member, the remainder of the section is filled with 310 | /// zeroes. If the section contains only uninitialized data, the member is zero. 311 | /// 312 | /// [`IMAGE_OPTIONAL_HEADER`]: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header32 313 | pub size_of_raw_data: u32, 314 | 315 | /// A file pointer to the first page within the COFF file. This value must be a multiple of the 316 | /// `FileAlignment` member of the [`IMAGE_OPTIONAL_HEADER`] structure. If a section contains only 317 | /// uninitialized data, set this member is zero. 318 | /// 319 | /// [`IMAGE_OPTIONAL_HEADER`]: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header32 320 | pub pointer_to_raw_data: u32, 321 | 322 | /// A file pointer to the beginning of the relocation entries for the section. If there are no 323 | /// relocations, this value is zero. 324 | pub pointer_to_relocations: u32, 325 | 326 | /// A file pointer to the beginning of the line-number entries for the section. If there are no 327 | /// COFF line numbers, this value is zero. 328 | pub pointer_to_line_numbers: u32, 329 | 330 | /// The number of relocation entries for the section. This value is zero for executable images. 331 | /// 332 | /// If the value is `0xffff`, the actual relocation count is stored in the `virtual_address` 333 | /// field of the first relocation. It is an error if this flag is set and there are fewer than 334 | /// `0xffff` relocations in the section. 335 | pub number_of_relocations: u16, 336 | 337 | /// The number of line-number entries for the section. 338 | pub number_of_line_numbers: u16, 339 | 340 | /// The characteristics of the image. 341 | pub characteristics: SectionCharacteristics, 342 | } 343 | 344 | impl ImageSectionHeader { 345 | pub(crate) fn parse(parse_buffer: &mut ParseBuffer<'_>) -> Result { 346 | let name_bytes = parse_buffer.take(8)?; 347 | 348 | Ok(Self { 349 | name: [ 350 | name_bytes[0], 351 | name_bytes[1], 352 | name_bytes[2], 353 | name_bytes[3], 354 | name_bytes[4], 355 | name_bytes[5], 356 | name_bytes[6], 357 | name_bytes[7], 358 | ], 359 | virtual_size: parse_buffer.parse_u32()?, 360 | virtual_address: parse_buffer.parse_u32()?, 361 | size_of_raw_data: parse_buffer.parse_u32()?, 362 | pointer_to_raw_data: parse_buffer.parse_u32()?, 363 | pointer_to_relocations: parse_buffer.parse_u32()?, 364 | pointer_to_line_numbers: parse_buffer.parse_u32()?, 365 | number_of_relocations: parse_buffer.parse_u16()?, 366 | number_of_line_numbers: parse_buffer.parse_u16()?, 367 | characteristics: parse_buffer.parse()?, 368 | }) 369 | } 370 | 371 | /// Returns the name of the section. 372 | pub fn name(&self) -> &str { 373 | let end = self 374 | .name 375 | .iter() 376 | .position(|ch| *ch == 0) 377 | .unwrap_or(self.name.len()); 378 | 379 | // The spec guarantees that the name is a proper UTF-8 string. 380 | // TODO: Look up long names from the string table. 381 | std::str::from_utf8(&self.name[0..end]).unwrap_or("") 382 | } 383 | } 384 | 385 | impl fmt::Debug for ImageSectionHeader { 386 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 387 | f.debug_struct("ImageSectionHeader") 388 | .field("name()", &self.name()) 389 | .field("virtual_size", &format_args!("{:#x}", self.virtual_size)) 390 | .field( 391 | "virtual_address", 392 | &format_args!("{:#x}", self.virtual_address), 393 | ) 394 | .field("size_of_raw_data", &self.size_of_raw_data) 395 | .field( 396 | "pointer_to_raw_data", 397 | &format_args!("{:#x}", self.pointer_to_raw_data), 398 | ) 399 | .field( 400 | "pointer_to_relocations", 401 | &format_args!("{:#x}", self.pointer_to_relocations), 402 | ) 403 | .field( 404 | "pointer_to_line_numbers", 405 | &format_args!("{:#x}", self.pointer_to_line_numbers), 406 | ) 407 | .field("number_of_relocations", &self.number_of_relocations) 408 | .field("number_of_line_numbers", &self.number_of_line_numbers) 409 | .field("characteristics", &self.characteristics) 410 | .finish() 411 | } 412 | } 413 | 414 | #[cfg(test)] 415 | mod tests { 416 | use super::*; 417 | 418 | #[test] 419 | fn test_section_characteristics() { 420 | let bytes: Vec = vec![0x40, 0x00, 0x00, 0xC8]; 421 | let mut parse_buffer = ParseBuffer::from(bytes.as_slice()); 422 | let characteristics = parse_buffer 423 | .parse::() 424 | .expect("parse"); 425 | 426 | assert_eq!(characteristics, SectionCharacteristics(0xc800_0040)); 427 | 428 | assert!(characteristics.initialized_data()); 429 | assert!(characteristics.not_paged()); 430 | assert!(characteristics.read()); 431 | assert!(characteristics.write()); 432 | 433 | assert_eq!(characteristics.alignment(), None); 434 | } 435 | 436 | #[test] 437 | fn test_section_characteristics_nopad() { 438 | let characteristics = SectionCharacteristics(IMAGE_SCN_TYPE_NO_PAD); 439 | assert_eq!(characteristics.alignment(), Some(1)); 440 | } 441 | 442 | #[test] 443 | fn test_section_characteristics_alignment() { 444 | let characteristics = SectionCharacteristics(IMAGE_SCN_ALIGN_64BYTES); 445 | assert_eq!(characteristics.alignment(), Some(64)); 446 | } 447 | 448 | #[test] 449 | fn test_image_section_header() { 450 | let bytes: Vec = vec![ 451 | 0x2E, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x48, 0x35, 0x09, 0x00, 0x00, 0xD0, 452 | 0x1E, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0xA2, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 453 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0xC8, 454 | ]; 455 | 456 | let mut parse_buffer = ParseBuffer::from(bytes.as_slice()); 457 | 458 | let ish = ImageSectionHeader::parse(&mut parse_buffer).expect("parse"); 459 | assert_eq!(&ish.name, b".data\0\0\0"); 460 | assert_eq!(ish.name(), ".data"); 461 | assert_eq!(ish.virtual_size, 0x93548); 462 | assert_eq!(ish.virtual_address, 0x001e_d000); 463 | assert_eq!(ish.size_of_raw_data, 0xfe00); 464 | assert_eq!(ish.pointer_to_raw_data, 0x001e_a200); 465 | assert_eq!(ish.pointer_to_relocations, 0); 466 | assert_eq!(ish.pointer_to_line_numbers, 0); 467 | assert_eq!(ish.number_of_relocations, 0); 468 | assert_eq!(ish.number_of_line_numbers, 0); 469 | assert_eq!(ish.characteristics, SectionCharacteristics(0xc800_0040)); 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /src/source.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 pdb Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | use std::fmt; 9 | use std::io; 10 | 11 | /// Represents an offset + size of the source file. 12 | /// 13 | /// The multi-stream file implementation (used by `pdb::PDB`) determines which byte ranges it needs 14 | /// to satisfy its requests, and it describes those requests as a `&[SourceSlice]`. 15 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 16 | pub struct SourceSlice { 17 | /// Offset into the source file. 18 | pub offset: u64, 19 | /// Size of the slice. 20 | pub size: usize, 21 | } 22 | 23 | /// The `pdb` crate accesses PDB files via the `pdb::Source` trait. 24 | /// 25 | /// This library is written with zero-copy in mind. `Source`s provide [`SourceView`]s which need not 26 | /// outlive their parent, supporting implementations of e.g. memory mapped files. 27 | /// 28 | /// PDB files are "multi-stream files" (MSF) under the hood. MSFs have various layers of 29 | /// indirection, but ultimately the MSF code asks a `Source` to view a series of 30 | /// [`{ offset, size }` records](SourceSlice), which the `Source` provides as a 31 | /// contiguous `&[u8]`. 32 | /// 33 | /// # Default 34 | /// 35 | /// There is a default `Source` implementation for `std::io::Read` + `std::io::Seek` + 36 | /// `std::fmt::Debug`, allowing a `std::fs::File` to be treated as `pdb::Source`. This 37 | /// implementation provides views by allocating a buffer, seeking, and reading the contents into 38 | /// that buffer. 39 | /// 40 | /// # Alignment 41 | /// 42 | /// The requested offsets will always be aligned to the MSF's page size, which is always a power of 43 | /// two and is usually (but not always) 4096 bytes. The requested sizes will also be multiples of 44 | /// the page size, except for the size of the final `SourceSlice`, which may be smaller. 45 | /// 46 | /// PDB files are specified as always being a multiple of the page size, so `Source` implementations 47 | /// are free to e.g. map whole pages and return a sub-slice of the requested length. 48 | /// 49 | pub trait Source<'s>: fmt::Debug { 50 | /// Provides a contiguous view of the source file composed of the requested position(s). 51 | /// 52 | /// Note that the SourceView's as_slice() method cannot fail, so `view()` is the time to raise 53 | /// IO errors. 54 | fn view(&mut self, slices: &[SourceSlice]) -> Result>, io::Error>; 55 | } 56 | 57 | /// An owned, droppable, read-only view of the source file which can be referenced as a byte slice. 58 | pub trait SourceView<'s>: fmt::Debug { 59 | /// Returns a view to the raw data. 60 | fn as_slice(&self) -> &[u8]; 61 | } 62 | 63 | #[derive(Clone)] 64 | struct ReadView { 65 | bytes: Vec, 66 | } 67 | 68 | impl fmt::Debug for ReadView { 69 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 70 | write!(f, "ReadView({} bytes)", self.bytes.len()) 71 | } 72 | } 73 | 74 | impl SourceView<'_> for ReadView { 75 | fn as_slice(&self) -> &[u8] { 76 | self.bytes.as_slice() 77 | } 78 | } 79 | 80 | impl<'s, T> Source<'s> for T 81 | where 82 | T: io::Read + io::Seek + fmt::Debug + 's, 83 | { 84 | fn view(&mut self, slices: &[SourceSlice]) -> Result>, io::Error> { 85 | let len = slices.iter().fold(0, |acc, s| acc + s.size); 86 | 87 | let mut v = ReadView { 88 | bytes: Vec::with_capacity(len), 89 | }; 90 | v.bytes.resize(len, 0); 91 | 92 | { 93 | let bytes = v.bytes.as_mut_slice(); 94 | let mut output_offset: usize = 0; 95 | for slice in slices { 96 | self.seek(io::SeekFrom::Start(slice.offset))?; 97 | self.read_exact(&mut bytes[output_offset..(output_offset + slice.size)])?; 98 | output_offset += slice.size; 99 | } 100 | } 101 | 102 | Ok(Box::new(v)) 103 | } 104 | } 105 | 106 | #[cfg(test)] 107 | mod tests { 108 | mod read_view { 109 | use crate::source::*; 110 | use std::io::Cursor; 111 | use std::io::ErrorKind; 112 | 113 | #[test] 114 | fn test_basic_reading() { 115 | let mut data = vec![0; 4096]; 116 | data[42] = 42; 117 | 118 | let mut source: Box> = Box::new(Cursor::new(data.as_slice())); 119 | 120 | let source_slices = vec![SourceSlice { 121 | offset: 40, 122 | size: 4, 123 | }]; 124 | let view = source 125 | .view(source_slices.as_slice()) 126 | .expect("viewing must succeed"); 127 | assert_eq!(&[0u8, 0, 42, 0], view.as_slice()); 128 | } 129 | 130 | #[test] 131 | fn test_discontinuous_reading() { 132 | let mut data = vec![0; 4096]; 133 | data[42] = 42; 134 | data[88] = 88; 135 | 136 | let mut source: Box> = Box::new(Cursor::new(data.as_slice())); 137 | 138 | let source_slices = vec![ 139 | SourceSlice { 140 | offset: 88, 141 | size: 1, 142 | }, 143 | SourceSlice { 144 | offset: 40, 145 | size: 4, 146 | }, 147 | ]; 148 | let view = source 149 | .view(source_slices.as_slice()) 150 | .expect("viewing must succeed"); 151 | assert_eq!(&[88u8, 0, 0, 42, 0], view.as_slice()); 152 | } 153 | 154 | #[test] 155 | fn test_duplicate_reading() { 156 | let mut data = vec![0; 4096]; 157 | data[42] = 42; 158 | data[88] = 88; 159 | 160 | let mut source: Box> = Box::new(Cursor::new(data.as_slice())); 161 | 162 | let source_slices = vec![ 163 | SourceSlice { 164 | offset: 88, 165 | size: 1, 166 | }, 167 | SourceSlice { 168 | offset: 40, 169 | size: 4, 170 | }, 171 | SourceSlice { 172 | offset: 88, 173 | size: 1, 174 | }, 175 | ]; 176 | let view = source 177 | .view(source_slices.as_slice()) 178 | .expect("viewing must succeed"); 179 | assert_eq!(&[88u8, 0, 0, 42, 0, 88], view.as_slice()); 180 | } 181 | 182 | #[test] 183 | fn test_eof_reading() { 184 | let data = vec![0; 4096]; 185 | 186 | let mut source: Box> = Box::new(Cursor::new(data.as_slice())); 187 | 188 | // one byte is readable, but we asked for two 189 | let source_slices = vec![SourceSlice { 190 | offset: 4095, 191 | size: 2, 192 | }]; 193 | let r = source.view(source_slices.as_slice()); 194 | match r { 195 | Ok(_) => panic!("should have failed"), 196 | Err(e) => { 197 | assert_eq!(ErrorKind::UnexpectedEof, e.kind()); 198 | } 199 | } 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/strings.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use scroll::{ctx::TryFromCtx, Endian, Pread}; 4 | 5 | use crate::common::*; 6 | use crate::msf::Stream; 7 | 8 | /// Magic bytes identifying the string name table. 9 | /// 10 | /// This value is declared as `NMT::verHdr` in `nmt.h`. 11 | const PDB_NMT_HDR: u32 = 0xEFFE_EFFE; 12 | 13 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 14 | enum StringTableHashVersion { 15 | /// Default hash method used for reverse string lookups. 16 | /// 17 | /// The hash function has originally been defined in `LHashPbCb`. 18 | LongHash = 1, 19 | 20 | /// Revised hash method used for reverse string lookups. 21 | /// 22 | /// The hash function has originally been defined in `LHashPbCbV2`. 23 | LongHashV2 = 2, 24 | } 25 | 26 | impl StringTableHashVersion { 27 | fn parse_u32(value: u32) -> Result { 28 | match value { 29 | 1 => Ok(Self::LongHash), 30 | 2 => Ok(Self::LongHashV2), 31 | _ => Err(Error::UnimplementedFeature( 32 | "unknown string table hash version", 33 | )), 34 | } 35 | } 36 | } 37 | 38 | /// Raw header of the string table stream. 39 | #[repr(C)] 40 | #[derive(Clone, Copy, Debug)] 41 | struct StringTableHeader { 42 | /// Magic bytes of the string table. 43 | magic: u32, 44 | /// Version of the hash table after the names. 45 | hash_version: u32, 46 | /// The size of all names in bytes. 47 | names_size: u32, 48 | } 49 | 50 | impl<'t> TryFromCtx<'t, Endian> for StringTableHeader { 51 | type Error = scroll::Error; 52 | 53 | fn try_from_ctx(this: &'t [u8], le: Endian) -> scroll::Result<(Self, usize)> { 54 | let mut offset = 0; 55 | let data = Self { 56 | magic: this.gread_with(&mut offset, le)?, 57 | hash_version: this.gread_with(&mut offset, le)?, 58 | names_size: this.gread_with(&mut offset, le)?, 59 | }; 60 | Ok((data, offset)) 61 | } 62 | } 63 | 64 | impl StringTableHeader { 65 | /// Start index of the names buffer in the string table stream. 66 | fn names_start(self) -> usize { 67 | std::mem::size_of::() 68 | } 69 | 70 | /// End index of the names buffer in the string table stream. 71 | fn names_end(self) -> usize { 72 | self.names_start() + self.names_size as usize 73 | } 74 | } 75 | 76 | /// The global string table of a PDB. 77 | /// 78 | /// The string table is a two-way mapping from offset to string and back. It can be used to resolve 79 | /// [`StringRef`] offsets to their string values. Sometimes, it is also referred to as "Name table". 80 | /// The mapping from string to offset has not been implemented yet. 81 | /// 82 | /// Use [`PDB::string_table`](crate::PDB::string_table) to obtain an instance. 83 | #[derive(Debug)] 84 | pub struct StringTable<'s> { 85 | header: StringTableHeader, 86 | #[allow(dead_code)] // reason = "reverse-lookups through hash table not implemented" 87 | hash_version: StringTableHashVersion, 88 | stream: Stream<'s>, 89 | } 90 | 91 | impl<'s> StringTable<'s> { 92 | pub(crate) fn parse(stream: Stream<'s>) -> Result { 93 | let mut buf = stream.parse_buffer(); 94 | let header = buf.parse::()?; 95 | 96 | if header.magic != PDB_NMT_HDR { 97 | return Err(Error::UnimplementedFeature( 98 | "invalid string table signature", 99 | )); 100 | } 101 | 102 | // The string table should at least contain all names as C-strings. Their combined size is 103 | // declared in the `names_size` header field. 104 | if buf.len() < header.names_end() { 105 | return Err(Error::UnexpectedEof); 106 | } 107 | 108 | let hash_version = StringTableHashVersion::parse_u32(header.hash_version)?; 109 | 110 | // After the name buffer, the stream contains a closed hash table for reverse mapping. From 111 | // the original header file (`nmi.h`): 112 | // 113 | // Strings are mapped into name indices using a closed hash table of NIs. 114 | // To find a string, we hash it and probe into the table, and compare the 115 | // string against each successive ni's name until we hit or find an empty 116 | // hash table entry. 117 | 118 | Ok(StringTable { 119 | header, 120 | hash_version, 121 | stream, 122 | }) 123 | } 124 | } 125 | 126 | impl<'s> StringTable<'s> { 127 | /// Resolves a string value from this string table. 128 | /// 129 | /// Errors if the offset is out of bounds, otherwise returns the raw binary string value. 130 | pub fn get(&self, offset: StringRef) -> Result> { 131 | if offset.0 >= self.header.names_size { 132 | return Err(Error::UnexpectedEof); 133 | } 134 | 135 | let string_offset = self.header.names_start() + offset.0 as usize; 136 | let data = &self.stream.as_slice()[string_offset..self.header.names_end()]; 137 | ParseBuffer::from(data).parse_cstring() 138 | } 139 | } 140 | 141 | impl StringRef { 142 | /// Resolves the raw string value of this reference. 143 | /// 144 | /// This method errors if the offset is out of bounds of the string table. Use 145 | /// [`PDB::string_table`](crate::PDB::string_table) to obtain an instance of the string table. 146 | pub fn to_raw_string<'s>(self, strings: &'s StringTable<'_>) -> Result> { 147 | strings.get(self) 148 | } 149 | 150 | /// Resolves and decodes the UTF-8 string value of this reference. 151 | /// 152 | /// This method errors if the offset is out of bounds of the string table. Use 153 | /// [`PDB::string_table`](crate::PDB::string_table) to obtain an instance of the string table. 154 | pub fn to_string_lossy<'s>(self, strings: &'s StringTable<'_>) -> Result> { 155 | strings.get(self).map(|r| r.to_string()) 156 | } 157 | } 158 | 159 | #[cfg(test)] 160 | mod tests { 161 | use super::*; 162 | 163 | use std::mem; 164 | 165 | #[test] 166 | fn test_string_table_header() { 167 | assert_eq!(mem::size_of::(), 12); 168 | assert_eq!(mem::align_of::(), 4); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/symbol/annotations.rs: -------------------------------------------------------------------------------- 1 | use crate::common::*; 2 | use crate::FallibleIterator; 3 | 4 | /// These values correspond to the BinaryAnnotationOpcode enum from the 5 | /// cvinfo.h 6 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 7 | enum BinaryAnnotationOpcode { 8 | /// Link time pdb contains PADDINGs. 9 | /// 10 | /// These are represented with the 0 opcode which is in some PDB 11 | /// implementation called "invalid". 12 | Eof = 0, 13 | /// param : start offset 14 | CodeOffset = 1, 15 | /// param : nth separated code chunk (main code chunk == 0) 16 | ChangeCodeOffsetBase = 2, 17 | /// param : delta of offset 18 | ChangeCodeOffset = 3, 19 | /// param : length of code, default next start 20 | ChangeCodeLength = 4, 21 | /// param : fileId 22 | ChangeFile = 5, 23 | /// param : line offset (signed) 24 | ChangeLineOffset = 6, 25 | /// param : how many lines, default 1 26 | ChangeLineEndDelta = 7, 27 | /// param : either 1 (default, for statement) 28 | /// or 0 (for expression) 29 | ChangeRangeKind = 8, 30 | /// param : start column number, 0 means no column info 31 | ChangeColumnStart = 9, 32 | /// param : end column number delta (signed) 33 | ChangeColumnEndDelta = 10, 34 | /// param : ((sourceDelta << 4) | CodeDelta) 35 | ChangeCodeOffsetAndLineOffset = 11, 36 | /// param : codeLength, codeOffset 37 | ChangeCodeLengthAndCodeOffset = 12, 38 | /// param : end column number 39 | ChangeColumnEnd = 13, 40 | } 41 | 42 | impl BinaryAnnotationOpcode { 43 | fn parse(value: u32) -> Result { 44 | Ok(match value { 45 | 0 => Self::Eof, 46 | 1 => Self::CodeOffset, 47 | 2 => Self::ChangeCodeOffsetBase, 48 | 3 => Self::ChangeCodeOffset, 49 | 4 => Self::ChangeCodeLength, 50 | 5 => Self::ChangeFile, 51 | 6 => Self::ChangeLineOffset, 52 | 7 => Self::ChangeLineEndDelta, 53 | 8 => Self::ChangeRangeKind, 54 | 9 => Self::ChangeColumnStart, 55 | 10 => Self::ChangeColumnEndDelta, 56 | 11 => Self::ChangeCodeOffsetAndLineOffset, 57 | 12 => Self::ChangeCodeLengthAndCodeOffset, 58 | 13 => Self::ChangeColumnEnd, 59 | _ => return Err(Error::UnknownBinaryAnnotation(value)), 60 | }) 61 | } 62 | } 63 | 64 | /// Represents a parsed `BinaryAnnotation`. 65 | /// 66 | /// Binary annotations are used by `S_INLINESITE` to encode opcodes for how to 67 | /// evaluate the state changes for inline information. 68 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 69 | pub enum BinaryAnnotation { 70 | /// Sets the code offset to the given absolute value. 71 | CodeOffset(u32), 72 | /// Sets the base for all code offsets to the given absolute value. All following code offsets 73 | /// are relative to this base value. 74 | ChangeCodeOffsetBase(u32), 75 | /// **Emitting**. Advances the code offset by the given value. 76 | /// 77 | /// This annotation emits a line record. The length of the covered code block can be established 78 | /// by a following `ChangeCodeLength` annotation, or by the offset of the next emitted record. 79 | ChangeCodeOffset(u32), 80 | /// Adjusts the code length of the previously emitted line record. The code length resets with 81 | /// every line record. 82 | ChangeCodeLength(u32), 83 | /// Sets the file index of following line records. 84 | ChangeFile(FileIndex), 85 | /// Advances the line number by the given value. 86 | ChangeLineOffset(i32), 87 | /// Sets the number of source lines covered by following line records. Defaults to `1`. 88 | ChangeLineEndDelta(u32), 89 | /// Sets the kind of the line record. Defaults to `Statement`. 90 | ChangeRangeKind(u32), 91 | /// Sets the start column number. Defaults to `None`. 92 | ChangeColumnStart(u32), 93 | /// Advances the end column number by the given value. 94 | ChangeColumnEndDelta(i32), 95 | /// **Emitting**. Advances the code offset and the line number by the given values. 96 | /// 97 | /// This annotation emits a line record. The length of the covered code block can be established 98 | /// by a following `ChangeCodeLength` annotation, or by the offset of the next emitted record. 99 | ChangeCodeOffsetAndLineOffset(u32, i32), 100 | /// **Emitting**. Sets the code length and advances the code offset by the given value. 101 | /// 102 | /// This annotation emits a line record that is valid for the given code length. This is usually 103 | /// issued as one of the last annotations, as there is no subsequent line record to derive the 104 | /// code length from. 105 | ChangeCodeLengthAndCodeOffset(u32, u32), 106 | /// Sets the end column number. 107 | ChangeColumnEnd(u32), 108 | } 109 | 110 | impl BinaryAnnotation { 111 | /// Does this annotation emit a line info? 112 | pub fn emits_line_info(self) -> bool { 113 | matches!( 114 | self, 115 | BinaryAnnotation::ChangeCodeOffset(..) 116 | | BinaryAnnotation::ChangeCodeOffsetAndLineOffset(..) 117 | | BinaryAnnotation::ChangeCodeLengthAndCodeOffset(..) 118 | ) 119 | } 120 | } 121 | 122 | /// An iterator over binary annotations used by `S_INLINESITE`. 123 | #[derive(Clone, Debug, Default)] 124 | pub struct BinaryAnnotationsIter<'t> { 125 | buffer: ParseBuffer<'t>, 126 | } 127 | 128 | impl<'t> BinaryAnnotationsIter<'t> { 129 | /// Parse a compact version of an unsigned integer. 130 | /// 131 | /// This implements `CVUncompressData`, which can decode numbers no larger than 0x1FFFFFFF. It 132 | /// seems that values compressed this way are only used for binary annotations at this point. 133 | fn uncompress_next(&mut self) -> Result { 134 | let b1 = u32::from(self.buffer.parse::()?); 135 | if (b1 & 0x80) == 0x00 { 136 | let value = b1; 137 | return Ok(value); 138 | } 139 | 140 | let b2 = u32::from(self.buffer.parse::()?); 141 | if (b1 & 0xc0) == 0x80 { 142 | let value = (b1 & 0x3f) << 8 | b2; 143 | return Ok(value); 144 | } 145 | 146 | let b3 = u32::from(self.buffer.parse::()?); 147 | let b4 = u32::from(self.buffer.parse::()?); 148 | if (b1 & 0xe0) == 0xc0 { 149 | let value = ((b1 & 0x1f) << 24) | (b2 << 16) | (b3 << 8) | b4; 150 | return Ok(value); 151 | } 152 | 153 | Err(Error::InvalidCompressedAnnotation) 154 | } 155 | } 156 | 157 | /// Resembles `DecodeSignedInt32`. 158 | fn decode_signed_operand(value: u32) -> i32 { 159 | if value & 1 != 0 { 160 | -((value >> 1) as i32) 161 | } else { 162 | (value >> 1) as i32 163 | } 164 | } 165 | 166 | impl<'t> FallibleIterator for BinaryAnnotationsIter<'t> { 167 | type Item = BinaryAnnotation; 168 | type Error = Error; 169 | 170 | fn next(&mut self) -> Result> { 171 | if self.buffer.is_empty() { 172 | return Ok(None); 173 | } 174 | 175 | let op = self.uncompress_next()?; 176 | let annotation = match BinaryAnnotationOpcode::parse(op)? { 177 | BinaryAnnotationOpcode::Eof => { 178 | // This makes the end of the stream 179 | self.buffer = ParseBuffer::default(); 180 | return Ok(None); 181 | } 182 | BinaryAnnotationOpcode::CodeOffset => { 183 | BinaryAnnotation::CodeOffset(self.uncompress_next()?) 184 | } 185 | BinaryAnnotationOpcode::ChangeCodeOffsetBase => { 186 | BinaryAnnotation::ChangeCodeOffsetBase(self.uncompress_next()?) 187 | } 188 | BinaryAnnotationOpcode::ChangeCodeOffset => { 189 | BinaryAnnotation::ChangeCodeOffset(self.uncompress_next()?) 190 | } 191 | BinaryAnnotationOpcode::ChangeCodeLength => { 192 | BinaryAnnotation::ChangeCodeLength(self.uncompress_next()?) 193 | } 194 | BinaryAnnotationOpcode::ChangeFile => { 195 | BinaryAnnotation::ChangeFile(FileIndex(self.uncompress_next()?)) 196 | } 197 | BinaryAnnotationOpcode::ChangeLineOffset => { 198 | BinaryAnnotation::ChangeLineOffset(decode_signed_operand(self.uncompress_next()?)) 199 | } 200 | BinaryAnnotationOpcode::ChangeLineEndDelta => { 201 | BinaryAnnotation::ChangeLineEndDelta(self.uncompress_next()?) 202 | } 203 | BinaryAnnotationOpcode::ChangeRangeKind => { 204 | BinaryAnnotation::ChangeRangeKind(self.uncompress_next()?) 205 | } 206 | BinaryAnnotationOpcode::ChangeColumnStart => { 207 | BinaryAnnotation::ChangeColumnStart(self.uncompress_next()?) 208 | } 209 | BinaryAnnotationOpcode::ChangeColumnEndDelta => BinaryAnnotation::ChangeColumnEndDelta( 210 | decode_signed_operand(self.uncompress_next()?), 211 | ), 212 | BinaryAnnotationOpcode::ChangeCodeOffsetAndLineOffset => { 213 | let operand = self.uncompress_next()?; 214 | BinaryAnnotation::ChangeCodeOffsetAndLineOffset( 215 | operand & 0xf, 216 | decode_signed_operand(operand >> 4), 217 | ) 218 | } 219 | BinaryAnnotationOpcode::ChangeCodeLengthAndCodeOffset => { 220 | BinaryAnnotation::ChangeCodeLengthAndCodeOffset( 221 | self.uncompress_next()?, 222 | self.uncompress_next()?, 223 | ) 224 | } 225 | BinaryAnnotationOpcode::ChangeColumnEnd => { 226 | BinaryAnnotation::ChangeColumnEnd(self.uncompress_next()?) 227 | } 228 | }; 229 | 230 | Ok(Some(annotation)) 231 | } 232 | } 233 | 234 | /// Binary annotations of a symbol. 235 | /// 236 | /// The binary annotation mechanism supports recording a list of annotations in an instruction 237 | /// stream. The X64 unwind code and the DWARF standard have a similar design. 238 | /// 239 | /// Binary annotations are primarily used as line programs for inline function calls. 240 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 241 | pub struct BinaryAnnotations<'t> { 242 | data: &'t [u8], 243 | } 244 | 245 | impl<'t> BinaryAnnotations<'t> { 246 | /// Creates a new instance of binary annotations. 247 | pub(crate) fn new(data: &'t [u8]) -> Self { 248 | BinaryAnnotations { data } 249 | } 250 | 251 | /// Iterates through binary annotations. 252 | pub fn iter(&self) -> BinaryAnnotationsIter<'t> { 253 | BinaryAnnotationsIter { 254 | buffer: ParseBuffer::from(self.data), 255 | } 256 | } 257 | } 258 | 259 | #[test] 260 | fn test_binary_annotation_iter() { 261 | 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"; 262 | let annotations = BinaryAnnotations::new(inp) 263 | .iter() 264 | .collect::>() 265 | .unwrap(); 266 | 267 | assert_eq!( 268 | annotations, 269 | vec![ 270 | BinaryAnnotation::ChangeCodeOffsetAndLineOffset(3, 0), 271 | BinaryAnnotation::ChangeLineOffset(5), 272 | BinaryAnnotation::ChangeCodeOffset(8), 273 | BinaryAnnotation::ChangeLineOffset(3), 274 | BinaryAnnotation::ChangeCodeOffset(45), 275 | BinaryAnnotation::ChangeLineOffset(4), 276 | BinaryAnnotation::ChangeCodeOffset(7), 277 | BinaryAnnotation::ChangeCodeOffsetAndLineOffset(2, -3), 278 | BinaryAnnotation::ChangeLineOffset(3), 279 | BinaryAnnotation::ChangeCodeLengthAndCodeOffset(3, 7), 280 | BinaryAnnotation::ChangeLineOffset(-7), 281 | BinaryAnnotation::ChangeCodeLengthAndCodeOffset(6, 5) 282 | ] 283 | ); 284 | 285 | 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"; 286 | let annotations = BinaryAnnotations::new(inp) 287 | .iter() 288 | .collect::>() 289 | .unwrap(); 290 | 291 | assert_eq!( 292 | annotations, 293 | vec![ 294 | BinaryAnnotation::ChangeCodeOffset(80), 295 | BinaryAnnotation::ChangeLineOffset(7), 296 | BinaryAnnotation::ChangeCodeOffset(12), 297 | BinaryAnnotation::ChangeLineOffset(2), 298 | BinaryAnnotation::ChangeCodeOffset(50), 299 | BinaryAnnotation::ChangeLineOffset(3), 300 | BinaryAnnotation::ChangeCodeOffset(84), 301 | BinaryAnnotation::ChangeCodeOffsetAndLineOffset(3, 1), 302 | BinaryAnnotation::ChangeCodeOffsetAndLineOffset(12, -2), 303 | BinaryAnnotation::ChangeCodeOffsetAndLineOffset(3, 2), 304 | BinaryAnnotation::ChangeCodeOffsetAndLineOffset(15, 1), 305 | BinaryAnnotation::ChangeLineOffset(2), 306 | BinaryAnnotation::ChangeCodeLengthAndCodeOffset(45, 9), 307 | BinaryAnnotation::ChangeCodeOffset(59), 308 | BinaryAnnotation::ChangeLineOffset(-14), 309 | BinaryAnnotation::ChangeCodeLengthAndCodeOffset(5, 6), 310 | ] 311 | ); 312 | } 313 | -------------------------------------------------------------------------------- /src/tpi/constants.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 pdb Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | #![allow(unused, non_upper_case_globals)] 9 | 10 | // TODO: special types 11 | // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L328 12 | 13 | // A list of known type kinds: 14 | // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L772 15 | 16 | // leaf indices starting records but referenced from symbol records 17 | 18 | pub const LF_MODIFIER_16t: u16 = 0x0001; 19 | pub const LF_POINTER_16t: u16 = 0x0002; 20 | pub const LF_ARRAY_16t: u16 = 0x0003; 21 | pub const LF_CLASS_16t: u16 = 0x0004; 22 | pub const LF_STRUCTURE_16t: u16 = 0x0005; 23 | pub const LF_UNION_16t: u16 = 0x0006; 24 | pub const LF_ENUM_16t: u16 = 0x0007; 25 | pub const LF_PROCEDURE_16t: u16 = 0x0008; 26 | pub const LF_MFUNCTION_16t: u16 = 0x0009; 27 | pub const LF_VTSHAPE: u16 = 0x000a; 28 | pub const LF_COBOL0_16t: u16 = 0x000b; 29 | pub const LF_COBOL1: u16 = 0x000c; 30 | pub const LF_BARRAY_16t: u16 = 0x000d; 31 | pub const LF_LABEL: u16 = 0x000e; 32 | pub const LF_NULL: u16 = 0x000f; 33 | pub const LF_NOTTRAN: u16 = 0x0010; 34 | pub const LF_DIMARRAY_16t: u16 = 0x0011; 35 | pub const LF_VFTPATH_16t: u16 = 0x0012; 36 | pub const LF_PRECOMP_16t: u16 = 0x0013; // not referenced from symbol 37 | pub const LF_ENDPRECOMP: u16 = 0x0014; // not referenced from symbol 38 | pub const LF_OEM_16t: u16 = 0x0015; // oem definable type string 39 | pub const LF_TYPESERVER_ST: u16 = 0x0016; // not referenced from symbol 40 | 41 | // leaf indices starting records but referenced only from type records 42 | 43 | pub const LF_SKIP_16t: u16 = 0x0200; 44 | pub const LF_ARGLIST_16t: u16 = 0x0201; 45 | pub const LF_DEFARG_16t: u16 = 0x0202; 46 | pub const LF_LIST: u16 = 0x0203; 47 | pub const LF_FIELDLIST_16t: u16 = 0x0204; 48 | pub const LF_DERIVED_16t: u16 = 0x0205; 49 | pub const LF_BITFIELD_16t: u16 = 0x0206; 50 | pub const LF_METHODLIST_16t: u16 = 0x0207; 51 | pub const LF_DIMCONU_16t: u16 = 0x0208; 52 | pub const LF_DIMCONLU_16t: u16 = 0x0209; 53 | pub const LF_DIMVARU_16t: u16 = 0x020a; 54 | pub const LF_DIMVARLU_16t: u16 = 0x020b; 55 | pub const LF_REFSYM: u16 = 0x020c; 56 | 57 | pub const LF_BCLASS_16t: u16 = 0x0400; 58 | pub const LF_VBCLASS_16t: u16 = 0x0401; 59 | pub const LF_IVBCLASS_16t: u16 = 0x0402; 60 | pub const LF_ENUMERATE_ST: u16 = 0x0403; 61 | pub const LF_FRIENDFCN_16t: u16 = 0x0404; 62 | pub const LF_INDEX_16t: u16 = 0x0405; 63 | pub const LF_MEMBER_16t: u16 = 0x0406; 64 | pub const LF_STMEMBER_16t: u16 = 0x0407; 65 | pub const LF_METHOD_16t: u16 = 0x0408; 66 | pub const LF_NESTTYPE_16t: u16 = 0x0409; 67 | pub const LF_VFUNCTAB_16t: u16 = 0x040a; 68 | pub const LF_FRIENDCLS_16t: u16 = 0x040b; 69 | pub const LF_ONEMETHOD_16t: u16 = 0x040c; 70 | pub const LF_VFUNCOFF_16t: u16 = 0x040d; 71 | 72 | // 32-bit type index versions of leaves all have the 0x1000 bit set 73 | // 74 | pub const LF_TI16_MAX: u16 = 0x1000; 75 | 76 | pub const LF_MODIFIER: u16 = 0x1001; 77 | pub const LF_POINTER: u16 = 0x1002; 78 | pub const LF_ARRAY_ST: u16 = 0x1003; 79 | pub const LF_CLASS_ST: u16 = 0x1004; 80 | pub const LF_STRUCTURE_ST: u16 = 0x1005; 81 | pub const LF_UNION_ST: u16 = 0x1006; 82 | pub const LF_ENUM_ST: u16 = 0x1007; 83 | pub const LF_PROCEDURE: u16 = 0x1008; 84 | pub const LF_MFUNCTION: u16 = 0x1009; 85 | pub const LF_COBOL0: u16 = 0x100a; 86 | pub const LF_BARRAY: u16 = 0x100b; 87 | pub const LF_DIMARRAY_ST: u16 = 0x100c; 88 | pub const LF_VFTPATH: u16 = 0x100d; 89 | pub const LF_PRECOMP_ST: u16 = 0x100e; // not referenced from symbol 90 | pub const LF_OEM: u16 = 0x100f; // oem definable type string 91 | pub const LF_ALIAS_ST: u16 = 0x1010; // alias (typedef) type 92 | pub const LF_OEM2: u16 = 0x1011; // oem definable type string 93 | 94 | // leaf indices starting records but referenced only from type records 95 | 96 | pub const LF_SKIP: u16 = 0x1200; 97 | pub const LF_ARGLIST: u16 = 0x1201; 98 | pub const LF_DEFARG_ST: u16 = 0x1202; 99 | pub const LF_FIELDLIST: u16 = 0x1203; 100 | pub const LF_DERIVED: u16 = 0x1204; 101 | pub const LF_BITFIELD: u16 = 0x1205; 102 | pub const LF_METHODLIST: u16 = 0x1206; 103 | pub const LF_DIMCONU: u16 = 0x1207; 104 | pub const LF_DIMCONLU: u16 = 0x1208; 105 | pub const LF_DIMVARU: u16 = 0x1209; 106 | pub const LF_DIMVARLU: u16 = 0x120a; 107 | 108 | pub const LF_BCLASS: u16 = 0x1400; 109 | pub const LF_VBCLASS: u16 = 0x1401; 110 | pub const LF_IVBCLASS: u16 = 0x1402; 111 | pub const LF_FRIENDFCN_ST: u16 = 0x1403; 112 | pub const LF_INDEX: u16 = 0x1404; 113 | pub const LF_MEMBER_ST: u16 = 0x1405; 114 | pub const LF_STMEMBER_ST: u16 = 0x1406; 115 | pub const LF_METHOD_ST: u16 = 0x1407; 116 | pub const LF_NESTTYPE_ST: u16 = 0x1408; 117 | pub const LF_VFUNCTAB: u16 = 0x1409; 118 | pub const LF_FRIENDCLS: u16 = 0x140a; 119 | pub const LF_ONEMETHOD_ST: u16 = 0x140b; 120 | pub const LF_VFUNCOFF: u16 = 0x140c; 121 | pub const LF_NESTTYPEEX_ST: u16 = 0x140d; 122 | pub const LF_MEMBERMODIFY_ST: u16 = 0x140e; 123 | pub const LF_MANAGED_ST: u16 = 0x140f; 124 | 125 | // Types w/ SZ names 126 | 127 | pub const LF_ST_MAX: u16 = 0x1500; 128 | 129 | pub const LF_TYPESERVER: u16 = 0x1501; // not referenced from symbol 130 | pub const LF_ENUMERATE: u16 = 0x1502; 131 | pub const LF_ARRAY: u16 = 0x1503; 132 | pub const LF_CLASS: u16 = 0x1504; 133 | pub const LF_STRUCTURE: u16 = 0x1505; 134 | pub const LF_UNION: u16 = 0x1506; 135 | pub const LF_ENUM: u16 = 0x1507; 136 | pub const LF_DIMARRAY: u16 = 0x1508; 137 | pub const LF_PRECOMP: u16 = 0x1509; // not referenced from symbol 138 | pub const LF_ALIAS: u16 = 0x150a; // alias (typedef) type 139 | pub const LF_DEFARG: u16 = 0x150b; 140 | pub const LF_FRIENDFCN: u16 = 0x150c; 141 | pub const LF_MEMBER: u16 = 0x150d; 142 | pub const LF_STMEMBER: u16 = 0x150e; 143 | pub const LF_METHOD: u16 = 0x150f; 144 | pub const LF_NESTTYPE: u16 = 0x1510; 145 | pub const LF_ONEMETHOD: u16 = 0x1511; 146 | pub const LF_NESTTYPEEX: u16 = 0x1512; 147 | pub const LF_MEMBERMODIFY: u16 = 0x1513; 148 | pub const LF_MANAGED: u16 = 0x1514; 149 | pub const LF_TYPESERVER2: u16 = 0x1515; 150 | 151 | pub const LF_STRIDED_ARRAY: u16 = 0x1516; // same as LF_ARRAY but with stride between adjacent elements 152 | pub const LF_HLSL: u16 = 0x1517; 153 | pub const LF_MODIFIER_EX: u16 = 0x1518; 154 | pub const LF_INTERFACE: u16 = 0x1519; 155 | pub const LF_BINTERFACE: u16 = 0x151a; 156 | pub const LF_VECTOR: u16 = 0x151b; 157 | pub const LF_MATRIX: u16 = 0x151c; 158 | 159 | pub const LF_VFTABLE: u16 = 0x151d; // a virtual function table 160 | pub const LF_ENDOFLEAFRECORD: u16 = LF_VFTABLE; 161 | 162 | pub const LF_TYPE_LAST: u16 = LF_ENDOFLEAFRECORD + 1; // one greater than the last type record 163 | pub const LF_TYPE_MAX: u16 = LF_TYPE_LAST - 1; 164 | 165 | pub const LF_FUNC_ID: u16 = 0x1601; // global func ID 166 | pub const LF_MFUNC_ID: u16 = 0x1602; // member func ID 167 | pub const LF_BUILDINFO: u16 = 0x1603; // build info: tool version command line src/pdb file 168 | pub const LF_SUBSTR_LIST: u16 = 0x1604; // similar to LF_ARGLIST for list of sub strings 169 | pub const LF_STRING_ID: u16 = 0x1605; // string ID 170 | 171 | pub const LF_UDT_SRC_LINE: u16 = 0x1606; // source and line on where an UDT is defined 172 | // only generated by compiler 173 | 174 | pub const LF_UDT_MOD_SRC_LINE: u16 = 0x1607; // module source and line on where an UDT is defined 175 | // only generated by linker 176 | 177 | pub const LF_STRUCTURE19: u16 = 0x1609; 178 | 179 | pub const LF_ID_LAST: u16 = LF_UDT_MOD_SRC_LINE + 1; // one greater than the last ID record 180 | pub const LF_ID_MAX: u16 = LF_ID_LAST - 1; 181 | 182 | pub const LF_NUMERIC: u16 = 0x8000; 183 | pub const LF_CHAR: u16 = 0x8000; 184 | pub const LF_SHORT: u16 = 0x8001; 185 | pub const LF_USHORT: u16 = 0x8002; 186 | pub const LF_LONG: u16 = 0x8003; 187 | pub const LF_ULONG: u16 = 0x8004; 188 | pub const LF_REAL32: u16 = 0x8005; 189 | pub const LF_REAL64: u16 = 0x8006; 190 | pub const LF_REAL80: u16 = 0x8007; 191 | pub const LF_REAL128: u16 = 0x8008; 192 | pub const LF_QUADWORD: u16 = 0x8009; 193 | pub const LF_UQUADWORD: u16 = 0x800a; 194 | pub const LF_REAL48: u16 = 0x800b; 195 | pub const LF_COMPLEX32: u16 = 0x800c; 196 | pub const LF_COMPLEX64: u16 = 0x800d; 197 | pub const LF_COMPLEX80: u16 = 0x800e; 198 | pub const LF_COMPLEX128: u16 = 0x800f; 199 | pub const LF_VARSTRING: u16 = 0x8010; 200 | 201 | pub const LF_OCTWORD: u16 = 0x8017; 202 | pub const LF_UOCTWORD: u16 = 0x8018; 203 | 204 | pub const LF_DECIMAL: u16 = 0x8019; 205 | pub const LF_DATE: u16 = 0x801a; 206 | pub const LF_UTF8STRING: u16 = 0x801b; 207 | 208 | pub const LF_REAL16: u16 = 0x801c; 209 | 210 | pub const LF_PAD0: u16 = 0xf0; 211 | pub const LF_PAD1: u16 = 0xf1; 212 | pub const LF_PAD2: u16 = 0xf2; 213 | pub const LF_PAD3: u16 = 0xf3; 214 | pub const LF_PAD4: u16 = 0xf4; 215 | pub const LF_PAD5: u16 = 0xf5; 216 | pub const LF_PAD6: u16 = 0xf6; 217 | pub const LF_PAD7: u16 = 0xf7; 218 | pub const LF_PAD8: u16 = 0xf8; 219 | pub const LF_PAD9: u16 = 0xf9; 220 | pub const LF_PAD10: u16 = 0xfa; 221 | pub const LF_PAD11: u16 = 0xfb; 222 | pub const LF_PAD12: u16 = 0xfc; 223 | pub const LF_PAD13: u16 = 0xfd; 224 | pub const LF_PAD14: u16 = 0xfe; 225 | pub const LF_PAD15: u16 = 0xff; 226 | -------------------------------------------------------------------------------- /src/tpi/header.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 pdb Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | use crate::common::*; 9 | 10 | // OFFCB: 11 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 12 | pub struct Slice { 13 | pub offset: i32, // technically a "long", but... 32 bits for life? 14 | pub size: u32, 15 | } 16 | 17 | // HDR: 18 | // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/dbi/tpi.h#L45 19 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 20 | pub struct Header { 21 | pub version: u32, 22 | pub header_size: u32, 23 | pub minimum_index: u32, 24 | pub maximum_index: u32, 25 | pub gprec_size: u32, 26 | pub tpi_hash_stream: u16, 27 | pub tpi_hash_pad_stream: u16, 28 | pub hash_key_size: u32, 29 | pub hash_bucket_size: u32, 30 | pub hash_values: Slice, 31 | pub ti_off: Slice, 32 | pub hash_adj: Slice, // "offcb of hash head list, maps (hashval,ti), where ti is the head of the hashval chain." 33 | } 34 | 35 | impl Header { 36 | pub(crate) fn empty() -> Self { 37 | let empty_slice = Slice { offset: 0, size: 0 }; 38 | 39 | Self { 40 | version: 0, 41 | header_size: 0, 42 | minimum_index: 0, 43 | maximum_index: 0, 44 | gprec_size: 0, 45 | tpi_hash_stream: 0, 46 | tpi_hash_pad_stream: 0, 47 | hash_key_size: 0, 48 | hash_bucket_size: 0, 49 | hash_values: empty_slice, 50 | ti_off: empty_slice, 51 | hash_adj: empty_slice, 52 | } 53 | } 54 | 55 | pub(crate) fn parse(buf: &mut ParseBuffer<'_>) -> Result { 56 | debug_assert!(buf.pos() == 0); 57 | 58 | if buf.is_empty() { 59 | // Special case when the buffer is completely empty. This indicates a missing TPI or IPI 60 | // stream. In this case, `ItemInformation` acts like an empty shell that never resolves 61 | // any types. 62 | return Ok(Self::empty()); 63 | } 64 | 65 | let header = Self { 66 | version: buf.parse()?, 67 | header_size: buf.parse()?, 68 | minimum_index: buf.parse()?, 69 | maximum_index: buf.parse()?, 70 | gprec_size: buf.parse()?, 71 | tpi_hash_stream: buf.parse()?, 72 | tpi_hash_pad_stream: buf.parse()?, 73 | hash_key_size: buf.parse()?, 74 | hash_bucket_size: buf.parse()?, 75 | hash_values: Slice { 76 | offset: buf.parse()?, 77 | size: buf.parse()?, 78 | }, 79 | ti_off: Slice { 80 | offset: buf.parse()?, 81 | size: buf.parse()?, 82 | }, 83 | hash_adj: Slice { 84 | offset: buf.parse()?, 85 | size: buf.parse()?, 86 | }, 87 | }; 88 | 89 | // we read 56 bytes 90 | // make sure that's okay 91 | let bytes_read = buf.pos() as u32; 92 | if header.header_size < bytes_read { 93 | return Err(Error::InvalidTypeInformationHeader( 94 | "header size is impossibly small", 95 | )); 96 | } else if header.header_size > 1024 { 97 | return Err(Error::InvalidTypeInformationHeader( 98 | "header size is unreasonably large", 99 | )); 100 | } 101 | 102 | // consume anything else the header says belongs to the header 103 | buf.take((header.header_size - bytes_read) as usize)?; 104 | 105 | // do some final validations 106 | if header.minimum_index < 4096 { 107 | return Err(Error::InvalidTypeInformationHeader( 108 | "minimum type index is < 4096", 109 | )); 110 | } 111 | if header.maximum_index < header.minimum_index { 112 | return Err(Error::InvalidTypeInformationHeader( 113 | "maximum type index is < minimum type index", 114 | )); 115 | } 116 | 117 | // success 118 | Ok(header) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/tpi/id.rs: -------------------------------------------------------------------------------- 1 | use scroll::ctx::TryFromCtx; 2 | 3 | use crate::common::*; 4 | use crate::tpi::constants::*; 5 | 6 | #[inline] 7 | fn parse_optional_id_index(buf: &mut ParseBuffer<'_>) -> Result> { 8 | Ok(match buf.parse()? { 9 | IdIndex(0) => None, 10 | index => Some(index), 11 | }) 12 | } 13 | 14 | #[inline] 15 | fn parse_string<'t>(leaf: u16, buf: &mut ParseBuffer<'t>) -> Result> { 16 | if leaf > LF_ST_MAX { 17 | buf.parse_cstring() 18 | } else { 19 | buf.parse_u8_pascal_string() 20 | } 21 | } 22 | 23 | /// Encapsulates parsed data about an `Id`. 24 | #[non_exhaustive] 25 | #[derive(Debug, Clone, PartialEq, Eq)] 26 | pub enum IdData<'t> { 27 | /// Global function, usually inlined. 28 | Function(FunctionId<'t>), 29 | /// Member function, usually inlined. 30 | MemberFunction(MemberFunctionId<'t>), 31 | /// Tool, version and command line build information. 32 | BuildInfo(BuildInfoId), 33 | /// A list of substrings. 34 | StringList(StringListId), 35 | /// A string. 36 | String(StringId<'t>), 37 | /// Source and line of the definition of a User Defined Type (UDT). 38 | UserDefinedTypeSource(UserDefinedTypeSourceId), 39 | } 40 | 41 | impl<'t> IdData<'t> {} 42 | 43 | impl<'t> TryFromCtx<'t, scroll::Endian> for IdData<'t> { 44 | type Error = Error; 45 | 46 | fn try_from_ctx(this: &'t [u8], _ctx: scroll::Endian) -> Result<(Self, usize)> { 47 | let mut buf = ParseBuffer::from(this); 48 | let leaf = buf.parse_u16()?; 49 | 50 | let data = match leaf { 51 | LF_FUNC_ID => IdData::Function(FunctionId { 52 | scope: parse_optional_id_index(&mut buf)?, 53 | function_type: buf.parse()?, 54 | name: parse_string(leaf, &mut buf)?, 55 | }), 56 | LF_MFUNC_ID => IdData::MemberFunction(MemberFunctionId { 57 | parent: buf.parse()?, 58 | function_type: buf.parse()?, 59 | name: parse_string(leaf, &mut buf)?, 60 | }), 61 | LF_BUILDINFO => IdData::BuildInfo({ 62 | let count = buf.parse::()?; 63 | let mut arguments = Vec::with_capacity(count as usize); 64 | for _ in 0..count { 65 | arguments.push(buf.parse()?); 66 | } 67 | BuildInfoId { arguments } 68 | }), 69 | LF_SUBSTR_LIST => IdData::StringList({ 70 | let count = buf.parse::()?; 71 | let mut substrings = Vec::with_capacity(count as usize); 72 | for _ in 0..count { 73 | substrings.push(buf.parse()?); 74 | } 75 | StringListId { substrings } 76 | }), 77 | LF_STRING_ID => IdData::String(StringId { 78 | substrings: parse_optional_id_index(&mut buf)?, 79 | name: parse_string(leaf, &mut buf)?, 80 | }), 81 | LF_UDT_SRC_LINE | LF_UDT_MOD_SRC_LINE => { 82 | let udt = buf.parse()?; 83 | let file_id = buf.parse()?; 84 | let line = buf.parse()?; 85 | 86 | let source_file = if leaf == self::LF_UDT_SRC_LINE { 87 | UserDefinedTypeSourceFileRef::Local(IdIndex(file_id)) 88 | } else { 89 | UserDefinedTypeSourceFileRef::Remote(buf.parse()?, StringRef(file_id)) 90 | }; 91 | 92 | IdData::UserDefinedTypeSource(UserDefinedTypeSourceId { 93 | udt, 94 | source_file, 95 | line, 96 | }) 97 | } 98 | _ => return Err(Error::UnimplementedTypeKind(leaf)), 99 | }; 100 | 101 | Ok((data, buf.pos())) 102 | } 103 | } 104 | 105 | /// Global function, usually inlined. 106 | /// 107 | /// This Id is usually referenced by [`InlineSiteSymbol`](crate::InlineSiteSymbol). 108 | #[derive(Clone, Debug, PartialEq, Eq)] 109 | pub struct FunctionId<'t> { 110 | /// Parent scope of this id. 111 | pub scope: Option, 112 | /// Index of the function type declaration. 113 | pub function_type: TypeIndex, 114 | /// Name of the function. 115 | pub name: RawString<'t>, 116 | } 117 | 118 | /// Member function, usually inlined. 119 | /// 120 | /// This Id is usually referenced by [`InlineSiteSymbol`](crate::InlineSiteSymbol). 121 | #[derive(Clone, Debug, PartialEq, Eq)] 122 | pub struct MemberFunctionId<'t> { 123 | /// Index of the parent type. 124 | pub parent: TypeIndex, 125 | /// Index of the member function type declaration. 126 | pub function_type: TypeIndex, 127 | /// Name of the member function. 128 | pub name: RawString<'t>, 129 | } 130 | 131 | /// Tool, version and command line build information. 132 | /// 133 | /// This Id is usually referenced by [`BuildInfoSymbol`](crate::BuildInfoSymbol). 134 | #[derive(Clone, Debug, PartialEq, Eq)] 135 | pub struct BuildInfoId { 136 | /// Indexes of build arguments. 137 | pub arguments: Vec, 138 | } 139 | 140 | /// A list of substrings. 141 | /// 142 | /// This Id is usually referenced by [`StringId`]. 143 | #[derive(Clone, Debug, PartialEq, Eq)] 144 | pub struct StringListId { 145 | /// The list of substrings. 146 | pub substrings: Vec, 147 | } 148 | 149 | /// A string. 150 | /// 151 | /// This Id is usually referenced by [`FunctionId`] and contains the full namespace of a function. 152 | #[derive(Clone, Debug, PartialEq, Eq)] 153 | pub struct StringId<'t> { 154 | /// Index of the list of substrings. 155 | pub substrings: Option, 156 | /// The string. 157 | pub name: RawString<'t>, 158 | } 159 | 160 | /// A reference to the source file name of a [`UserDefinedTypeSourceId`]. 161 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 162 | pub enum UserDefinedTypeSourceFileRef { 163 | /// Index of the source file name in the [`IdInformation`](crate::IdInformation) of the same module. 164 | /// 165 | /// The index should resolve to a [`IdData::String`]. 166 | Local(IdIndex), 167 | /// Reference into the [`StringTable`](crate::StringTable) of another module that contributes 168 | /// this UDT definition. 169 | /// 170 | /// Use [`DebugInformation::modules`](crate::DebugInformation::modules) to resolve the 171 | /// corresponding module. 172 | Remote(u16, StringRef), 173 | } 174 | 175 | /// Source and line of the definition of a User Defined Type (UDT). 176 | #[derive(Clone, Debug, PartialEq, Eq)] 177 | pub struct UserDefinedTypeSourceId { 178 | /// Index of the UDT's type definition. 179 | pub udt: TypeIndex, 180 | /// Reference to the source file name. 181 | pub source_file: UserDefinedTypeSourceFileRef, 182 | /// Line number in the source file. 183 | pub line: u32, 184 | } 185 | -------------------------------------------------------------------------------- /src/tpi/primitive.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 pdb Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | use crate::common::*; 9 | use crate::tpi::data::TypeData; 10 | 11 | // References for primitive types: 12 | // 13 | // cvinfo.h provides an enumeration: 14 | // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L328-L750 15 | // 16 | // pdbparse.cpp describes them as strings: 17 | // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/pdbdump/pdbdump.cpp#L1896-L1974 18 | // 19 | // The most obscure: MSDN Library October 2001 Disk 2 contains a \MSDN\specs.chm file which contains 20 | // html\S66CD.HTM which actually documents the *format* of the primitive type descriptors rather 21 | // than just listing them. TypeData::Primitive is designed to model the orthogonal information 22 | // encoded into the bits of the TypeIndex rather than exploding the matrix like the reference 23 | // implementations. 24 | 25 | /// Represents a primitive type like `void` or `char *`. 26 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 27 | pub struct PrimitiveType { 28 | /// The kind of the primitive type. 29 | pub kind: PrimitiveKind, 30 | 31 | /// Pointer indirection applied to the primitive type. 32 | pub indirection: Option, 33 | } 34 | 35 | /// A simple type. 36 | #[non_exhaustive] 37 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 38 | pub enum PrimitiveKind { 39 | /// Uncharacterized type (no type) 40 | NoType, 41 | 42 | /// Void type 43 | Void, 44 | 45 | /// Character (byte) 46 | Char, 47 | 48 | /// Unsigned character 49 | UChar, 50 | 51 | /// "Really a char" 52 | RChar, 53 | 54 | /// Wide characters, i.e. 16 bits 55 | WChar, 56 | 57 | /// "Really a 16-bit char" 58 | RChar16, 59 | 60 | /// "Really a 32-bit char" 61 | RChar32, 62 | 63 | /// Signed 8-bit integer 64 | I8, 65 | 66 | /// Unsigned 8-bit integer 67 | U8, 68 | 69 | /// Signed 16-bit integer 70 | Short, 71 | 72 | /// Unsigned 16-bit integer 73 | UShort, 74 | 75 | /// Signed 16-bit integer 76 | I16, 77 | 78 | /// Unsigned 16-bit integer 79 | U16, 80 | 81 | /// Signed 32-bit integer 82 | Long, 83 | 84 | /// Unsigned 32-bit inteer 85 | ULong, 86 | 87 | /// Signed 32-bit integer 88 | I32, 89 | 90 | /// Unsigned 32-bit inteer 91 | U32, 92 | 93 | /// Signed 64-bit integer 94 | Quad, 95 | 96 | /// Unsigned 64-bit integer 97 | UQuad, 98 | 99 | /// Signed 64-bit integer 100 | I64, 101 | 102 | /// Unsigned 64-bit integer 103 | U64, 104 | 105 | /// Signed 128-bit integer 106 | Octa, 107 | 108 | /// Unsigned 128-bit integer 109 | UOcta, 110 | 111 | /// Signed 128-bit integer 112 | I128, 113 | 114 | /// Unsigned 128-bit integer 115 | U128, 116 | 117 | /// 16-bit floating point 118 | F16, 119 | 120 | /// 32-bit floating point 121 | F32, 122 | 123 | /// 32-bit partial precision floating point 124 | F32PP, 125 | 126 | /// 48-bit floating point 127 | F48, 128 | 129 | /// 64-bit floating point 130 | F64, 131 | 132 | /// 80-bit floating point 133 | F80, 134 | 135 | /// 128-bit floating point 136 | F128, 137 | 138 | /// 32-bit complex number 139 | Complex32, 140 | 141 | /// 64-bit complex number 142 | Complex64, 143 | 144 | /// 80-bit complex number 145 | Complex80, 146 | 147 | /// 128-bit complex number 148 | Complex128, 149 | 150 | /// 8-bit boolean value 151 | Bool8, 152 | 153 | /// 16-bit boolean value 154 | Bool16, 155 | 156 | /// 32-bit boolean value 157 | Bool32, 158 | 159 | /// 16-bit boolean value 160 | Bool64, 161 | 162 | /// Windows `HRESULT` error code. 163 | /// 164 | /// See: 165 | HRESULT, 166 | } 167 | 168 | /// Pointer mode of primitive types. 169 | /// 170 | /// This is partially overlapping with [`PointerKind`](crate::PointerKind) for regular pointer type 171 | /// definitions. While `PointerKind` can specify many more pointer types, including relative 172 | /// pointers, `Indirection` also contains a 128-bit variant. 173 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 174 | pub enum Indirection { 175 | /// 16-bit ("near") pointer. 176 | Near16, 177 | /// 16:16 far pointer. 178 | Far16, 179 | /// 16:16 huge pointer. 180 | Huge16, 181 | /// 32-bit pointer. 182 | Near32, 183 | /// 48-bit 16:32 pointer. 184 | Far32, 185 | /// 64-bit near pointer. 186 | Near64, 187 | /// 128-bit near pointer. 188 | Near128, 189 | } 190 | 191 | pub fn type_data_for_primitive(index: TypeIndex) -> Result> { 192 | // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L326-L750 193 | 194 | // primitives live under 0x1000, and we should never reach here for non-primitive indexes 195 | assert!(index < TypeIndex(0x1000)); 196 | 197 | // indirection is stored in these bits 198 | let indirection = match index.0 & 0xf00 { 199 | 0x000 => None, 200 | 0x100 => Some(Indirection::Near16), 201 | 0x200 => Some(Indirection::Far16), 202 | 0x300 => Some(Indirection::Huge16), 203 | 0x400 => Some(Indirection::Near32), 204 | 0x500 => Some(Indirection::Far32), 205 | 0x600 => Some(Indirection::Near64), 206 | 0x700 => Some(Indirection::Near128), 207 | _ => { 208 | return Err(Error::TypeNotFound(index.0)); 209 | } 210 | }; 211 | 212 | // primitive types are stored in the lowest octet 213 | let kind = match index.0 & 0xff { 214 | 0x00 => PrimitiveKind::NoType, 215 | 216 | 0x03 => PrimitiveKind::Void, 217 | 0x08 => PrimitiveKind::HRESULT, 218 | 219 | 0x10 => PrimitiveKind::Char, 220 | 0x20 => PrimitiveKind::UChar, 221 | 0x68 => PrimitiveKind::I8, 222 | 0x69 => PrimitiveKind::U8, 223 | 224 | 0x70 => PrimitiveKind::RChar, 225 | 0x71 => PrimitiveKind::WChar, 226 | 0x7a => PrimitiveKind::RChar16, 227 | 0x7b => PrimitiveKind::RChar32, 228 | 229 | 0x11 => PrimitiveKind::Short, 230 | 0x21 => PrimitiveKind::UShort, 231 | 0x72 => PrimitiveKind::I16, 232 | 0x73 => PrimitiveKind::U16, 233 | 234 | 0x12 => PrimitiveKind::Long, 235 | 0x22 => PrimitiveKind::ULong, 236 | 0x74 => PrimitiveKind::I32, 237 | 0x75 => PrimitiveKind::U32, 238 | 239 | 0x13 => PrimitiveKind::Quad, 240 | 0x23 => PrimitiveKind::UQuad, 241 | 0x76 => PrimitiveKind::I64, 242 | 0x77 => PrimitiveKind::U64, 243 | 244 | 0x14 => PrimitiveKind::Octa, 245 | 0x24 => PrimitiveKind::UOcta, 246 | 0x78 => PrimitiveKind::I128, 247 | 0x79 => PrimitiveKind::U128, 248 | 249 | 0x46 => PrimitiveKind::F16, 250 | 0x40 => PrimitiveKind::F32, 251 | 0x45 => PrimitiveKind::F32PP, 252 | 0x44 => PrimitiveKind::F48, 253 | 0x41 => PrimitiveKind::F64, 254 | 0x42 => PrimitiveKind::F80, 255 | 0x43 => PrimitiveKind::F128, 256 | 257 | 0x50 => PrimitiveKind::Complex32, 258 | 0x51 => PrimitiveKind::Complex64, 259 | 0x52 => PrimitiveKind::Complex80, 260 | 0x53 => PrimitiveKind::Complex128, 261 | 262 | 0x30 => PrimitiveKind::Bool8, 263 | 0x31 => PrimitiveKind::Bool16, 264 | 0x32 => PrimitiveKind::Bool32, 265 | 0x33 => PrimitiveKind::Bool64, 266 | 267 | _ => { 268 | return Err(Error::TypeNotFound(index.0)); 269 | } 270 | }; 271 | 272 | Ok(TypeData::Primitive(PrimitiveType { kind, indirection })) 273 | } 274 | -------------------------------------------------------------------------------- /tests/debug_information.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn pdb_info() { 3 | let file = std::fs::File::open("fixtures/self/foo.pdb").expect("opening file"); 4 | 5 | let mut pdb = pdb::PDB::open(file).expect("opening pdb"); 6 | let pdb_info = pdb.debug_information().expect("pdb information"); 7 | 8 | assert_eq!( 9 | pdb_info.machine_type().expect("machien type"), 10 | pdb::MachineType::Amd64 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /tests/id_information.rs: -------------------------------------------------------------------------------- 1 | //! Tests that IdInformation works on files where the IPI is missing (empty stream). 2 | 3 | use pdb::{FallibleIterator, IdIndex, PDB}; 4 | 5 | fn open_file() -> std::fs::File { 6 | let path = "fixtures/symbol_server/0ea7c70545374958ad3307514bdfc8642-wntdll.pdb"; 7 | std::fs::File::open(path).expect("missing fixtures, please run scripts/download from the root") 8 | } 9 | 10 | #[test] 11 | fn test_missing_ipi() { 12 | let mut pdb = PDB::open(open_file()).expect("opening pdb"); 13 | 14 | let id_information = pdb.id_information().expect("get id information"); 15 | 16 | // Check ItemInformation API 17 | assert_eq!(id_information.len(), 0); 18 | assert!(id_information.is_empty()); 19 | 20 | // Check ItemIter API 21 | let mut iter = id_information.iter(); 22 | assert!(iter.next().expect("iter empty IPI").is_none()); 23 | 24 | // Check ItemFinder API 25 | let finder = id_information.finder(); 26 | assert_eq!(finder.max_index(), IdIndex(0)); 27 | finder.find(IdIndex(0)).expect_err("find index"); 28 | finder.find(IdIndex(4097)).expect_err("find index"); 29 | } 30 | -------------------------------------------------------------------------------- /tests/modi_symbol_depth.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | 3 | use pdb::{FallibleIterator, Result, PDB}; 4 | 5 | #[test] 6 | fn test_symbol_depth() -> Result<()> { 7 | let file = File::open("fixtures/self/foo.pdb")?; 8 | let mut pdb = PDB::open(file)?; 9 | 10 | let dbi = pdb.debug_information()?; 11 | let mut modules = dbi.modules()?; 12 | 13 | while let Some(module) = modules.next()? { 14 | let module_info = match pdb.module_info(&module)? { 15 | Some(module_info) => module_info, 16 | None => continue, 17 | }; 18 | 19 | let mut depth = 0isize; 20 | let mut symbols = module_info.symbols()?; 21 | while let Some(symbol) = symbols.next()? { 22 | if symbol.starts_scope() { 23 | depth += 1; 24 | } else if symbol.ends_scope() { 25 | depth -= 1; 26 | } 27 | 28 | // The most common case here will be that we forgot to add a raw kind to `starts_scope`. 29 | // PDBs seem to use `S_END` for most symbols with inline sites being the notable 30 | // exception. In case we forgot a start scope symbol, the depth will become negative. 31 | assert!(depth >= 0, "depth must not be negative"); 32 | } 33 | } 34 | 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /tests/omap_address_translation.rs: -------------------------------------------------------------------------------- 1 | use pdb::{FallibleIterator, PdbInternalRva, PdbInternalSectionOffset, Rva}; 2 | 3 | // This test is intended to cover OMAP address translation: 4 | // https://github.com/willglynn/pdb/issues/17 5 | 6 | fn open_file() -> std::fs::File { 7 | let path = "fixtures/symbol_server/3844dbb920174967be7aa4a2c20430fa2-ntkrnlmp.pdb"; 8 | std::fs::File::open(path).expect("missing fixtures, please run scripts/download from the root") 9 | } 10 | 11 | #[test] 12 | fn test_omap_section_zero() { 13 | // https://github.com/willglynn/pdb/issues/87 14 | 15 | let mut pdb = pdb::PDB::open(open_file()).expect("opening pdb"); 16 | 17 | let address = pdb::PdbInternalSectionOffset { 18 | offset: 0, 19 | section: 0x1234, 20 | }; 21 | 22 | let address_map = pdb.address_map().expect("address map"); 23 | 24 | assert_eq!(address.to_rva(&address_map), None); 25 | } 26 | 27 | #[test] 28 | fn test_omap_symbol() { 29 | let mut pdb = pdb::PDB::open(open_file()).expect("opening pdb"); 30 | 31 | let global_symbols = pdb.global_symbols().expect("global_symbols"); 32 | 33 | // find the target symbol 34 | let target_symbol = { 35 | let target_name = pdb::RawString::from("NtWaitForSingleObject"); 36 | let mut iter = global_symbols.iter(); 37 | iter.find(|sym| { 38 | let matches = sym 39 | .parse() 40 | .ok() 41 | .and_then(|d| d.name()) 42 | .map_or(false, |n| n == target_name); 43 | Ok(matches) 44 | }) 45 | .expect("iterate symbols") 46 | .expect("find target symbol") 47 | }; 48 | 49 | // extract the PublicSymbol data 50 | let pubsym = match target_symbol.parse().expect("parse symbol") { 51 | pdb::SymbolData::Public(pubsym) => pubsym, 52 | _ => panic!("expected public symbol"), 53 | }; 54 | 55 | // ensure the symbol has the correct location 56 | assert_eq!( 57 | pubsym.offset, 58 | PdbInternalSectionOffset { 59 | section: 0xc, 60 | offset: 0x0004_aeb0, 61 | } 62 | ); 63 | 64 | // translate the segment offset to an RVA 65 | let address_map = pdb.address_map().expect("address map"); 66 | assert_eq!(pubsym.offset.to_rva(&address_map), Some(Rva(0x0037_68c0))); 67 | assert_eq!( 68 | Rva(0x0037_68c0).to_internal_offset(&address_map), 69 | Some(pubsym.offset) 70 | ); 71 | } 72 | 73 | #[test] 74 | fn test_omap_range() { 75 | let mut pdb = pdb::PDB::open(open_file()).expect("opening pdb"); 76 | let address_map = pdb.address_map().expect("address map"); 77 | 78 | // Range partially covered by OMAPs 79 | // [ 80 | // OMAPRecord { 81 | // source_address: 0x000010aa, 82 | // target_address: 0x00015de6 83 | // }, 84 | // OMAPRecord { 85 | // source_address: 0x000010bd, 86 | // target_address: 0x00000000 87 | // }, 88 | // OMAPRecord { 89 | // source_address: 0x000010c4, 90 | // target_address: 0x0002da00 91 | // }, 92 | // OMAPRecord { 93 | // source_address: 0x000010c8, 94 | // target_address: 0x0002da04 95 | // }, 96 | // ] 97 | let start = PdbInternalRva(0x10b0); 98 | let end = PdbInternalRva(0x10c6); 99 | 100 | assert_eq!( 101 | address_map.rva_ranges(start..end).collect::>(), 102 | vec![ 103 | Rva(0x15dec)..Rva(0x15df9), // 0x10aa - 0x10bd 104 | // 0x10bd - 0x10c4 omitted due to missing target address 105 | Rva(0x2da00)..Rva(0x2da02), // 0x10c4 - 0x10c6 106 | ], 107 | ); 108 | 109 | // Range starting outside OMAPs 110 | // [ 111 | // OMAPRecord { 112 | // source_address: 0x00001000, 113 | // target_address: 0x00000000 114 | // }, 115 | // OMAPRecord { 116 | // source_address: 0x00001008, 117 | // target_address: 0x00015d44 118 | // }, 119 | // ] 120 | let start = PdbInternalRva(0x0); 121 | let end = PdbInternalRva(0x1010); 122 | assert_eq!( 123 | address_map.rva_ranges(start..end).collect::>(), 124 | vec![Rva(0x15d44)..Rva(0x15d4c)], 125 | ); 126 | 127 | // Range ending outside OMAPs 128 | // [ 129 | // OMAPRecord { 130 | // source_address: 0x005e40e0, 131 | // target_address: 0x005e50e0 132 | // }, 133 | // OMAPRecord { 134 | // source_address: 0x005e5000, 135 | // target_address: 0x00000000 136 | // }, 137 | // OMAPRecord { 138 | // source_address: 0x005e70c0, 139 | // target_address: 0x00000000 140 | // } 141 | // ] 142 | let start = PdbInternalRva(0x5e_4fe0); 143 | let end = PdbInternalRva(0x5e_8000); 144 | assert_eq!( 145 | address_map.rva_ranges(start..end).collect::>(), 146 | vec![Rva(0x005e_5fe0)..Rva(0x5e_6000)], 147 | ); 148 | 149 | // Range fully before OMAPs 150 | let start = PdbInternalRva(0x0); 151 | let end = PdbInternalRva(0x100); 152 | assert_eq!( 153 | address_map.rva_ranges(start..end).collect::>(), 154 | vec![], 155 | ); 156 | 157 | // Range fully after OMAPs 158 | let start = PdbInternalRva(0x005e_8000); 159 | let end = PdbInternalRva(0x005e_9000); 160 | assert_eq!( 161 | address_map.rva_ranges(start..end).collect::>(), 162 | vec![], // last record targets 0, thus the range is omitted 163 | ); 164 | } 165 | -------------------------------------------------------------------------------- /tests/pdb_information.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn pdb_info() { 3 | let file = std::fs::File::open("fixtures/self/foo.pdb").expect("opening file"); 4 | 5 | let mut pdb = pdb::PDB::open(file).expect("opening pdb"); 6 | let pdb_info = pdb.pdb_information().expect("pdb information"); 7 | 8 | assert_eq!(pdb_info.age, 2); 9 | assert_eq!( 10 | pdb_info.guid, 11 | "2B3C3FA5-5A2E-44B8-8BBA-C3300FF69F62".parse().unwrap(), 12 | ); 13 | assert_eq!(pdb_info.signature, 0x587B_A621); 14 | } 15 | -------------------------------------------------------------------------------- /tests/pdb_lines.rs: -------------------------------------------------------------------------------- 1 | use pdb::{FallibleIterator, Rva, PDB}; 2 | 3 | #[test] 4 | fn test_module_lines() { 5 | let file = std::fs::File::open("fixtures/self/foo.pdb").expect("opening file"); 6 | let mut pdb = PDB::open(file).expect("parse pdb"); 7 | 8 | let address_map = pdb.address_map().expect("address map"); 9 | let string_table = pdb.string_table().expect("string table"); 10 | 11 | let dbi = pdb.debug_information().expect("dbi"); 12 | let mut modules = dbi.modules().expect("modules"); 13 | let module = modules.next().expect("parse module").expect("no module"); 14 | let module_info = pdb 15 | .module_info(&module) 16 | .expect("parse module info") 17 | .expect("module info"); 18 | 19 | let line_program = module_info.line_program().expect("line program"); 20 | let mut lines = line_program.lines(); 21 | let line_info = lines.next().expect("parse line info").expect("no lines"); 22 | 23 | let rva = line_info.offset.to_rva(&address_map).expect("line rva"); 24 | let file_info = line_program 25 | .get_file_info(line_info.file_index) 26 | .expect("file info"); 27 | let file_name = file_info 28 | .name 29 | .to_string_lossy(&string_table) 30 | .expect("file name"); 31 | 32 | assert_eq!(line_info.line_start, 29); 33 | assert_eq!(line_info.column_start, None); 34 | assert_eq!(rva, Rva(0x64f0)); 35 | assert_eq!(file_name, "c:\\users\\user\\desktop\\self\\foo.cpp"); 36 | } 37 | -------------------------------------------------------------------------------- /tests/symbol_table.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::{Entry, HashMap}; 2 | 3 | use pdb::FallibleIterator; 4 | 5 | fn setup(func: F) 6 | where 7 | F: FnOnce(&pdb::SymbolTable<'_>, bool), 8 | { 9 | let (file, is_fixture) = if let Ok(filename) = std::env::var("PDB_FILE") { 10 | (std::fs::File::open(filename).expect("opening file"), false) 11 | } else { 12 | ( 13 | std::fs::File::open("fixtures/self/foo.pdb").expect("opening file"), 14 | true, 15 | ) 16 | }; 17 | 18 | let mut pdb = pdb::PDB::open(file).expect("opening pdb"); 19 | let symbol_table = pdb.global_symbols().expect("global symbols"); 20 | 21 | func(&symbol_table, is_fixture); 22 | } 23 | 24 | #[test] 25 | fn count_symbols() { 26 | setup(|global_symbols, is_fixture| { 27 | let mut map: HashMap = HashMap::new(); 28 | 29 | // walk the symbol table 30 | let mut iter = global_symbols.iter(); 31 | while let Some(sym) = iter.next().expect("next symbol") { 32 | let kind = sym.raw_kind(); 33 | let entry = map.entry(kind).or_insert(0); 34 | 35 | if *entry == 0 && is_fixture { 36 | // first symbol of this kind seen 37 | // emit a unit test 38 | println!("#[test]"); 39 | println!("fn kind_{:04x}() {{", sym.raw_kind()); 40 | println!(" let buf = &{:?};", sym.raw_bytes()); 41 | println!(" let (symbol, data, name) = parse(buf).expect(\"parse\");"); 42 | println!( 43 | " assert_eq!(symbol.raw_kind(), 0x{:04x});", 44 | sym.raw_kind() 45 | ); 46 | println!( 47 | " assert_eq!(data, SymbolData::{:?});", 48 | sym.parse().expect("parse") 49 | ); 50 | println!("}}"); 51 | println!(); 52 | } 53 | 54 | *entry += 1; 55 | } 56 | 57 | println!("symbol counts by kind:"); 58 | for (kind, count) in &map { 59 | println!(" - kind: 0x{:04x}, count: {}", kind, count); 60 | } 61 | 62 | assert!(*map.get(&0x1107).expect("0x1107") >= 500); 63 | assert!(*map.get(&0x1108).expect("0x1108") >= 400); 64 | assert!(*map.get(&0x110c).expect("0x110c") >= 90); 65 | assert!(*map.get(&0x110d).expect("0x110d") >= 120); 66 | assert!(*map.get(&0x110e).expect("0x110e") >= 3000); 67 | assert!(*map.get(&0x110e).expect("0x110e") >= 3000); 68 | assert!(*map.get(&0x1125).expect("0x1125") >= 2000); 69 | assert!(*map.get(&0x1127).expect("0x1127") >= 500); 70 | }) 71 | } 72 | 73 | #[test] 74 | fn find_symbols() { 75 | setup(|global_symbols, is_fixture| { 76 | // can't do much if we don't know which PDB we're using 77 | if !is_fixture { 78 | return; 79 | } 80 | 81 | let mut map: HashMap<&[u8], Option>> = HashMap::new(); 82 | 83 | // look for: 84 | // main(), defined in the program 85 | map.insert(b"main", None); 86 | 87 | // malloc(), defined in libc 88 | map.insert(b"memcpy", None); 89 | 90 | // HeapAlloc(), defined... somewhere 91 | map.insert(b"HeapAlloc", None); 92 | 93 | // Baz::static_f_public(), except MSVC-mangled 94 | map.insert(b"?static_f_public@Baz@@SAXXZ", None); 95 | 96 | // walk the symbol table 97 | let mut iter = global_symbols.iter(); 98 | while let Some(sym) = iter.next().expect("next symbol") { 99 | // ensure we can parse all the symbols, even though we only want a few 100 | let data = sym.parse().expect("symbol parsing"); 101 | 102 | // get symbol name 103 | let name = data.name().unwrap_or_default(); 104 | 105 | if let Entry::Occupied(mut e) = map.entry(name.as_bytes()) { 106 | // this is a symbol we wanted to find 107 | // store our data 108 | e.insert(Some(data)); 109 | } 110 | } 111 | 112 | for (key, value) in map { 113 | match value { 114 | Some(data) => { 115 | println!("found {} => {:?}", String::from_utf8_lossy(key), data); 116 | } 117 | None => { 118 | panic!("couldn't find {}", String::from_utf8_lossy(key)); 119 | } 120 | } 121 | } 122 | }) 123 | } 124 | -------------------------------------------------------------------------------- /tests/type_information.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use pdb::FallibleIterator; 4 | 5 | fn setup(func: F) 6 | where 7 | F: FnOnce(&pdb::TypeInformation<'_>), 8 | { 9 | let file = if let Ok(filename) = std::env::var("PDB_FILE") { 10 | std::fs::File::open(filename) 11 | } else { 12 | std::fs::File::open("fixtures/self/foo.pdb") 13 | } 14 | .expect("opening file"); 15 | 16 | let mut pdb = pdb::PDB::open(file).expect("opening pdb"); 17 | let type_information = pdb.type_information().expect("type information"); 18 | 19 | func(&type_information); 20 | } 21 | 22 | #[test] 23 | fn iteration() { 24 | setup(|type_information| { 25 | let len = type_information.len(); 26 | 27 | let mut count: usize = 0; 28 | let mut last_index = pdb::TypeIndex(4095); 29 | let mut iter = type_information.iter(); 30 | while let Some(typ) = iter.next().expect("next type") { 31 | assert_eq!(typ.index().0, last_index.0 + 1); 32 | last_index = typ.index(); 33 | count += 1; 34 | } 35 | 36 | assert_eq!(len, count); 37 | }); 38 | } 39 | 40 | #[test] 41 | fn type_finder() { 42 | setup(|type_information| { 43 | let mut type_finder = type_information.finder(); 44 | let mut map: HashMap> = HashMap::new(); 45 | 46 | assert_eq!(type_finder.max_index().0 >> 3, 4096 >> 3); 47 | 48 | // iterate over all the types 49 | let mut iter = type_information.iter(); 50 | while let Some(typ) = iter.next().expect("next type") { 51 | assert_eq!(type_finder.max_index().0 >> 3, typ.index().0 >> 3); 52 | 53 | // update the type finder 54 | type_finder.update(&iter); 55 | 56 | // record this type in our map 57 | map.insert(typ.index(), typ); 58 | } 59 | 60 | // iterate over the map -- which is randomized -- making sure the type finder finds identical types 61 | for (index, typ) in map.iter() { 62 | let found = type_finder.find(*index).expect("find"); 63 | assert_eq!(*typ, found); 64 | } 65 | }) 66 | } 67 | 68 | #[test] 69 | fn find_classes() { 70 | setup(|type_information| { 71 | let mut type_finder = type_information.finder(); 72 | 73 | // iterate over all the types 74 | let mut iter = type_information.iter(); 75 | while let Some(typ) = iter.next().expect("next type") { 76 | // update the type finder 77 | type_finder.update(&iter); 78 | 79 | // parse the type record 80 | match typ.parse() { 81 | Ok(pdb::TypeData::Class(pdb::ClassType { 82 | name, 83 | fields: Some(fields), 84 | .. 85 | })) => { 86 | // this Type describes a class-like type with fields 87 | println!("class {} (type {}):", name, typ.index()); 88 | 89 | // fields is presently a TypeIndex 90 | // find and parse the list of fields 91 | match type_finder.find(fields).expect("find fields").parse() { 92 | Ok(pdb::TypeData::FieldList(list)) => { 93 | for field in list.fields { 94 | println!(" - {:?}", field); 95 | } 96 | 97 | if let Some(c) = list.continuation { 98 | println!("TODO: follow to type {}", c); 99 | } 100 | } 101 | Ok(value) => { 102 | panic!("expected a field list, got {:?}", value); 103 | } 104 | Err(e) => { 105 | println!("field parse error: {}", e); 106 | } 107 | } 108 | } 109 | Ok(pdb::TypeData::Enumeration(data)) => { 110 | println!("enum {} (type {}):", data.name, data.fields); 111 | 112 | // fields is presently a TypeIndex 113 | match type_finder.find(data.fields).expect("find fields").parse() { 114 | Ok(pdb::TypeData::FieldList(list)) => { 115 | for field in list.fields { 116 | println!(" - {:?}", field); 117 | } 118 | 119 | if let Some(c) = list.continuation { 120 | println!("TODO: follow to type {}", c); 121 | } 122 | } 123 | Ok(value) => { 124 | panic!("expected a field list, got {:?}", value); 125 | } 126 | Err(e) => { 127 | println!("field parse error: {}", e); 128 | } 129 | } 130 | } 131 | Ok(pdb::TypeData::FieldList(_)) => { 132 | // ignore, since we find these by class 133 | } 134 | Ok(_) => { 135 | //println!("type: {:?}", data); 136 | } 137 | Err(pdb::Error::UnimplementedTypeKind(kind)) => { 138 | println!("unimplemented: 0x{:04x}", kind); 139 | // TODO: parse everything 140 | // ignore for now 141 | } 142 | Err(e) => { 143 | // other parse error 144 | println!( 145 | "other parse error on type {} (raw type {:04x}): {}", 146 | typ.index(), 147 | typ.raw_kind(), 148 | e 149 | ); 150 | panic!("dying due to parse error"); 151 | } 152 | } 153 | } 154 | 155 | // hooah! 156 | }) 157 | } 158 | 159 | /* 160 | #[bench] 161 | fn bench_type_finder(b: &mut test::Bencher) { 162 | setup(|type_information| { 163 | let mut type_finder = type_information.finder(); 164 | 165 | assert_eq!(type_finder.max_index() >> 3, 4096 >> 3); 166 | 167 | // iterate over all the types 168 | let mut iter = type_information.iter(); 169 | while let Some(typ) = iter.next().expect("next type") { 170 | assert_eq!(type_finder.max_index() >> 3, typ.index() >> 3); 171 | type_finder.update(&iter); 172 | } 173 | 174 | let mut rng = rand::thread_rng(); 175 | let count: pdb::TypeIndex = type_information.len() as pdb::TypeIndex; 176 | let base: pdb::TypeIndex = 4096; 177 | 178 | // time how long it takes to build a map 179 | b.iter(|| { 180 | let lucky = rng.gen_range(base, base + count); 181 | let found = type_finder.find(lucky).expect("find"); 182 | test::black_box(&found); 183 | }); 184 | }) 185 | } 186 | */ 187 | 188 | /* 189 | #[test] 190 | fn type_length_histogram() { 191 | setup(|type_information| { 192 | let mut lens: Vec = Vec::new(); 193 | lens.resize(1025, 0); 194 | 195 | // iterate over all the types 196 | let mut iter = type_information.iter(); 197 | while let Some(typ) = iter.next().expect("next type") { 198 | let mut len = typ.len() + 2; 199 | if len > 1024 { 200 | len = 1024; 201 | } 202 | lens[len] += 1; 203 | } 204 | 205 | for (len, count) in lens.as_slice().iter().enumerate() { 206 | println!("{}\t{}", len, count); 207 | } 208 | 209 | panic!(); 210 | }) 211 | } 212 | */ 213 | --------------------------------------------------------------------------------