pdb-addr2line-0.11.1/.cargo_vcs_info.json0000644000000001360000000000100135230ustar { "git": { "sha1": "1fd6b791aaa3e43f61fa0f54da02e892b53e62da" }, "path_in_vcs": "" }pdb-addr2line-0.11.1/.gitignore000064400000000000000000000000351046102023000143010ustar 00000000000000/target Cargo.lock .DS_Store pdb-addr2line-0.11.1/Cargo.lock0000644000000250550000000000100115050ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "anstream" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", "windows-sys", ] [[package]] name = "binary-merge" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597bb81c80a54b6a4381b23faba8d7774b144c94cbd1d6fe3f1329bd776554ab" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "clap" version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_lex" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "elsa" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714f766f3556b44e7e4776ad133fcc3445a489517c25c704ace411bb14790194" dependencies = [ "stable_deref_trait", ] [[package]] name = "fallible-iterator" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "getopts" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ "unicode-width", ] [[package]] name = "inplace-vec-builder" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf64c2edc8226891a71f127587a2861b132d2b942310843814d5001d99a1d307" dependencies = [ "smallvec", ] [[package]] name = "libc" version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "maybe-owned" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" [[package]] name = "memmap2" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375" dependencies = [ "libc", ] [[package]] name = "msvc-demangler" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfb67c6dd0fa9b00619c41c5700b6f92d5f418be49b45ddb9970fbd4569df3c8" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "pdb-addr2line" version = "0.11.1" dependencies = [ "bitflags 2.4.1", "clap", "elsa", "getopts", "maybe-owned", "memmap2", "msvc-demangler", "pdb2", "range-collections", "thiserror", ] [[package]] name = "pdb2" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00e30e131bcab0d41a2e471cf777ea9b1402f2a0764bcf1780251eab1b0d175d" dependencies = [ "fallible-iterator", "scroll", "uuid", ] [[package]] name = "proc-macro2" version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "range-collections" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca9edd21e2db51000ac63eccddabba622f826e631a60be7bade9bd6a76b69537" dependencies = [ "binary-merge", "inplace-vec-builder", "ref-cast", "smallvec", ] [[package]] name = "ref-cast" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53313ec9f12686aeeffb43462c3ac77aa25f590a5f630eb2cde0de59417b29c7" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2566c4bf6845f2c2e83b27043c3f5dfcd5ba8f2937d6c00dc009bfb51a079dc4" dependencies = [ "proc-macro2", "quote", "syn 2.0.41", ] [[package]] name = "scroll" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" [[package]] name = "smallvec" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", "syn 1.0.98", ] [[package]] name = "unicode-ident" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" pdb-addr2line-0.11.1/Cargo.toml0000644000000033420000000000100115230ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "pdb-addr2line" version = "0.11.1" authors = [ "Jeff Muizelaar ", "Markus Stange ", ] build = false exclude = [ "/.github", "/tests", ] autobins = false autoexamples = false autotests = false autobenches = false description = "Symbolicate addresses from PDBs, like addr2line." homepage = "https://github.com/mstange/pdb-addr2line" documentation = "https://docs.rs/pdb-addr2line" readme = "Readme.md" keywords = [ "PDB", "debug", "addr2line", "symbolicate", "windows", ] categories = ["development-tools::debugging"] license = "MIT/Apache-2.0" repository = "https://github.com/mstange/pdb-addr2line" [lib] name = "pdb_addr2line" path = "src/lib.rs" [[example]] name = "pdb-addr2line" path = "examples/pdb-addr2line.rs" [dependencies.bitflags] version = "2.0" [dependencies.elsa] version = "1.9.0" [dependencies.maybe-owned] version = "0.3.4" [dependencies.pdb] version = "0.9" package = "pdb2" [dependencies.range-collections] version = "0.4.5" [dependencies.thiserror] version = "1.0" [dev-dependencies.clap] version = "4.4.6" [dev-dependencies.getopts] version = "0.2.21" [dev-dependencies.memmap2] version = "0.9.0" [dev-dependencies.msvc-demangler] version = "0.9.0" pdb-addr2line-0.11.1/Cargo.toml.orig000064400000000000000000000015461046102023000152100ustar 00000000000000[package] authors = ["Jeff Muizelaar ", "Markus Stange "] categories = ["development-tools::debugging"] description = "Symbolicate addresses from PDBs, like addr2line." documentation = "https://docs.rs/pdb-addr2line" edition = "2018" homepage = "https://github.com/mstange/pdb-addr2line" keywords = ["PDB", "debug", "addr2line", "symbolicate", "windows"] license = "MIT/Apache-2.0" name = "pdb-addr2line" readme = "Readme.md" repository = "https://github.com/mstange/pdb-addr2line" version = "0.11.1" exclude = ["/.github", "/tests"] [dependencies] bitflags = "2.0" maybe-owned = "0.3.4" pdb = { package = "pdb2", version = "0.9" } range-collections = "0.4.5" thiserror = "1.0" elsa = "1.9.0" [dev-dependencies] clap = "4.4.6" getopts = "0.2.21" memmap2 = "0.9.0" msvc-demangler = "0.9.0" [[example]] name = "pdb-addr2line" pdb-addr2line-0.11.1/LICENSE-APACHE000064400000000000000000000251371046102023000142470ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. pdb-addr2line-0.11.1/LICENSE-MIT000064400000000000000000000020701046102023000137460ustar 00000000000000Copyright (c) 2018 Markus Stange Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pdb-addr2line-0.11.1/Readme.md000064400000000000000000000111351046102023000140330ustar 00000000000000[![crates.io page](https://img.shields.io/crates/v/pdb-addr2line.svg)](https://crates.io/crates/pdb-addr2line) [![docs.rs page](https://docs.rs/pdb-addr2line/badge.svg)](https://docs.rs/pdb-addr2line/) # pdb-addr2line Resolve addresses to function names, and to file name and line number information, with the help of a PDB file. Inline stacks are supported. The API of this crate is intended to be similar to the API of the [`addr2line` crate](https://docs.rs/addr2line/); the two `Context` APIs have comparable functionality. This crate is for PDB files whereas `addr2line` is for DWARF data (which is used in ELF and mach-o binaries, for example). This crate also has a `TypeFormatter` API which can be used to get function signature strings independently from a `Context`. To create a `Context`, use `ContextPdbData`. The implementation makes use of the excellent [`pdb` crate](https://crates.io/crates/pdb). ## Example ```rust use pdb_addr2line::pdb; // (this is a re-export of the pdb crate) fn look_up_addresses<'s, S: pdb::Source<'s> + 's>(stream: S, addresses: &[u32]) -> std::result::Result<(), pdb_addr2line::Error> { let pdb = pdb::PDB::open(stream)?; let context_data = pdb_addr2line::ContextPdbData::try_from_pdb(pdb)?; let context = context_data.make_context()?; for address in addresses { if let Some(procedure_frames) = context.find_frames(*address)? { eprintln!("0x{:x} - {} frames:", address, procedure_frames.frames.len()); for frame in procedure_frames.frames { let line_str = frame.line.map(|l| format!("{}", l)); eprintln!( " {} at {}:{}", frame.function.as_deref().unwrap_or(""), frame.file.as_deref().unwrap_or("??"), line_str.as_deref().unwrap_or("??"), ) } } else { eprintln!("{:x} - no frames found", address); } } Ok(()) } ``` # Command-line usage This repository also contains a CLI executable modelled after addr2line. You can install it using `cargo install`: ``` cargo install --examples pdb-addr2line ``` Here are some example uses: ``` $ curl -o dcomp.pdb -L "https://msdl.microsoft.com/download/symbols/dcomp.pdb/648B8DD0780A4E22FA7FA89B84633C231/dcomp.pdb" $ pdb-addr2line --exe dcomp.pdb -fC 0x59aa0 0x52340 0x13498 Windows::UI::Composition::Compositor::Api::CreateScalarKeyFrameAnimation(Windows::UI::Composition::IScalarKeyFrameAnimation**) ??:? std::map, std::allocator > >::_Try_emplace(unsigned int const&) ??:? DirectComposition::CDxDevice::RemoveGuardRect(ID3D11Texture2D*) ??:? ``` ``` $ curl -o mozglue.pdb -L "https://github.com/mstange/profiler-get-symbols/raw/master/fixtures/win64-ci/mozglue.pdb" $ pdb-addr2line -e mozglue.pdb -psfi 0x3b9fb mozilla::JSONWriter::StartCollection(char const*, char const*, mozilla::JSONWriter::CollectionStyle) at JSONWriter.h:318 (inlined by) mozilla::JSONWriter::StartArrayProperty(char const*, mozilla::JSONWriter::CollectionStyle) at JSONWriter.h:417 (inlined by) mozilla::JSONWriter::StartArrayElement(mozilla::JSONWriter::CollectionStyle) at JSONWriter.h:422 (inlined by) mozilla::baseprofiler::AutoArraySchemaWriter::AutoArraySchemaWriter(mozilla::baseprofiler::SpliceableJSONWriter&, mozilla::baseprofiler::UniqueJSONStrings&) at ProfileBufferEntry.cpp:141 (inlined by) mozilla::baseprofiler::WriteSample(mozilla::baseprofiler::SpliceableJSONWriter&, mozilla::baseprofiler::UniqueJSONStrings&, mozilla::baseprofiler::ProfileSample const&) at ProfileBufferEntry.cpp:361 (inlined by) mozilla::baseprofiler::ProfileBuffer::StreamSamplesToJSON::::operator()(mozilla::ProfileChunkedBuffer::Reader*) const at ProfileBufferEntry.cpp:809 ``` # Performance `pdb-addr2line` optimizes for speed over memory by caching parsed information. The debug information about inlines, files and line numbers is parsed lazily where possible. ## License Licensed under either of * Apache License, Version 2.0 ([`LICENSE-APACHE`](./LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([`LICENSE-MIT`](./LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. pdb-addr2line-0.11.1/examples/pdb-addr2line.rs000064400000000000000000000215471046102023000171170ustar 00000000000000use std::borrow::Cow; use std::fmt; use std::fs::File; use std::io::{BufRead, Lines, StdinLock, Write}; use std::path::{Path, PathBuf}; use clap::parser::ValuesRef; use clap::{value_parser, Arg, ArgAction, Command}; use msvc_demangler::DemangleFlags; use pdb_addr2line::pdb; fn parse_uint_from_hex_string(string: &str) -> u32 { if string.len() > 2 && string.starts_with("0x") { u32::from_str_radix(&string[2..], 16).expect("Failed to parse address") } else { u32::from_str_radix(string, 16).expect("Failed to parse address") } } enum Addrs<'a> { Args(ValuesRef<'a, String>), Stdin(Lines>), } impl<'a> Iterator for Addrs<'a> { type Item = u32; fn next(&mut self) -> Option { let text = match *self { Addrs::Args(ref mut vals) => vals.next().map(Cow::from), Addrs::Stdin(ref mut lines) => lines.next().map(Result::unwrap).map(Cow::from), }; text.as_ref() .map(Cow::as_ref) .map(parse_uint_from_hex_string) } } fn print_loc(file: &Option>, line: Option, basenames: bool, llvm: bool) { if let Some(file) = file { let file: &str = file; let path = if basenames { Path::new(Path::new(file).file_name().unwrap()) } else { Path::new(file) }; print!("{}:", path.display()); if llvm { print!("{}:0", line.unwrap_or(0)); } else if let Some(line) = line { print!("{}", line); } else { print!("?"); } println!(); } else if llvm { println!("??:0:0"); } else { println!("??:?"); } } fn print_function(name: &str, demangle: bool) { if demangle && name.starts_with('?') { let flags = DemangleFlags::NO_ACCESS_SPECIFIERS | DemangleFlags::NO_FUNCTION_RETURNS | DemangleFlags::NO_MEMBER_TYPE | DemangleFlags::NO_MS_KEYWORDS | DemangleFlags::NO_THISTYPE | DemangleFlags::NO_CLASS_TYPE | DemangleFlags::SPACE_AFTER_COMMA | DemangleFlags::HUG_TYPE; print!( "{}", msvc_demangler::demangle(name, flags) .as_deref() .unwrap_or(name) ); } else { print!("{}", name); } } fn main() { let matches = Command::new("pdb-addr2line") .version("0.1") .about("A fast addr2line port for PDBs") .args(&[ Arg::new("exe") .short('e') .long("exe") .value_name("filename") .value_parser(value_parser!(PathBuf)) .help( "Specify the name of the executable for which addresses should be translated.", ) .required(true), Arg::new("sup") .long("sup") .value_name("filename") .help("Path to supplementary object file."), Arg::new("functions") .action(ArgAction::SetTrue) .short('f') .long("functions") .help("Display function names as well as file and line number information."), Arg::new("pretty") .action(ArgAction::SetTrue) .short('p') .long("pretty-print") .help( "Make the output more human friendly: each location are printed on \ one line.", ), Arg::new("inlines") .action(ArgAction::SetTrue) .short('i') .long("inlines") .help( "If the address belongs to a function that was inlined, the source \ information for all enclosing scopes back to the first non-inlined \ function will also be printed.", ), Arg::new("addresses") .action(ArgAction::SetTrue) .short('a') .long("addresses") .help( "Display the address before the function name, file and line \ number information.", ), Arg::new("basenames") .action(ArgAction::SetTrue) .short('s') .long("basenames") .help("Display only the base of each file name."), Arg::new("demangle") .action(ArgAction::SetTrue) .short('C') .long("demangle") .help( "Demangle function names. \ Specifying a specific demangling style (like GNU addr2line) \ is not supported. (TODO)", ), Arg::new("llvm") .action(ArgAction::SetTrue) .long("llvm") .help("Display output in the same format as llvm-symbolizer."), Arg::new("addrs") .action(ArgAction::Append) .help("Addresses to use instead of reading from stdin."), ]) .get_matches(); let do_functions = matches.get_flag("functions"); let do_inlines = matches.get_flag("inlines"); let pretty = matches.get_flag("pretty"); let print_addrs = matches.get_flag("addresses"); let basenames = matches.get_flag("basenames"); let demangle = matches.get_flag("demangle"); let llvm = matches.get_flag("llvm"); let path = matches.get_one::("exe").unwrap(); let file = File::open(path).unwrap(); let map = unsafe { memmap2::MmapOptions::new().map(&file).unwrap() }; let source = Source(map); let pdb = pdb::PDB::open(source).unwrap(); let context_data = pdb_addr2line::ContextPdbData::try_from_pdb(pdb).unwrap(); let ctx = context_data.make_context().unwrap(); let stdin = std::io::stdin(); let addrs = matches .get_many("addrs") .map(Addrs::Args) .unwrap_or_else(|| Addrs::Stdin(stdin.lock().lines())); for probe in addrs { if print_addrs { if llvm { print!("0x{:x}", probe); } else { print!("0x{:016x}", probe); } if pretty { print!(": "); } else { println!(); } } if do_functions || do_inlines { let mut printed_anything = false; if let Some(frames) = ctx.find_frames(probe).unwrap() { for (i, frame) in frames.frames.iter().enumerate() { if pretty && i != 0 { print!(" (inlined by) "); } if do_functions { if let Some(func) = &frame.function { print_function(func, demangle); } else { print!("??"); } if pretty { print!(" at "); } else { println!(); } } print_loc(&frame.file, frame.line, basenames, llvm); printed_anything = true; if !do_inlines { break; } } } if !printed_anything { if do_functions { print!("??"); if pretty { print!(" at "); } else { println!(); } } if llvm { println!("??:0:0"); } else { println!("??:?"); } } } else { let frames = ctx.find_frames(probe).unwrap().unwrap(); let frame = &frames.frames[0]; print_loc(&frame.file, frame.line, basenames, llvm); } if llvm { println!(); } std::io::stdout().flush().unwrap(); } } #[derive(Debug)] struct Source(memmap2::Mmap); #[derive(Clone)] struct ReadView { bytes: Vec, } impl fmt::Debug for ReadView { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ReadView({} bytes)", self.bytes.len()) } } impl pdb::SourceView<'_> for ReadView { fn as_slice(&self) -> &[u8] { self.bytes.as_slice() } } impl<'s> pdb::Source<'s> for Source { fn view( &mut self, slices: &[pdb::SourceSlice], ) -> Result + Send + Sync>, std::io::Error> { let len = slices.iter().fold(0, |acc, s| acc + s.size); let mut bytes = Vec::with_capacity(len); for slice in slices { bytes.extend_from_slice(&self.0[slice.offset as usize..][..slice.size]); } Ok(Box::new(ReadView { bytes })) } } pdb-addr2line-0.11.1/src/constants.rs000064400000000000000000000016771046102023000154770ustar 00000000000000// Constants copied from the pdb crate // see https://github.com/willglynn/pdb/issues/120 pub const S_PUB32: u16 = 0x110e; // a public symbol (CV internal reserved) pub const S_PUB32_ST: u16 = 0x1009; // a public symbol (CV internal reserved) pub const S_LPROC32_ST: u16 = 0x100a; // Local procedure start pub const S_GPROC32_ST: u16 = 0x100b; // Global procedure start pub const S_LPROC32: u16 = 0x110f; // Local procedure start pub const S_GPROC32: u16 = 0x1110; // Global procedure start pub const S_LPROC32_ID: u16 = 0x1146; pub const S_GPROC32_ID: u16 = 0x1147; pub const S_LPROC32_DPC: u16 = 0x1155; // DPC local procedure start pub const S_LPROC32_DPC_ID: u16 = 0x1156; pub const S_THUNK32_ST: u16 = 0x0206; // Thunk Start pub const S_THUNK32: u16 = 0x1102; // Thunk Start pub const S_SEPCODE: u16 = 0x1132; pub const S_INLINESITE: u16 = 0x114d; // inlined function callsite. pub const S_INLINESITE2: u16 = 0x115d; // extended inline site information pdb-addr2line-0.11.1/src/error.rs000064400000000000000000000040531046102023000146030ustar 00000000000000#[derive(thiserror::Error, Debug)] #[non_exhaustive] pub enum Error { #[error("Formatting error: {0}")] FormatError(#[source] std::fmt::Error), #[error("PDB error: {0}")] PdbError(#[source] pdb::Error), #[error("Unexpected type for argument list")] ArgumentTypeNotArgumentList, #[error("Id of type Function doesn't have type of Procedure")] FunctionIdIsNotProcedureType, #[error("Id of type MemberFunction doesn't have type of MemberFunction")] MemberFunctionIdIsNotMemberFunctionType, #[error("There are consecutive section contributions for module {0} and section {1} which are not ordered by offset")] UnorderedSectionContributions(usize, u16), #[error("Overlapping section contributions in section {0} from modules {1} and {2}")] OverlappingSectionContributions(u16, usize, usize), #[error("Getting the procedure lines was unsuccessful")] ProcedureLinesUnsuccessful, #[error("Getting the procedure inline ranges was unsuccessful")] ProcedureInlineRangesUnsuccessful, #[error("Getting the extended module info was unsuccessful")] ExtendedModuleInfoUnsuccessful, #[error("Could not resolve cross-module reference due to missing string table")] CantResolveCrossModuleRefWithoutStringTable, #[error("Getting the module imports was unsuccessful")] ModuleImportsUnsuccessful, #[error("Could not find the module with name {0}")] ModuleNameNotFound(String), #[error("Getting the module exports was unsuccessful")] ModuleExportsUnsuccessful, #[error("The local index {0} was not found in the module exports")] LocalIndexNotInExports(u32), #[error("The module index {0} was out-of-range.")] OutOfRangeModuleIndex(usize), #[error("Could not get the ModuleInfo for module index {0}")] ModuleInfoNotFound(usize), } impl From for Error { fn from(err: pdb::Error) -> Self { Self::PdbError(err) } } impl From for Error { fn from(err: std::fmt::Error) -> Self { Self::FormatError(err) } } pdb-addr2line-0.11.1/src/lib.rs000064400000000000000000001515241046102023000142260ustar 00000000000000//! Resolve addresses to function names, and to file name and line number //! information, with the help of a PDB file. Inline stacks are supported. //! //! The API of this crate is intended to be similar to the API of the //! [`addr2line` crate](https://docs.rs/addr2line/); the two [`Context`] APIs //! have comparable functionality. This crate is for PDB files whereas `addr2line` //! is for DWARF data (which is used in ELF and mach-o binaries, for example). //! //! This crate also has a [`TypeFormatter`] API which can be used to get function signature //! strings independently from a [`Context`]. //! //! To create a [`Context`], use [`ContextPdbData`]. //! //! # Example //! //! ``` //! use pdb_addr2line::pdb; // (this is a re-export of the pdb crate) //! //! fn look_up_addresses<'s, S: pdb::Source<'s> + Send + 's>(stream: S, addresses: &[u32]) -> std::result::Result<(), pdb_addr2line::Error> { //! let pdb = pdb::PDB::open(stream)?; //! let context_data = pdb_addr2line::ContextPdbData::try_from_pdb(pdb)?; //! let context = context_data.make_context()?; //! //! for address in addresses { //! if let Some(procedure_frames) = context.find_frames(*address)? { //! eprintln!("0x{:x} - {} frames:", address, procedure_frames.frames.len()); //! for frame in procedure_frames.frames { //! let line_str = frame.line.map(|l| format!("{}", l)); //! eprintln!( //! " {} at {}:{}", //! frame.function.as_deref().unwrap_or(""), //! frame.file.as_deref().unwrap_or("??"), //! line_str.as_deref().unwrap_or("??"), //! ) //! } //! } else { //! eprintln!("{:x} - no frames found", address); //! } //! } //! Ok(()) //! } //! ``` pub use maybe_owned; pub use pdb; mod constants; mod error; mod type_formatter; pub use error::Error; pub use type_formatter::*; use constants::*; use elsa::sync::FrozenMap; use maybe_owned::{MaybeOwned, MaybeOwnedMut}; use pdb::{ AddressMap, DebugInformation, FallibleIterator, FileIndex, IdIndex, IdInformation, ImageSectionHeader, InlineSiteSymbol, Inlinee, LineProgram, Module, ModuleInfo, PdbInternalSectionOffset, PublicSymbol, RawString, Rva, Source, StringTable, SymbolData, SymbolIndex, SymbolIter, SymbolTable, TypeIndex, TypeInformation, PDB, }; use range_collections::range_set::RangeSetRange; use range_collections::{RangeSet, RangeSet2}; use std::cmp::Ordering; use std::collections::HashMap; use std::fmt::LowerHex; use std::mem; use std::sync::{Arc, Mutex}; use std::{borrow::Cow, cell::RefCell, collections::BTreeMap}; type Result = std::result::Result; /// Allows to easily create a [`Context`] directly from a [`pdb::PDB`]. /// /// ``` /// # fn wrapper<'s, S: pdb::Source<'s> + Send + 's>(stream: S) -> std::result::Result<(), pdb_addr2line::Error> { /// let pdb = pdb::PDB::open(stream)?; /// let context_data = pdb_addr2line::ContextPdbData::try_from_pdb(pdb)?; /// let context = context_data.make_context()?; /// # Ok(()) /// # } /// ``` /// /// Implementation note: /// It would be nice if a [`Context`] could be created from a [`PDB`] directly, without /// going through an intermediate [`ContextPdbData`] object. However, there doesn't /// seem to be an easy way to do this, due to certain lifetime dependencies: The /// [`Context`] object wants to store certain objects inside itself (mostly for caching) /// which have a lifetime dependency on [`pdb::ModuleInfo`], so the [`ModuleInfo`] has to be /// owned outside of the [`Context`]. So the [`ContextPdbData`] object acts as that external /// [`ModuleInfo`] owner. pub struct ContextPdbData<'p, 's, S: Source<'s> + Send + 's> { pdb: Mutex>>, /// ModuleInfo objects are stored on this object (outside Context) so that the /// Context can internally store objects which have a lifetime dependency on /// ModuleInfo, such as Inlinees, LinePrograms, and RawStrings from modules. module_infos: FrozenMap>>, address_map: AddressMap<'s>, string_table: Option>, global_symbols: SymbolTable<'s>, debug_info: DebugInformation<'s>, type_info: TypeInformation<'s>, id_info: IdInformation<'s>, } // Assert that `ContextPdbData` is Send. const _: fn() = || { fn assert() {} // Use `File` as `S` since it implements `Source` and is `Send`. assert::>(); }; impl<'p, 's, S: Source<'s> + Send + 's> ContextPdbData<'p, 's, S> { /// Create a [`ContextPdbData`] from a [`PDB`](pdb::PDB). This parses many of the PDB /// streams and stores them in the [`ContextPdbData`]. /// This creator function takes ownership of the pdb object and never gives it back. pub fn try_from_pdb(pdb: PDB<'s, S>) -> Result { Self::try_from_maybe_owned(MaybeOwnedMut::Owned(pdb)) } /// Create a [`ContextPdbData`] from a [`PDB`](pdb::PDB). This parses many of the PDB /// streams and stores them in the [`ContextPdbData`]. /// This creator function takes an exclusive reference to the pdb object, for consumers /// that want to keep using the pdb object once the `ContextPdbData` object is dropped. pub fn try_from_pdb_ref(pdb: &'p mut PDB<'s, S>) -> Result { Self::try_from_maybe_owned(MaybeOwnedMut::Borrowed(pdb)) } fn try_from_maybe_owned(mut pdb: MaybeOwnedMut<'p, PDB<'s, S>>) -> Result { let global_symbols = pdb.global_symbols()?; let debug_info = pdb.debug_information()?; let type_info = pdb.type_information()?; let id_info = pdb.id_information()?; let address_map = pdb.address_map()?; let string_table = pdb.string_table().ok(); Ok(Self { pdb: Mutex::new(pdb), module_infos: FrozenMap::new(), global_symbols, debug_info, type_info, id_info, address_map, string_table, }) } /// Create a [`TypeFormatter`]. This uses the default [`TypeFormatter`] settings. pub fn make_type_formatter(&self) -> Result> { self.make_type_formatter_with_flags(Default::default()) } /// Create a [`TypeFormatter`], using the specified [`TypeFormatter`] flags. pub fn make_type_formatter_with_flags( &self, flags: TypeFormatterFlags, ) -> Result> { // Get the list of all modules. This only reads the list, not the actual module // info. To get the module info, you need to call pdb.module_info(&module), and // that's when the actual module stream is read. We use the list of modules so // that we can call pdb.module_info with the right module, which we look up based // on its module_index. let modules = self.debug_info.modules()?.collect::>()?; Ok(TypeFormatter::new_from_parts( self, modules, &self.debug_info, &self.type_info, &self.id_info, self.string_table.as_ref(), flags, )?) } /// Create a [`Context`]. This uses the default [`TypeFormatter`] settings. pub fn make_context(&self) -> Result> { self.make_context_with_formatter_flags(Default::default()) } /// Create a [`Context`], using the specified [`TypeFormatterFlags`]. pub fn make_context_with_formatter_flags( &self, flags: TypeFormatterFlags, ) -> Result> { let type_formatter = self.make_type_formatter_with_flags(flags)?; let sections = self.pdb.lock().unwrap().sections()?; Context::new_from_parts( self, sections.as_deref().unwrap_or(&[]), &self.address_map, &self.global_symbols, self.string_table.as_ref(), &self.debug_info, MaybeOwned::Owned(type_formatter), ) } } impl<'p, 's, S: Source<'s> + Send + 's> ModuleProvider<'s> for ContextPdbData<'p, 's, S> { fn get_module_info( &self, module_index: usize, module: &Module, ) -> std::result::Result>, pdb::Error> { if let Some(module_info) = self.module_infos.get(&module_index) { return Ok(Some(module_info)); } let mut pdb = self.pdb.lock().unwrap(); Ok(pdb.module_info(module)?.map(|module_info| { self.module_infos .insert(module_index, Box::new(module_info)) })) } } /// Basic information about a function. #[derive(Clone)] pub struct Function { /// The start address of the function, as a relative address (rva). pub start_rva: u32, /// The end address of the function, if known. pub end_rva: Option, /// The function name. `None` if there was an error during stringification. /// If this function is based on a public symbol, the consumer may need to demangle /// ("undecorate") the name. This can be detected based on a leading '?' byte. pub name: Option, } /// The result of an address lookup from [`Context::find_frames`]. #[derive(Clone)] pub struct FunctionFrames<'a> { /// The start address of the function which contained the looked-up address. pub start_rva: u32, /// The end address of the function which contained the looked-up address, if known. pub end_rva: Option, /// The inline stack at the looked-up address, ordered from inside to outside. /// Always contains at least one entry: the last element is always the function /// which contains the looked-up address. pub frames: Vec>, } /// One frame of the inline stack at the looked-up address. #[derive(Clone)] pub struct Frame<'a> { /// The function name. `None` if there was an error during stringification. pub function: Option, /// The file name, if known. pub file: Option>, /// The line number, if known. This is the source line inside this function /// that is associated with the instruction at the looked-up address. pub line: Option, } /// The main API of this crate. Resolves addresses to function information. pub struct Context<'a, 's> { address_map: &'a AddressMap<'s>, section_contributions: Vec, string_table: Option<&'a StringTable<'s>>, type_formatter: MaybeOwned<'a, TypeFormatter<'a, 's>>, /// Contains an entry for hopefully every function in an executable section. /// The entries come from the public function symbols, and from the section /// contributions: We create an unnamed "placeholder" entry for each section /// contribution. global_functions: Vec>, cache: RefCell>, } // Assert that `Context` is Send. const _: fn() = || { fn assert() {} assert::(); }; impl<'a, 's> Context<'a, 's> { /// Create a [`Context`] manually. Most consumers will want to use /// [`ContextPdbData::make_context`] instead. /// /// However, if you interact with a PDB directly and parse some of its contents /// for other uses, you may want to call this method in order to avoid overhead /// from repeatedly parsing the same streams. #[allow(clippy::too_many_arguments)] pub fn new_from_parts( module_info_provider: &'a (dyn ModuleProvider<'s> + Sync), sections: &[ImageSectionHeader], address_map: &'a AddressMap<'s>, global_symbols: &'a SymbolTable<'s>, string_table: Option<&'a StringTable<'s>>, debug_info: &'a DebugInformation, type_formatter: MaybeOwned<'a, TypeFormatter<'a, 's>>, ) -> Result { let mut global_functions = Vec::new(); // Start with the public function symbols. let mut symbol_iter = global_symbols.iter(); while let Some(symbol) = symbol_iter.next()? { if let S_PUB32 | S_PUB32_ST = symbol.raw_kind() { if let Ok(SymbolData::Public(PublicSymbol { name, offset, .. })) = symbol.parse() { if is_executable_section(offset.section, sections) { global_functions.push(PublicSymbolFunctionOrPlaceholder { start_offset: offset, name: Some(name), }); } } } } // Read the section contributions. This will let us find the right module // based on the PdbSectionInternalOffset that corresponds to the looked-up // address. This allows reading module info on demand. // The section contributions also give us more function start addresses. We // create placeholder symbols for them so we don't account missing functions to // the nearest public function, and so that we can find line information for // those missing functions if present. let section_contributions = compute_section_contributions(debug_info, sections, &mut global_functions)?; // Add a few more placeholder entries for the end addresses of executable sections. // These act as terminator addresses for the last function in a section. for (section_index_zero_based, section) in sections.iter().enumerate() { let section_index = (section_index_zero_based + 1) as u16; if !is_executable_section(section_index, sections) { continue; } let size = section.virtual_size; let section_end_offset = PdbInternalSectionOffset::new(section_index, size); global_functions.push(PublicSymbolFunctionOrPlaceholder { start_offset: section_end_offset, name: None, }); } // Sort and de-duplicate, so that we can use binary search during lookup. // If we have both a public symbol and a placeholder symbol at the same offset, // make it so that the symbol with name comes first, so that we keep it during // the deduplication. // If there are multiple symbols at the same address, we want to keep the first // one, so we use a stable sort. global_functions.sort_by_key(|p| { ( p.start_offset.section, p.start_offset.offset, p.name.is_none(), ) }); global_functions.dedup_by_key(|p| p.start_offset); Ok(Self { address_map, section_contributions, string_table, type_formatter, global_functions, cache: RefCell::new(ContextCache { module_cache: BasicModuleInfoCache { cache: Default::default(), module_info_provider, }, function_line_cache: Default::default(), procedure_cache: Default::default(), extended_module_cache: Default::default(), inline_name_cache: Default::default(), full_rva_list: Default::default(), }), }) } /// The number of functions found in public symbols. pub fn function_count(&self) -> usize { self.global_functions.len() } /// Iterate over all functions in the modules. pub fn functions(&self) -> FunctionIter<'_, 'a, 's> { let mut cache = self.cache.borrow_mut(); let ContextCache { full_rva_list, module_cache, .. } = &mut *cache; let full_rva_list = full_rva_list .get_or_insert_with(|| Arc::new(self.compute_full_rva_list(module_cache))) .clone(); FunctionIter { context: self, full_rva_list, cur_index: 0, } } /// Find the function whose code contains the provided address. /// The return value only contains the function name and the rva range, but /// no file or line information. pub fn find_function(&self, probe: u32) -> Result> { let offset = match Rva(probe).to_internal_offset(self.address_map) { Some(offset) => offset, None => return Ok(None), }; let mut cache = self.cache.borrow_mut(); let ContextCache { module_cache, procedure_cache, .. } = &mut *cache; let func = match self.lookup_function(offset, module_cache) { Some(func) => func, None => return Ok(None), }; match func { PublicOrProcedureSymbol::Public(_, _, global_function_index) => { let func = &self.global_functions[global_function_index]; let name = func.name.map(|name| name.to_string().to_string()); let start_rva = match func.start_offset.to_rva(self.address_map) { Some(rva) => rva.0, None => return Ok(None), }; // Get the end address from the address of the next entry in the global function list. let end_rva = match self.global_functions.get(global_function_index + 1) { Some(next_entry) if next_entry.start_offset.section == func.start_offset.section => { match next_entry.start_offset.to_rva(self.address_map) { Some(rva) => Some(rva.0), None => return Ok(None), } } _ => None, }; Ok(Some(Function { start_rva, end_rva, name, })) } PublicOrProcedureSymbol::Procedure(module_index, _, func) => { let extended_info = procedure_cache.entry(func.offset).or_default(); let name = extended_info .get_name( func, &self.type_formatter, &self.global_functions, module_index, ) .map(String::from); let start_rva = match func.offset.to_rva(self.address_map) { Some(rva) => rva.0, None => return Ok(None), }; let end_rva = start_rva + func.len; Ok(Some(Function { start_rva, end_rva: Some(end_rva), name, })) } } } /// Find information about the source code which generated the instruction at the /// provided address. This information includes the function name, file name and /// line number, of the containing procedure and of any functions that were inlined /// into the procedure by the compiler, at that address. /// /// A lot of information is cached so that repeated calls are fast. pub fn find_frames(&self, probe: u32) -> Result> { let offset = match Rva(probe).to_internal_offset(self.address_map) { Some(offset) => offset, None => return Ok(None), }; let mut cache = self.cache.borrow_mut(); let ContextCache { module_cache, procedure_cache, function_line_cache, extended_module_cache, inline_name_cache, .. } = &mut *cache; let func = match self.lookup_function(offset, module_cache) { Some(func) => func, None => return Ok(None), }; // We can have a pretty wild mix of available information, depending on what's in // the PDB file. // - Some PDBs have everything. // - Some PDBs only have public symbols and no modules at all, so no procedures // and no file / line info. // - Some PDBs have public symbols and modules, but the modules only have file / // line info and no procedures. let (module_index, module_info, func_offset, func_size, func_name, proc_stuff) = match func { PublicOrProcedureSymbol::Public(module_index, module_info, global_function_index) => { let func = &self.global_functions[global_function_index]; let func_name = func.name.map(|name| name.to_string().to_string()); // Get the function size from the address of the next entry in the global function list. let size = match self.global_functions.get(global_function_index + 1) { Some(next_entry) if next_entry.start_offset.section == func.start_offset.section => { Some(next_entry.start_offset.offset - func.start_offset.offset) } _ => None, }; ( module_index, module_info, func.start_offset, size, func_name, None, ) } PublicOrProcedureSymbol::Procedure(module_index, module_info, proc) => { let proc_extended_info = procedure_cache.entry(proc.offset).or_default(); let func_name = proc_extended_info .get_name( proc, &self.type_formatter, &self.global_functions, module_index, ) .map(String::from); ( module_index, Some(module_info), proc.offset, Some(proc.len), func_name, Some((proc, proc_extended_info)), ) } }; let extended_module_info = match module_info { Some(module_info) => Some( extended_module_cache .entry(module_index) .or_insert_with(|| self.compute_extended_module_info(module_info)) .as_mut() .map_err(|err| mem::replace(err, Error::ExtendedModuleInfoUnsuccessful))?, ), None => None, }; let (file, line) = if let Some(ExtendedModuleInfo { line_program, .. }) = &extended_module_info { let function_line_info = function_line_cache.entry(func_offset).or_default(); let lines = function_line_info.get_lines(func_offset, line_program)?; let search = match lines.binary_search_by_key(&offset.offset, |li| li.start_offset) { Err(0) => None, Ok(i) => Some(i), Err(i) => Some(i - 1), }; match search { Some(index) => { let line_info = &lines[index]; ( self.resolve_filename(line_program, line_info.file_index), Some(line_info.line_start), ) } None => (None, None), } } else { (None, None) }; let frame = Frame { function: func_name, file, line, }; // Ordered outside to inside, until just before the end of this function. let mut frames = vec![frame]; if let (Some((proc, proc_extended_info)), Some(extended_module_info)) = (proc_stuff, extended_module_info) { let ExtendedModuleInfo { inlinees, line_program, module_info, .. } = extended_module_info; let mut inline_ranges = proc_extended_info.get_inline_ranges(module_info, proc, inlinees)?; loop { let current_depth = (frames.len() - 1) as u16; // Look up (offset.offset, current_depth) in inline_ranges. // `inlined_addresses` is sorted in "breadth-first traversal order", i.e. // by `call_depth` first, and then by `start_offset`. See the comment at // the sort call for more information about why. let search = inline_ranges.binary_search_by(|range| { if range.call_depth > current_depth { Ordering::Greater } else if range.call_depth < current_depth { Ordering::Less } else if range.start_offset > offset.offset { Ordering::Greater } else if range.end_offset <= offset.offset { Ordering::Less } else { Ordering::Equal } }); let (inline_range, remainder) = match search { Ok(index) => (&inline_ranges[index], &inline_ranges[index + 1..]), Err(_) => break, }; let function = inline_name_cache .entry(inline_range.inlinee) .or_insert_with(|| { self.type_formatter .format_id(module_index, inline_range.inlinee) }) .as_ref() .ok() .cloned(); let file = inline_range .file_index .and_then(|file_index| self.resolve_filename(line_program, file_index)); let line = inline_range.line_start; frames.push(Frame { function, file, line, }); inline_ranges = remainder; } // Now order from inside to outside. frames.reverse(); } let start_rva = match func_offset.to_rva(self.address_map) { Some(rva) => rva.0, None => return Ok(None), }; let end_rva = func_size.and_then(|size| start_rva.checked_add(size)); Ok(Some(FunctionFrames { start_rva, end_rva, frames, })) } fn compute_full_rva_list(&self, module_cache: &mut BasicModuleInfoCache<'a, 's>) -> Vec { let mut list = Vec::new(); for func in &self.global_functions { if let Some(rva) = func.start_offset.to_rva(self.address_map) { list.push(rva.0); } } for module_index in 0..self.type_formatter.modules().len() { if let Some(BasicModuleInfo { procedures, .. }) = module_cache.get_basic_module_info(self.type_formatter.modules(), module_index) { for proc in procedures { if let Some(rva) = proc.offset.to_rva(self.address_map) { list.push(rva.0); } } } } list.sort_unstable(); list.dedup(); list } fn lookup_function<'m>( &self, offset: PdbInternalSectionOffset, module_cache: &'m mut BasicModuleInfoCache<'a, 's>, ) -> Option> { let sc_index = match self.section_contributions.binary_search_by(|sc| { if sc.section_index < offset.section { Ordering::Less } else if sc.section_index > offset.section { Ordering::Greater } else if sc.end_offset <= offset.offset { Ordering::Less } else if sc.start_offset > offset.offset { Ordering::Greater } else { Ordering::Equal } }) { Ok(sc_index) => sc_index, Err(_) => { // The requested address is not present in any section contribution. return None; } }; let sc = &self.section_contributions[sc_index]; let basic_module_info = module_cache.get_basic_module_info(self.type_formatter.modules(), sc.module_index); let module_info = if let Some(BasicModuleInfo { procedures, module_info, }) = basic_module_info { if let Ok(procedure_index) = procedures.binary_search_by(|p| { if p.offset.section < offset.section { Ordering::Less } else if p.offset.section > offset.section { Ordering::Greater } else if p.offset.offset + p.len <= offset.offset { Ordering::Less } else if p.offset.offset > offset.offset { Ordering::Greater } else { Ordering::Equal } }) { // Found a procedure at the requested offset. return Some(PublicOrProcedureSymbol::Procedure( sc.module_index, module_info, &procedures[procedure_index], )); } Some(*module_info) } else { None }; // No procedure was found at this offset in the module that the section // contribution pointed us at. // This is not uncommon. // Fall back to the public symbols. let last_global_function_starting_lte_address = match self .global_functions .binary_search_by_key(&(offset.section, offset.offset), |p| { (p.start_offset.section, p.start_offset.offset) }) { Err(0) => return None, Ok(i) => i, Err(i) => i - 1, }; let fun = &self.global_functions[last_global_function_starting_lte_address]; debug_assert!( fun.start_offset.section < offset.section || (fun.start_offset.section == offset.section && fun.start_offset.offset <= offset.offset) ); if fun.start_offset.section != offset.section { return None; } // Ignore symbols outside the section contribution. if fun.start_offset.offset < sc.start_offset { return None; } Some(PublicOrProcedureSymbol::Public( sc.module_index, module_info, last_global_function_starting_lte_address, )) } fn compute_extended_module_info( &self, module_info: &'a ModuleInfo<'s>, ) -> Result> { let line_program = module_info.line_program()?; let inlinees: BTreeMap = module_info .inlinees()? .map(|i| Ok((i.index(), i))) .collect()?; Ok(ExtendedModuleInfo { module_info, inlinees, line_program, }) } fn resolve_filename( &self, line_program: &LineProgram, file_index: FileIndex, ) -> Option> { if let Some(string_table) = self.string_table { if let Ok(file_info) = line_program.get_file_info(file_index) { return file_info.name.to_string_lossy(string_table).ok(); } } None } } /// An iterator over all functions in a [`Context`]. #[derive(Clone)] pub struct FunctionIter<'c, 'a, 's> { context: &'c Context<'a, 's>, full_rva_list: Arc>, cur_index: usize, } impl<'c, 'a, 's> Iterator for FunctionIter<'c, 'a, 's> { type Item = Function; fn next(&mut self) -> Option { loop { if self.cur_index >= self.full_rva_list.len() { return None; } let rva = self.full_rva_list[self.cur_index]; self.cur_index += 1; if let Ok(Some(fun)) = self.context.find_function(rva) { return Some(fun); } } } } struct ContextCache<'a, 's> { module_cache: BasicModuleInfoCache<'a, 's>, function_line_cache: HashMap, procedure_cache: HashMap, extended_module_cache: BTreeMap>>, inline_name_cache: BTreeMap>, full_rva_list: Option>>, } struct BasicModuleInfoCache<'a, 's> { cache: HashMap>>, module_info_provider: &'a (dyn ModuleProvider<'s> + Sync), } impl<'a, 's> BasicModuleInfoCache<'a, 's> { pub fn get_basic_module_info( &mut self, modules: &[Module<'a>], module_index: usize, ) -> Option<&BasicModuleInfo<'a, 's>> { // TODO: 2021 edition let module_info_provider = self.module_info_provider; self.cache .entry(module_index) .or_insert_with(|| { let module = modules.get(module_index)?; let module_info = module_info_provider .get_module_info(module_index, module) .ok()??; BasicModuleInfo::try_from_module_info(module_info).ok() }) .as_ref() } } struct BasicModuleInfo<'a, 's> { module_info: &'a ModuleInfo<'s>, procedures: Vec>, } impl<'a, 's> BasicModuleInfo<'a, 's> { pub fn try_from_module_info( module_info: &'a ModuleInfo<'s>, ) -> Result> { let mut symbols_iter = module_info.symbols()?; let mut functions = Vec::new(); while let Some(symbol) = symbols_iter.next()? { if let S_LPROC32 | S_LPROC32_ST | S_GPROC32 | S_GPROC32_ST | S_LPROC32_ID | S_GPROC32_ID | S_LPROC32_DPC | S_LPROC32_DPC_ID | S_THUNK32 | S_THUNK32_ST | S_SEPCODE = symbol.raw_kind() { match symbol.parse() { Ok(SymbolData::Procedure(proc)) => { if proc.len == 0 { continue; } functions.push(ProcedureSymbolFunction { offset: proc.offset, len: proc.len, name: proc.name, symbol_index: symbol.index(), end_symbol_index: proc.end, type_index: proc.type_index, }); } Ok(SymbolData::SeparatedCode(data)) => { if data.len == 0 { continue; } // SeparatedCode references another procedure with data.parent_offset. // Usually the SeparatedCode symbol comes right after the referenced symbol. // Take the name and type_index from the referenced procedure. let (name, type_index) = match functions.last() { Some(proc) if proc.offset == data.parent_offset => { (proc.name, proc.type_index) } _ => continue, }; functions.push(ProcedureSymbolFunction { offset: data.offset, len: data.len, name, symbol_index: symbol.index(), end_symbol_index: data.end, type_index, }); } Ok(SymbolData::Thunk(thunk)) => { if thunk.len == 0 { continue; } // Treat thunks as procedures. This isn't perfectly accurate but it // doesn't cause any harm. functions.push(ProcedureSymbolFunction { offset: thunk.offset, len: thunk.len as u32, name: thunk.name, symbol_index: symbol.index(), end_symbol_index: thunk.end, type_index: TypeIndex(0), }); } _ => {} } } } // Sort and de-duplicate, so that we can use binary search during lookup. // Use a stable sort: if there are multiple symbols at the same address, // we want to keep the first one. functions.sort_by_key(|p| (p.offset.section, p.offset.offset)); functions.dedup_by_key(|p| p.offset); Ok(BasicModuleInfo { module_info, procedures: functions, }) } } /// The order of the fields matters for the lexicographical sort. #[derive(Debug, Clone, PartialOrd, PartialEq, Eq, Ord)] pub struct ModuleSectionContribution { section_index: u16, start_offset: u32, end_offset: u32, module_index: usize, } /// Returns an array of non-overlapping `ModuleSectionContribution` objects, /// sorted by section and then by start offset. /// Contributions from the same module to the same section are combined into /// one contiguous contribution. The hope is that there is no interleaving, /// and this function returns an error if any interleaving is detected. fn compute_section_contributions( debug_info: &DebugInformation<'_>, sections: &[ImageSectionHeader], placeholder_functions: &mut Vec, ) -> Result> { let mut section_contribution_iter = debug_info .section_contributions()? .filter(|sc| Ok(sc.size != 0 && is_executable_section(sc.offset.section, sections))); let mut section_contributions = Vec::new(); if let Some(first_sc) = section_contribution_iter.next()? { let mut current_combined_sc = ModuleSectionContribution { section_index: first_sc.offset.section, start_offset: first_sc.offset.offset, end_offset: first_sc.offset.offset + first_sc.size, module_index: first_sc.module, }; let mut is_executable = is_executable_section(first_sc.offset.section, sections); // Assume that section contributions from the same section and module are // sorted and non-interleaved. while let Some(sc) = section_contribution_iter.next()? { let section_index = sc.offset.section; let start_offset = sc.offset.offset; let end_offset = start_offset + sc.size; let module_index = sc.module; if section_index == current_combined_sc.section_index && module_index == current_combined_sc.module_index { // Enforce ordered contributions. If you find a pdb where this errors out, // please file an issue. if end_offset < current_combined_sc.end_offset { return Err(Error::UnorderedSectionContributions( module_index, section_index, )); } // Combine with current section contribution. current_combined_sc.end_offset = end_offset; } else { section_contributions.push(current_combined_sc); current_combined_sc = ModuleSectionContribution { section_index: sc.offset.section, start_offset: sc.offset.offset, end_offset, module_index: sc.module, }; is_executable = is_executable_section(sc.offset.section, sections); } if is_executable { placeholder_functions.push(PublicSymbolFunctionOrPlaceholder { start_offset: sc.offset, name: None, }); } } section_contributions.push(current_combined_sc); } // Sort. This sorts by section index first, and then start offset within the section. section_contributions.sort_unstable(); // Enforce no overlap. If you encounter a PDB where this errors out, please file an issue. if let Some((first_sc, rest)) = section_contributions.split_first() { let mut prev_sc = first_sc; for sc in rest { if sc.section_index == prev_sc.section_index && sc.start_offset < prev_sc.end_offset { return Err(Error::OverlappingSectionContributions( sc.section_index, prev_sc.module_index, sc.module_index, )); } prev_sc = sc; } } Ok(section_contributions) } /// section_index is a 1-based index from PdbInternalSectionOffset. fn get_section(section_index: u16, sections: &[ImageSectionHeader]) -> Option<&ImageSectionHeader> { if section_index == 0 { None } else { sections.get((section_index - 1) as usize) } } /// section_index is a 1-based index from PdbInternalSectionOffset. fn is_executable_section(section_index: u16, sections: &[ImageSectionHeader]) -> bool { match get_section(section_index, sections) { Some(section) => section.characteristics.execute(), // TODO: should this use .executable()? None => false, } } /// Offset and name of a function from a public symbol, or from a placeholder symbol from /// the section contributions. #[derive(Clone, Debug)] struct PublicSymbolFunctionOrPlaceholder<'s> { /// The address at which this function starts, as a section internal offset. The end /// address for global function symbols is not known. During symbol lookup, if the address /// is not covered by a procedure symbol (for those, the end addresses are known), then /// we assume that functions with no end address cover the range up to the next function. start_offset: PdbInternalSectionOffset, /// The symbol name of the public symbol. This is the mangled ("decorated") function signature. /// None if this is a placeholder. name: Option>, } #[derive(Clone, Debug)] struct ProcedureSymbolFunction<'a> { /// The address at which this function starts, as a section internal offset. offset: PdbInternalSectionOffset, /// The length of this function, in bytes, beginning from start_offset. len: u32, /// The symbol name. If type_index is 0, then this can be the mangled ("decorated") /// function signature from a PublicSymbol or from a Thunk. If type_index is non-zero, /// name is just the function name, potentially including class scope and namespace, /// but no args. The args are then found in the type. name: RawString<'a>, /// The index of the ProcedureSymbol. This allows starting a symbol iteration /// cheaply from this symbol, for example to find subsequent symbols about /// inlines in this procedure. symbol_index: SymbolIndex, /// The index of the symbol that ends this procedure. This is where the symbol /// iteration should stop. end_symbol_index: SymbolIndex, /// The type of this procedure, or 0. This is needed to get the arguments for the /// function signature. type_index: TypeIndex, } enum PublicOrProcedureSymbol<'a, 's, 'm> { Public(usize, Option<&'a ModuleInfo<'s>>, usize), Procedure(usize, &'a ModuleInfo<'s>, &'m ProcedureSymbolFunction<'a>), } #[derive(Default)] struct FunctionLineInfo { lines: Option>>, } impl FunctionLineInfo { fn get_lines( &mut self, function_offset: PdbInternalSectionOffset, line_program: &LineProgram, ) -> Result<&[CachedLineInfo]> { let lines = self .lines .get_or_insert_with(|| { let mut iterator = line_program.lines_for_symbol(function_offset); let mut lines = Vec::new(); let mut next_item = iterator.next()?; while let Some(line_info) = next_item { next_item = iterator.next()?; lines.push(CachedLineInfo { start_offset: line_info.offset.offset, file_index: line_info.file_index, line_start: line_info.line_start, }); } Ok(lines) }) .as_mut() .map_err(|e| mem::replace(e, Error::ProcedureLinesUnsuccessful))?; Ok(lines) } } #[derive(Default)] struct ExtendedProcedureInfo { name: Option>, inline_ranges: Option>>, } impl ExtendedProcedureInfo { fn get_name( &mut self, proc: &ProcedureSymbolFunction, type_formatter: &TypeFormatter, global_functions: &[PublicSymbolFunctionOrPlaceholder], module_index: usize, ) -> Option<&str> { self.name .get_or_insert_with(|| { if proc.type_index == TypeIndex(0) && !proc.name.as_bytes().starts_with(b"?") { // We have no type, so proc.name might be an argument-less string. // If we have a public symbol at this address which is a decorated name // (starts with a '?'), prefer to use that because it'll usually include // the arguments. if let Ok(public_fun_index) = global_functions .binary_search_by_key(&(proc.offset.section, proc.offset.offset), |f| { (f.start_offset.section, f.start_offset.offset) }) { if let Some(name) = global_functions[public_fun_index].name { if name.as_bytes().starts_with(b"?") { return Some(name.to_string().to_string()); } } } } type_formatter .format_function(&proc.name.to_string(), module_index, proc.type_index) .ok() }) .as_deref() } fn get_inline_ranges( &mut self, module_info: &ModuleInfo, proc: &ProcedureSymbolFunction, inlinees: &BTreeMap, ) -> Result<&[InlineRange]> { let inline_ranges = self .inline_ranges .get_or_insert_with(|| compute_procedure_inline_ranges(module_info, proc, inlinees)) .as_mut() .map_err(|e| mem::replace(e, Error::ProcedureInlineRangesUnsuccessful))?; Ok(inline_ranges) } } fn compute_procedure_inline_ranges( module_info: &ModuleInfo, proc: &ProcedureSymbolFunction, inlinees: &BTreeMap, ) -> Result> { let mut lines = Vec::new(); let mut symbols_iter = module_info.symbols_at(proc.symbol_index)?; let _proc_sym = symbols_iter.next()?; while let Some(symbol) = symbols_iter.next()? { if symbol.index() >= proc.end_symbol_index { break; } if let S_LPROC32 | S_LPROC32_ST | S_GPROC32 | S_GPROC32_ST | S_LPROC32_ID | S_GPROC32_ID | S_LPROC32_DPC | S_LPROC32_DPC_ID | S_INLINESITE | S_INLINESITE2 = symbol.raw_kind() { match symbol.parse() { Ok(SymbolData::Procedure(p)) => { // This is a nested procedure. Skip it. symbols_iter.skip_to(p.end)?; } Ok(SymbolData::InlineSite(site)) => { process_inlinee_symbols( &mut symbols_iter, inlinees, proc.offset, site, 0, &mut lines, )?; } _ => {} } } } lines.sort_unstable_by(|r1, r2| { if r1.call_depth < r2.call_depth { Ordering::Less } else if r1.call_depth > r2.call_depth { Ordering::Greater } else if r1.start_offset < r2.start_offset { Ordering::Less } else if r1.start_offset > r2.start_offset { Ordering::Greater } else { Ordering::Equal } }); Ok(lines) } fn process_inlinee_symbols( symbols_iter: &mut SymbolIter, inlinees: &BTreeMap, proc_offset: PdbInternalSectionOffset, site: InlineSiteSymbol, call_depth: u16, lines: &mut Vec, ) -> Result> { let mut ranges = RangeSet2::empty(); let mut file_index = None; if let Some(inlinee) = inlinees.get(&site.inlinee) { let mut iter = inlinee.lines(proc_offset, &site); while let Ok(Some(line_info)) = iter.next() { let length = match line_info.length { Some(0) | None => { continue; } Some(l) => l, }; let start_offset = line_info.offset.offset; let end_offset = line_info.offset.offset + length; lines.push(InlineRange { start_offset, end_offset, call_depth, inlinee: site.inlinee, file_index: Some(line_info.file_index), line_start: Some(line_info.line_start), }); ranges |= RangeSet::from(start_offset..end_offset); if file_index.is_none() { file_index = Some(line_info.file_index); } } } let mut callee_ranges = RangeSet2::empty(); while let Some(symbol) = symbols_iter.next()? { if symbol.index() >= site.end { break; } if let S_LPROC32 | S_LPROC32_ST | S_GPROC32 | S_GPROC32_ST | S_LPROC32_ID | S_GPROC32_ID | S_LPROC32_DPC | S_LPROC32_DPC_ID | S_INLINESITE | S_INLINESITE2 = symbol.raw_kind() { match symbol.parse() { Ok(SymbolData::Procedure(p)) => { // This is a nested procedure. Skip it. symbols_iter.skip_to(p.end)?; } Ok(SymbolData::InlineSite(site)) => { callee_ranges |= process_inlinee_symbols( symbols_iter, inlinees, proc_offset, site, call_depth + 1, lines, )?; } _ => {} } } } if !ranges.is_superset(&callee_ranges) { // Workaround bad debug info. let missing_ranges: RangeSet2 = &callee_ranges - &ranges; for range in missing_ranges.iter() { let (start_offset, end_offset) = match range { RangeSetRange::Range(r) => (*r.start, *r.end), other => { panic!("Unexpected range bounds {:?}", other); } }; lines.push(InlineRange { start_offset, end_offset, call_depth, inlinee: site.inlinee, file_index, line_start: None, }); } ranges |= missing_ranges; } Ok(ranges) } struct ExtendedModuleInfo<'a, 's> { module_info: &'a ModuleInfo<'s>, inlinees: BTreeMap>, line_program: LineProgram<'a>, } #[derive(Clone, Debug)] struct CachedLineInfo { pub start_offset: u32, pub file_index: FileIndex, pub line_start: u32, } struct HexNum(pub N); impl std::fmt::Debug for HexNum { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { LowerHex::fmt(&self.0, f) } } /// A contiguous address range covering a line record inside an /// inlined function call. These are meaningful in the context of the /// outer function which contains these inline calls; specifically, the /// offsets are expressed relative to the same section that the outer /// function is in. #[derive(Clone)] struct InlineRange { /// The section-internal offset of the start of the range, /// relative to the section that the outer function is in. pub start_offset: u32, /// The section-internal offset of the end of the range, /// relative to the section that the outer function is in. pub end_offset: u32, pub call_depth: u16, pub inlinee: IdIndex, pub file_index: Option, pub line_start: Option, } impl std::fmt::Debug for InlineRange { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("InlineRange") .field("start_offset", &HexNum(self.start_offset)) .field("end_offset", &HexNum(self.end_offset)) .field("call_depth", &self.call_depth) .field("inlinee", &self.inlinee) .field("file_index", &self.file_index) .field("line_start", &self.line_start) .finish() } } pdb-addr2line-0.11.1/src/type_formatter.rs000064400000000000000000001410461046102023000165220ustar 00000000000000use crate::error::Error; use bitflags::bitflags; use pdb::{ ArgumentList, ArrayType, ClassKind, ClassType, CrossModuleExports, CrossModuleImports, CrossModuleRef, DebugInformation, FallibleIterator, FunctionAttributes, IdData, IdIndex, IdInformation, Item, ItemFinder, ItemIndex, ItemIter, MachineType, MemberFunctionType, ModifierType, Module, ModuleInfo, PointerMode, PointerType, PrimitiveKind, PrimitiveType, ProcedureType, RawString, StringTable, TypeData, TypeIndex, TypeInformation, UnionType, Variant, }; use range_collections::range_set::RangeSetRange; use range_collections::{RangeSet, RangeSet2}; use std::cmp::Ordering; use std::collections::HashMap; use std::fmt::Write; use std::mem; use std::sync::Mutex; type Result = std::result::Result; bitflags! { /// Flags for [`TypeFormatter`]. #[derive(Clone, Copy)] pub struct TypeFormatterFlags: u32 { /// Do not print the return type for the root function. const NO_FUNCTION_RETURN = 0b1; /// Do not print static before the signature of a static method. const NO_MEMBER_FUNCTION_STATIC = 0b10; /// Add a space after each comma in an argument list. const SPACE_AFTER_COMMA = 0b100; /// Add a space before the * or & sigil of a pointer or reference. const SPACE_BEFORE_POINTER = 0b1000; /// Only print "MyClassName" instead of "class MyClassName", "struct MyClassName", or "interface MyClassName". const NAME_ONLY = 0b10000; /// Do not print a functions argument types. const NO_ARGUMENTS = 0b100000; } } impl Default for TypeFormatterFlags { fn default() -> Self { Self::NO_FUNCTION_RETURN | Self::NO_MEMBER_FUNCTION_STATIC | Self::SPACE_AFTER_COMMA | Self::NAME_ONLY } } /// This trait is only needed for consumers who want to call Context::new_from_parts /// or TypeFormatter::new_from_parts manually, instead of using ContextPdbData. If you /// use ContextPdbData you do not need to worry about this trait. /// This trait allows Context and TypeFormatter to request parsing of module info /// on-demand. It also does some lifetime acrobatics so that Context can cache objects /// which have a lifetime dependency on the module info. pub trait ModuleProvider<'s> { /// Get the module info for this module from the PDB. fn get_module_info( &self, module_index: usize, module: &Module, ) -> std::result::Result>, pdb::Error>; } /// Allows printing function signatures, for example for use in stack traces. /// /// Procedure symbols in PDBs usually have a name string which only includes the function name, /// and no function arguments. Instead, the arguments need to be obtained from the symbol's type /// information. [`TypeFormatter`] handles that. /// /// The same is true for "inlinee" functions - these are referenced by their [`pdb::IdIndex`], and their /// [`IdData`]'s name string again only contains the raw function name but no arguments and also /// no namespace or class name. [`TypeFormatter`] handles those, too, in [`TypeFormatter::format_id`]. // Lifetimes: // 'a: Lifetime of the thing that owns the various streams, e.g. ContextPdbData. // 's: The PDB Source lifetime. pub struct TypeFormatter<'a, 's> { module_provider: &'a (dyn ModuleProvider<'s> + Sync), modules: Vec>, string_table: Option<&'a StringTable<'s>>, cache: Mutex>, ptr_size: u64, flags: TypeFormatterFlags, } struct TypeFormatterCache<'a> { type_map: TypeMap<'a>, type_size_cache: TypeSizeCache<'a>, id_map: IdMap<'a>, /// lower case module_name() -> module_index module_name_map: Option>, module_imports: HashMap>>, module_exports: HashMap>, } // 'a: Lifetime of the thing that owns the various streams. // 's: The PDB Source lifetime. // 'cache: Lifetime of the exclusive reference to the TypeFormatterCache, outlived by // the reference to the TypeFormatter. struct TypeFormatterForModule<'cache, 'a, 's> { module_index: usize, module_provider: &'a (dyn ModuleProvider<'s> + Sync), modules: &'cache [Module<'a>], string_table: Option<&'a StringTable<'s>>, cache: &'cache mut TypeFormatterCache<'a>, ptr_size: u64, flags: TypeFormatterFlags, } impl<'a, 's> TypeFormatter<'a, 's> { /// Create a [`TypeFormatter`] manually. Most consumers will want to use /// [`ContextPdbData::make_type_formatter`] instead. /// /// However, if you interact with a PDB directly and parse some of its contents /// for other uses, you may want to call this method in order to avoid overhead /// from repeatedly parsing the same streams. pub fn new_from_parts( module_provider: &'a (dyn ModuleProvider<'s> + Sync), modules: Vec>, debug_info: &DebugInformation<'s>, type_info: &'a TypeInformation<'s>, id_info: &'a IdInformation<'s>, string_table: Option<&'a StringTable<'s>>, flags: TypeFormatterFlags, ) -> std::result::Result { let type_map = TypeMap { iter: type_info.iter(), finder: type_info.finder(), }; let type_size_cache = TypeSizeCache { forward_ref_sizes: HashMap::new(), cached_ranges: RangeSet::empty(), }; let id_map = IdMap { iter: id_info.iter(), finder: id_info.finder(), }; let ptr_size = match debug_info.machine_type()? { MachineType::Amd64 | MachineType::Arm64 | MachineType::Ia64 | MachineType::RiscV64 => 8, MachineType::RiscV128 => 16, _ => 4, }; Ok(Self { module_provider, modules, string_table, cache: Mutex::new(TypeFormatterCache { type_map, type_size_cache, id_map, module_name_map: None, module_imports: HashMap::new(), module_exports: HashMap::new(), }), ptr_size, flags, }) } /// A reference to the `Module` list that is owned by the type formatter. pub fn modules(&self) -> &[Module<'a>] { &self.modules } fn for_module(&self, module_index: usize, f: F) -> R where F: FnOnce(&mut TypeFormatterForModule<'_, 'a, 's>) -> R, { let mut cache = self.cache.lock().unwrap(); let mut for_module = TypeFormatterForModule { module_index, module_provider: self.module_provider, modules: &self.modules, string_table: self.string_table, cache: &mut cache, ptr_size: self.ptr_size, flags: self.flags, }; f(&mut for_module) } /// Get the size, in bytes, of the type at `index`. pub fn get_type_size(&self, module_index: usize, index: TypeIndex) -> u64 { self.for_module(module_index, |tf| tf.get_type_size(index)) } /// Return a string with the function or method signature, including return type (if /// requested), namespace and/or class qualifiers, and arguments. /// If the TypeIndex is 0, then only the raw name is emitted. In that case, the /// name may need to go through additional demangling / "undecorating", but this /// is the responsibility of the caller. /// This method is used for [`ProcedureSymbol`s](pdb::ProcedureSymbol). /// The module_index is the index of the module in which this procedure was found. It /// is necessary in order to properly resolve cross-module references. pub fn format_function( &self, name: &str, module_index: usize, function_type_index: TypeIndex, ) -> Result { let mut s = String::new(); self.emit_function(&mut s, name, module_index, function_type_index)?; Ok(s) } /// Write out the function or method signature, including return type (if requested), /// namespace and/or class qualifiers, and arguments. /// If the TypeIndex is 0, then only the raw name is emitted. In that case, the /// name may need to go through additional demangling / "undecorating", but this /// is the responsibility of the caller. /// This method is used for [`ProcedureSymbol`s](pdb::ProcedureSymbol). /// The module_index is the index of the module in which this procedure was found. It /// is necessary in order to properly resolve cross-module references. pub fn emit_function( &self, w: &mut impl Write, name: &str, module_index: usize, function_type_index: TypeIndex, ) -> Result<()> { self.for_module(module_index, |tf| { tf.emit_function(w, name, function_type_index) }) } /// Return a string with the function or method signature, including return type (if /// requested), namespace and/or class qualifiers, and arguments. /// This method is used for inlined functions. /// The module_index is the index of the module in which this IdIndex was found. It /// is necessary in order to properly resolve cross-module references. pub fn format_id(&self, module_index: usize, id_index: IdIndex) -> Result { let mut s = String::new(); self.emit_id(&mut s, module_index, id_index)?; Ok(s) } /// Write out the function or method signature, including return type (if requested), /// namespace and/or class qualifiers, and arguments. /// This method is used for inlined functions. /// The module_index is the index of the module in which this IdIndex was found. It /// is necessary in order to properly resolve cross-module references. pub fn emit_id( &self, w: &mut impl Write, module_index: usize, id_index: IdIndex, ) -> Result<()> { self.for_module(module_index, |tf| tf.emit_id(w, id_index)) } } impl<'cache, 'a, 's> TypeFormatterForModule<'cache, 'a, 's> { /// Get the size, in bytes, of the type at `index`. pub fn get_type_size(&mut self, index: TypeIndex) -> u64 { if let Ok(type_data) = self.parse_type_index(index) { self.get_data_size(index, &type_data) } else { 0 } } /// Write out the function or method signature, including return type (if requested), /// namespace and/or class qualifiers, and arguments. /// If the TypeIndex is 0, then only the raw name is emitted. In that case, the /// name may need to go through additional demangling / "undecorating", but this /// is the responsibility of the caller. /// This method is used for [`ProcedureSymbol`s](pdb::ProcedureSymbol). pub fn emit_function( &mut self, w: &mut impl Write, name: &str, function_type_index: TypeIndex, ) -> Result<()> { if function_type_index == TypeIndex(0) { return self.emit_name_str(w, name); } match self.parse_type_index(function_type_index)? { TypeData::MemberFunction(t) => { if t.this_pointer_type.is_none() { self.maybe_emit_static(w)?; } self.maybe_emit_return_type(w, Some(t.return_type), t.attributes)?; self.emit_name_str(w, name)?; self.emit_method_args(w, t, true)?; } TypeData::Procedure(t) => { self.maybe_emit_return_type(w, t.return_type, t.attributes)?; self.emit_name_str(w, name)?; if !self.has_flags(TypeFormatterFlags::NO_ARGUMENTS) { write!(w, "(")?; self.emit_type_index(w, t.argument_list)?; write!(w, ")")?; } } _ => { write!(w, "{}", name)?; } } Ok(()) } /// Write out the function or method signature, including return type (if requested), /// namespace and/or class qualifiers, and arguments. /// This method is used for inlined functions. pub fn emit_id(&mut self, w: &mut impl Write, id_index: IdIndex) -> Result<()> { let id_data = match self.parse_id_index(id_index) { Ok(id_data) => id_data, Err(Error::PdbError(pdb::Error::UnimplementedTypeKind(t))) => { write!(w, "", t)?; return Ok(()); } Err(Error::PdbError(pdb::Error::TypeNotFound(type_index))) => { write!(w, "", type_index)?; return Ok(()); } Err(e) => return Err(e), }; match id_data { IdData::MemberFunction(m) => { let t = match self.parse_type_index(m.function_type)? { TypeData::MemberFunction(t) => t, _ => return Err(Error::MemberFunctionIdIsNotMemberFunctionType), }; if t.this_pointer_type.is_none() { self.maybe_emit_static(w)?; } self.maybe_emit_return_type(w, Some(t.return_type), t.attributes)?; self.emit_type_index(w, m.parent)?; write!(w, "::")?; self.emit_name_str(w, &m.name.to_string())?; self.emit_method_args(w, t, true)?; } IdData::Function(f) => { let t = match self.parse_type_index(f.function_type)? { TypeData::Procedure(t) => t, _ => return Err(Error::FunctionIdIsNotProcedureType), }; self.maybe_emit_return_type(w, t.return_type, t.attributes)?; if let Some(scope) = f.scope { self.emit_id(w, scope)?; write!(w, "::")?; } self.emit_name_str(w, &f.name.to_string())?; if !self.has_flags(TypeFormatterFlags::NO_ARGUMENTS) { write!(w, "(")?; self.emit_type_index(w, t.argument_list)?; write!(w, ")")?; } } IdData::String(s) => { let name = s.name.to_string(); if Self::is_anonymous_namespace(&name) { write!(w, "`anonymous namespace'")?; } else { write!(w, "{}", name)?; } } IdData::StringList(s) => { write!(w, "\"")?; for (i, type_index) in s.substrings.iter().enumerate() { if i > 0 { write!(w, "\" \"")?; } self.emit_type_index(w, *type_index)?; } write!(w, "\"")?; } other => write!(w, "::", other)?, } Ok(()) } /// Checks whether the given name declares an anonymous namespace. /// /// ID records specify the mangled format for anonymous namespaces: `?A0x`, where `id` is a hex /// identifier of the namespace. Demanglers usually resolve this as "anonymous namespace". fn is_anonymous_namespace(name: &str) -> bool { name.strip_prefix("?A0x") .map_or(false, |rest| u32::from_str_radix(rest, 16).is_ok()) } fn resolve_index(&mut self, index: I) -> Result where I: ItemIndex, { if !index.is_cross_module() { return Ok(index); } // We have a cross-module reference. // First, we prepare some information which we will need below. let string_table = self .string_table .ok_or(Error::CantResolveCrossModuleRefWithoutStringTable)?; let TypeFormatterCache { module_name_map, module_imports, module_exports, .. } = self.cache; let modules = self.modules; let module_provider = self.module_provider; let self_module_index = self.module_index; let get_module = |module_index: usize| -> Result<&'a ModuleInfo<'s>> { let module = modules .get(module_index) .ok_or(Error::OutOfRangeModuleIndex(module_index))?; let module_info = module_provider .get_module_info(module_index, module)? .ok_or(Error::ModuleInfoNotFound(module_index))?; Ok(module_info) }; let module_name_map = module_name_map.get_or_insert_with(|| { modules .iter() .enumerate() .map(|(module_index, module)| { let name = module.module_name().to_ascii_lowercase(); (name, module_index) }) .collect() }); // Now we follow the steps outlined in the comment for is_cross_module. // 1. Look up the index in [`CrossModuleImports`](crate::CrossModuleImports) of the current // module. let imports = module_imports .entry(self_module_index) .or_insert_with(|| Ok(get_module(self_module_index)?.imports()?)) .as_mut() .map_err(|err| mem::replace(err, Error::ModuleImportsUnsuccessful))?; let CrossModuleRef(module_ref, local_index) = imports.resolve_import(index)?; // 2. Use [`StringTable`](crate::StringTable) to resolve the name of the referenced module. let ref_module_name = module_ref .0 .to_string_lossy(string_table)? .to_ascii_lowercase(); // 3. Find the [`Module`](crate::Module) with the same module name and load its // [`ModuleInfo`](crate::ModuleInfo). let ref_module_index = *module_name_map .get(&ref_module_name) .ok_or(Error::ModuleNameNotFound(ref_module_name))?; let module_exports = module_exports .entry(ref_module_index) .or_insert_with(|| Ok(get_module(ref_module_index)?.exports()?)) .as_mut() .map_err(|err| mem::replace(err, Error::ModuleExportsUnsuccessful))?; // 4. Resolve the [`Local`](crate::Local) index into a global one using // [`CrossModuleExports`](crate::CrossModuleExports). let index = module_exports .resolve_import(local_index)? .ok_or_else(|| Error::LocalIndexNotInExports(local_index.0.into()))?; Ok(index) } fn parse_type_index(&mut self, index: TypeIndex) -> Result> { let index = self.resolve_index(index)?; let item = self.cache.type_map.try_get(index)?; Ok(item.parse()?) } fn parse_id_index(&mut self, index: IdIndex) -> Result> { let index = self.resolve_index(index)?; let item = self.cache.id_map.try_get(index)?; Ok(item.parse()?) } fn get_class_size(&mut self, index: TypeIndex, class_type: &ClassType<'a>) -> u64 { if class_type.properties.forward_reference() { let name = class_type.unique_name.unwrap_or(class_type.name); let size = self.cache.type_size_cache.get_size_for_forward_reference( index, name, &mut self.cache.type_map, ); // Sometimes the name will not be in self.forward_ref_sizes - this can occur for // the empty struct, which can be a forward reference to itself! size.unwrap_or(class_type.size) } else { class_type.size } } fn get_union_size(&mut self, index: TypeIndex, union_type: &UnionType<'a>) -> u64 { if union_type.properties.forward_reference() { let name = union_type.unique_name.unwrap_or(union_type.name); let size = self.cache.type_size_cache.get_size_for_forward_reference( index, name, &mut self.cache.type_map, ); size.unwrap_or(union_type.size) } else { union_type.size } } fn get_data_size(&mut self, type_index: TypeIndex, type_data: &TypeData<'a>) -> u64 { match type_data { TypeData::Primitive(t) => { if t.indirection.is_some() { return self.ptr_size; } match t.kind { PrimitiveKind::NoType | PrimitiveKind::Void => 0, PrimitiveKind::Char | PrimitiveKind::UChar | PrimitiveKind::RChar | PrimitiveKind::I8 | PrimitiveKind::U8 | PrimitiveKind::Bool8 => 1, PrimitiveKind::WChar | PrimitiveKind::RChar16 | PrimitiveKind::Short | PrimitiveKind::UShort | PrimitiveKind::I16 | PrimitiveKind::U16 | PrimitiveKind::F16 | PrimitiveKind::Bool16 => 2, PrimitiveKind::RChar32 | PrimitiveKind::Long | PrimitiveKind::ULong | PrimitiveKind::I32 | PrimitiveKind::U32 | PrimitiveKind::F32 | PrimitiveKind::F32PP | PrimitiveKind::Bool32 | PrimitiveKind::HRESULT => 4, PrimitiveKind::I64 | PrimitiveKind::U64 | PrimitiveKind::Quad | PrimitiveKind::UQuad | PrimitiveKind::F64 | PrimitiveKind::Complex32 | PrimitiveKind::Bool64 => 8, PrimitiveKind::I128 | PrimitiveKind::U128 | PrimitiveKind::Octa | PrimitiveKind::UOcta | PrimitiveKind::F128 | PrimitiveKind::Complex64 => 16, PrimitiveKind::F48 => 6, PrimitiveKind::F80 => 10, PrimitiveKind::Complex80 => 20, PrimitiveKind::Complex128 => 32, _ => panic!("Unknown PrimitiveKind {:?} in get_data_size", t.kind), } } TypeData::Class(t) => self.get_class_size(type_index, t), TypeData::MemberFunction(_) => self.ptr_size, TypeData::Procedure(_) => self.ptr_size, TypeData::Pointer(t) => t.attributes.size().into(), TypeData::Array(t) => (*t.dimensions.last().unwrap()).into(), TypeData::Union(t) => self.get_union_size(type_index, t), TypeData::Enumeration(t) => self.get_type_size(t.underlying_type), TypeData::Enumerate(t) => match t.value { Variant::I8(_) | Variant::U8(_) => 1, Variant::I16(_) | Variant::U16(_) => 2, Variant::I32(_) | Variant::U32(_) => 4, Variant::I64(_) | Variant::U64(_) => 8, }, TypeData::Modifier(t) => self.get_type_size(t.underlying_type), _ => 0, } } fn has_flags(&self, flags: TypeFormatterFlags) -> bool { self.flags.intersects(flags) } fn maybe_emit_static(&self, w: &mut impl Write) -> Result<()> { if self.has_flags(TypeFormatterFlags::NO_MEMBER_FUNCTION_STATIC) { return Ok(()); } w.write_str("static ")?; Ok(()) } fn maybe_emit_return_type( &mut self, w: &mut impl Write, type_index: Option, attrs: FunctionAttributes, ) -> Result<()> { if self.has_flags(TypeFormatterFlags::NO_FUNCTION_RETURN) { return Ok(()); } self.emit_return_type(w, type_index, attrs)?; Ok(()) } fn emit_name_str(&mut self, w: &mut impl Write, name: &str) -> Result<()> { if name.is_empty() { write!(w, "")?; } else { write!(w, "{}", name)?; } Ok(()) } fn emit_return_type( &mut self, w: &mut impl Write, type_index: Option, attrs: FunctionAttributes, ) -> Result<()> { if !attrs.is_constructor() { if let Some(index) = type_index { self.emit_type_index(w, index)?; write!(w, " ")?; } } Ok(()) } /// Check if ptr points to the specified class, and if so, whether it points to const or non-const class. /// If it points to a different class than the one supplied in the `class` argument, don'a check constness. fn check_ptr_class(&mut self, ptr: TypeIndex, class: TypeIndex) -> Result { if let TypeData::Pointer(ptr_type) = self.parse_type_index(ptr)? { let underlying_type = ptr_type.underlying_type; if underlying_type == class { return Ok(PtrToClassKind::PtrToGivenClass { constant: false }); } let underlying_type_data = self.parse_type_index(underlying_type)?; if let TypeData::Modifier(modifier) = underlying_type_data { if modifier.underlying_type == class { return Ok(PtrToClassKind::PtrToGivenClass { constant: modifier.constant, }); } } }; Ok(PtrToClassKind::OtherType) } /// Return value: (this is pointer to const class, optional extra first argument) fn get_class_constness_and_extra_arguments( &mut self, this: TypeIndex, class: TypeIndex, ) -> Result<(bool, Option)> { match self.check_ptr_class(this, class)? { PtrToClassKind::PtrToGivenClass { constant } => { // The this type looks normal. Don'a return an extra argument. Ok((constant, None)) } PtrToClassKind::OtherType => { // The type of the "this" pointer did not match the class type. // This is arguably bad type information. // It looks like this bad type information is emitted for all Rust "associated // functions" whose first argument is a reference. Associated functions don'a // take a self argument, so it would make sense to treat them as static. // But instead, these functions are marked as non-static, and the first argument's // type, rather than being part of the arguments list, is stored in the "this" type. // For example, for ProfileScope::new(name: &'static CStr), the arguments list is // empty and the this type is CStr*. // To work around this, return the this type as an extra first argument. Ok((false, Some(this))) } } } fn emit_method_args( &mut self, w: &mut impl Write, method_type: MemberFunctionType, allow_emit_const: bool, ) -> Result<()> { if self.has_flags(TypeFormatterFlags::NO_ARGUMENTS) { return Ok(()); } let args_list = match self.parse_type_index(method_type.argument_list)? { TypeData::ArgumentList(t) => t, _ => { return Err(Error::ArgumentTypeNotArgumentList); } }; let (is_const_method, extra_first_arg) = match method_type.this_pointer_type { None => { // No this pointer - this is a static method. // Static methods cannot be const, and they have the correct arguments. (false, None) } Some(this_type) => { // For non-static methods, check whether the method is const, and work around a // problem with bad type information for Rust associated functions. self.get_class_constness_and_extra_arguments(this_type, method_type.class_type)? } }; write!(w, "(")?; if let Some(first_arg) = extra_first_arg { self.emit_type_index(w, first_arg)?; self.emit_arg_list(w, args_list, true)?; } else { self.emit_arg_list(w, args_list, false)?; } write!(w, ")")?; if is_const_method && allow_emit_const { write!(w, " const")?; } Ok(()) } // Should we emit a space as the first byte from emit_attributes? It depends. // "*" in a table cell means "value has no impact on the outcome". // // caller allows space | attributes start with | SPACE_BEFORE_POINTER mode | previous byte was | put space at the beginning? // ---------------------+-----------------------+---------------------------+---------------------+---------------------------- // no | * | * | * | no // yes | const | * | * | yes // yes | pointer sigil | off | * | no // yes | pointer sigil | on | pointer sigil | no // yes | pointer sigil | on | not a pointer sigil | yes fn emit_attributes( &mut self, w: &mut impl Write, attrs: Vec, allow_space_at_beginning: bool, mut previous_byte_was_pointer_sigil: bool, ) -> Result<()> { let mut is_at_beginning = true; for attr in attrs.iter().rev() { if attr.is_pointee_const { if !is_at_beginning || allow_space_at_beginning { write!(w, " ")?; } write!(w, "const")?; is_at_beginning = false; previous_byte_was_pointer_sigil = false; } if self.has_flags(TypeFormatterFlags::SPACE_BEFORE_POINTER) && !previous_byte_was_pointer_sigil && (!is_at_beginning || allow_space_at_beginning) { write!(w, " ")?; } is_at_beginning = false; match attr.mode { PointerMode::Pointer => write!(w, "*")?, PointerMode::LValueReference => write!(w, "&")?, PointerMode::Member => write!(w, "::*")?, PointerMode::MemberFunction => write!(w, "::*")?, PointerMode::RValueReference => write!(w, "&&")?, } previous_byte_was_pointer_sigil = true; if attr.is_pointer_const { write!(w, " const")?; previous_byte_was_pointer_sigil = false; } } Ok(()) } fn emit_member_ptr( &mut self, w: &mut impl Write, fun: MemberFunctionType, attributes: Vec, ) -> Result<()> { self.emit_return_type(w, Some(fun.return_type), fun.attributes)?; write!(w, "(")?; self.emit_type_index(w, fun.class_type)?; self.emit_attributes(w, attributes, false, false)?; write!(w, ")")?; self.emit_method_args(w, fun, false)?; Ok(()) } fn emit_proc_ptr( &mut self, w: &mut impl Write, fun: ProcedureType, attributes: Vec, ) -> Result<()> { self.emit_return_type(w, fun.return_type, fun.attributes)?; write!(w, "(")?; self.emit_attributes(w, attributes, false, false)?; write!(w, ")")?; write!(w, "(")?; self.emit_type_index(w, fun.argument_list)?; write!(w, ")")?; Ok(()) } fn emit_other_ptr( &mut self, w: &mut impl Write, type_data: TypeData, attributes: Vec, ) -> Result<()> { let mut buf = String::new(); self.emit_type(&mut buf, type_data)?; let previous_byte_was_pointer_sigil = buf .as_bytes() .last() .map(|&b| b == b'*' || b == b'&') .unwrap_or(false); w.write_str(&buf)?; self.emit_attributes(w, attributes, true, previous_byte_was_pointer_sigil)?; Ok(()) } fn emit_ptr_helper( &mut self, w: &mut impl Write, attributes: Vec, type_data: TypeData, ) -> Result<()> { match type_data { TypeData::MemberFunction(t) => self.emit_member_ptr(w, t, attributes)?, TypeData::Procedure(t) => self.emit_proc_ptr(w, t, attributes)?, _ => self.emit_other_ptr(w, type_data, attributes)?, }; Ok(()) } fn emit_ptr(&mut self, w: &mut impl Write, ptr: PointerType, is_const: bool) -> Result<()> { let mut attributes = vec![PtrAttributes { is_pointer_const: ptr.attributes.is_const() || is_const, is_pointee_const: false, mode: ptr.attributes.pointer_mode(), }]; let mut ptr = ptr; loop { let type_data = self.parse_type_index(ptr.underlying_type)?; match type_data { TypeData::Pointer(t) => { attributes.push(PtrAttributes { is_pointer_const: t.attributes.is_const(), is_pointee_const: false, mode: t.attributes.pointer_mode(), }); ptr = t; } TypeData::Modifier(t) => { // the vec cannot be empty since we push something in just before the loop attributes.last_mut().unwrap().is_pointee_const = t.constant; let underlying_type_data = self.parse_type_index(t.underlying_type)?; if let TypeData::Pointer(t) = underlying_type_data { attributes.push(PtrAttributes { is_pointer_const: t.attributes.is_const(), is_pointee_const: false, mode: t.attributes.pointer_mode(), }); ptr = t; } else { self.emit_ptr_helper(w, attributes, underlying_type_data)?; return Ok(()); } } _ => { self.emit_ptr_helper(w, attributes, type_data)?; return Ok(()); } } } } /// The returned Vec has the array dimensions in bytes, with the "lower" dimensions /// aggregated into the "higher" dimensions. fn get_array_info(&mut self, array: ArrayType) -> Result<(Vec, TypeIndex, TypeData<'a>)> { // For an array int[12][34] it'll be represented as "int[34] *". // For any reason the 12 is lost... // The internal representation is: Pointer{ base: Array{ base: int, dim: 34 * sizeof(int)} } let mut base = array; let mut dims = Vec::new(); dims.push(base.dimensions[0].into()); // See the documentation for ArrayType::dimensions: // // > Contains array dimensions as specified in the PDB. This is not what you expect: // > // > * Dimensions are specified in terms of byte sizes, not element counts. // > * Multidimensional arrays aggregate the lower dimensions into the sizes of the higher // > dimensions. // > // > Thus a `float[4][4]` has `dimensions: [16, 64]`. Determining array dimensions in terms // > of element counts requires determining the size of the `element_type` and iteratively // > dividing. // // XXXmstange the docs above imply that dimensions can have more than just one entry. // But this code only processes dimensions[0]. Is that a bug? loop { let type_index = base.element_type; let type_data = self.parse_type_index(type_index)?; match type_data { TypeData::Array(a) => { dims.push(a.dimensions[0].into()); base = a; } _ => { return Ok((dims, type_index, type_data)); } } } } fn emit_array(&mut self, w: &mut impl Write, array: ArrayType) -> Result<()> { let (dimensions_as_bytes, base_index, base) = self.get_array_info(array)?; let base_size = self.get_data_size(base_index, &base); self.emit_type(w, base)?; let mut iter = dimensions_as_bytes.into_iter().peekable(); while let Some(current_level_byte_size) = iter.next() { let next_level_byte_size = *iter.peek().unwrap_or(&base_size); if next_level_byte_size != 0 { let element_count = current_level_byte_size / next_level_byte_size; write!(w, "[{}]", element_count)?; } else { // The base size can be zero: struct A{}; void foo(A x[10]) // No way to get the array dimension in such a case write!(w, "[]")?; }; } Ok(()) } fn emit_modifier(&mut self, w: &mut impl Write, modifier: ModifierType) -> Result<()> { let type_data = self.parse_type_index(modifier.underlying_type)?; match type_data { TypeData::Pointer(ptr) => self.emit_ptr(w, ptr, modifier.constant)?, TypeData::Primitive(prim) => self.emit_primitive(w, prim, modifier.constant)?, _ => { if modifier.constant { write!(w, "const ")? } self.emit_type(w, type_data)?; } } Ok(()) } fn emit_class(&mut self, w: &mut impl Write, class: ClassType) -> Result<()> { if self.has_flags(TypeFormatterFlags::NAME_ONLY) { write!(w, "{}", class.name)?; } else { let name = match class.kind { ClassKind::Class => "class", ClassKind::Interface => "interface", ClassKind::Struct => "struct", }; write!(w, "{} {}", name, class.name)? } Ok(()) } fn emit_arg_list( &mut self, w: &mut impl Write, list: ArgumentList, comma_before_first: bool, ) -> Result<()> { if let Some((first, args)) = list.arguments.split_first() { if comma_before_first { write!(w, ",")?; if self.has_flags(TypeFormatterFlags::SPACE_AFTER_COMMA) { write!(w, " ")?; } } self.emit_type_index(w, *first)?; for index in args.iter() { write!(w, ",")?; if self.has_flags(TypeFormatterFlags::SPACE_AFTER_COMMA) { write!(w, " ")?; } self.emit_type_index(w, *index)?; } } Ok(()) } fn emit_primitive( &mut self, w: &mut impl Write, prim: PrimitiveType, is_const: bool, ) -> Result<()> { // TODO: check that these names are what we want to see let name = match prim.kind { PrimitiveKind::NoType => "", PrimitiveKind::Void => "void", PrimitiveKind::Char => "signed char", PrimitiveKind::UChar => "unsigned char", PrimitiveKind::RChar => "char", PrimitiveKind::WChar => "wchar_t", PrimitiveKind::RChar16 => "char16_t", PrimitiveKind::RChar32 => "char32_t", PrimitiveKind::I8 => "int8_t", PrimitiveKind::U8 => "uint8_t", PrimitiveKind::Short => "short", PrimitiveKind::UShort => "unsigned short", PrimitiveKind::I16 => "int16_t", PrimitiveKind::U16 => "uint16_t", PrimitiveKind::Long => "long", PrimitiveKind::ULong => "unsigned long", PrimitiveKind::I32 => "int", PrimitiveKind::U32 => "unsigned int", PrimitiveKind::Quad => "long long", PrimitiveKind::UQuad => "unsigned long long", PrimitiveKind::I64 => "int64_t", PrimitiveKind::U64 => "uint64_t", PrimitiveKind::I128 | PrimitiveKind::Octa => "int128_t", PrimitiveKind::U128 | PrimitiveKind::UOcta => "uint128_t", PrimitiveKind::F16 => "float16_t", PrimitiveKind::F32 => "float", PrimitiveKind::F32PP => "float", PrimitiveKind::F48 => "float48_t", PrimitiveKind::F64 => "double", PrimitiveKind::F80 => "long double", PrimitiveKind::F128 => "long double", PrimitiveKind::Complex32 => "complex", PrimitiveKind::Complex64 => "complex", PrimitiveKind::Complex80 => "complex", PrimitiveKind::Complex128 => "complex", PrimitiveKind::Bool8 => "bool", PrimitiveKind::Bool16 => "bool16_t", PrimitiveKind::Bool32 => "bool32_t", PrimitiveKind::Bool64 => "bool64_t", PrimitiveKind::HRESULT => "HRESULT", _ => panic!("Unknown PrimitiveKind {:?} in emit_primitive", prim.kind), }; if prim.indirection.is_some() { if self.has_flags(TypeFormatterFlags::SPACE_BEFORE_POINTER) { if is_const { write!(w, "{} const *", name)? } else { write!(w, "{} *", name)? } } else if is_const { write!(w, "{} const*", name)? } else { write!(w, "{}*", name)? } } else if is_const { write!(w, "const {}", name)? } else { write!(w, "{}", name)? } Ok(()) } fn emit_named(&mut self, w: &mut impl Write, base: &str, name: RawString) -> Result<()> { if self.has_flags(TypeFormatterFlags::NAME_ONLY) { write!(w, "{}", name)? } else { write!(w, "{} {}", base, name)? } Ok(()) } fn emit_type_index(&mut self, w: &mut impl Write, index: TypeIndex) -> Result<()> { match self.parse_type_index(index) { Ok(type_data) => self.emit_type(w, type_data), Err(Error::PdbError(pdb::Error::UnimplementedTypeKind(t))) => { write!(w, "", t)?; Ok(()) } Err(Error::PdbError(pdb::Error::TypeNotFound(type_index))) => { write!(w, "", type_index)?; Ok(()) } Err(e) => Err(e), } } fn emit_type(&mut self, w: &mut impl Write, type_data: TypeData) -> Result<()> { match self.emit_type_inner(w, type_data) { Ok(()) => Ok(()), Err(Error::PdbError(pdb::Error::TypeNotFound(type_index))) => { write!(w, "", type_index)?; Ok(()) } Err(e) => Err(e), } } fn emit_type_inner(&mut self, w: &mut impl Write, type_data: TypeData) -> Result<()> { match type_data { TypeData::Primitive(t) => self.emit_primitive(w, t, false)?, TypeData::Class(t) => self.emit_class(w, t)?, TypeData::MemberFunction(t) => { self.maybe_emit_return_type(w, Some(t.return_type), t.attributes)?; write!(w, "()")?; self.emit_method_args(w, t, false)?; } TypeData::Procedure(t) => { self.maybe_emit_return_type(w, t.return_type, t.attributes)?; write!(w, "()(")?; self.emit_type_index(w, t.argument_list)?; write!(w, "")?; } TypeData::ArgumentList(t) => self.emit_arg_list(w, t, false)?, TypeData::Pointer(t) => self.emit_ptr(w, t, false)?, TypeData::Array(t) => self.emit_array(w, t)?, TypeData::Union(t) => self.emit_named(w, "union", t.name)?, TypeData::Enumeration(t) => self.emit_named(w, "enum", t.name)?, TypeData::Enumerate(t) => self.emit_named(w, "enum class", t.name)?, TypeData::Modifier(t) => self.emit_modifier(w, t)?, _ => write!(w, "unhandled type /* {:?} */", type_data)?, } Ok(()) } } #[derive(Eq, PartialEq)] enum PtrToClassKind { PtrToGivenClass { /// If true, the pointer is a "pointer to const ClassType". constant: bool, }, OtherType, } #[derive(Debug)] struct PtrAttributes { is_pointer_const: bool, is_pointee_const: bool, mode: PointerMode, } struct ItemMap<'a, I: ItemIndex> { iter: ItemIter<'a, I>, finder: ItemFinder<'a, I>, } impl<'a, I> ItemMap<'a, I> where I: ItemIndex, { pub fn try_get(&mut self, index: I) -> std::result::Result, pdb::Error> { if index <= self.finder.max_index() { return self.finder.find(index); } while let Some(item) = self.iter.next()? { self.finder.update(&self.iter); match item.index().partial_cmp(&index) { Some(Ordering::Equal) => return Ok(item), Some(Ordering::Greater) => break, _ => continue, } } Err(pdb::Error::TypeNotFound(index.into())) } } type IdMap<'a> = ItemMap<'a, IdIndex>; type TypeMap<'a> = ItemMap<'a, TypeIndex>; struct TypeSizeCache<'a> { /// A hashmap that maps a type's (unique) name to its type size. /// /// When computing type sizes, special care must be taken for types which are /// marked as "forward references": For these types, the size must be taken from /// the occurrence of the type with the same (unique) name which is not marked as /// a forward reference. /// /// In order to be able to look up these sizes, we create a map which /// contains all sizes for non-forward_reference types. This map is populated on /// demand as the type iter is advanced. /// /// Type sizes are needed when computing array lengths based on byte lengths, when /// printing array types. They are also needed for the public get_type_size method. forward_ref_sizes: HashMap, u64>, cached_ranges: RangeSet2, } impl<'a> TypeSizeCache<'a> { pub fn get_size_for_forward_reference( &mut self, index: TypeIndex, name: RawString<'a>, type_map: &mut TypeMap<'a>, ) -> Option { if let Some(size) = self.forward_ref_sizes.get(&name) { return Some(*size); } let start_index = index.0; let candidate_range = RangeSet::from((start_index + 1)..); let uncached_ranges = &candidate_range - &self.cached_ranges; for uncached_range in uncached_ranges.iter() { let (range_start, range_end) = match uncached_range { RangeSetRange::Range(r) => (*r.start, Some(*r.end)), RangeSetRange::RangeFrom(r) => (*r.start, None), }; for index in range_start.. { if let Some(range_end) = range_end { if index >= range_end { break; } } if let Ok(item) = type_map.try_get(TypeIndex(index)) { let s = self.update_forward_ref_size_map(&item); if let Some((found_name, found_size)) = s { if found_name == name { self.cached_ranges |= RangeSet::from(start_index..(index + 1)); return Some(found_size); } } } else { break; } } } self.cached_ranges |= RangeSet::from(start_index..); None } pub fn update_forward_ref_size_map( &mut self, item: &Item<'a, TypeIndex>, ) -> Option<(RawString<'a>, u64)> { if let Ok(type_data) = item.parse() { match type_data { TypeData::Class(t) => { if !t.properties.forward_reference() { let name = t.unique_name.unwrap_or(t.name); self.forward_ref_sizes.insert(name, t.size); return Some((name, t.size)); } } TypeData::Union(t) => { if !t.properties.forward_reference() { let name = t.unique_name.unwrap_or(t.name); self.forward_ref_sizes.insert(name, t.size); return Some((name, t.size)); } } _ => {} } } None } }