addr2line-0.21.0/.cargo_vcs_info.json0000644000000001360000000000100127600ustar { "git": { "sha1": "a736feffcc9892b0d164bfe3f2a7ac1f2d78ff4d" }, "path_in_vcs": "" }addr2line-0.21.0/.gitignore000064400000000000000000000000411046102023000135330ustar 00000000000000target Cargo.lock *.png tmp.* *~ addr2line-0.21.0/CHANGELOG.md000064400000000000000000000224501046102023000133640ustar 00000000000000# `addr2line` Change Log -------------------------------------------------------------------------------- ## 0.21.0 (2023/08/12) ### Breaking changes * Updated `gimli`, `object`, and `fallible-iterator` dependencies. ### Changed * The minimum supported rust version is 1.65.0. * Store boxed slices instead of `Vec` objects in `Context`. [#278](https://github.com/gimli-rs/addr2line/pull/278) -------------------------------------------------------------------------------- ## 0.20.0 (2023/04/15) ### Breaking changes * The minimum supported rust version is 1.58.0. * Changed `Context::find_frames` to return `LookupResult`. Use `LookupResult::skip_all_loads` to obtain the result without loading split DWARF. [#260](https://github.com/gimli-rs/addr2line/pull/260) * Replaced `Context::find_dwarf_unit` with `Context::find_dwarf_and_unit`. [#260](https://github.com/gimli-rs/addr2line/pull/260) * Updated `object` dependency. ### Changed * Fix handling of file index 0 for DWARF 5. [#264](https://github.com/gimli-rs/addr2line/pull/264) ### Added * Added types and methods to support loading split DWARF: `LookupResult`, `SplitDwarfLoad`, `SplitDwarfLoader`, `Context::preload_units`. [#260](https://github.com/gimli-rs/addr2line/pull/260) [#262](https://github.com/gimli-rs/addr2line/pull/262) [#263](https://github.com/gimli-rs/addr2line/pull/263) -------------------------------------------------------------------------------- ## 0.19.0 (2022/11/24) ### Breaking changes * Updated `gimli` and `object` dependencies. -------------------------------------------------------------------------------- ## 0.18.0 (2022/07/16) ### Breaking changes * Updated `object` dependency. ### Changed * Fixed handling of relative path for `DW_AT_comp_dir`. [#239](https://github.com/gimli-rs/addr2line/pull/239) * Fixed handling of `DW_FORM_addrx` for DWARF 5 support. [#243](https://github.com/gimli-rs/addr2line/pull/243) * Fixed handling of units that are missing range information. [#249](https://github.com/gimli-rs/addr2line/pull/249) -------------------------------------------------------------------------------- ## 0.17.0 (2021/10/24) ### Breaking changes * Updated `gimli` and `object` dependencies. ### Changed * Use `skip_attributes` to improve performance. [#236](https://github.com/gimli-rs/addr2line/pull/236) -------------------------------------------------------------------------------- ## 0.16.0 (2021/07/26) ### Breaking changes * Updated `gimli` and `object` dependencies. -------------------------------------------------------------------------------- ## 0.15.2 (2021/06/04) ### Fixed * Allow `Context` to be `Send`. [#219](https://github.com/gimli-rs/addr2line/pull/219) -------------------------------------------------------------------------------- ## 0.15.1 (2021/05/02) ### Fixed * Don't ignore aranges with address 0. [#217](https://github.com/gimli-rs/addr2line/pull/217) -------------------------------------------------------------------------------- ## 0.15.0 (2021/05/02) ### Breaking changes * Updated `gimli` and `object` dependencies. [#215](https://github.com/gimli-rs/addr2line/pull/215) * Added `debug_aranges` parameter to `Context::from_sections`. [#200](https://github.com/gimli-rs/addr2line/pull/200) ### Added * Added `.debug_aranges` support. [#200](https://github.com/gimli-rs/addr2line/pull/200) * Added supplementary object file support. [#208](https://github.com/gimli-rs/addr2line/pull/208) ### Fixed * Fixed handling of Windows paths in locations. [#209](https://github.com/gimli-rs/addr2line/pull/209) * examples/addr2line: Flush stdout after each response. [#210](https://github.com/gimli-rs/addr2line/pull/210) * examples/addr2line: Avoid copying every section. [#213](https://github.com/gimli-rs/addr2line/pull/213) -------------------------------------------------------------------------------- ## 0.14.1 (2020/12/31) ### Fixed * Fix location lookup for skeleton units. [#201](https://github.com/gimli-rs/addr2line/pull/201) ### Added * Added `Context::find_location_range`. [#196](https://github.com/gimli-rs/addr2line/pull/196) [#199](https://github.com/gimli-rs/addr2line/pull/199) -------------------------------------------------------------------------------- ## 0.14.0 (2020/10/27) ### Breaking changes * Updated `gimli` and `object` dependencies. ### Fixed * Handle units that only have line information. [#188](https://github.com/gimli-rs/addr2line/pull/188) * Handle DWARF units with version <= 4 and no `DW_AT_name`. [#191](https://github.com/gimli-rs/addr2line/pull/191) * Fix handling of `DW_FORM_ref_addr`. [#193](https://github.com/gimli-rs/addr2line/pull/193) -------------------------------------------------------------------------------- ## 0.13.0 (2020/07/07) ### Breaking changes * Updated `gimli` and `object` dependencies. * Added `rustc-dep-of-std` feature. [#166](https://github.com/gimli-rs/addr2line/pull/166) ### Changed * Improve performance by parsing function contents lazily. [#178](https://github.com/gimli-rs/addr2line/pull/178) * Don't skip `.debug_info` and `.debug_line` entries with a zero address. [#182](https://github.com/gimli-rs/addr2line/pull/182) -------------------------------------------------------------------------------- ## 0.12.2 (2020/06/21) ### Fixed * Avoid linear search for `DW_FORM_ref_addr`. [#175](https://github.com/gimli-rs/addr2line/pull/175) -------------------------------------------------------------------------------- ## 0.12.1 (2020/05/19) ### Fixed * Handle units with overlapping address ranges. [#163](https://github.com/gimli-rs/addr2line/pull/163) * Don't assert for functions with overlapping address ranges. [#168](https://github.com/gimli-rs/addr2line/pull/168) -------------------------------------------------------------------------------- ## 0.12.0 (2020/05/12) ### Breaking changes * Updated `gimli` and `object` dependencies. * Added more optional features: `smallvec` and `fallible-iterator`. [#160](https://github.com/gimli-rs/addr2line/pull/160) ### Added * Added `Context::dwarf` and `Context::find_dwarf_unit`. [#159](https://github.com/gimli-rs/addr2line/pull/159) ### Changed * Removed `lazycell` dependency. [#160](https://github.com/gimli-rs/addr2line/pull/160) -------------------------------------------------------------------------------- ## 0.11.0 (2020/01/11) ### Breaking changes * Updated `gimli` and `object` dependencies. * [#130](https://github.com/gimli-rs/addr2line/pull/130) Changed `Location::file` from `Option` to `Option<&str>`. This required adding lifetime parameters to `Location` and other structs that contain it. * [#152](https://github.com/gimli-rs/addr2line/pull/152) Changed `Location::line` and `Location::column` from `Option`to `Option`. * [#156](https://github.com/gimli-rs/addr2line/pull/156) Deleted `alloc` feature, and fixed `no-std` builds with stable rust. Removed default `Reader` parameter for `Context`, and added `ObjectContext` instead. ### Added * [#134](https://github.com/gimli-rs/addr2line/pull/134) Added `Context::from_dwarf`. ### Changed * [#133](https://github.com/gimli-rs/addr2line/pull/133) Fixed handling of units that can't be parsed. * [#155](https://github.com/gimli-rs/addr2line/pull/155) Fixed `addr2line` output to match binutils. * [#130](https://github.com/gimli-rs/addr2line/pull/130) Improved `.debug_line` parsing performance. * [#148](https://github.com/gimli-rs/addr2line/pull/148) [#150](https://github.com/gimli-rs/addr2line/pull/150) [#151](https://github.com/gimli-rs/addr2line/pull/151) [#152](https://github.com/gimli-rs/addr2line/pull/152) Improved `.debug_info` parsing performance. * [#137](https://github.com/gimli-rs/addr2line/pull/137) [#138](https://github.com/gimli-rs/addr2line/pull/138) [#139](https://github.com/gimli-rs/addr2line/pull/139) [#140](https://github.com/gimli-rs/addr2line/pull/140) [#146](https://github.com/gimli-rs/addr2line/pull/146) Improved benchmarks. -------------------------------------------------------------------------------- ## 0.10.0 (2019/07/07) ### Breaking changes * [#127](https://github.com/gimli-rs/addr2line/pull/127) Update `gimli`. -------------------------------------------------------------------------------- ## 0.9.0 (2019/05/02) ### Breaking changes * [#121](https://github.com/gimli-rs/addr2line/pull/121) Update `gimli`, `object`, and `fallible-iterator` dependencies. ### Added * [#121](https://github.com/gimli-rs/addr2line/pull/121) Reexport `gimli`, `object`, and `fallible-iterator`. -------------------------------------------------------------------------------- ## 0.8.0 (2019/02/06) ### Breaking changes * [#107](https://github.com/gimli-rs/addr2line/pull/107) Update `object` dependency to 0.11. This is part of the public API. ### Added * [#101](https://github.com/gimli-rs/addr2line/pull/101) Add `object` feature (enabled by default). Disable this feature to remove the `object` dependency and `Context::new` API. * [#102](https://github.com/gimli-rs/addr2line/pull/102) Add `std` (enabled by default) and `alloc` features. ### Changed * [#108](https://github.com/gimli-rs/addr2line/issues/108) `demangle` no longer outputs the hash for rust symbols. * [#109](https://github.com/gimli-rs/addr2line/issues/109) Set default `R` for `Context`. addr2line-0.21.0/Cargo.lock0000644000000446240000000000100107450ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" dependencies = [ "gimli 0.27.2", ] [[package]] name = "addr2line" version = "0.21.0" dependencies = [ "backtrace", "clap", "compiler_builtins", "cpp_demangle", "fallible-iterator", "findshlibs", "gimli 0.28.0", "libtest-mimic", "memmap2", "object 0.32.0", "rustc-demangle", "rustc-std-workspace-alloc", "rustc-std-workspace-core", "smallvec", "typed-arena", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "anstream" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is-terminal", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" [[package]] name = "anstyle-parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", "windows-sys", ] [[package]] name = "backtrace" version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ "addr2line 0.19.0", "cc", "cfg-if", "libc", "miniz_oxide", "object 0.30.3", "rustc-demangle", ] [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" dependencies = [ "clap_builder", "clap_derive", "once_cell", ] [[package]] name = "clap_builder" version = "4.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", "terminal_size", ] [[package]] name = "clap_derive" version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.15", ] [[package]] name = "clap_lex" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "compiler_builtins" version = "0.1.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "571298a3cce7e2afbd3d61abb91a18667d5ab25993ec577a88ee8ac45f00cc3a" [[package]] name = "cpp_demangle" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c76f98bdfc7f66172e6c7065f981ebb576ffc903fe4c0561d9f0c2509226dc6" dependencies = [ "cfg-if", ] [[package]] name = "crc32fast" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] name = "errno" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", "windows-sys", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "fallible-iterator" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "findshlibs" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" dependencies = [ "cc", "lazy_static", "libc", "winapi", ] [[package]] name = "flate2" version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "gimli" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" [[package]] name = "gimli" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" dependencies = [ "compiler_builtins", "fallible-iterator", "rustc-std-workspace-alloc", "rustc-std-workspace-core", "stable_deref_trait", ] [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] [[package]] name = "hermit-abi" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "io-lifetimes" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.2", "libc", "windows-sys", ] [[package]] name = "is-terminal" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.2", "rustix 0.38.8", "windows-sys", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libtest-mimic" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d8de370f98a6cb8a4606618e53e802f93b094ddec0f96988eaec2c27e6e9ce7" dependencies = [ "clap", "termcolor", "threadpool", ] [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" dependencies = [ "libc", ] [[package]] name = "miniz_oxide" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "num_cpus" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ "hermit-abi 0.2.6", "libc", ] [[package]] name = "object" version = "0.30.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" dependencies = [ "memchr", ] [[package]] name = "object" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" dependencies = [ "flate2", "memchr", "ruzstd", ] [[package]] name = "once_cell" version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "proc-macro2" version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] [[package]] name = "rustc-demangle" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b" [[package]] name = "rustc-std-workspace-alloc" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff66d57013a5686e1917ed6a025d54dd591fcda71a41fe07edf4d16726aefa86" [[package]] name = "rustc-std-workspace-core" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1956f5517128a2b6f23ab2dadf1a976f4f5b27962e7724c2bf3d45e539ec098c" [[package]] name = "rustix" version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ "bitflags 1.3.2", "errno", "io-lifetimes", "libc", "linux-raw-sys 0.3.8", "windows-sys", ] [[package]] name = "rustix" version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ "bitflags 2.4.0", "errno", "libc", "linux-raw-sys 0.4.5", "windows-sys", ] [[package]] name = "ruzstd" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3ffab8f9715a0d455df4bbb9d21e91135aab3cd3ca187af0cd0c3c3f868fdc" dependencies = [ "byteorder", "thiserror-core", "twox-hash", ] [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[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.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "termcolor" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "terminal_size" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ "rustix 0.37.23", "windows-sys", ] [[package]] name = "thiserror-core" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d97345f6437bb2004cd58819d8a9ef8e36cdd7661c2abc4bbde0a7c40d9f497" dependencies = [ "thiserror-core-impl", ] [[package]] name = "thiserror-core-impl" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10ac1c5050e43014d16b2f94d0d2ce79e65ffdd8b38d8048f9c8f6a8a6da62ac" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "threadpool" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" dependencies = [ "num_cpus", ] [[package]] name = "twox-hash" version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "static_assertions", ] [[package]] name = "typed-arena" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" 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.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" addr2line-0.21.0/Cargo.toml0000644000000054100000000000100107560ustar # 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" rust-version = "1.65" name = "addr2line" version = "0.21.0" exclude = [ "/benches/*", "/fixtures/*", ".github", ] description = "A cross-platform symbolication library written in Rust, using `gimli`" documentation = "https://docs.rs/addr2line" readme = "./README.md" keywords = [ "DWARF", "debug", "elf", "symbolicate", "atos", ] categories = ["development-tools::debugging"] license = "Apache-2.0 OR MIT" repository = "https://github.com/gimli-rs/addr2line" [profile.bench] codegen-units = 1 debug = true [profile.release] debug = true [[example]] name = "addr2line" required-features = ["default"] [[test]] name = "output_equivalence" harness = false required-features = ["default"] [[test]] name = "correctness" required-features = ["default"] [[test]] name = "parse" required-features = ["std-object"] [dependencies.alloc] version = "1.0.0" optional = true package = "rustc-std-workspace-alloc" [dependencies.compiler_builtins] version = "0.1.2" optional = true [dependencies.core] version = "1.0.0" optional = true package = "rustc-std-workspace-core" [dependencies.cpp_demangle] version = "0.4" features = ["alloc"] optional = true default-features = false [dependencies.fallible-iterator] version = "0.3.0" optional = true default-features = false [dependencies.gimli] version = "0.28.0" features = ["read"] default-features = false [dependencies.memmap2] version = "0.5.5" optional = true [dependencies.object] version = "0.32.0" features = ["read"] optional = true default-features = false [dependencies.rustc-demangle] version = "0.1" optional = true [dependencies.smallvec] version = "1" optional = true default-features = false [dev-dependencies.backtrace] version = "0.3.13" [dev-dependencies.clap] version = "4.3.21" features = ["wrap_help"] [dev-dependencies.findshlibs] version = "0.10" [dev-dependencies.libtest-mimic] version = "0.6.1" [dev-dependencies.typed-arena] version = "2" [features] default = [ "rustc-demangle", "cpp_demangle", "std-object", "fallible-iterator", "smallvec", "memmap2", ] rustc-dep-of-std = [ "core", "alloc", "compiler_builtins", "gimli/rustc-dep-of-std", ] std = ["gimli/std"] std-object = [ "std", "object", "object/std", "object/compression", "gimli/endian-reader", ] addr2line-0.21.0/Cargo.toml.orig000064400000000000000000000044551046102023000144470ustar 00000000000000[package] name = "addr2line" version = "0.21.0" description = "A cross-platform symbolication library written in Rust, using `gimli`" documentation = "https://docs.rs/addr2line" exclude = ["/benches/*", "/fixtures/*", ".github"] keywords = ["DWARF", "debug", "elf", "symbolicate", "atos"] categories = ["development-tools::debugging"] license = "Apache-2.0 OR MIT" readme = "./README.md" repository = "https://github.com/gimli-rs/addr2line" edition = "2018" rust-version = "1.65" [dependencies] gimli = { version = "0.28.0", default-features = false, features = ["read"] } fallible-iterator = { version = "0.3.0", default-features = false, optional = true } memmap2 = { version = "0.5.5", optional = true } object = { version = "0.32.0", default-features = false, features = ["read"], optional = true } smallvec = { version = "1", default-features = false, optional = true } rustc-demangle = { version = "0.1", optional = true } cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true } # Internal feature, only used when building as part of libstd, not part of the # stable interface of this crate. core = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-core' } alloc = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-alloc' } compiler_builtins = { version = '0.1.2', optional = true } [dev-dependencies] clap = { version = "4.3.21", features = ["wrap_help"] } backtrace = "0.3.13" findshlibs = "0.10" libtest-mimic = "0.6.1" auxiliary = { path = "tests/auxiliary" } typed-arena = "2" [profile.release] debug = true [profile.bench] debug = true codegen-units = 1 [features] default = ["rustc-demangle", "cpp_demangle", "std-object", "fallible-iterator", "smallvec", "memmap2"] std = ["gimli/std"] std-object = ["std", "object", "object/std", "object/compression", "gimli/endian-reader"] # Internal feature, only used when building as part of libstd, not part of the # stable interface of this crate. rustc-dep-of-std = ['core', 'alloc', 'compiler_builtins', 'gimli/rustc-dep-of-std'] [[test]] name = "output_equivalence" harness = false required-features = ["default"] [[test]] name = "correctness" required-features = ["default"] [[test]] name = "parse" required-features = ["std-object"] [[example]] name = "addr2line" required-features = ["default"] addr2line-0.21.0/LICENSE-APACHE000064400000000000000000000251371046102023000135040ustar 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. addr2line-0.21.0/LICENSE-MIT000064400000000000000000000020551046102023000132060ustar 00000000000000Copyright (c) 2016-2018 The gimli Developers 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. addr2line-0.21.0/README.md000064400000000000000000000047661046102023000130440ustar 00000000000000# addr2line [![](https://img.shields.io/crates/v/addr2line.svg)](https://crates.io/crates/addr2line) [![](https://img.shields.io/docsrs/addr2line.svg)](https://docs.rs/addr2line) [![Coverage Status](https://coveralls.io/repos/github/gimli-rs/addr2line/badge.svg?branch=master)](https://coveralls.io/github/gimli-rs/addr2line?branch=master) A cross-platform library for retrieving per-address debug information from files with DWARF debug information. `addr2line` uses [`gimli`](https://github.com/gimli-rs/gimli) to parse the debug information, and exposes an interface for finding the source file, line number, and wrapping function for instruction addresses within the target program. These lookups can either be performed programmatically through `Context::find_location` and `Context::find_frames`, or via the included example binary, `addr2line` (named and modelled after the equivalent utility from [GNU binutils](https://sourceware.org/binutils/docs/binutils/addr2line.html)). # Quickstart - Add the [`addr2line` crate](https://crates.io/crates/addr2line) to your `Cargo.toml` - Load the file and parse it with [`addr2line::object::read::File::parse`](https://docs.rs/object/*/object/read/struct.File.html#method.parse) - Pass the parsed file to [`addr2line::Context::new` ](https://docs.rs/addr2line/*/addr2line/struct.Context.html#method.new) - Use [`addr2line::Context::find_location`](https://docs.rs/addr2line/*/addr2line/struct.Context.html#method.find_location) or [`addr2line::Context::find_frames`](https://docs.rs/addr2line/*/addr2line/struct.Context.html#method.find_frames) to look up debug information for an address # Performance `addr2line` optimizes for speed over memory by caching parsed information. The DWARF information is parsed lazily where possible. The library aims to perform similarly to equivalent existing tools such as `addr2line` from binutils, `eu-addr2line` from elfutils, and `llvm-symbolize` from the llvm project, and in the past some benchmarking was done that indicates a comparable performance. ## License Licensed under either of * Apache License, Version 2.0 ([`LICENSE-APACHE`](./LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) * MIT license ([`LICENSE-MIT`](./LICENSE-MIT) or https://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. addr2line-0.21.0/bench.plot.r000064400000000000000000000017501046102023000137720ustar 00000000000000v <- read.table(file("stdin")) t <- data.frame(prog=v[,1], funcs=(v[,2]=="func"), time=v[,3], mem=v[,4], stringsAsFactors=FALSE) t$prog <- as.character(t$prog) t$prog[t$prog == "master"] <- "gimli-rs/addr2line" t$funcs[t$funcs == TRUE] <- "With functions" t$funcs[t$funcs == FALSE] <- "File/line only" t$mem = t$mem / 1024.0 library(ggplot2) p <- ggplot(data=t, aes(x=prog, y=time, fill=prog)) p <- p + geom_bar(stat = "identity") p <- p + facet_wrap(~ funcs) p <- p + theme(axis.title.x=element_blank(), axis.text.x=element_blank(), axis.ticks.x=element_blank()) p <- p + ylab("time (s)") + ggtitle("addr2line runtime") ggsave('time.png',plot=p,width=10,height=6) p <- ggplot(data=t, aes(x=prog, y=mem, fill=prog)) p <- p + geom_bar(stat = "identity") p <- p + facet_wrap(~ funcs) p <- p + theme(axis.title.x=element_blank(), axis.text.x=element_blank(), axis.ticks.x=element_blank()) p <- p + ylab("memory (kB)") + ggtitle("addr2line memory usage") ggsave('memory.png',plot=p,width=10,height=6) addr2line-0.21.0/benchmark.sh000075500000000000000000000060141046102023000140420ustar 00000000000000#!/bin/bash if [[ $# -le 1 ]]; then echo "Usage: $0 [] REFS..." exit 1 fi target="$1" shift addresses="" if [[ -e "$1" ]]; then addresses="$1" shift fi # path to "us" # readlink -f, but more portable: dirname=$(perl -e 'use Cwd "abs_path";print abs_path(shift)' "$(dirname "$0")") # https://stackoverflow.com/a/2358432/472927 { # compile all refs pushd "$dirname" > /dev/null # if the user has some local changes, preserve them nstashed=$(git stash list | wc -l) echo "==> Stashing any local modifications" git stash --keep-index > /dev/null popstash() { # https://stackoverflow.com/q/24520791/472927 if [[ "$(git stash list | wc -l)" -ne "$nstashed" ]]; then echo "==> Restoring stashed state" git stash pop > /dev/null fi } # if the user has added stuff to the index, abort if ! git diff-index --quiet HEAD --; then echo "Refusing to overwrite outstanding git changes" popstash exit 2 fi current=$(git symbolic-ref --short HEAD) for ref in "$@"; do echo "==> Compiling $ref" git checkout -q "$ref" commit=$(git rev-parse HEAD) fn="target/release/addr2line-$commit" if [[ ! -e "$fn" ]]; then cargo build --release --example addr2line cp target/release/examples/addr2line "$fn" fi if [[ "$ref" != "$commit" ]]; then ln -sfn "addr2line-$commit" target/release/addr2line-"$ref" fi done git checkout -q "$current" popstash popd > /dev/null # get us some addresses to look up if [[ -z "$addresses" ]]; then echo "==> Looking for benchmarking addresses (this may take a while)" addresses=$(mktemp tmp.XXXXXXXXXX) objdump -C -x --disassemble -l "$target" \ | grep -P '0[048]:' \ | awk '{print $1}' \ | sed 's/:$//' \ > "$addresses" echo " -> Addresses stored in $addresses; you should re-use it next time" fi run() { func="$1" name="$2" cmd="$3" args="$4" printf "%s\t%s\t" "$name" "$func" if [[ "$cmd" =~ llvm-symbolizer ]]; then /usr/bin/time -f '%e\t%M' "$cmd" $args -obj="$target" < "$addresses" 2>&1 >/dev/null else /usr/bin/time -f '%e\t%M' "$cmd" $args -e "$target" < "$addresses" 2>&1 >/dev/null fi } # run without functions log1=$(mktemp tmp.XXXXXXXXXX) echo "==> Benchmarking" run nofunc binutils addr2line >> "$log1" #run nofunc elfutils eu-addr2line >> "$log1" run nofunc llvm-sym llvm-symbolizer -functions=none >> "$log1" for ref in "$@"; do run nofunc "$ref" "$dirname/target/release/addr2line-$ref" >> "$log1" done cat "$log1" | column -t # run with functions log2=$(mktemp tmp.XXXXXXXXXX) echo "==> Benchmarking with -f" run func binutils addr2line "-f -i" >> "$log2" #run func elfutils eu-addr2line "-f -i" >> "$log2" run func llvm-sym llvm-symbolizer "-functions=linkage -demangle=0" >> "$log2" for ref in "$@"; do run func "$ref" "$dirname/target/release/addr2line-$ref" "-f -i" >> "$log2" done cat "$log2" | column -t cat "$log2" >> "$log1"; rm "$log2" echo "==> Plotting" Rscript --no-readline --no-restore --no-save "$dirname/bench.plot.r" < "$log1" echo "==> Cleaning up" rm "$log1" exit 0 } addr2line-0.21.0/coverage.sh000064400000000000000000000002611046102023000136760ustar 00000000000000#!/bin/sh # Run tarpaulin and pycobertura to generate coverage.html. cargo tarpaulin --skip-clean --out Xml pycobertura show --format html --output coverage.html cobertura.xml addr2line-0.21.0/examples/addr2line.rs000064400000000000000000000247431046102023000156120ustar 00000000000000use std::borrow::Cow; use std::fs::File; use std::io::{BufRead, Lines, StdinLock, Write}; use std::path::{Path, PathBuf}; use clap::{Arg, ArgAction, Command}; use fallible_iterator::FallibleIterator; use object::{Object, ObjectSection, SymbolMap, SymbolMapName}; use typed_arena::Arena; use addr2line::{Context, Location}; fn parse_uint_from_hex_string(string: &str) -> Option { if string.len() > 2 && string.starts_with("0x") { u64::from_str_radix(&string[2..], 16).ok() } else { u64::from_str_radix(string, 16).ok() } } enum Addrs<'a> { Args(clap::parser::ValuesRef<'a, String>), Stdin(Lines>), } impl<'a> Iterator for Addrs<'a> { type Item = Option; 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(loc: Option<&Location<'_>>, basenames: bool, llvm: bool) { if let Some(loc) = loc { if let Some(ref file) = loc.file.as_ref() { let path = if basenames { Path::new(Path::new(file).file_name().unwrap()) } else { Path::new(file) }; print!("{}:", path.display()); } else { print!("??:"); } if llvm { print!("{}:{}", loc.line.unwrap_or(0), loc.column.unwrap_or(0)); } else if let Some(line) = loc.line { print!("{}", line); } else { print!("?"); } println!(); } else if llvm { println!("??:0:0"); } else { println!("??:0"); } } fn print_function(name: Option<&str>, language: Option, demangle: bool) { if let Some(name) = name { if demangle { print!("{}", addr2line::demangle_auto(Cow::from(name), language)); } else { print!("{}", name); } } else { print!("??"); } } fn load_file_section<'input, 'arena, Endian: gimli::Endianity>( id: gimli::SectionId, file: &object::File<'input>, endian: Endian, arena_data: &'arena Arena>, ) -> Result, ()> { // TODO: Unify with dwarfdump.rs in gimli. let name = id.name(); match file.section_by_name(name) { Some(section) => match section.uncompressed_data().unwrap() { Cow::Borrowed(b) => Ok(gimli::EndianSlice::new(b, endian)), Cow::Owned(b) => Ok(gimli::EndianSlice::new(arena_data.alloc(b.into()), endian)), }, None => Ok(gimli::EndianSlice::new(&[][..], endian)), } } fn find_name_from_symbols<'a>( symbols: &'a SymbolMap>, probe: u64, ) -> Option<&'a str> { symbols.get(probe).map(|x| x.name()) } struct Options<'a> { do_functions: bool, do_inlines: bool, pretty: bool, print_addrs: bool, basenames: bool, demangle: bool, llvm: bool, exe: &'a PathBuf, sup: Option<&'a PathBuf>, } fn main() { let matches = Command::new("addr2line") .version(env!("CARGO_PKG_VERSION")) .about("A fast addr2line Rust port") .max_term_width(100) .args(&[ Arg::new("exe") .short('e') .long("exe") .value_name("filename") .value_parser(clap::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") .value_parser(clap::value_parser!(PathBuf)) .help("Path to supplementary object file."), Arg::new("functions") .short('f') .long("functions") .action(ArgAction::SetTrue) .help("Display function names as well as file and line number information."), Arg::new("pretty").short('p').long("pretty-print") .action(ArgAction::SetTrue) .help( "Make the output more human friendly: each location are printed on one line.", ), Arg::new("inlines").short('i').long("inlines") .action(ArgAction::SetTrue) .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").short('a').long("addresses") .action(ArgAction::SetTrue) .help( "Display the address before the function name, file and line number information.", ), Arg::new("basenames") .short('s') .long("basenames") .action(ArgAction::SetTrue) .help("Display only the base of each file name."), Arg::new("demangle").short('C').long("demangle") .action(ArgAction::SetTrue) .help( "Demangle function names. \ Specifying a specific demangling style (like GNU addr2line) is not supported. \ (TODO)" ), Arg::new("llvm") .long("llvm") .action(ArgAction::SetTrue) .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 arena_data = Arena::new(); let opts = Options { do_functions: matches.get_flag("functions"), do_inlines: matches.get_flag("inlines"), pretty: matches.get_flag("pretty"), print_addrs: matches.get_flag("addresses"), basenames: matches.get_flag("basenames"), demangle: matches.get_flag("demangle"), llvm: matches.get_flag("llvm"), exe: matches.get_one::("exe").unwrap(), sup: matches.get_one::("sup"), }; let file = File::open(opts.exe).unwrap(); let map = unsafe { memmap2::Mmap::map(&file).unwrap() }; let object = &object::File::parse(&*map).unwrap(); let endian = if object.is_little_endian() { gimli::RunTimeEndian::Little } else { gimli::RunTimeEndian::Big }; let mut load_section = |id: gimli::SectionId| -> Result<_, _> { load_file_section(id, object, endian, &arena_data) }; let sup_map; let sup_object = if let Some(sup_path) = opts.sup { let sup_file = File::open(sup_path).unwrap(); sup_map = unsafe { memmap2::Mmap::map(&sup_file).unwrap() }; Some(object::File::parse(&*sup_map).unwrap()) } else { None }; let symbols = object.symbol_map(); let mut dwarf = gimli::Dwarf::load(&mut load_section).unwrap(); if let Some(ref sup_object) = sup_object { let mut load_sup_section = |id: gimli::SectionId| -> Result<_, _> { load_file_section(id, sup_object, endian, &arena_data) }; dwarf.load_sup(&mut load_sup_section).unwrap(); } let mut split_dwarf_loader = addr2line::builtin_split_dwarf_loader::SplitDwarfLoader::new( |data, endian| { gimli::EndianSlice::new(arena_data.alloc(Cow::Owned(data.into_owned())), endian) }, Some(opts.exe.clone()), ); let ctx = Context::from_dwarf(dwarf).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 opts.print_addrs { let addr = probe.unwrap_or(0); if opts.llvm { print!("0x{:x}", addr); } else { print!("0x{:016x}", addr); } if opts.pretty { print!(": "); } else { println!(); } } if opts.do_functions || opts.do_inlines { let mut printed_anything = false; if let Some(probe) = probe { let frames = ctx.find_frames(probe); let frames = split_dwarf_loader.run(frames).unwrap(); let mut frames = frames.enumerate(); while let Some((i, frame)) = frames.next().unwrap() { if opts.pretty && i != 0 { print!(" (inlined by) "); } if opts.do_functions { if let Some(func) = frame.function { print_function( func.raw_name().ok().as_ref().map(AsRef::as_ref), func.language, opts.demangle, ); } else { let name = find_name_from_symbols(&symbols, probe); print_function(name, None, opts.demangle); } if opts.pretty { print!(" at "); } else { println!(); } } print_loc(frame.location.as_ref(), opts.basenames, opts.llvm); printed_anything = true; if !opts.do_inlines { break; } } } if !printed_anything { if opts.do_functions { let name = probe.and_then(|probe| find_name_from_symbols(&symbols, probe)); print_function(name, None, opts.demangle); if opts.pretty { print!(" at "); } else { println!(); } } print_loc(None, opts.basenames, opts.llvm); } } else { let loc = probe.and_then(|probe| ctx.find_location(probe).unwrap()); print_loc(loc.as_ref(), opts.basenames, opts.llvm); } if opts.llvm { println!(); } std::io::stdout().flush().unwrap(); } } addr2line-0.21.0/rustfmt.toml000064400000000000000000000000011046102023000141400ustar 00000000000000 addr2line-0.21.0/src/builtin_split_dwarf_loader.rs000064400000000000000000000122411046102023000202770ustar 00000000000000use alloc::borrow::Cow; use alloc::sync::Arc; use std::fs::File; use std::path::PathBuf; use object::Object; use crate::{LookupContinuation, LookupResult}; #[cfg(unix)] fn convert_path>( r: &R, ) -> Result { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; let bytes = r.to_slice()?; let s = OsStr::from_bytes(&bytes); Ok(PathBuf::from(s)) } #[cfg(not(unix))] fn convert_path>( r: &R, ) -> Result { let bytes = r.to_slice()?; let s = std::str::from_utf8(&bytes).map_err(|_| gimli::Error::BadUtf8)?; Ok(PathBuf::from(s)) } fn load_section<'data: 'file, 'file, O, R, F>( id: gimli::SectionId, file: &'file O, endian: R::Endian, loader: &mut F, ) -> Result where O: object::Object<'data, 'file>, R: gimli::Reader, F: FnMut(Cow<'data, [u8]>, R::Endian) -> R, { use object::ObjectSection; let data = id .dwo_name() .and_then(|dwo_name| { file.section_by_name(dwo_name) .and_then(|section| section.uncompressed_data().ok()) }) .unwrap_or(Cow::Borrowed(&[])); Ok(loader(data, endian)) } /// A simple builtin split DWARF loader. pub struct SplitDwarfLoader where R: gimli::Reader, F: FnMut(Cow<'_, [u8]>, R::Endian) -> R, { loader: F, dwarf_package: Option>, } impl SplitDwarfLoader where R: gimli::Reader, F: FnMut(Cow<'_, [u8]>, R::Endian) -> R, { fn load_dwarf_package(loader: &mut F, path: Option) -> Option> { let mut path = path.map(Ok).unwrap_or_else(std::env::current_exe).ok()?; let dwp_extension = path .extension() .map(|previous_extension| { let mut previous_extension = previous_extension.to_os_string(); previous_extension.push(".dwp"); previous_extension }) .unwrap_or_else(|| "dwp".into()); path.set_extension(dwp_extension); let file = File::open(&path).ok()?; let map = unsafe { memmap2::Mmap::map(&file).ok()? }; let dwp = object::File::parse(&*map).ok()?; let endian = if dwp.is_little_endian() { gimli::RunTimeEndian::Little } else { gimli::RunTimeEndian::Big }; let empty = loader(Cow::Borrowed(&[]), endian); gimli::DwarfPackage::load( |section_id| load_section(section_id, &dwp, endian, loader), empty, ) .ok() } /// Create a new split DWARF loader. pub fn new(mut loader: F, path: Option) -> SplitDwarfLoader { let dwarf_package = SplitDwarfLoader::load_dwarf_package(&mut loader, path); SplitDwarfLoader { loader, dwarf_package, } } /// Run the provided `LookupResult` to completion, loading any necessary /// split DWARF along the way. pub fn run(&mut self, mut l: LookupResult) -> L::Output where L: LookupContinuation, { loop { let (load, continuation) = match l { LookupResult::Output(output) => break output, LookupResult::Load { load, continuation } => (load, continuation), }; let mut r: Option>> = None; if let Some(dwp) = self.dwarf_package.as_ref() { if let Ok(Some(cu)) = dwp.find_cu(load.dwo_id, &load.parent) { r = Some(Arc::new(cu)); } } if r.is_none() { let mut path = PathBuf::new(); if let Some(p) = load.comp_dir.as_ref() { if let Ok(p) = convert_path(p) { path.push(p); } } if let Some(p) = load.path.as_ref() { if let Ok(p) = convert_path(p) { path.push(p); } } if let Ok(file) = File::open(&path) { if let Ok(map) = unsafe { memmap2::Mmap::map(&file) } { if let Ok(file) = object::File::parse(&*map) { let endian = if file.is_little_endian() { gimli::RunTimeEndian::Little } else { gimli::RunTimeEndian::Big }; r = gimli::Dwarf::load(|id| { load_section(id, &file, endian, &mut self.loader) }) .ok() .map(|mut dwo_dwarf| { dwo_dwarf.make_dwo(&load.parent); Arc::new(dwo_dwarf) }); } } } } l = continuation.resume(r); } } } addr2line-0.21.0/src/function.rs000064400000000000000000000510701046102023000145350ustar 00000000000000use alloc::boxed::Box; use alloc::vec::Vec; use core::cmp::Ordering; use core::iter; use crate::lazy::LazyCell; use crate::maybe_small; use crate::{Context, DebugFile, Error, RangeAttributes}; pub(crate) struct Functions { /// List of all `DW_TAG_subprogram` details in the unit. pub(crate) functions: Box< [( gimli::UnitOffset, LazyCell, Error>>, )], >, /// List of `DW_TAG_subprogram` address ranges in the unit. pub(crate) addresses: Box<[FunctionAddress]>, } /// A single address range for a function. /// /// It is possible for a function to have multiple address ranges; this /// is handled by having multiple `FunctionAddress` entries with the same /// `function` field. pub(crate) struct FunctionAddress { range: gimli::Range, /// An index into `Functions::functions`. pub(crate) function: usize, } pub(crate) struct Function { pub(crate) dw_die_offset: gimli::UnitOffset, pub(crate) name: Option, /// List of all `DW_TAG_inlined_subroutine` details in this function. inlined_functions: Box<[InlinedFunction]>, /// List of `DW_TAG_inlined_subroutine` address ranges in this function. inlined_addresses: Box<[InlinedFunctionAddress]>, } pub(crate) struct InlinedFunctionAddress { range: gimli::Range, call_depth: usize, /// An index into `Function::inlined_functions`. function: usize, } pub(crate) struct InlinedFunction { pub(crate) dw_die_offset: gimli::UnitOffset, pub(crate) name: Option, pub(crate) call_file: Option, pub(crate) call_line: u32, pub(crate) call_column: u32, } impl Functions { pub(crate) fn parse( unit: &gimli::Unit, sections: &gimli::Dwarf, ) -> Result, Error> { let mut functions = Vec::new(); let mut addresses = Vec::new(); let mut entries = unit.entries_raw(None)?; while !entries.is_empty() { let dw_die_offset = entries.next_offset(); if let Some(abbrev) = entries.read_abbreviation()? { if abbrev.tag() == gimli::DW_TAG_subprogram { let mut ranges = RangeAttributes::default(); for spec in abbrev.attributes() { match entries.read_attribute(*spec) { Ok(ref attr) => { match attr.name() { gimli::DW_AT_low_pc => match attr.value() { gimli::AttributeValue::Addr(val) => { ranges.low_pc = Some(val) } gimli::AttributeValue::DebugAddrIndex(index) => { ranges.low_pc = Some(sections.address(unit, index)?); } _ => {} }, gimli::DW_AT_high_pc => match attr.value() { gimli::AttributeValue::Addr(val) => { ranges.high_pc = Some(val) } gimli::AttributeValue::DebugAddrIndex(index) => { ranges.high_pc = Some(sections.address(unit, index)?); } gimli::AttributeValue::Udata(val) => { ranges.size = Some(val) } _ => {} }, gimli::DW_AT_ranges => { ranges.ranges_offset = sections.attr_ranges_offset(unit, attr.value())?; } _ => {} }; } Err(e) => return Err(e), } } let function_index = functions.len(); if ranges.for_each_range(sections, unit, |range| { addresses.push(FunctionAddress { range, function: function_index, }); })? { functions.push((dw_die_offset, LazyCell::new())); } } else { entries.skip_attributes(abbrev.attributes())?; } } } // The binary search requires the addresses to be sorted. // // It also requires them to be non-overlapping. In practice, overlapping // function ranges are unlikely, so we don't try to handle that yet. // // It's possible for multiple functions to have the same address range if the // compiler can detect and remove functions with identical code. In that case // we'll nondeterministically return one of them. addresses.sort_by_key(|x| x.range.begin); Ok(Functions { functions: functions.into_boxed_slice(), addresses: addresses.into_boxed_slice(), }) } pub(crate) fn find_address(&self, probe: u64) -> Option { self.addresses .binary_search_by(|address| { if probe < address.range.begin { Ordering::Greater } else if probe >= address.range.end { Ordering::Less } else { Ordering::Equal } }) .ok() } pub(crate) fn parse_inlined_functions( &self, file: DebugFile, unit: &gimli::Unit, ctx: &Context, sections: &gimli::Dwarf, ) -> Result<(), Error> { for function in &*self.functions { function .1 .borrow_with(|| Function::parse(function.0, file, unit, ctx, sections)) .as_ref() .map_err(Error::clone)?; } Ok(()) } } impl Function { pub(crate) fn parse( dw_die_offset: gimli::UnitOffset, file: DebugFile, unit: &gimli::Unit, ctx: &Context, sections: &gimli::Dwarf, ) -> Result { let mut entries = unit.entries_raw(Some(dw_die_offset))?; let depth = entries.next_depth(); let abbrev = entries.read_abbreviation()?.unwrap(); debug_assert_eq!(abbrev.tag(), gimli::DW_TAG_subprogram); let mut name = None; for spec in abbrev.attributes() { match entries.read_attribute(*spec) { Ok(ref attr) => { match attr.name() { gimli::DW_AT_linkage_name | gimli::DW_AT_MIPS_linkage_name => { if let Ok(val) = sections.attr_string(unit, attr.value()) { name = Some(val); } } gimli::DW_AT_name => { if name.is_none() { name = sections.attr_string(unit, attr.value()).ok(); } } gimli::DW_AT_abstract_origin | gimli::DW_AT_specification => { if name.is_none() { name = name_attr(attr.value(), file, unit, ctx, sections, 16)?; } } _ => {} }; } Err(e) => return Err(e), } } let mut inlined_functions = Vec::new(); let mut inlined_addresses = Vec::new(); Function::parse_children( &mut entries, depth, file, unit, ctx, sections, &mut inlined_functions, &mut inlined_addresses, 0, )?; // Sort ranges in "breadth-first traversal order", i.e. first by call_depth // and then by range.begin. This allows finding the range containing an // address at a certain depth using binary search. // Note: Using DFS order, i.e. ordering by range.begin first and then by // call_depth, would not work! Consider the two examples // "[0..10 at depth 0], [0..2 at depth 1], [6..8 at depth 1]" and // "[0..5 at depth 0], [0..2 at depth 1], [5..10 at depth 0], [6..8 at depth 1]". // In this example, if you want to look up address 7 at depth 0, and you // encounter [0..2 at depth 1], are you before or after the target range? // You don't know. inlined_addresses.sort_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.range.begin < r2.range.begin { Ordering::Less } else if r1.range.begin > r2.range.begin { Ordering::Greater } else { Ordering::Equal } }); Ok(Function { dw_die_offset, name, inlined_functions: inlined_functions.into_boxed_slice(), inlined_addresses: inlined_addresses.into_boxed_slice(), }) } fn parse_children( entries: &mut gimli::EntriesRaw<'_, '_, R>, depth: isize, file: DebugFile, unit: &gimli::Unit, ctx: &Context, sections: &gimli::Dwarf, inlined_functions: &mut Vec>, inlined_addresses: &mut Vec, inlined_depth: usize, ) -> Result<(), Error> { loop { let dw_die_offset = entries.next_offset(); let next_depth = entries.next_depth(); if next_depth <= depth { return Ok(()); } if let Some(abbrev) = entries.read_abbreviation()? { match abbrev.tag() { gimli::DW_TAG_subprogram => { Function::skip(entries, abbrev, next_depth)?; } gimli::DW_TAG_inlined_subroutine => { InlinedFunction::parse( dw_die_offset, entries, abbrev, next_depth, file, unit, ctx, sections, inlined_functions, inlined_addresses, inlined_depth, )?; } _ => { entries.skip_attributes(abbrev.attributes())?; } } } } } fn skip( entries: &mut gimli::EntriesRaw<'_, '_, R>, abbrev: &gimli::Abbreviation, depth: isize, ) -> Result<(), Error> { // TODO: use DW_AT_sibling entries.skip_attributes(abbrev.attributes())?; while entries.next_depth() > depth { if let Some(abbrev) = entries.read_abbreviation()? { entries.skip_attributes(abbrev.attributes())?; } } Ok(()) } /// Build the list of inlined functions that contain `probe`. pub(crate) fn find_inlined_functions( &self, probe: u64, ) -> iter::Rev>> { // `inlined_functions` is ordered from outside to inside. let mut inlined_functions = maybe_small::Vec::new(); let mut inlined_addresses = &self.inlined_addresses[..]; loop { let current_depth = inlined_functions.len(); // Look up (probe, current_depth) in inline_ranges. // `inlined_addresses` is sorted in "breadth-first traversal order", i.e. // by `call_depth` first, and then by `range.begin`. See the comment at // the sort call for more information about why. let search = inlined_addresses.binary_search_by(|range| { if range.call_depth > current_depth { Ordering::Greater } else if range.call_depth < current_depth { Ordering::Less } else if range.range.begin > probe { Ordering::Greater } else if range.range.end <= probe { Ordering::Less } else { Ordering::Equal } }); if let Ok(index) = search { let function_index = inlined_addresses[index].function; inlined_functions.push(&self.inlined_functions[function_index]); inlined_addresses = &inlined_addresses[index + 1..]; } else { break; } } inlined_functions.into_iter().rev() } } impl InlinedFunction { fn parse( dw_die_offset: gimli::UnitOffset, entries: &mut gimli::EntriesRaw<'_, '_, R>, abbrev: &gimli::Abbreviation, depth: isize, file: DebugFile, unit: &gimli::Unit, ctx: &Context, sections: &gimli::Dwarf, inlined_functions: &mut Vec>, inlined_addresses: &mut Vec, inlined_depth: usize, ) -> Result<(), Error> { let mut ranges = RangeAttributes::default(); let mut name = None; let mut call_file = None; let mut call_line = 0; let mut call_column = 0; for spec in abbrev.attributes() { match entries.read_attribute(*spec) { Ok(ref attr) => match attr.name() { gimli::DW_AT_low_pc => match attr.value() { gimli::AttributeValue::Addr(val) => ranges.low_pc = Some(val), gimli::AttributeValue::DebugAddrIndex(index) => { ranges.low_pc = Some(sections.address(unit, index)?); } _ => {} }, gimli::DW_AT_high_pc => match attr.value() { gimli::AttributeValue::Addr(val) => ranges.high_pc = Some(val), gimli::AttributeValue::DebugAddrIndex(index) => { ranges.high_pc = Some(sections.address(unit, index)?); } gimli::AttributeValue::Udata(val) => ranges.size = Some(val), _ => {} }, gimli::DW_AT_ranges => { ranges.ranges_offset = sections.attr_ranges_offset(unit, attr.value())?; } gimli::DW_AT_linkage_name | gimli::DW_AT_MIPS_linkage_name => { if let Ok(val) = sections.attr_string(unit, attr.value()) { name = Some(val); } } gimli::DW_AT_name => { if name.is_none() { name = sections.attr_string(unit, attr.value()).ok(); } } gimli::DW_AT_abstract_origin | gimli::DW_AT_specification => { if name.is_none() { name = name_attr(attr.value(), file, unit, ctx, sections, 16)?; } } gimli::DW_AT_call_file => { // There is a spec issue [1] with how DW_AT_call_file is specified in DWARF 5. // Before, a file index of 0 would indicate no source file, however in // DWARF 5 this could be a valid index into the file table. // // Implementations such as LLVM generates a file index of 0 when DWARF 5 is // used. // // Thus, if we see a version of 5 or later, treat a file index of 0 as such. // [1]: http://wiki.dwarfstd.org/index.php?title=DWARF5_Line_Table_File_Numbers if let gimli::AttributeValue::FileIndex(fi) = attr.value() { if fi > 0 || unit.header.version() >= 5 { call_file = Some(fi); } } } gimli::DW_AT_call_line => { call_line = attr.udata_value().unwrap_or(0) as u32; } gimli::DW_AT_call_column => { call_column = attr.udata_value().unwrap_or(0) as u32; } _ => {} }, Err(e) => return Err(e), } } let function_index = inlined_functions.len(); inlined_functions.push(InlinedFunction { dw_die_offset, name, call_file, call_line, call_column, }); ranges.for_each_range(sections, unit, |range| { inlined_addresses.push(InlinedFunctionAddress { range, call_depth: inlined_depth, function: function_index, }); })?; Function::parse_children( entries, depth, file, unit, ctx, sections, inlined_functions, inlined_addresses, inlined_depth + 1, ) } } fn name_attr( attr: gimli::AttributeValue, mut file: DebugFile, unit: &gimli::Unit, ctx: &Context, sections: &gimli::Dwarf, recursion_limit: usize, ) -> Result, Error> where R: gimli::Reader, { if recursion_limit == 0 { return Ok(None); } match attr { gimli::AttributeValue::UnitRef(offset) => { name_entry(file, unit, offset, ctx, sections, recursion_limit) } gimli::AttributeValue::DebugInfoRef(dr) => { let (unit, offset) = ctx.find_unit(dr, file)?; name_entry(file, unit, offset, ctx, sections, recursion_limit) } gimli::AttributeValue::DebugInfoRefSup(dr) => { if let Some(sup_sections) = sections.sup.as_ref() { file = DebugFile::Supplementary; let (unit, offset) = ctx.find_unit(dr, file)?; name_entry(file, unit, offset, ctx, sup_sections, recursion_limit) } else { Ok(None) } } _ => Ok(None), } } fn name_entry( file: DebugFile, unit: &gimli::Unit, offset: gimli::UnitOffset, ctx: &Context, sections: &gimli::Dwarf, recursion_limit: usize, ) -> Result, Error> where R: gimli::Reader, { let mut entries = unit.entries_raw(Some(offset))?; let abbrev = if let Some(abbrev) = entries.read_abbreviation()? { abbrev } else { return Err(gimli::Error::NoEntryAtGivenOffset); }; let mut name = None; let mut next = None; for spec in abbrev.attributes() { match entries.read_attribute(*spec) { Ok(ref attr) => match attr.name() { gimli::DW_AT_linkage_name | gimli::DW_AT_MIPS_linkage_name => { if let Ok(val) = sections.attr_string(unit, attr.value()) { return Ok(Some(val)); } } gimli::DW_AT_name => { if let Ok(val) = sections.attr_string(unit, attr.value()) { name = Some(val); } } gimli::DW_AT_abstract_origin | gimli::DW_AT_specification => { next = Some(attr.value()); } _ => {} }, Err(e) => return Err(e), } } if name.is_some() { return Ok(name); } if let Some(next) = next { return name_attr(next, file, unit, ctx, sections, recursion_limit - 1); } Ok(None) } addr2line-0.21.0/src/lazy.rs000064400000000000000000000017041046102023000136660ustar 00000000000000use core::cell::UnsafeCell; pub struct LazyCell { contents: UnsafeCell>, } impl LazyCell { pub fn new() -> LazyCell { LazyCell { contents: UnsafeCell::new(None), } } pub fn borrow(&self) -> Option<&T> { unsafe { &*self.contents.get() }.as_ref() } pub fn borrow_with(&self, closure: impl FnOnce() -> T) -> &T { // First check if we're already initialized... let ptr = self.contents.get(); if let Some(val) = unsafe { &*ptr } { return val; } // Note that while we're executing `closure` our `borrow_with` may // be called recursively. This means we need to check again after // the closure has executed. For that we use the `get_or_insert` // method which will only perform mutation if we aren't already // `Some`. let val = closure(); unsafe { (*ptr).get_or_insert(val) } } } addr2line-0.21.0/src/lib.rs000064400000000000000000001675521046102023000134730ustar 00000000000000//! This crate provides a cross-platform library and binary for translating addresses into //! function names, file names and line numbers. Given an address in an executable or an //! offset in a section of a relocatable object, it uses the debugging information to //! figure out which file name and line number are associated with it. //! //! When used as a library, files must first be loaded using the //! [`object`](https://github.com/gimli-rs/object) crate. //! A context can then be created with [`Context::new`](./struct.Context.html#method.new). //! The context caches some of the parsed information so that multiple lookups are //! efficient. //! Location information is obtained with //! [`Context::find_location`](./struct.Context.html#method.find_location) or //! [`Context::find_location_range`](./struct.Context.html#method.find_location_range). //! Function information is obtained with //! [`Context::find_frames`](./struct.Context.html#method.find_frames), which returns //! a frame for each inline function. Each frame contains both name and location. //! //! The crate has an example CLI wrapper around the library which provides some of //! the functionality of the `addr2line` command line tool distributed with [GNU //! binutils](https://www.gnu.org/software/binutils/). //! //! Currently this library only provides information from the DWARF debugging information, //! which is parsed using [`gimli`](https://github.com/gimli-rs/gimli). The example CLI //! wrapper also uses symbol table information provided by the `object` crate. #![deny(missing_docs)] #![no_std] #[cfg(feature = "std")] extern crate std; #[allow(unused_imports)] #[macro_use] extern crate alloc; #[cfg(feature = "fallible-iterator")] pub extern crate fallible_iterator; pub extern crate gimli; #[cfg(feature = "object")] pub extern crate object; use alloc::borrow::Cow; use alloc::boxed::Box; #[cfg(feature = "object")] use alloc::rc::Rc; use alloc::string::{String, ToString}; use alloc::sync::Arc; use alloc::vec::Vec; use core::cmp::{self, Ordering}; use core::iter; use core::marker::PhantomData; use core::mem; use core::num::NonZeroU64; use core::ops::ControlFlow; use core::u64; use crate::function::{Function, Functions, InlinedFunction}; use crate::lazy::LazyCell; #[cfg(feature = "smallvec")] mod maybe_small { pub type Vec = smallvec::SmallVec<[T; 16]>; pub type IntoIter = smallvec::IntoIter<[T; 16]>; } #[cfg(not(feature = "smallvec"))] mod maybe_small { pub type Vec = alloc::vec::Vec; pub type IntoIter = alloc::vec::IntoIter; } #[cfg(all(feature = "std", feature = "object", feature = "memmap2"))] /// A simple builtin split DWARF loader. pub mod builtin_split_dwarf_loader; mod function; mod lazy; type Error = gimli::Error; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum DebugFile { Primary, Supplementary, Dwo, } /// Operations that consult debug information may require additional files /// to be loaded if split DWARF is being used. This enum returns the result /// of the operation in the `Break` variant, or information about the split /// DWARF that is required and a continuation to invoke once it is available /// in the `Continue` variant. /// /// This enum is intended to be used in a loop like so: /// ```no_run /// # use addr2line::*; /// # use std::sync::Arc; /// # let ctx: Context> = todo!(); /// # let do_split_dwarf_load = |load: SplitDwarfLoad>| -> Option>>> { None }; /// const ADDRESS: u64 = 0xdeadbeef; /// let mut r = ctx.find_frames(ADDRESS); /// let result = loop { /// match r { /// LookupResult::Output(result) => break result, /// LookupResult::Load { load, continuation } => { /// let dwo = do_split_dwarf_load(load); /// r = continuation.resume(dwo); /// } /// } /// }; /// ``` pub enum LookupResult { /// The lookup requires split DWARF data to be loaded. Load { /// The information needed to find the split DWARF data. load: SplitDwarfLoad<::Buf>, /// The continuation to resume with the loaded split DWARF data. continuation: L, }, /// The lookup has completed and produced an output. Output(::Output), } /// This trait represents a partially complete operation that can be resumed /// once a load of needed split DWARF data is completed or abandoned by the /// API consumer. pub trait LookupContinuation: Sized { /// The final output of this operation. type Output; /// The type of reader used. type Buf: gimli::Reader; /// Resumes the operation with the provided data. /// /// After the caller loads the split DWARF data required, call this /// method to resume the operation. The return value of this method /// indicates if the computation has completed or if further data is /// required. /// /// If the additional data cannot be located, or the caller does not /// support split DWARF, `resume(None)` can be used to continue the /// operation with the data that is available. fn resume(self, input: Option>>) -> LookupResult; } impl LookupResult { /// Callers that do not handle split DWARF can call `skip_all_loads` /// to fast-forward to the end result. This result is produced with /// the data that is available and may be less accurate than the /// the results that would be produced if the caller did properly /// support split DWARF. pub fn skip_all_loads(mut self) -> L::Output { loop { self = match self { LookupResult::Output(t) => return t, LookupResult::Load { continuation, .. } => continuation.resume(None), }; } } fn map T>(self, f: F) -> LookupResult> { match self { LookupResult::Output(t) => LookupResult::Output(f(t)), LookupResult::Load { load, continuation } => LookupResult::Load { load, continuation: MappedLookup { original: continuation, mutator: f, }, }, } } fn unwrap(self) -> L::Output { match self { LookupResult::Output(t) => t, LookupResult::Load { .. } => unreachable!("Internal API misuse"), } } } /// The state necessary to perform address to line translation. /// /// Constructing a `Context` is somewhat costly, so users should aim to reuse `Context`s /// when performing lookups for many addresses in the same executable. pub struct Context { sections: Arc>, unit_ranges: Box<[UnitRange]>, units: Box<[ResUnit]>, sup_units: Box<[SupUnit]>, } /// The type of `Context` that supports the `new` method. #[cfg(feature = "std-object")] pub type ObjectContext = Context>; #[cfg(feature = "std-object")] impl Context> { /// Construct a new `Context`. /// /// The resulting `Context` uses `gimli::EndianRcSlice`. /// This means it is not thread safe, has no lifetime constraints (since it copies /// the input data), and works for any endianity. /// /// Performance sensitive applications may want to use `Context::from_dwarf` /// with a more specialised `gimli::Reader` implementation. #[inline] pub fn new<'data: 'file, 'file, O: object::Object<'data, 'file>>( file: &'file O, ) -> Result { Self::new_with_sup(file, None) } /// Construct a new `Context`. /// /// Optionally also use a supplementary object file. /// /// The resulting `Context` uses `gimli::EndianRcSlice`. /// This means it is not thread safe, has no lifetime constraints (since it copies /// the input data), and works for any endianity. /// /// Performance sensitive applications may want to use `Context::from_dwarf` /// with a more specialised `gimli::Reader` implementation. pub fn new_with_sup<'data: 'file, 'file, O: object::Object<'data, 'file>>( file: &'file O, sup_file: Option<&'file O>, ) -> Result { let endian = if file.is_little_endian() { gimli::RunTimeEndian::Little } else { gimli::RunTimeEndian::Big }; fn load_section<'data: 'file, 'file, O, Endian>( id: gimli::SectionId, file: &'file O, endian: Endian, ) -> Result, Error> where O: object::Object<'data, 'file>, Endian: gimli::Endianity, { use object::ObjectSection; let data = file .section_by_name(id.name()) .and_then(|section| section.uncompressed_data().ok()) .unwrap_or(Cow::Borrowed(&[])); Ok(gimli::EndianRcSlice::new(Rc::from(&*data), endian)) } let mut dwarf = gimli::Dwarf::load(|id| load_section(id, file, endian))?; if let Some(sup_file) = sup_file { dwarf.load_sup(|id| load_section(id, sup_file, endian))?; } Context::from_dwarf(dwarf) } } impl Context { /// Construct a new `Context` from DWARF sections. /// /// This method does not support using a supplementary object file. pub fn from_sections( debug_abbrev: gimli::DebugAbbrev, debug_addr: gimli::DebugAddr, debug_aranges: gimli::DebugAranges, debug_info: gimli::DebugInfo, debug_line: gimli::DebugLine, debug_line_str: gimli::DebugLineStr, debug_ranges: gimli::DebugRanges, debug_rnglists: gimli::DebugRngLists, debug_str: gimli::DebugStr, debug_str_offsets: gimli::DebugStrOffsets, default_section: R, ) -> Result { Self::from_dwarf(gimli::Dwarf { debug_abbrev, debug_addr, debug_aranges, debug_info, debug_line, debug_line_str, debug_str, debug_str_offsets, debug_types: default_section.clone().into(), locations: gimli::LocationLists::new( default_section.clone().into(), default_section.into(), ), ranges: gimli::RangeLists::new(debug_ranges, debug_rnglists), file_type: gimli::DwarfFileType::Main, sup: None, abbreviations_cache: gimli::AbbreviationsCache::new(), }) } /// Construct a new `Context` from an existing [`gimli::Dwarf`] object. #[inline] pub fn from_dwarf(sections: gimli::Dwarf) -> Result, Error> { let sections = Arc::new(sections); let (unit_ranges, units) = Context::parse_units(§ions)?; let sup_units = if let Some(sup) = sections.sup.as_ref() { Context::parse_sup(sup)? } else { Vec::new() }; Ok(Context { sections, unit_ranges: unit_ranges.into_boxed_slice(), units: units.into_boxed_slice(), sup_units: sup_units.into_boxed_slice(), }) } /// Finds the CUs for the function address given. /// /// There might be multiple CUs whose range contains this address. /// Weak symbols have shown up in the wild which cause this to happen /// but otherwise this can happen if the CU has non-contiguous functions /// but only reports a single range. /// /// Consequently we return an iterator for all CUs which may contain the /// address, and the caller must check if there is actually a function or /// location in the CU for that address. fn find_units(&self, probe: u64) -> impl Iterator> { self.find_units_range(probe, probe + 1) .map(|(unit, _range)| unit) } /// Finds the CUs covering the range of addresses given. /// /// The range is [low, high) (ie, the upper bound is exclusive). This can return multiple /// ranges for the same unit. #[inline] fn find_units_range( &self, probe_low: u64, probe_high: u64, ) -> impl Iterator, &gimli::Range)> { // First up find the position in the array which could have our function // address. let pos = match self .unit_ranges .binary_search_by_key(&probe_high, |i| i.range.begin) { // Although unlikely, we could find an exact match. Ok(i) => i + 1, // No exact match was found, but this probe would fit at slot `i`. // This means that slot `i` is bigger than `probe`, along with all // indices greater than `i`, so we need to search all previous // entries. Err(i) => i, }; // Once we have our index we iterate backwards from that position // looking for a matching CU. self.unit_ranges[..pos] .iter() .rev() .take_while(move |i| { // We know that this CU's start is beneath the probe already because // of our sorted array. debug_assert!(i.range.begin <= probe_high); // Each entry keeps track of the maximum end address seen so far, // starting from the beginning of the array of unit ranges. We're // iterating in reverse so if our probe is beyond the maximum range // of this entry, then it's guaranteed to not fit in any prior // entries, so we break out. probe_low < i.max_end }) .filter_map(move |i| { // If this CU doesn't actually contain this address, move to the // next CU. if probe_low >= i.range.end || probe_high <= i.range.begin { return None; } Some((&self.units[i.unit_id], &i.range)) }) } /// Find the DWARF unit corresponding to the given virtual memory address. pub fn find_dwarf_and_unit( &self, probe: u64, ) -> LookupResult< impl LookupContinuation, &gimli::Unit)>, Buf = R>, > { let mut units_iter = self.find_units(probe); if let Some(unit) = units_iter.next() { return LoopingLookup::new_lookup( unit.find_function_or_location(probe, self), move |r| { ControlFlow::Break(match r { Ok((Some(_), _)) | Ok((_, Some(_))) => { let (_file, sections, unit) = unit .dwarf_and_unit_dwo(self) // We've already been through both error cases here to get to this point. .unwrap() .unwrap(); Some((sections, unit)) } _ => match units_iter.next() { Some(next_unit) => { return ControlFlow::Continue( next_unit.find_function_or_location(probe, self), ); } None => None, }, }) }, ); } LoopingLookup::new_complete(None) } /// Find the source file and line corresponding to the given virtual memory address. pub fn find_location(&self, probe: u64) -> Result>, Error> { for unit in self.find_units(probe) { if let Some(location) = unit.find_location(probe, &self.sections)? { return Ok(Some(location)); } } Ok(None) } /// Return source file and lines for a range of addresses. For each location it also /// returns the address and size of the range of the underlying instructions. pub fn find_location_range( &self, probe_low: u64, probe_high: u64, ) -> Result, Error> { LocationRangeIter::new(self, probe_low, probe_high) } /// Return an iterator for the function frames corresponding to the given virtual /// memory address. /// /// If the probe address is not for an inline function then only one frame is /// returned. /// /// If the probe address is for an inline function then the first frame corresponds /// to the innermost inline function. Subsequent frames contain the caller and call /// location, until an non-inline caller is reached. pub fn find_frames( &self, probe: u64, ) -> LookupResult, Error>, Buf = R>> { let mut units_iter = self.find_units(probe); if let Some(unit) = units_iter.next() { LoopingLookup::new_lookup(unit.find_function_or_location(probe, self), move |r| { ControlFlow::Break(match r { Err(e) => Err(e), Ok((Some(function), location)) => { let inlined_functions = function.find_inlined_functions(probe); Ok(FrameIter(FrameIterState::Frames(FrameIterFrames { unit, sections: &self.sections, function, inlined_functions, next: location, }))) } Ok((None, Some(location))) => { Ok(FrameIter(FrameIterState::Location(Some(location)))) } Ok((None, None)) => match units_iter.next() { Some(next_unit) => { return ControlFlow::Continue( next_unit.find_function_or_location(probe, self), ); } None => Ok(FrameIter(FrameIterState::Empty)), }, }) }) } else { LoopingLookup::new_complete(Ok(FrameIter(FrameIterState::Empty))) } } /// Preload units for `probe`. /// /// The iterator returns pairs of `SplitDwarfLoad`s containing the /// information needed to locate and load split DWARF for `probe` and /// a matching callback to invoke once that data is available. /// /// If this method is called, and all of the returned closures are invoked, /// addr2line guarantees that any future API call for the address `probe` /// will not require the loading of any split DWARF. /// /// ```no_run /// # use addr2line::*; /// # use std::sync::Arc; /// # let ctx: Context> = todo!(); /// # let do_split_dwarf_load = |load: SplitDwarfLoad>| -> Option>>> { None }; /// const ADDRESS: u64 = 0xdeadbeef; /// ctx.preload_units(ADDRESS).for_each(|(load, callback)| { /// let dwo = do_split_dwarf_load(load); /// callback(dwo); /// }); /// /// let frames_iter = match ctx.find_frames(ADDRESS) { /// LookupResult::Output(result) => result, /// LookupResult::Load { .. } => unreachable!("addr2line promised we wouldn't get here"), /// }; /// /// // ... /// ``` pub fn preload_units( &'_ self, probe: u64, ) -> impl Iterator< Item = ( SplitDwarfLoad, impl FnOnce(Option>>) -> Result<(), gimli::Error> + '_, ), > { self.find_units(probe) .filter_map(move |unit| match unit.dwarf_and_unit_dwo(self) { LookupResult::Output(_) => None, LookupResult::Load { load, continuation } => Some((load, |result| { continuation.resume(result).unwrap().map(|_| ()) })), }) } /// Initialize all line data structures. This is used for benchmarks. #[doc(hidden)] pub fn parse_lines(&self) -> Result<(), Error> { for unit in self.units.iter() { unit.parse_lines(&self.sections)?; } Ok(()) } /// Initialize all function data structures. This is used for benchmarks. #[doc(hidden)] pub fn parse_functions(&self) -> Result<(), Error> { for unit in self.units.iter() { unit.parse_functions(self).skip_all_loads()?; } Ok(()) } /// Initialize all inlined function data structures. This is used for benchmarks. #[doc(hidden)] pub fn parse_inlined_functions(&self) -> Result<(), Error> { for unit in self.units.iter() { unit.parse_inlined_functions(self).skip_all_loads()?; } Ok(()) } } struct UnitRange { unit_id: usize, max_end: u64, range: gimli::Range, } struct ResUnit { offset: gimli::DebugInfoOffset, dw_unit: gimli::Unit, lang: Option, lines: LazyCell>, funcs: LazyCell, Error>>, dwo: LazyCell>, gimli::Unit)>>, Error>>, } struct SupUnit { offset: gimli::DebugInfoOffset, dw_unit: gimli::Unit, } impl Context { fn parse_units(sections: &gimli::Dwarf) -> Result<(Vec, Vec>), Error> { // Find all the references to compilation units in .debug_aranges. // Note that we always also iterate through all of .debug_info to // find compilation units, because .debug_aranges may be missing some. let mut aranges = Vec::new(); let mut headers = sections.debug_aranges.headers(); while let Some(header) = headers.next()? { aranges.push((header.debug_info_offset(), header.offset())); } aranges.sort_by_key(|i| i.0); let mut unit_ranges = Vec::new(); let mut res_units = Vec::new(); let mut units = sections.units(); while let Some(header) = units.next()? { let unit_id = res_units.len(); let offset = match header.offset().as_debug_info_offset() { Some(offset) => offset, None => continue, }; // We mainly want compile units, but we may need to follow references to entries // within other units for function names. We don't need anything from type units. match header.type_() { gimli::UnitType::Type { .. } | gimli::UnitType::SplitType { .. } => continue, _ => {} } let dw_unit = match sections.unit(header) { Ok(dw_unit) => dw_unit, Err(_) => continue, }; let mut lang = None; let mut have_unit_range = false; { let mut entries = dw_unit.entries_raw(None)?; let abbrev = match entries.read_abbreviation()? { Some(abbrev) => abbrev, None => continue, }; let mut ranges = RangeAttributes::default(); for spec in abbrev.attributes() { let attr = entries.read_attribute(*spec)?; match attr.name() { gimli::DW_AT_low_pc => match attr.value() { gimli::AttributeValue::Addr(val) => ranges.low_pc = Some(val), gimli::AttributeValue::DebugAddrIndex(index) => { ranges.low_pc = Some(sections.address(&dw_unit, index)?); } _ => {} }, gimli::DW_AT_high_pc => match attr.value() { gimli::AttributeValue::Addr(val) => ranges.high_pc = Some(val), gimli::AttributeValue::DebugAddrIndex(index) => { ranges.high_pc = Some(sections.address(&dw_unit, index)?); } gimli::AttributeValue::Udata(val) => ranges.size = Some(val), _ => {} }, gimli::DW_AT_ranges => { ranges.ranges_offset = sections.attr_ranges_offset(&dw_unit, attr.value())?; } gimli::DW_AT_language => { if let gimli::AttributeValue::Language(val) = attr.value() { lang = Some(val); } } _ => {} } } // Find the address ranges for the CU, using in order of preference: // - DW_AT_ranges // - .debug_aranges // - DW_AT_low_pc/DW_AT_high_pc // // Using DW_AT_ranges before .debug_aranges is possibly an arbitrary choice, // but the feeling is that DW_AT_ranges is more likely to be reliable or complete // if it is present. // // .debug_aranges must be used before DW_AT_low_pc/DW_AT_high_pc because // it has been observed on macOS that DW_AT_ranges was not emitted even for // discontiguous CUs. let i = match ranges.ranges_offset { Some(_) => None, None => aranges.binary_search_by_key(&offset, |x| x.0).ok(), }; if let Some(mut i) = i { // There should be only one set per CU, but in practice multiple // sets have been observed. This is probably a compiler bug, but // either way we need to handle it. while i > 0 && aranges[i - 1].0 == offset { i -= 1; } for (_, aranges_offset) in aranges[i..].iter().take_while(|x| x.0 == offset) { let aranges_header = sections.debug_aranges.header(*aranges_offset)?; let mut aranges = aranges_header.entries(); while let Some(arange) = aranges.next()? { if arange.length() != 0 { unit_ranges.push(UnitRange { range: arange.range(), unit_id, max_end: 0, }); have_unit_range = true; } } } } else { have_unit_range |= ranges.for_each_range(sections, &dw_unit, |range| { unit_ranges.push(UnitRange { range, unit_id, max_end: 0, }); })?; } } let lines = LazyCell::new(); if !have_unit_range { // The unit did not declare any ranges. // Try to get some ranges from the line program sequences. if let Some(ref ilnp) = dw_unit.line_program { if let Ok(lines) = lines .borrow_with(|| Lines::parse(&dw_unit, ilnp.clone(), sections)) .as_ref() { for sequence in lines.sequences.iter() { unit_ranges.push(UnitRange { range: gimli::Range { begin: sequence.start, end: sequence.end, }, unit_id, max_end: 0, }) } } } } res_units.push(ResUnit { offset, dw_unit, lang, lines, funcs: LazyCell::new(), dwo: LazyCell::new(), }); } // Sort this for faster lookup in `find_unit_and_address` below. unit_ranges.sort_by_key(|i| i.range.begin); // Calculate the `max_end` field now that we've determined the order of // CUs. let mut max = 0; for i in unit_ranges.iter_mut() { max = max.max(i.range.end); i.max_end = max; } Ok((unit_ranges, res_units)) } fn parse_sup(sections: &gimli::Dwarf) -> Result>, Error> { let mut sup_units = Vec::new(); let mut units = sections.units(); while let Some(header) = units.next()? { let offset = match header.offset().as_debug_info_offset() { Some(offset) => offset, None => continue, }; let dw_unit = match sections.unit(header) { Ok(dw_unit) => dw_unit, Err(_) => continue, }; sup_units.push(SupUnit { dw_unit, offset }); } Ok(sup_units) } // Find the unit containing the given offset, and convert the offset into a unit offset. fn find_unit( &self, offset: gimli::DebugInfoOffset, file: DebugFile, ) -> Result<(&gimli::Unit, gimli::UnitOffset), Error> { let unit = match file { DebugFile::Primary => { match self .units .binary_search_by_key(&offset.0, |unit| unit.offset.0) { // There is never a DIE at the unit offset or before the first unit. Ok(_) | Err(0) => return Err(gimli::Error::NoEntryAtGivenOffset), Err(i) => &self.units[i - 1].dw_unit, } } DebugFile::Supplementary => { match self .sup_units .binary_search_by_key(&offset.0, |unit| unit.offset.0) { // There is never a DIE at the unit offset or before the first unit. Ok(_) | Err(0) => return Err(gimli::Error::NoEntryAtGivenOffset), Err(i) => &self.sup_units[i - 1].dw_unit, } } DebugFile::Dwo => return Err(gimli::Error::NoEntryAtGivenOffset), }; let unit_offset = offset .to_unit_offset(&unit.header) .ok_or(gimli::Error::NoEntryAtGivenOffset)?; Ok((unit, unit_offset)) } } struct Lines { files: Box<[String]>, sequences: Box<[LineSequence]>, } impl Lines { fn parse( dw_unit: &gimli::Unit, ilnp: gimli::IncompleteLineProgram, sections: &gimli::Dwarf, ) -> Result { let mut sequences = Vec::new(); let mut sequence_rows = Vec::::new(); let mut rows = ilnp.rows(); while let Some((_, row)) = rows.next_row()? { if row.end_sequence() { if let Some(start) = sequence_rows.first().map(|x| x.address) { let end = row.address(); let mut rows = Vec::new(); mem::swap(&mut rows, &mut sequence_rows); sequences.push(LineSequence { start, end, rows: rows.into_boxed_slice(), }); } continue; } let address = row.address(); let file_index = row.file_index(); let line = row.line().map(NonZeroU64::get).unwrap_or(0) as u32; let column = match row.column() { gimli::ColumnType::LeftEdge => 0, gimli::ColumnType::Column(x) => x.get() as u32, }; if let Some(last_row) = sequence_rows.last_mut() { if last_row.address == address { last_row.file_index = file_index; last_row.line = line; last_row.column = column; continue; } } sequence_rows.push(LineRow { address, file_index, line, column, }); } sequences.sort_by_key(|x| x.start); let mut files = Vec::new(); let header = rows.header(); match header.file(0) { Some(file) => files.push(render_file(dw_unit, file, header, sections)?), None => files.push(String::from("")), // DWARF version <= 4 may not have 0th index } let mut index = 1; while let Some(file) = header.file(index) { files.push(render_file(dw_unit, file, header, sections)?); index += 1; } Ok(Self { files: files.into_boxed_slice(), sequences: sequences.into_boxed_slice(), }) } } fn render_file( dw_unit: &gimli::Unit, file: &gimli::FileEntry, header: &gimli::LineProgramHeader, sections: &gimli::Dwarf, ) -> Result { let mut path = if let Some(ref comp_dir) = dw_unit.comp_dir { comp_dir.to_string_lossy()?.into_owned() } else { String::new() }; // The directory index 0 is defined to correspond to the compilation unit directory. if file.directory_index() != 0 { if let Some(directory) = file.directory(header) { path_push( &mut path, sections .attr_string(dw_unit, directory)? .to_string_lossy()? .as_ref(), ); } } path_push( &mut path, sections .attr_string(dw_unit, file.path_name())? .to_string_lossy()? .as_ref(), ); Ok(path) } struct LineSequence { start: u64, end: u64, rows: Box<[LineRow]>, } struct LineRow { address: u64, file_index: u64, line: u32, column: u32, } /// This struct contains the information needed to find split DWARF data /// and to produce a `gimli::Dwarf` for it. pub struct SplitDwarfLoad { /// The dwo id, for looking up in a DWARF package, or for /// verifying an unpacked dwo found on the file system pub dwo_id: gimli::DwoId, /// The compilation directory `path` is relative to. pub comp_dir: Option, /// A path on the filesystem, relative to `comp_dir` to find this dwo. pub path: Option, /// Once the split DWARF data is loaded, the loader is expected /// to call [make_dwo(parent)](gimli::read::Dwarf::make_dwo) before /// returning the data. pub parent: Arc>, } struct SimpleLookup where F: FnOnce(Option>>) -> T, R: gimli::Reader, { f: F, phantom: PhantomData<(T, R)>, } impl SimpleLookup where F: FnOnce(Option>>) -> T, R: gimli::Reader, { fn new_complete(t: F::Output) -> LookupResult> { LookupResult::Output(t) } fn new_needs_load(load: SplitDwarfLoad, f: F) -> LookupResult> { LookupResult::Load { load, continuation: SimpleLookup { f, phantom: PhantomData, }, } } } impl LookupContinuation for SimpleLookup where F: FnOnce(Option>>) -> T, R: gimli::Reader, { type Output = T; type Buf = R; fn resume(self, v: Option>>) -> LookupResult { LookupResult::Output((self.f)(v)) } } struct MappedLookup where L: LookupContinuation, F: FnOnce(L::Output) -> T, { original: L, mutator: F, } impl LookupContinuation for MappedLookup where L: LookupContinuation, F: FnOnce(L::Output) -> T, { type Output = T; type Buf = L::Buf; fn resume(self, v: Option>>) -> LookupResult { match self.original.resume(v) { LookupResult::Output(t) => LookupResult::Output((self.mutator)(t)), LookupResult::Load { load, continuation } => LookupResult::Load { load, continuation: MappedLookup { original: continuation, mutator: self.mutator, }, }, } } } /// Some functions (e.g. `find_frames`) require considering multiple /// compilation units, each of which might require their own split DWARF /// lookup (and thus produce a continuation). /// /// We store the underlying continuation here as well as a mutator function /// that will either a) decide that the result of this continuation is /// what is needed and mutate it to the final result or b) produce another /// `LookupResult`. `new_lookup` will in turn eagerly drive any non-continuation /// `LookupResult` with successive invocations of the mutator, until a new /// continuation or a final result is produced. And finally, the impl of /// `LookupContinuation::resume` will call `new_lookup` each time the /// computation is resumed. struct LoopingLookup where L: LookupContinuation, F: FnMut(L::Output) -> ControlFlow>, { continuation: L, mutator: F, } impl LoopingLookup where L: LookupContinuation, F: FnMut(L::Output) -> ControlFlow>, { fn new_complete(t: T) -> LookupResult { LookupResult::Output(t) } fn new_lookup(mut r: LookupResult, mut mutator: F) -> LookupResult { // Drive the loop eagerly so that we only ever have to represent one state // (the r == ControlFlow::Continue state) in LoopingLookup. loop { match r { LookupResult::Output(l) => match mutator(l) { ControlFlow::Break(t) => return LookupResult::Output(t), ControlFlow::Continue(r2) => { r = r2; } }, LookupResult::Load { load, continuation } => { return LookupResult::Load { load, continuation: LoopingLookup { continuation, mutator, }, }; } } } } } impl LookupContinuation for LoopingLookup where L: LookupContinuation, F: FnMut(L::Output) -> ControlFlow>, { type Output = T; type Buf = L::Buf; fn resume(self, v: Option>>) -> LookupResult { let r = self.continuation.resume(v); LoopingLookup::new_lookup(r, self.mutator) } } impl ResUnit { fn dwarf_and_unit_dwo<'unit, 'ctx: 'unit>( &'unit self, ctx: &'ctx Context, ) -> LookupResult< SimpleLookup< Result<(DebugFile, &'unit gimli::Dwarf, &'unit gimli::Unit), Error>, R, impl FnOnce( Option>>, ) -> Result<(DebugFile, &'unit gimli::Dwarf, &'unit gimli::Unit), Error>, >, > { loop { break SimpleLookup::new_complete(match self.dwo.borrow() { Some(Ok(Some(v))) => Ok((DebugFile::Dwo, &*v.0, &v.1)), Some(Ok(None)) => Ok((DebugFile::Primary, &*ctx.sections, &self.dw_unit)), Some(Err(e)) => Err(*e), None => { let dwo_id = match self.dw_unit.dwo_id { None => { self.dwo.borrow_with(|| Ok(None)); continue; } Some(dwo_id) => dwo_id, }; let comp_dir = self.dw_unit.comp_dir.clone(); let dwo_name = self.dw_unit.dwo_name().and_then(|s| { if let Some(s) = s { Ok(Some(ctx.sections.attr_string(&self.dw_unit, s)?)) } else { Ok(None) } }); let path = match dwo_name { Ok(v) => v, Err(e) => { self.dwo.borrow_with(|| Err(e)); continue; } }; let process_dwo = move |dwo_dwarf: Option>>| { let dwo_dwarf = match dwo_dwarf { None => return Ok(None), Some(dwo_dwarf) => dwo_dwarf, }; let mut dwo_units = dwo_dwarf.units(); let dwo_header = match dwo_units.next()? { Some(dwo_header) => dwo_header, None => return Ok(None), }; let mut dwo_unit = dwo_dwarf.unit(dwo_header)?; dwo_unit.copy_relocated_attributes(&self.dw_unit); Ok(Some(Box::new((dwo_dwarf, dwo_unit)))) }; return SimpleLookup::new_needs_load( SplitDwarfLoad { dwo_id, comp_dir, path, parent: ctx.sections.clone(), }, move |dwo_dwarf| match self.dwo.borrow_with(|| process_dwo(dwo_dwarf)) { Ok(Some(v)) => Ok((DebugFile::Dwo, &*v.0, &v.1)), Ok(None) => Ok((DebugFile::Primary, &*ctx.sections, &self.dw_unit)), Err(e) => Err(*e), }, ); } }); } } fn parse_lines(&self, sections: &gimli::Dwarf) -> Result, Error> { // NB: line information is always stored in the main debug file so this does not need // to handle DWOs. let ilnp = match self.dw_unit.line_program { Some(ref ilnp) => ilnp, None => return Ok(None), }; self.lines .borrow_with(|| Lines::parse(&self.dw_unit, ilnp.clone(), sections)) .as_ref() .map(Some) .map_err(Error::clone) } fn parse_functions_dwarf_and_unit( &self, unit: &gimli::Unit, sections: &gimli::Dwarf, ) -> Result<&Functions, Error> { self.funcs .borrow_with(|| Functions::parse(unit, sections)) .as_ref() .map_err(Error::clone) } fn parse_functions<'unit, 'ctx: 'unit>( &'unit self, ctx: &'ctx Context, ) -> LookupResult, Error>, Buf = R>> { self.dwarf_and_unit_dwo(ctx).map(move |r| { let (_file, sections, unit) = r?; self.parse_functions_dwarf_and_unit(unit, sections) }) } fn parse_inlined_functions<'unit, 'ctx: 'unit>( &'unit self, ctx: &'ctx Context, ) -> LookupResult, Buf = R> + 'unit> { self.dwarf_and_unit_dwo(ctx).map(move |r| { let (file, sections, unit) = r?; self.funcs .borrow_with(|| Functions::parse(unit, sections)) .as_ref() .map_err(Error::clone)? .parse_inlined_functions(file, unit, ctx, sections) }) } fn find_location( &self, probe: u64, sections: &gimli::Dwarf, ) -> Result>, Error> { if let Some(mut iter) = LocationRangeUnitIter::new(self, sections, probe, probe + 1)? { match iter.next() { None => Ok(None), Some((_addr, _len, loc)) => Ok(Some(loc)), } } else { Ok(None) } } #[inline] fn find_location_range( &self, probe_low: u64, probe_high: u64, sections: &gimli::Dwarf, ) -> Result>, Error> { LocationRangeUnitIter::new(self, sections, probe_low, probe_high) } fn find_function_or_location<'unit, 'ctx: 'unit>( &'unit self, probe: u64, ctx: &'ctx Context, ) -> LookupResult< impl LookupContinuation< Output = Result<(Option<&'unit Function>, Option>), Error>, Buf = R, >, > { self.dwarf_and_unit_dwo(ctx).map(move |r| { let (file, sections, unit) = r?; let functions = self.parse_functions_dwarf_and_unit(unit, sections)?; let function = match functions.find_address(probe) { Some(address) => { let function_index = functions.addresses[address].function; let (offset, ref function) = functions.functions[function_index]; Some( function .borrow_with(|| Function::parse(offset, file, unit, ctx, sections)) .as_ref() .map_err(Error::clone)?, ) } None => None, }; let location = self.find_location(probe, sections)?; Ok((function, location)) }) } } /// Iterator over `Location`s in a range of addresses, returned by `Context::find_location_range`. pub struct LocationRangeIter<'ctx, R: gimli::Reader> { unit_iter: Box, &'ctx gimli::Range)> + 'ctx>, iter: Option>, probe_low: u64, probe_high: u64, sections: &'ctx gimli::Dwarf, } impl<'ctx, R: gimli::Reader> LocationRangeIter<'ctx, R> { #[inline] fn new(ctx: &'ctx Context, probe_low: u64, probe_high: u64) -> Result { let sections = &ctx.sections; let unit_iter = ctx.find_units_range(probe_low, probe_high); Ok(Self { unit_iter: Box::new(unit_iter), iter: None, probe_low, probe_high, sections, }) } fn next_loc(&mut self) -> Result)>, Error> { loop { let iter = self.iter.take(); match iter { None => match self.unit_iter.next() { Some((unit, range)) => { self.iter = unit.find_location_range( cmp::max(self.probe_low, range.begin), cmp::min(self.probe_high, range.end), self.sections, )?; } None => return Ok(None), }, Some(mut iter) => { if let item @ Some(_) = iter.next() { self.iter = Some(iter); return Ok(item); } } } } } } impl<'ctx, R> Iterator for LocationRangeIter<'ctx, R> where R: gimli::Reader + 'ctx, { type Item = (u64, u64, Location<'ctx>); #[inline] fn next(&mut self) -> Option { match self.next_loc() { Err(_) => None, Ok(loc) => loc, } } } #[cfg(feature = "fallible-iterator")] impl<'ctx, R> fallible_iterator::FallibleIterator for LocationRangeIter<'ctx, R> where R: gimli::Reader + 'ctx, { type Item = (u64, u64, Location<'ctx>); type Error = Error; #[inline] fn next(&mut self) -> Result, Self::Error> { self.next_loc() } } struct LocationRangeUnitIter<'ctx> { lines: &'ctx Lines, seqs: &'ctx [LineSequence], seq_idx: usize, row_idx: usize, probe_high: u64, } impl<'ctx> LocationRangeUnitIter<'ctx> { fn new( resunit: &'ctx ResUnit, sections: &gimli::Dwarf, probe_low: u64, probe_high: u64, ) -> Result, Error> { let lines = resunit.parse_lines(sections)?; if let Some(lines) = lines { // Find index for probe_low. let seq_idx = lines.sequences.binary_search_by(|sequence| { if probe_low < sequence.start { Ordering::Greater } else if probe_low >= sequence.end { Ordering::Less } else { Ordering::Equal } }); let seq_idx = match seq_idx { Ok(x) => x, Err(0) => 0, // probe below sequence, but range could overlap Err(_) => lines.sequences.len(), }; let row_idx = if let Some(seq) = lines.sequences.get(seq_idx) { let idx = seq.rows.binary_search_by(|row| row.address.cmp(&probe_low)); match idx { Ok(x) => x, Err(0) => 0, // probe below sequence, but range could overlap Err(x) => x - 1, } } else { 0 }; Ok(Some(Self { lines, seqs: &*lines.sequences, seq_idx, row_idx, probe_high, })) } else { Ok(None) } } } impl<'ctx> Iterator for LocationRangeUnitIter<'ctx> { type Item = (u64, u64, Location<'ctx>); fn next(&mut self) -> Option<(u64, u64, Location<'ctx>)> { while let Some(seq) = self.seqs.get(self.seq_idx) { if seq.start >= self.probe_high { break; } match seq.rows.get(self.row_idx) { Some(row) => { if row.address >= self.probe_high { break; } let file = self .lines .files .get(row.file_index as usize) .map(String::as_str); let nextaddr = seq .rows .get(self.row_idx + 1) .map(|row| row.address) .unwrap_or(seq.end); let item = ( row.address, nextaddr - row.address, Location { file, line: if row.line != 0 { Some(row.line) } else { None }, column: if row.column != 0 { Some(row.column) } else { None }, }, ); self.row_idx += 1; return Some(item); } None => { self.seq_idx += 1; self.row_idx = 0; } } } None } } fn path_push(path: &mut String, p: &str) { if has_unix_root(p) || has_windows_root(p) { *path = p.to_string(); } else { let dir_separator = if has_windows_root(path.as_str()) { '\\' } else { '/' }; if !path.is_empty() && !path.ends_with(dir_separator) { path.push(dir_separator); } *path += p; } } /// Check if the path in the given string has a unix style root fn has_unix_root(p: &str) -> bool { p.starts_with('/') } /// Check if the path in the given string has a windows style root fn has_windows_root(p: &str) -> bool { p.starts_with('\\') || p.get(1..3) == Some(":\\") } struct RangeAttributes { low_pc: Option, high_pc: Option, size: Option, ranges_offset: Option::Offset>>, } impl Default for RangeAttributes { fn default() -> Self { RangeAttributes { low_pc: None, high_pc: None, size: None, ranges_offset: None, } } } impl RangeAttributes { fn for_each_range( &self, sections: &gimli::Dwarf, unit: &gimli::Unit, mut f: F, ) -> Result { let mut added_any = false; let mut add_range = |range: gimli::Range| { if range.begin < range.end { f(range); added_any = true } }; if let Some(ranges_offset) = self.ranges_offset { let mut range_list = sections.ranges(unit, ranges_offset)?; while let Some(range) = range_list.next()? { add_range(range); } } else if let (Some(begin), Some(end)) = (self.low_pc, self.high_pc) { add_range(gimli::Range { begin, end }); } else if let (Some(begin), Some(size)) = (self.low_pc, self.size) { add_range(gimli::Range { begin, end: begin + size, }); } Ok(added_any) } } /// An iterator over function frames. pub struct FrameIter<'ctx, R>(FrameIterState<'ctx, R>) where R: gimli::Reader; enum FrameIterState<'ctx, R> where R: gimli::Reader, { Empty, Location(Option>), Frames(FrameIterFrames<'ctx, R>), } struct FrameIterFrames<'ctx, R> where R: gimli::Reader, { unit: &'ctx ResUnit, sections: &'ctx gimli::Dwarf, function: &'ctx Function, inlined_functions: iter::Rev>>, next: Option>, } impl<'ctx, R> FrameIter<'ctx, R> where R: gimli::Reader + 'ctx, { /// Advances the iterator and returns the next frame. pub fn next(&mut self) -> Result>, Error> { let frames = match &mut self.0 { FrameIterState::Empty => return Ok(None), FrameIterState::Location(location) => { // We can't move out of a mutable reference, so use `take` instead. let location = location.take(); self.0 = FrameIterState::Empty; return Ok(Some(Frame { dw_die_offset: None, function: None, location, })); } FrameIterState::Frames(frames) => frames, }; let loc = frames.next.take(); let func = match frames.inlined_functions.next() { Some(func) => func, None => { let frame = Frame { dw_die_offset: Some(frames.function.dw_die_offset), function: frames.function.name.clone().map(|name| FunctionName { name, language: frames.unit.lang, }), location: loc, }; self.0 = FrameIterState::Empty; return Ok(Some(frame)); } }; let mut next = Location { file: None, line: if func.call_line != 0 { Some(func.call_line) } else { None }, column: if func.call_column != 0 { Some(func.call_column) } else { None }, }; if let Some(call_file) = func.call_file { if let Some(lines) = frames.unit.parse_lines(frames.sections)? { next.file = lines.files.get(call_file as usize).map(String::as_str); } } frames.next = Some(next); Ok(Some(Frame { dw_die_offset: Some(func.dw_die_offset), function: func.name.clone().map(|name| FunctionName { name, language: frames.unit.lang, }), location: loc, })) } } #[cfg(feature = "fallible-iterator")] impl<'ctx, R> fallible_iterator::FallibleIterator for FrameIter<'ctx, R> where R: gimli::Reader + 'ctx, { type Item = Frame<'ctx, R>; type Error = Error; #[inline] fn next(&mut self) -> Result>, Error> { self.next() } } /// A function frame. pub struct Frame<'ctx, R: gimli::Reader> { /// The DWARF unit offset corresponding to the DIE of the function. pub dw_die_offset: Option>, /// The name of the function. pub function: Option>, /// The source location corresponding to this frame. pub location: Option>, } /// A function name. pub struct FunctionName { /// The name of the function. pub name: R, /// The language of the compilation unit containing this function. pub language: Option, } impl FunctionName { /// The raw name of this function before demangling. pub fn raw_name(&self) -> Result, Error> { self.name.to_string_lossy() } /// The name of this function after demangling (if applicable). pub fn demangle(&self) -> Result, Error> { self.raw_name().map(|x| demangle_auto(x, self.language)) } } /// Demangle a symbol name using the demangling scheme for the given language. /// /// Returns `None` if demangling failed or is not required. #[allow(unused_variables)] pub fn demangle(name: &str, language: gimli::DwLang) -> Option { match language { #[cfg(feature = "rustc-demangle")] gimli::DW_LANG_Rust => rustc_demangle::try_demangle(name) .ok() .as_ref() .map(|x| format!("{:#}", x)), #[cfg(feature = "cpp_demangle")] gimli::DW_LANG_C_plus_plus | gimli::DW_LANG_C_plus_plus_03 | gimli::DW_LANG_C_plus_plus_11 | gimli::DW_LANG_C_plus_plus_14 => cpp_demangle::Symbol::new(name) .ok() .and_then(|x| x.demangle(&Default::default()).ok()), _ => None, } } /// Apply 'best effort' demangling of a symbol name. /// /// If `language` is given, then only the demangling scheme for that language /// is used. /// /// If `language` is `None`, then heuristics are used to determine how to /// demangle the name. Currently, these heuristics are very basic. /// /// If demangling fails or is not required, then `name` is returned unchanged. pub fn demangle_auto(name: Cow<'_, str>, language: Option) -> Cow<'_, str> { match language { Some(language) => demangle(name.as_ref(), language), None => demangle(name.as_ref(), gimli::DW_LANG_Rust) .or_else(|| demangle(name.as_ref(), gimli::DW_LANG_C_plus_plus)), } .map(Cow::from) .unwrap_or(name) } /// A source location. pub struct Location<'a> { /// The file name. pub file: Option<&'a str>, /// The line number. pub line: Option, /// The column number. pub column: Option, } #[cfg(test)] mod tests { #[test] fn context_is_send() { fn assert_is_send() {} assert_is_send::>>(); } } addr2line-0.21.0/tests/correctness.rs000064400000000000000000000075331046102023000156220ustar 00000000000000use addr2line::Context; use fallible_iterator::FallibleIterator; use findshlibs::{IterationControl, SharedLibrary, TargetSharedLibrary}; use object::Object; use std::borrow::Cow; use std::fs::File; use std::sync::Arc; fn find_debuginfo() -> memmap2::Mmap { let path = std::env::current_exe().unwrap(); let file = File::open(&path).unwrap(); let map = unsafe { memmap2::Mmap::map(&file).unwrap() }; let file = &object::File::parse(&*map).unwrap(); if let Ok(uuid) = file.mach_uuid() { for candidate in path.parent().unwrap().read_dir().unwrap() { let path = candidate.unwrap().path(); if !path.to_str().unwrap().ends_with(".dSYM") { continue; } for candidate in path.join("Contents/Resources/DWARF").read_dir().unwrap() { let path = candidate.unwrap().path(); let file = File::open(&path).unwrap(); let map = unsafe { memmap2::Mmap::map(&file).unwrap() }; let file = &object::File::parse(&*map).unwrap(); if file.mach_uuid().unwrap() == uuid { return map; } } } } return map; } #[test] fn correctness() { let map = find_debuginfo(); let file = &object::File::parse(&*map).unwrap(); let module_base = file.relative_address_base(); let endian = if file.is_little_endian() { gimli::RunTimeEndian::Little } else { gimli::RunTimeEndian::Big }; fn load_section<'data: 'file, 'file, O, Endian>( id: gimli::SectionId, file: &'file O, endian: Endian, ) -> Result, gimli::Error> where O: object::Object<'data, 'file>, Endian: gimli::Endianity, { use object::ObjectSection; let data = file .section_by_name(id.name()) .and_then(|section| section.uncompressed_data().ok()) .unwrap_or(Cow::Borrowed(&[])); Ok(gimli::EndianArcSlice::new(Arc::from(&*data), endian)) } let dwarf = gimli::Dwarf::load(|id| load_section(id, file, endian)).unwrap(); let ctx = Context::from_dwarf(dwarf).unwrap(); let mut split_dwarf_loader = addr2line::builtin_split_dwarf_loader::SplitDwarfLoader::new( |data, endian| gimli::EndianArcSlice::new(Arc::from(&*data), endian), None, ); let mut bias = None; TargetSharedLibrary::each(|lib| { bias = Some((lib.virtual_memory_bias().0 as u64).wrapping_sub(module_base)); IterationControl::Break }); #[allow(unused_mut)] let mut test = |sym: u64, expected_prefix: &str| { let ip = sym.wrapping_sub(bias.unwrap()); let frames = ctx.find_frames(ip); let frames = split_dwarf_loader.run(frames).unwrap(); let frame = frames.last().unwrap().unwrap(); let name = frame.function.as_ref().unwrap().demangle().unwrap(); // Old rust versions generate DWARF with wrong linkage name, // so only check the start. if !name.starts_with(expected_prefix) { panic!("incorrect name '{}', expected {:?}", name, expected_prefix); } }; test(test_function as u64, "correctness::test_function"); test( small::test_function as u64, "correctness::small::test_function", ); test(auxiliary::foo as u64, "auxiliary::foo"); } mod small { pub fn test_function() { println!("y"); } } fn test_function() { println!("x"); } #[test] fn zero_function() { let map = find_debuginfo(); let file = &object::File::parse(&*map).unwrap(); let ctx = Context::new(file).unwrap(); for probe in 0..10 { assert!( ctx.find_frames(probe) .skip_all_loads() .unwrap() .count() .unwrap() < 10 ); } } addr2line-0.21.0/tests/output_equivalence.rs000064400000000000000000000067421046102023000172120ustar 00000000000000use std::env; use std::ffi::OsStr; use std::path::Path; use std::process::Command; use backtrace::Backtrace; use findshlibs::{IterationControl, SharedLibrary, TargetSharedLibrary}; use libtest_mimic::{Arguments, Failed, Trial}; #[inline(never)] fn make_trace() -> Vec { fn foo() -> Backtrace { bar() } #[inline(never)] fn bar() -> Backtrace { baz() } #[inline(always)] fn baz() -> Backtrace { Backtrace::new_unresolved() } let mut base_addr = None; TargetSharedLibrary::each(|lib| { base_addr = Some(lib.virtual_memory_bias().0 as isize); IterationControl::Break }); let addrfix = -base_addr.unwrap(); let trace = foo(); trace .frames() .iter() .take(5) .map(|x| format!("{:p}", (x.ip() as *const u8).wrapping_offset(addrfix))) .collect() } fn run_cmd>(exe: P, me: &Path, flags: Option<&str>, trace: &str) -> String { let mut cmd = Command::new(exe); cmd.env("LC_ALL", "C"); // GNU addr2line is localized, we aren't cmd.env("RUST_BACKTRACE", "1"); // if a child crashes, we want to know why if let Some(flags) = flags { cmd.arg(flags); } cmd.arg("--exe").arg(me).arg(trace); let output = cmd.output().unwrap(); assert!(output.status.success()); String::from_utf8(output.stdout).unwrap() } fn run_test(flags: Option<&str>) -> Result<(), Failed> { let me = env::current_exe().unwrap(); let mut exe = me.clone(); assert!(exe.pop()); if exe.file_name().unwrap().to_str().unwrap() == "deps" { assert!(exe.pop()); } exe.push("examples"); exe.push("addr2line"); assert!(exe.is_file()); let trace = make_trace(); // HACK: GNU addr2line has a bug where looking up multiple addresses can cause the second // lookup to fail. Workaround by doing one address at a time. for addr in &trace { let theirs = run_cmd("addr2line", &me, flags, addr); let ours = run_cmd(&exe, &me, flags, addr); // HACK: GNU addr2line does not tidy up paths properly, causing double slashes to be printed. // We consider our behavior to be correct, so we fix their output to match ours. let theirs = theirs.replace("//", "/"); assert!( theirs == ours, "Output not equivalent: $ addr2line {0} --exe {1} {2} {4} $ {3} {0} --exe {1} {2} {5} ", flags.unwrap_or(""), me.display(), trace.join(" "), exe.display(), theirs, ours ); } Ok(()) } static FLAGS: &str = "aipsf"; fn make_tests() -> Vec { (0..(1 << FLAGS.len())) .map(|bits| { if bits == 0 { None } else { let mut param = String::new(); param.push('-'); for (i, flag) in FLAGS.chars().enumerate() { if (bits & (1 << i)) != 0 { param.push(flag); } } Some(param) } }) .map(|param| { Trial::test( format!("addr2line {}", param.as_ref().map_or("", String::as_str)), move || run_test(param.as_ref().map(String::as_str)), ) }) .collect() } fn main() { if !cfg!(target_os = "linux") { return; } let args = Arguments::from_args(); libtest_mimic::run(&args, make_tests()).exit(); } addr2line-0.21.0/tests/parse.rs000064400000000000000000000056431046102023000144020ustar 00000000000000use std::borrow::Cow; use std::env; use std::fs::File; use std::path::{self, PathBuf}; use object::Object; fn release_fixture_path() -> PathBuf { if let Ok(p) = env::var("ADDR2LINE_FIXTURE_PATH") { return p.into(); } let mut path = PathBuf::new(); if let Ok(dir) = env::var("CARGO_MANIFEST_DIR") { path.push(dir); } path.push("fixtures"); path.push("addr2line-release"); path } fn with_file)>(target: &path::Path, f: F) { let file = File::open(target).unwrap(); let map = unsafe { memmap2::Mmap::map(&file).unwrap() }; let file = object::File::parse(&*map).unwrap(); f(&file) } fn dwarf_load<'a>(object: &object::File<'a>) -> gimli::Dwarf> { let load_section = |id: gimli::SectionId| -> Result, gimli::Error> { use object::ObjectSection; let data = object .section_by_name(id.name()) .and_then(|section| section.data().ok()) .unwrap_or(&[][..]); Ok(Cow::Borrowed(data)) }; gimli::Dwarf::load(&load_section).unwrap() } fn dwarf_borrow<'a>( dwarf: &'a gimli::Dwarf>, ) -> gimli::Dwarf> { let borrow_section: &dyn for<'b> Fn( &'b Cow<'_, [u8]>, ) -> gimli::EndianSlice<'b, gimli::LittleEndian> = &|section| gimli::EndianSlice::new(section, gimli::LittleEndian); dwarf.borrow(&borrow_section) } #[test] fn parse_base_rc() { let target = release_fixture_path(); with_file(&target, |file| { addr2line::ObjectContext::new(file).unwrap(); }); } #[test] fn parse_base_slice() { let target = release_fixture_path(); with_file(&target, |file| { let dwarf = dwarf_load(file); let dwarf = dwarf_borrow(&dwarf); addr2line::Context::from_dwarf(dwarf).unwrap(); }); } #[test] fn parse_lines_rc() { let target = release_fixture_path(); with_file(&target, |file| { let context = addr2line::ObjectContext::new(file).unwrap(); context.parse_lines().unwrap(); }); } #[test] fn parse_lines_slice() { let target = release_fixture_path(); with_file(&target, |file| { let dwarf = dwarf_load(file); let dwarf = dwarf_borrow(&dwarf); let context = addr2line::Context::from_dwarf(dwarf).unwrap(); context.parse_lines().unwrap(); }); } #[test] fn parse_functions_rc() { let target = release_fixture_path(); with_file(&target, |file| { let context = addr2line::ObjectContext::new(file).unwrap(); context.parse_functions().unwrap(); }); } #[test] fn parse_functions_slice() { let target = release_fixture_path(); with_file(&target, |file| { let dwarf = dwarf_load(file); let dwarf = dwarf_borrow(&dwarf); let context = addr2line::Context::from_dwarf(dwarf).unwrap(); context.parse_functions().unwrap(); }); }