diff --git a/.github/actions/build-with-patched-std/action.yml b/.github/actions/build-with-patched-std/action.yml new file mode 100644 index 000000000..5466a2289 --- /dev/null +++ b/.github/actions/build-with-patched-std/action.yml @@ -0,0 +1,48 @@ +# Github composite action to build a single-source-file test binary with an +# already-checked-out version of Rust's stdlib, that will be patched with a +# given revision of the backtrace crate. + +name: Build with patched std +description: > + Build a binary with a version of std that's had a specific revision of + backtrace patched in. +inputs: + backtrace-commit: + description: The git commit of backtrace to patch in to std + required: true + main-rs: + description: The (single) source code file to compile + required: true + rustc-dir: + description: The root directory of the rustc repo + required: true +outputs: + test-binary-size: + description: The size in bytes of the built test binary + value: ${{ steps.measure.outputs.test-binary-size }} +runs: + using: composite + steps: + - shell: bash + id: measure + env: + RUSTC_FLAGS: -Copt-level=3 -Cstrip=symbols + # This symlink is made by Build::new() in the bootstrap crate, using a + # symlink on Linux and a junction on Windows, so it will exist on both + # platforms. + RUSTC_BUILD_DIR: build/host + working-directory: ${{ inputs.rustc-dir }} + run: | + rm -rf "$RUSTC_BUILD_DIR/stage0-std" + + (cd library/backtrace && git checkout ${{ inputs.backtrace-commit }}) + git add library/backtrace + + python3 x.py build library --stage 0 + + TEMP_BUILD_OUTPUT=$(mktemp test-binary-XXXXXXXX) + "$RUSTC_BUILD_DIR/stage0-sysroot/bin/rustc" $RUSTC_FLAGS "${{ inputs.main-rs }}" -o "$TEMP_BUILD_OUTPUT" + BINARY_SIZE=$(stat -c '%s' "$TEMP_BUILD_OUTPUT") + rm "$TEMP_BUILD_OUTPUT" + + echo "test-binary-size=$BINARY_SIZE" >> "$GITHUB_OUTPUT" diff --git a/.github/actions/report-code-size-changes/action.yml b/.github/actions/report-code-size-changes/action.yml new file mode 100644 index 000000000..ede26975a --- /dev/null +++ b/.github/actions/report-code-size-changes/action.yml @@ -0,0 +1,111 @@ +# Github composite action to report on code size changes across different +# platforms. + +name: Report binary size changes on PR +description: | + Report on code size changes across different platforms resulting from a PR. + The only input argument is the path to a directory containing a set of + "*.json" files (extension required), each file containing the keys: + + - platform: the platform that the code size change was measured on + - reference: the size in bytes of the reference binary (base of PR) + - updated: the size in bytes of the updated binary (head of PR) + + The size is reported as a comment on the PR (accessed via context). +inputs: + data-directory: + description: > + Path to directory containing size data as a set of "*.json" files. + required: true +runs: + using: composite + steps: + - name: Post a PR comment if the size has changed + uses: actions/github-script@v6 + env: + DATA_DIRECTORY: ${{ inputs.data-directory }} + with: + script: | + const fs = require("fs"); + + const size_dir = process.env.DATA_DIRECTORY; + + // Map the set of all the *.json files into an array of objects. + const globber = await glob.create(`${size_dir}/*.json`); + const files = await globber.glob(); + const sizes = files.map(path => { + const contents = fs.readFileSync(path); + return JSON.parse(contents); + }); + + // Map each object into some text, but only if it shows any difference + // to report. + const size_reports = sizes.flatMap(size_data => { + const platform = size_data["platform"]; + const reference = size_data["reference"]; + const updated = size_data["updated"]; + + if (!(reference > 0)) { + core.setFailed(`Reference size invalid: ${reference}`); + return; + } + + if (!(updated > 0)) { + core.setFailed(`Updated size invalid: ${updated}`); + return; + } + + const formatter = Intl.NumberFormat("en", { + useGrouping: "always" + }); + + const updated_str = formatter.format(updated); + const reference_str = formatter.format(reference); + + const diff = updated - reference; + const diff_pct = (updated / reference) - 1; + + const diff_str = Intl.NumberFormat("en", { + useGrouping: "always", + sign: "exceptZero" + }).format(diff); + + const diff_pct_str = Intl.NumberFormat("en", { + style: "percent", + useGrouping: "always", + sign: "exceptZero", + maximumFractionDigits: 2 + }).format(diff_pct); + + if (diff !== 0) { + // The body is created here and wrapped so "weirdly" to avoid whitespace at the start of the lines, + // which is interpreted as a code block by Markdown. + const report = `On platform \`${platform}\`: + + - Original binary size: **${reference_str} B** + - Updated binary size: **${updated_str} B** + - Difference: **${diff_str} B** (${diff_pct_str}) + + `; + + return [report]; + } else { + return []; + } + }); + + // If there are any size changes to report, format a comment and post + // it. + if (size_reports.length > 0) { + const comment_sizes = size_reports.join(""); + const body = `Code size changes for a hello-world Rust program linked with libstd with backtrace: + + ${comment_sizes}`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body + }); + } diff --git a/.github/workflows/check-binary-size.yml b/.github/workflows/check-binary-size.yml index 0beae1da9..d045fb7b3 100644 --- a/.github/workflows/check-binary-size.yml +++ b/.github/workflows/check-binary-size.yml @@ -9,75 +9,143 @@ on: branches: - master +# Both the "measure" and "report" jobs need to know this. +env: + SIZE_DATA_DIR: sizes + +# Responsibility is divided between two jobs "measure" and "report", so that the +# job that builds (and potentnially runs) untrusted code does not have PR write +# permission, and vice-versa. jobs: - test: + measure: name: Check binary size - runs-on: ubuntu-latest + strategy: + matrix: + platform: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.platform }} permissions: - pull-requests: write + contents: read + env: + # This cannot be used as a context variable in the 'uses' key later. If it + # changes, update those steps too. + BACKTRACE_DIR: backtrace + RUSTC_DIR: rustc + TEST_MAIN_RS: foo.rs + BASE_COMMIT: ${{ github.event.pull_request.base.sha }} + HEAD_COMMIT: ${{ github.event.pull_request.head.sha }} + SIZE_DATA_FILE: size-${{ strategy.job-index }}.json steps: - name: Print info + shell: bash run: | - echo "Current SHA: ${{ github.event.pull_request.head.sha }}" - echo "Base SHA: ${{ github.event.pull_request.base.sha }}" + echo "Current SHA: $HEAD_COMMIT" + echo "Base SHA: $BASE_COMMIT" + # Note: the backtrace source that's cloned here is NOT the version to be + # patched in to std. It's cloned here to access the Github action for + # building and measuring the test binary. + - name: Clone backtrace to access Github action + uses: actions/checkout@v3 + with: + path: ${{ env.BACKTRACE_DIR }} - name: Clone Rustc uses: actions/checkout@v3 with: repository: rust-lang/rust - fetch-depth: 1 - - name: Fetch backtrace - run: git submodule update --init library/backtrace - - name: Create hello world program that uses backtrace - run: printf "fn main() { panic!(); }" > foo.rs - - name: Build binary with base version of backtrace + path: ${{ env.RUSTC_DIR }} + - name: Set up std repository and backtrace submodule for size test + shell: bash + working-directory: ${{ env.RUSTC_DIR }} + env: + PR_SOURCE_REPO: ${{ github.event.pull_request.head.repo.full_name }} run: | - printf "[llvm]\ndownload-ci-llvm = true\n\n[rust]\nincremental = false\n" > config.toml + # Bootstrap config + cat < config.toml + [llvm] + download-ci-llvm = true + [rust] + incremental = false + EOF + + # Test program source + cat < $TEST_MAIN_RS + fn main() { + panic!(); + } + EOF + + git submodule update --init library/backtrace + cd library/backtrace - git remote add head-pr https://github.com/${{ github.event.pull_request.head.repo.full_name }} + git remote add head-pr "https://github.com/$PR_SOURCE_REPO" git fetch --all - git checkout ${{ github.event.pull_request.base.sha }} - cd ../.. - git add library/backtrace - python3 x.py build library --stage 0 - ./build/x86_64-unknown-linux-gnu/stage0-sysroot/bin/rustc -O foo.rs -o binary-reference + - name: Build binary with base version of backtrace + uses: ./backtrace/.github/actions/build-with-patched-std + with: + backtrace-commit: ${{ env.BASE_COMMIT }} + main-rs: ${{ env.TEST_MAIN_RS }} + rustc-dir: ${{ env.RUSTC_DIR }} + id: size-reference - name: Build binary with PR version of backtrace - run: | - cd library/backtrace - git checkout ${{ github.event.pull_request.head.sha }} - cd ../.. - git add library/backtrace - rm -rf build/x86_64-unknown-linux-gnu/stage0-std - python3 x.py build library --stage 0 - ./build/x86_64-unknown-linux-gnu/stage0-sysroot/bin/rustc -O foo.rs -o binary-updated - - name: Display binary size - run: | - ls -la binary-* - echo "SIZE_REFERENCE=$(stat -c '%s' binary-reference)" >> "$GITHUB_ENV" - echo "SIZE_UPDATED=$(stat -c '%s' binary-updated)" >> "$GITHUB_ENV" - - name: Post a PR comment if the size has changed + uses: ./backtrace/.github/actions/build-with-patched-std + with: + backtrace-commit: ${{ env.HEAD_COMMIT }} + main-rs: ${{ env.TEST_MAIN_RS }} + rustc-dir: ${{ env.RUSTC_DIR }} + id: size-updated + # There is no built-in way to "collect" all the outputs of a set of jobs + # run with a matrix strategy. Subsequent jobs that have a "needs" + # dependency on this one will be run once, when the last matrix job is + # run. Appending data to a single file within a matrix is subject to race + # conditions. So we write the size data to files with distinct names + # generated from the job index. + - name: Write sizes to file uses: actions/github-script@v6 + env: + SIZE_REFERENCE: ${{ steps.size-reference.outputs.test-binary-size }} + SIZE_UPDATED: ${{ steps.size-updated.outputs.test-binary-size }} + PLATFORM: ${{ matrix.platform }} with: script: | - const reference = process.env.SIZE_REFERENCE; - const updated = process.env.SIZE_UPDATED; - const diff = updated - reference; - const plus = diff > 0 ? "+" : ""; - const diff_str = `${plus}${diff}B`; + const fs = require("fs"); + const path = require("path"); - if (diff !== 0) { - const percent = (((updated / reference) - 1) * 100).toFixed(2); - // The body is created here and wrapped so "weirdly" to avoid whitespace at the start of the lines, - // which is interpreted as a code block by Markdown. - const body = `Below is the size of a hello-world Rust program linked with libstd with backtrace. + fs.mkdirSync(process.env.SIZE_DATA_DIR, {recursive: true}); - Original binary size: **${reference}B** - Updated binary size: **${updated}B** - Difference: **${diff_str}** (${percent}%)`; + const output_data = JSON.stringify({ + platform: process.env.PLATFORM, + reference: process.env.SIZE_REFERENCE, + updated: process.env.SIZE_UPDATED, + }); - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body - }) - } + // The "wx" flag makes this fail if the file exists, which we want, + // because there should be no collisions. + fs.writeFileSync( + path.join(process.env.SIZE_DATA_DIR, process.env.SIZE_DATA_FILE), + output_data, + { flag: "wx" }, + ); + - name: Upload size data + uses: actions/upload-artifact@v3 + with: + name: size-files + path: ${{ env.SIZE_DATA_DIR }}/${{ env.SIZE_DATA_FILE }} + retention-days: 1 + if-no-files-found: error + report: + name: Report binary size changes + runs-on: ubuntu-latest + needs: measure + permissions: + pull-requests: write + steps: + # Clone backtrace to access Github composite actions to report size. + - uses: actions/checkout@v3 + - name: Download size data + uses: actions/download-artifact@v3 + with: + name: size-files + path: ${{ env.SIZE_DATA_DIR }} + - name: Analyze and report size changes + uses: ./.github/actions/report-code-size-changes + with: + data-directory: ${{ env.SIZE_DATA_DIR }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 21d7cb492..e28c55a97 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -110,6 +110,17 @@ jobs: - run: ./ci/debuglink-docker.sh if: contains(matrix.os, 'ubuntu') + # Test that backtraces are still symbolicated if we don't embed an absolute + # path to the PDB file in the binary. + # Add -Cforce-frame-pointers for stability. The test otherwise fails + # non-deterministically on i686-pc-windows-msvc because the stack cannot be + # unwound reliably. This failure is not related to the feature being tested. + - run: cargo clean && cargo test + if: contains(matrix.rust, 'msvc') + name: "Test that backtraces are symbolicated without absolute PDB path" + env: + RUSTFLAGS: "-Clink-arg=/PDBALTPATH:%_PDB% -Cforce-frame-pointers" + # Test that including as a submodule will still work, both with and without # the `backtrace` feature enabled. - run: cargo build --manifest-path crates/as-if-std/Cargo.toml diff --git a/.gitignore b/.gitignore index a9d37c560..eb5a316cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ target -Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..88f853ab9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,221 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "as-if-std" +version = "0.1.0" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "backtrace" +version = "0.3.70" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "cpp_demangle", + "dylib-dep", + "libc", + "libloading", + "miniz_oxide", + "object", + "rustc-demangle", + "rustc-serialize", + "serde", + "winapi", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpp_demangle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpp_smoke_test" +version = "0.1.0" +dependencies = [ + "backtrace", + "cc", +] + +[[package]] +name = "dylib-dep" +version = "0.1.0" + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "object" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +dependencies = [ + "memchr", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-serialize" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 6714b3b7d..7fe05d818 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "backtrace" -version = "0.3.69" +version = "0.3.70" authors = ["The Rust Project Developers"] build = "build.rs" license = "MIT OR Apache-2.0" @@ -13,8 +13,9 @@ A library to acquire a stack trace (backtrace) at runtime in a Rust program. """ autoexamples = true autotests = true -edition = "2018" +edition = "2021" exclude = ["/ci/"] +rust-version = "1.65.0" [workspace] members = ['crates/cpp_smoke_test', 'crates/as-if-std'] @@ -45,7 +46,7 @@ libc = { version = "0.2.146", default-features = false } [target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies.object] version = "0.32.0" default-features = false -features = ['read_core', 'elf', 'macho', 'pe', 'unaligned', 'archive'] +features = ['read_core', 'elf', 'macho', 'pe', 'xcoff', 'unaligned', 'archive'] [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.9", optional = true } @@ -118,12 +119,12 @@ required-features = ["std"] [[test]] name = "smoke" required-features = ["std"] -edition = '2018' +edition = '2021' [[test]] name = "accuracy" required-features = ["std"] -edition = '2018' +edition = '2021' [[test]] name = "concurrent-panics" diff --git a/README.md b/README.md index cd80a697c..cebbad6d9 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ fn main() { // do_some_work(); - println!("{:?}", bt); + println!("{bt:?}"); } ``` @@ -55,6 +55,18 @@ fn main() { } ``` +# Supported Rust Versions + +The `backtrace` crate is a core component of the standard library, and must +at times keep up with the evolution of various platforms in order to serve +the standard library's needs. This often means using recent libraries +that provide unwinding and symbolication for various platforms. +Thus `backtrace` is likely to use recent Rust features or depend on a library +which itself uses them. Its minimum supported Rust version, by policy, is +within a few versions of current stable, approximately "stable - 2". + +This policy takes precedence over versions written anywhere else in this repo. + # License This project is licensed under either of diff --git a/build.rs b/build.rs index 9bd3abd16..2a3da849f 100644 --- a/build.rs +++ b/build.rs @@ -11,17 +11,24 @@ pub fn main() { } } +// Used to detect the value of the `__ANDROID_API__` +// builtin #define +const MARKER: &str = "BACKTRACE_RS_ANDROID_APIVERSION"; +const ANDROID_API_C: &str = " +BACKTRACE_RS_ANDROID_APIVERSION __ANDROID_API__ +"; + fn build_android() { - // Resolve `src/android-api.c` relative to this file. + // Create `android-api.c` on demand. // Required to support calling this from the `std` build script. - let android_api_c = Path::new(file!()) - .parent() - .unwrap() - .join("src/android-api.c"); - let expansion = match cc::Build::new().file(android_api_c).try_expand() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + let android_api_c = Path::new(&out_dir).join("android-api.c"); + std::fs::write(&android_api_c, ANDROID_API_C).unwrap(); + + let expansion = match cc::Build::new().file(&android_api_c).try_expand() { Ok(result) => result, Err(e) => { - println!("failed to run C compiler: {}", e); + eprintln!("warning: android version detection failed while running C compiler: {e}"); return; } }; @@ -29,13 +36,12 @@ fn build_android() { Ok(s) => s, Err(_) => return, }; - println!("expanded android version detection:\n{}", expansion); - let marker = "APIVERSION"; - let i = match expansion.find(marker) { + eprintln!("expanded android version detection:\n{expansion}"); + let i = match expansion.find(MARKER) { Some(i) => i, None => return, }; - let version = match expansion[i + marker.len() + 1..].split_whitespace().next() { + let version = match expansion[i + MARKER.len() + 1..].split_whitespace().next() { Some(s) => s, None => return, }; diff --git a/ci/run-docker.sh b/ci/run-docker.sh index 8aa6d84a4..ea77c785d 100755 --- a/ci/run-docker.sh +++ b/ci/run-docker.sh @@ -18,7 +18,7 @@ run() { --volume `pwd`/target:/checkout/target \ --workdir /checkout \ --privileged \ - --env RUSTFLAGS \ + --env RUSTFLAGS="-Lnative=/usr/lib/x86_64-linux-musl/" \ backtrace \ bash \ -c 'PATH=$PATH:/rust/bin exec ci/run.sh' diff --git a/crates/as-if-std/Cargo.toml b/crates/as-if-std/Cargo.toml index bcbcfe159..7f12cfb56 100644 --- a/crates/as-if-std/Cargo.toml +++ b/crates/as-if-std/Cargo.toml @@ -2,7 +2,7 @@ name = "as-if-std" version = "0.1.0" authors = ["Alex Crichton "] -edition = "2018" +edition = "2021" publish = false [lib] @@ -24,7 +24,7 @@ addr2line = { version = "0.21.0", optional = true, default-features = false } version = "0.32.0" default-features = false optional = true -features = ['read_core', 'elf', 'macho', 'pe', 'unaligned', 'archive'] +features = ['read_core', 'elf', 'macho', 'pe', 'xcoff', 'unaligned', 'archive'] [build-dependencies] # Dependency of the `backtrace` crate diff --git a/crates/as-if-std/src/lib.rs b/crates/as-if-std/src/lib.rs index e1d8faaeb..89ed961f5 100644 --- a/crates/as-if-std/src/lib.rs +++ b/crates/as-if-std/src/lib.rs @@ -9,6 +9,7 @@ extern crate alloc; // We want to `pub use std::*` in the root but we don't want `std` available in // the root namespace, so do this in a funky inner module. +#[allow(unused_imports)] mod __internal { extern crate std; pub use std::*; diff --git a/crates/cpp_smoke_test/tests/smoke.rs b/crates/cpp_smoke_test/tests/smoke.rs index b9aef47f5..a303562f9 100644 --- a/crates/cpp_smoke_test/tests/smoke.rs +++ b/crates/cpp_smoke_test/tests/smoke.rs @@ -49,13 +49,13 @@ fn smoke_test_cpp() { .take(2) .collect(); - println!("actual names = {:#?}", names); + println!("actual names = {names:#?}"); let expected = [ "void space::templated_trampoline(void (*)())", "cpp_trampoline", ]; - println!("expected names = {:#?}", expected); + println!("expected names = {expected:#?}"); assert_eq!(names.len(), expected.len()); for (actual, expected) in names.iter().zip(expected.iter()) { diff --git a/crates/debuglink/Cargo.toml b/crates/debuglink/Cargo.toml index 6b55b1394..5e62abd37 100644 --- a/crates/debuglink/Cargo.toml +++ b/crates/debuglink/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "debuglink" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] backtrace = { path = "../.." } diff --git a/crates/debuglink/src/main.rs b/crates/debuglink/src/main.rs index 99265ae9a..b0f4063d7 100644 --- a/crates/debuglink/src/main.rs +++ b/crates/debuglink/src/main.rs @@ -9,7 +9,7 @@ fn main() { let expect = std::path::Path::new(&crate_dir).join("src/main.rs"); let bt = backtrace::Backtrace::new(); - println!("{:?}", bt); + println!("{bt:?}"); let mut found_main = false; @@ -20,7 +20,7 @@ fn main() { } if let Some(name) = symbols[0].name() { - let name = format!("{:#}", name); + let name = format!("{name:#}"); if name == "debuglink::main" { found_main = true; let filename = symbols[0].filename().unwrap(); diff --git a/crates/dylib-dep/Cargo.toml b/crates/dylib-dep/Cargo.toml index c3d4a8c2f..e6cc9c23b 100644 --- a/crates/dylib-dep/Cargo.toml +++ b/crates/dylib-dep/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dylib-dep" version = "0.1.0" -edition = "2018" +edition = "2021" authors = [] publish = false diff --git a/crates/line-tables-only/Cargo.toml b/crates/line-tables-only/Cargo.toml index e2967d3d3..8d17db58c 100644 --- a/crates/line-tables-only/Cargo.toml +++ b/crates/line-tables-only/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "line-tables-only" version = "0.1.0" -edition = "2018" +edition = "2021" [build-dependencies] cc = "1.0" diff --git a/crates/line-tables-only/src/lib.rs b/crates/line-tables-only/src/lib.rs index bd5afcb3a..860d3db59 100644 --- a/crates/line-tables-only/src/lib.rs +++ b/crates/line-tables-only/src/lib.rs @@ -1,8 +1,9 @@ #[cfg(test)] mod tests { - use std::path::Path; use backtrace::Backtrace; use libc::c_void; + use std::path::Path; + use std::ptr::addr_of_mut; pub type Callback = extern "C" fn(data: *mut c_void); @@ -12,14 +13,15 @@ mod tests { extern "C" fn store_backtrace(data: *mut c_void) { let bt = backtrace::Backtrace::new(); - unsafe { *(data as *mut Option) = Some(bt) }; + unsafe { *data.cast::>() = Some(bt) }; } - fn assert_contains(backtrace: &Backtrace, - expected_name: &str, - expected_file: &str, - expected_line: u32) { - + fn assert_contains( + backtrace: &Backtrace, + expected_name: &str, + expected_file: &str, + expected_line: u32, + ) { let expected_file = Path::new(expected_file); for frame in backtrace.frames() { @@ -34,7 +36,7 @@ mod tests { } } - panic!("symbol {:?} not found in backtrace: {:?}", expected_name, backtrace); + panic!("symbol {expected_name:?} not found in backtrace: {backtrace:?}"); } /// Verifies that when debug info includes only lines tables the generated @@ -48,7 +50,7 @@ mod tests { #[cfg_attr(windows, ignore)] fn backtrace_works_with_line_tables_only() { let mut backtrace: Option = None; - unsafe { foo(store_backtrace, &mut backtrace as *mut _ as *mut c_void) }; + unsafe { foo(store_backtrace, addr_of_mut!(backtrace).cast::()) }; let backtrace = backtrace.expect("backtrace"); assert_contains(&backtrace, "foo", "src/callback.c", 13); assert_contains(&backtrace, "bar", "src/callback.c", 9); diff --git a/crates/macos_frames_test/Cargo.toml b/crates/macos_frames_test/Cargo.toml index 278d51e79..849e76414 100644 --- a/crates/macos_frames_test/Cargo.toml +++ b/crates/macos_frames_test/Cargo.toml @@ -2,7 +2,7 @@ name = "macos_frames_test" version = "0.1.0" authors = ["Aaron Hill "] -edition = "2018" +edition = "2021" [dependencies.backtrace] path = "../.." diff --git a/crates/macos_frames_test/tests/main.rs b/crates/macos_frames_test/tests/main.rs index f0e905b24..5d74bdcc3 100644 --- a/crates/macos_frames_test/tests/main.rs +++ b/crates/macos_frames_test/tests/main.rs @@ -15,8 +15,7 @@ fn backtrace_no_dsym() { let executable_name = dsym_path.file_name().unwrap().to_str().unwrap().to_string(); assert!(dsym_path.pop()); // Pop executable dsym_path.push(format!( - "{}.dSYM/Contents/Resources/DWARF/{0}", - executable_name + "{executable_name}.dSYM/Contents/Resources/DWARF/{executable_name}" )); let _ = fs::OpenOptions::new() .read(false) diff --git a/crates/without_debuginfo/Cargo.toml b/crates/without_debuginfo/Cargo.toml index 19d76cbec..38e559971 100644 --- a/crates/without_debuginfo/Cargo.toml +++ b/crates/without_debuginfo/Cargo.toml @@ -2,7 +2,7 @@ name = "without_debuginfo" version = "0.1.0" authors = ["Alex Crichton "] -edition = "2018" +edition = "2021" [dependencies.backtrace] path = "../.." diff --git a/examples/raw.rs b/examples/raw.rs index d96a127a2..95e17dbd5 100644 --- a/examples/raw.rs +++ b/examples/raw.rs @@ -33,7 +33,7 @@ fn print() { } if let Some(name) = symbol.name() { - print!(" - {}", name); + print!(" - {name}"); } else { print!(" - "); } diff --git a/src/android-api.c b/src/android-api.c deleted file mode 100644 index 1bfeadf5b..000000000 --- a/src/android-api.c +++ /dev/null @@ -1,4 +0,0 @@ -// Used from the build script to detect the value of the `__ANDROID_API__` -// builtin #define - -APIVERSION __ANDROID_API__ diff --git a/src/backtrace/dbghelp.rs b/src/backtrace/dbghelp32.rs similarity index 81% rename from src/backtrace/dbghelp.rs rename to src/backtrace/dbghelp32.rs index ba0f05f3b..672bcba20 100644 --- a/src/backtrace/dbghelp.rs +++ b/src/backtrace/dbghelp32.rs @@ -49,6 +49,14 @@ impl Frame { Some(self.base_address) } + #[cfg(not(target_env = "gnu"))] + pub fn inline_context(&self) -> Option { + match self.stack_frame { + StackFrame::New(ref new) => Some(new.InlineFrameContext), + StackFrame::Old(_) => None, + } + } + fn addr_pc(&self) -> &ADDRESS64 { match self.stack_frame { StackFrame::New(ref new) => &new.AddrPC, @@ -111,25 +119,8 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { // // Note that `RtlLookupFunctionEntry` only works for in-process backtraces, // but that's all we support anyway, so it all lines up well. - cfg_if::cfg_if! { - if #[cfg(target_pointer_width = "64")] { - use core::ptr; - - unsafe extern "system" fn function_table_access(_process: HANDLE, addr: DWORD64) -> PVOID { - let mut base = 0; - RtlLookupFunctionEntry(addr, &mut base, ptr::null_mut()).cast() - } - - unsafe extern "system" fn get_module_base(_process: HANDLE, addr: DWORD64) -> DWORD64 { - let mut base = 0; - RtlLookupFunctionEntry(addr, &mut base, ptr::null_mut()); - base - } - } else { - let function_table_access = dbghelp.SymFunctionTableAccess64(); - let get_module_base = dbghelp.SymGetModuleBase64(); - } - } + let function_table_access = dbghelp.SymFunctionTableAccess64(); + let get_module_base = dbghelp.SymGetModuleBase64(); let process_handle = GetCurrentProcess(); @@ -206,18 +197,6 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { } } -#[cfg(target_arch = "x86_64")] -fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { - frame.addr_pc_mut().Offset = ctx.Rip as u64; - frame.addr_pc_mut().Mode = AddrModeFlat; - frame.addr_stack_mut().Offset = ctx.Rsp as u64; - frame.addr_stack_mut().Mode = AddrModeFlat; - frame.addr_frame_mut().Offset = ctx.Rbp as u64; - frame.addr_frame_mut().Mode = AddrModeFlat; - - IMAGE_FILE_MACHINE_AMD64 -} - #[cfg(target_arch = "x86")] fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { frame.addr_pc_mut().Offset = ctx.Eip as u64; @@ -230,19 +209,6 @@ fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { IMAGE_FILE_MACHINE_I386 } -#[cfg(target_arch = "aarch64")] -fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { - frame.addr_pc_mut().Offset = ctx.Pc as u64; - frame.addr_pc_mut().Mode = AddrModeFlat; - frame.addr_stack_mut().Offset = ctx.Sp as u64; - frame.addr_stack_mut().Mode = AddrModeFlat; - unsafe { - frame.addr_frame_mut().Offset = ctx.u.s().Fp as u64; - } - frame.addr_frame_mut().Mode = AddrModeFlat; - IMAGE_FILE_MACHINE_ARM64 -} - #[cfg(target_arch = "arm")] fn init_frame(frame: &mut Frame, ctx: &CONTEXT) -> WORD { frame.addr_pc_mut().Offset = ctx.Pc as u64; diff --git a/src/backtrace/dbghelp64.rs b/src/backtrace/dbghelp64.rs new file mode 100644 index 000000000..d3ae06dff --- /dev/null +++ b/src/backtrace/dbghelp64.rs @@ -0,0 +1,138 @@ +//! Backtrace strategy for MSVC platforms. +//! +//! This module contains the ability to capture a backtrace on MSVC using one +//! of three possible methods. For `x86_64` and `aarch64`, we use `RtlVirtualUnwind` +//! to walk the stack one frame at a time. This function is much faster than using +//! `dbghelp!StackWalk*` because it does not load debug info to report inlined frames. +//! We still report inlined frames during symbolization by consulting the appropriate +//! `dbghelp` functions. +//! +//! For all other platforms, primarily `i686`, the `StackWalkEx` function is used if +//! possible, but not all systems have that. Failing that the `StackWalk64` function +//! is used instead. Note that `StackWalkEx` is favored because it handles debuginfo +//! internally and returns inline frame information. +//! +//! Note that all dbghelp support is loaded dynamically, see `src/dbghelp.rs` +//! for more information about that. + +#![allow(bad_style)] + +use super::super::windows::*; +use core::ffi::c_void; + +#[derive(Clone, Copy)] +pub struct Frame { + base_address: *mut c_void, + ip: *mut c_void, + sp: *mut c_void, + #[cfg(not(target_env = "gnu"))] + inline_context: Option, +} + +// we're just sending around raw pointers and reading them, never interpreting +// them so this should be safe to both send and share across threads. +unsafe impl Send for Frame {} +unsafe impl Sync for Frame {} + +impl Frame { + pub fn ip(&self) -> *mut c_void { + self.ip + } + + pub fn sp(&self) -> *mut c_void { + self.sp + } + + pub fn symbol_address(&self) -> *mut c_void { + self.ip + } + + pub fn module_base_address(&self) -> Option<*mut c_void> { + Some(self.base_address) + } + + #[cfg(not(target_env = "gnu"))] + pub fn inline_context(&self) -> Option { + self.inline_context + } +} + +#[repr(C, align(16))] // required by `CONTEXT`, is a FIXME in winapi right now +struct MyContext(CONTEXT); + +#[cfg(any(target_arch = "x86_64", target_arch = "arm64ec"))] +impl MyContext { + #[inline(always)] + fn ip(&self) -> DWORD64 { + self.0.Rip + } + + #[inline(always)] + fn sp(&self) -> DWORD64 { + self.0.Rsp + } +} + +#[cfg(target_arch = "aarch64")] +impl MyContext { + #[inline(always)] + fn ip(&self) -> DWORD64 { + self.0.Pc + } + + #[inline(always)] + fn sp(&self) -> DWORD64 { + self.0.Sp + } +} + +#[cfg(any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "arm64ec" +))] +#[inline(always)] +pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { + use core::ptr; + + let mut context = core::mem::zeroed::(); + RtlCaptureContext(&mut context.0); + + // Call `RtlVirtualUnwind` to find the previous stack frame, walking until we hit ip = 0. + while context.ip() != 0 { + let mut base = 0; + + let fn_entry = RtlLookupFunctionEntry(context.ip(), &mut base, ptr::null_mut()); + if fn_entry.is_null() { + break; + } + + let frame = super::Frame { + inner: Frame { + base_address: fn_entry.cast::(), + ip: context.ip() as *mut c_void, + sp: context.sp() as *mut c_void, + #[cfg(not(target_env = "gnu"))] + inline_context: None, + }, + }; + + if !cb(&frame) { + break; + } + + let mut handler_data = 0usize; + let mut establisher_frame = 0; + + RtlVirtualUnwind( + 0, + base, + context.ip(), + fn_entry, + &mut context.0, + ptr::addr_of_mut!(handler_data).cast::(), + &mut establisher_frame, + ptr::null_mut(), + ); + } +} diff --git a/src/backtrace/libunwind.rs b/src/backtrace/libunwind.rs index aefa8b094..c3d88605c 100644 --- a/src/backtrace/libunwind.rs +++ b/src/backtrace/libunwind.rs @@ -17,6 +17,7 @@ use super::super::Bomb; use core::ffi::c_void; +use core::ptr::addr_of_mut; pub enum Frame { Raw(*mut uw::_Unwind_Context), @@ -40,7 +41,18 @@ impl Frame { Frame::Raw(ctx) => ctx, Frame::Cloned { ip, .. } => return ip, }; - unsafe { uw::_Unwind_GetIP(ctx) as *mut c_void } + #[allow(unused_mut)] + let mut ip = unsafe { uw::_Unwind_GetIP(ctx) as *mut c_void }; + + // To reduce TCB size in SGX enclaves, we do not want to implement + // symbol resolution functionality. Rather, we can print the offset of + // the address here, which could be later mapped to correct function. + #[cfg(all(target_env = "sgx", target_vendor = "fortanix"))] + { + let image_base = super::sgx_image_base::get_image_base(); + ip = usize::wrapping_sub(ip as usize, image_base as _) as _; + } + ip } pub fn sp(&self) -> *mut c_void { @@ -90,13 +102,13 @@ impl Clone for Frame { #[inline(always)] pub unsafe fn trace(mut cb: &mut dyn FnMut(&super::Frame) -> bool) { - uw::_Unwind_Backtrace(trace_fn, &mut cb as *mut _ as *mut _); + uw::_Unwind_Backtrace(trace_fn, addr_of_mut!(cb).cast()); extern "C" fn trace_fn( ctx: *mut uw::_Unwind_Context, arg: *mut c_void, ) -> uw::_Unwind_Reason_Code { - let cb = unsafe { &mut *(arg as *mut &mut dyn FnMut(&super::Frame) -> bool) }; + let cb = unsafe { &mut *arg.cast::<&mut dyn FnMut(&super::Frame) -> bool>() }; let cx = super::Frame { inner: Frame::Raw(ctx), }; @@ -187,6 +199,8 @@ mod uw { _Unwind_GetGR(ctx, 15) } } else { + use core::ptr::addr_of_mut; + // On android and arm, the function `_Unwind_GetIP` and a bunch of // others are macros, so we define functions containing the // expansion of the macros. @@ -231,13 +245,13 @@ mod uw { pub unsafe fn _Unwind_GetIP(ctx: *mut _Unwind_Context) -> libc::uintptr_t { let mut val: _Unwind_Word = 0; - let ptr = &mut val as *mut _Unwind_Word; + let ptr = addr_of_mut!(val); let _ = _Unwind_VRS_Get( ctx, _Unwind_VRS_RegClass::_UVRSC_CORE, 15, _Unwind_VRS_DataRepresentation::_UVRSD_UINT32, - ptr as *mut c_void, + ptr.cast::(), ); (val & !1) as libc::uintptr_t } @@ -247,13 +261,13 @@ mod uw { pub unsafe fn get_sp(ctx: *mut _Unwind_Context) -> libc::uintptr_t { let mut val: _Unwind_Word = 0; - let ptr = &mut val as *mut _Unwind_Word; + let ptr = addr_of_mut!(val); let _ = _Unwind_VRS_Get( ctx, _Unwind_VRS_RegClass::_UVRSC_CORE, SP, _Unwind_VRS_DataRepresentation::_UVRSD_UINT32, - ptr as *mut c_void, + ptr.cast::(), ); val as libc::uintptr_t } diff --git a/src/backtrace/miri.rs b/src/backtrace/miri.rs index f8c496428..ce06ed6bd 100644 --- a/src/backtrace/miri.rs +++ b/src/backtrace/miri.rs @@ -65,7 +65,7 @@ pub fn trace bool>(cb: F) { pub fn resolve_addr(ptr: *mut c_void) -> Frame { // SAFETY: Miri will stop execution with an error if this pointer // is invalid. - let frame = unsafe { miri_resolve_frame(ptr as *mut (), 1) }; + let frame = unsafe { miri_resolve_frame(ptr.cast::<()>(), 1) }; let mut name = Vec::with_capacity(frame.name_len); let mut filename = Vec::with_capacity(frame.filename_len); @@ -73,7 +73,12 @@ pub fn resolve_addr(ptr: *mut c_void) -> Frame { // SAFETY: name and filename have been allocated with the amount // of memory miri has asked for, and miri guarantees it will initialize it unsafe { - miri_resolve_frame_names(ptr as *mut (), 0, name.as_mut_ptr(), filename.as_mut_ptr()); + miri_resolve_frame_names( + ptr.cast::<()>(), + 0, + name.as_mut_ptr(), + filename.as_mut_ptr(), + ); name.set_len(frame.name_len); filename.set_len(frame.filename_len); @@ -101,7 +106,7 @@ unsafe fn trace_unsynchronized bool>(mut cb: F) { frames.set_len(len); for ptr in frames.iter() { - let frame = resolve_addr(*ptr as *mut c_void); + let frame = resolve_addr((*ptr).cast::()); if !cb(&super::Frame { inner: frame }) { return; } diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index 6ca1080c4..df9f536a4 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -125,6 +125,49 @@ impl fmt::Debug for Frame { } } +#[cfg(all(target_env = "sgx", target_vendor = "fortanix", not(miri)))] +mod sgx_image_base { + + #[cfg(not(feature = "std"))] + pub(crate) mod imp { + use core::ffi::c_void; + use core::sync::atomic::{AtomicUsize, Ordering::SeqCst}; + + static IMAGE_BASE: AtomicUsize = AtomicUsize::new(0); + + /// Set the image base address. This is only available for Fortanix SGX + /// target when the `std` feature is not enabled. This can be used in the + /// standard library to set the correct base address. + #[doc(hidden)] + pub fn set_image_base(base_addr: *mut c_void) { + IMAGE_BASE.store(base_addr as _, SeqCst); + } + + pub(crate) fn get_image_base() -> *mut c_void { + IMAGE_BASE.load(SeqCst) as _ + } + } + + #[cfg(feature = "std")] + mod imp { + use core::ffi::c_void; + + pub(crate) fn get_image_base() -> *mut c_void { + std::os::fortanix_sgx::mem::image_base() as _ + } + } + + pub(crate) use imp::get_image_base; +} + +#[cfg(all( + target_env = "sgx", + target_vendor = "fortanix", + not(feature = "std"), + not(miri) +))] +pub use sgx_image_base::imp::set_image_base; + cfg_if::cfg_if! { // This needs to come first, to ensure that // Miri takes priority over the host platform @@ -150,11 +193,17 @@ cfg_if::cfg_if! { use self::libunwind::trace as trace_imp; pub(crate) use self::libunwind::Frame as FrameImp; } else if #[cfg(all(windows, not(target_vendor = "uwp")))] { - mod dbghelp; + cfg_if::cfg_if! { + if #[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "arm64ec"))] { + mod dbghelp64; + use dbghelp64 as dbghelp; + } else if #[cfg(any(target_arch = "x86", target_arch = "arm"))] { + mod dbghelp32; + use dbghelp32 as dbghelp; + } + } use self::dbghelp::trace as trace_imp; pub(crate) use self::dbghelp::Frame as FrameImp; - #[cfg(target_env = "msvc")] // only used in dbghelp symbolize - pub(crate) use self::dbghelp::StackFrame; } else { mod noop; use self::noop::trace as trace_imp; diff --git a/src/backtrace/noop.rs b/src/backtrace/noop.rs index 7bcea67aa..cae53df14 100644 --- a/src/backtrace/noop.rs +++ b/src/backtrace/noop.rs @@ -2,6 +2,7 @@ //! appropriate. use core::ffi::c_void; +use core::ptr::null_mut; #[inline(always)] pub fn trace(_cb: &mut dyn FnMut(&super::Frame) -> bool) {} @@ -11,15 +12,15 @@ pub struct Frame; impl Frame { pub fn ip(&self) -> *mut c_void { - 0 as *mut _ + null_mut() } pub fn sp(&self) -> *mut c_void { - 0 as *mut _ + null_mut() } pub fn symbol_address(&self) -> *mut c_void { - 0 as *mut _ + null_mut() } pub fn module_base_address(&self) -> Option<*mut c_void> { diff --git a/src/capture.rs b/src/capture.rs index e0dd9c474..73d94dc4b 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -26,9 +26,6 @@ use serde::{Deserialize, Serialize}; pub struct Backtrace { // Frames here are listed from top-to-bottom of the stack frames: Vec, - // The index we believe is the actual start of the backtrace, omitting - // frames like `Backtrace::new` and `backtrace::trace`. - actual_start_index: usize, } fn _assert_send_sync() { @@ -86,6 +83,27 @@ impl Frame { } => module_base_address.map(|addr| addr as *mut c_void), } } + + /// Resolve all addresses in the frame to their symbolic names. + fn resolve_symbols(&self) -> Vec { + let mut symbols = Vec::new(); + let sym = |symbol: &Symbol| { + symbols.push(BacktraceSymbol { + name: symbol.name().map(|m| m.as_bytes().to_vec()), + addr: symbol.addr().map(|a| a as usize), + filename: symbol.filename().map(|m| m.to_owned()), + lineno: symbol.lineno(), + colno: symbol.colno(), + }); + }; + match *self { + Frame::Raw(ref f) => resolve_frame(f, sym), + Frame::Deserialized { ip, .. } => { + resolve(ip as *mut c_void, sym); + } + } + symbols + } } /// Captured version of a symbol in a backtrace. @@ -156,9 +174,9 @@ impl Backtrace { /// use backtrace::Backtrace; /// /// let mut current_backtrace = Backtrace::new_unresolved(); - /// println!("{:?}", current_backtrace); // no symbol names + /// println!("{current_backtrace:?}"); // no symbol names /// current_backtrace.resolve(); - /// println!("{:?}", current_backtrace); // symbol names now present + /// println!("{current_backtrace:?}"); // symbol names now present /// ``` /// /// # Required features @@ -172,23 +190,22 @@ impl Backtrace { fn create(ip: usize) -> Backtrace { let mut frames = Vec::new(); - let mut actual_start_index = None; trace(|frame| { frames.push(BacktraceFrame { frame: Frame::Raw(frame.clone()), symbols: None, }); - if frame.symbol_address() as usize == ip && actual_start_index.is_none() { - actual_start_index = Some(frames.len()); + // clear inner frames, and start with call site. + if frame.symbol_address() as usize == ip { + frames.clear(); } + true }); + frames.shrink_to_fit(); - Backtrace { - frames, - actual_start_index: actual_start_index.unwrap_or(0), - } + Backtrace { frames } } /// Returns the frames from when this backtrace was captured. @@ -202,7 +219,7 @@ impl Backtrace { /// This function requires the `std` feature of the `backtrace` crate to be /// enabled, and the `std` feature is enabled by default. pub fn frames(&self) -> &[BacktraceFrame] { - &self.frames[self.actual_start_index..] + self.frames.as_slice() } /// If this backtrace was created from `new_unresolved` then this function @@ -216,41 +233,18 @@ impl Backtrace { /// This function requires the `std` feature of the `backtrace` crate to be /// enabled, and the `std` feature is enabled by default. pub fn resolve(&mut self) { - for frame in self.frames.iter_mut().filter(|f| f.symbols.is_none()) { - let mut symbols = Vec::new(); - { - let sym = |symbol: &Symbol| { - symbols.push(BacktraceSymbol { - name: symbol.name().map(|m| m.as_bytes().to_vec()), - addr: symbol.addr().map(|a| a as usize), - filename: symbol.filename().map(|m| m.to_owned()), - lineno: symbol.lineno(), - colno: symbol.colno(), - }); - }; - match frame.frame { - Frame::Raw(ref f) => resolve_frame(f, sym), - Frame::Deserialized { ip, .. } => { - resolve(ip as *mut c_void, sym); - } - } - } - frame.symbols = Some(symbols); - } + self.frames.iter_mut().for_each(BacktraceFrame::resolve); } } impl From> for Backtrace { fn from(frames: Vec) -> Self { - Backtrace { - frames, - actual_start_index: 0, - } + Backtrace { frames } } } impl From for BacktraceFrame { - fn from(frame: crate::Frame) -> BacktraceFrame { + fn from(frame: crate::Frame) -> Self { BacktraceFrame { frame: Frame::Raw(frame), symbols: None, @@ -258,6 +252,9 @@ impl From for BacktraceFrame { } } +// we don't want implementing `impl From for Vec` on purpose, +// because "... additional directions for Vec can weaken type inference ..." +// more information on https://github.com/rust-lang/backtrace-rs/pull/526 impl Into> for Backtrace { fn into(self) -> Vec { self.frames @@ -272,7 +269,7 @@ impl BacktraceFrame { /// This function requires the `std` feature of the `backtrace` crate to be /// enabled, and the `std` feature is enabled by default. pub fn ip(&self) -> *mut c_void { - self.frame.ip() as *mut c_void + self.frame.ip() } /// Same as `Frame::symbol_address` @@ -282,7 +279,7 @@ impl BacktraceFrame { /// This function requires the `std` feature of the `backtrace` crate to be /// enabled, and the `std` feature is enabled by default. pub fn symbol_address(&self) -> *mut c_void { - self.frame.symbol_address() as *mut c_void + self.frame.symbol_address() } /// Same as `Frame::module_base_address` @@ -292,9 +289,7 @@ impl BacktraceFrame { /// This function requires the `std` feature of the `backtrace` crate to be /// enabled, and the `std` feature is enabled by default. pub fn module_base_address(&self) -> Option<*mut c_void> { - self.frame - .module_base_address() - .map(|addr| addr as *mut c_void) + self.frame.module_base_address() } /// Returns the list of symbols that this frame corresponds to. @@ -314,6 +309,20 @@ impl BacktraceFrame { pub fn symbols(&self) -> &[BacktraceSymbol] { self.symbols.as_ref().map(|s| &s[..]).unwrap_or(&[]) } + + /// Resolve all addresses in this frame to their symbolic names. + /// + /// If this frame has been previously resolved, this function does nothing. + /// + /// # Required features + /// + /// This function requires the `std` feature of the `backtrace` crate to be + /// enabled, and the `std` feature is enabled by default. + pub fn resolve(&mut self) { + if self.symbols.is_none() { + self.symbols = Some(self.frame.resolve_symbols()); + } + } } impl BacktraceSymbol { @@ -370,11 +379,10 @@ impl BacktraceSymbol { impl fmt::Debug for Backtrace { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - let full = fmt.alternate(); - let (frames, style) = if full { - (&self.frames[..], PrintFmt::Full) + let style = if fmt.alternate() { + PrintFmt::Full } else { - (&self.frames[self.actual_start_index..], PrintFmt::Short) + PrintFmt::Short }; // When printing paths we try to strip the cwd if it exists, otherwise @@ -385,7 +393,7 @@ impl fmt::Debug for Backtrace { let mut print_path = move |fmt: &mut fmt::Formatter<'_>, path: crate::BytesOrWideString<'_>| { let path = path.into_path_buf(); - if !full { + if style == PrintFmt::Full { if let Ok(cwd) = &cwd { if let Ok(suffix) = path.strip_prefix(cwd) { return fmt::Display::fmt(&suffix.display(), fmt); @@ -397,7 +405,7 @@ impl fmt::Debug for Backtrace { let mut f = BacktraceFmt::new(fmt, style, &mut print_path); f.add_context()?; - for frame in frames { + for frame in &self.frames { f.frame().backtrace_frame(frame)?; } f.finish()?; diff --git a/src/dbghelp.rs b/src/dbghelp.rs index c81766bae..711b53db6 100644 --- a/src/dbghelp.rs +++ b/src/dbghelp.rs @@ -23,9 +23,12 @@ #![allow(non_snake_case)] +use alloc::vec::Vec; + use super::windows::*; use core::mem; use core::ptr; +use core::slice; // Work around `SymGetOptions` and `SymSetOptions` not being present in winapi // itself. Otherwise this is only used when we're double-checking types against @@ -34,8 +37,8 @@ use core::ptr; mod dbghelp { use crate::windows::*; pub use winapi::um::dbghelp::{ - StackWalk64, StackWalkEx, SymCleanup, SymFromAddrW, SymFunctionTableAccess64, - SymGetLineFromAddrW64, SymGetModuleBase64, SymGetOptions, SymInitializeW, SymSetOptions, + StackWalk64, StackWalkEx, SymFromAddrW, SymFunctionTableAccess64, SymGetLineFromAddrW64, + SymGetModuleBase64, SymGetOptions, SymInitializeW, SymSetOptions, }; extern "system" { @@ -55,6 +58,27 @@ mod dbghelp { pdwDisplacement: PDWORD, Line: PIMAGEHLP_LINEW64, ) -> BOOL; + pub fn SymAddrIncludeInlineTrace(hProcess: HANDLE, Address: DWORD64) -> DWORD; + pub fn SymQueryInlineTrace( + hProcess: HANDLE, + StartAddress: DWORD64, + StartContext: DWORD, + StartRetAddress: DWORD64, + CurAddress: DWORD64, + CurContext: LPDWORD, + CurFrameIndex: LPDWORD, + ) -> BOOL; + pub fn SymGetSearchPathW( + hprocess: HANDLE, + searchpatha: PWSTR, + searchpathlength: DWORD, + ) -> BOOL; + pub fn SymSetSearchPathW(hprocess: HANDLE, searchpatha: PCWSTR) -> BOOL; + pub fn EnumerateLoadedModulesW64( + hprocess: HANDLE, + enumloadedmodulescallback: PENUMLOADED_MODULES_CALLBACKW64, + usercontext: PVOID, + ) -> BOOL; } pub fn assert_equal_types(a: T, _b: T) -> T { @@ -78,7 +102,7 @@ macro_rules! dbghelp { static mut DBGHELP: Dbghelp = Dbghelp { // Initially we haven't loaded the DLL - dll: 0 as *mut _, + dll: ptr::null_mut(), // Initially all functions are set to zero to say they need to be // dynamically loaded. $($name: 0,)* @@ -98,7 +122,7 @@ macro_rules! dbghelp { } let lib = b"dbghelp.dll\0"; unsafe { - self.dll = LoadLibraryA(lib.as_ptr() as *const i8); + self.dll = LoadLibraryA(lib.as_ptr().cast::()); if self.dll.is_null() { Err(()) } else { @@ -125,7 +149,7 @@ macro_rules! dbghelp { fn symbol(&self, symbol: &[u8]) -> Option { unsafe { - match GetProcAddress(self.dll, symbol.as_ptr() as *const _) as usize { + match GetProcAddress(self.dll, symbol.as_ptr().cast()) as usize { 0 => None, n => Some(n), } @@ -145,7 +169,7 @@ macro_rules! dbghelp { pub fn dbghelp(&self) -> *mut Dbghelp { unsafe { - &mut DBGHELP + ptr::addr_of_mut!(DBGHELP) } } } @@ -164,7 +188,20 @@ dbghelp! { path: PCWSTR, invade: BOOL ) -> BOOL; - fn SymCleanup(handle: HANDLE) -> BOOL; + fn SymGetSearchPathW( + hprocess: HANDLE, + searchpatha: PWSTR, + searchpathlength: DWORD + ) -> BOOL; + fn SymSetSearchPathW( + hprocess: HANDLE, + searchpatha: PCWSTR + ) -> BOOL; + fn EnumerateLoadedModulesW64( + hprocess: HANDLE, + enumloadedmodulescallback: PENUMLOADED_MODULES_CALLBACKW64, + usercontext: PVOID + ) -> BOOL; fn StackWalk64( MachineType: DWORD, hProcess: HANDLE, @@ -184,18 +221,6 @@ dbghelp! { hProcess: HANDLE, AddrBase: DWORD64 ) -> DWORD64; - fn SymFromAddrW( - hProcess: HANDLE, - Address: DWORD64, - Displacement: PDWORD64, - Symbol: PSYMBOL_INFOW - ) -> BOOL; - fn SymGetLineFromAddrW64( - hProcess: HANDLE, - dwAddr: DWORD64, - pdwDisplacement: PDWORD, - Line: PIMAGEHLP_LINEW64 - ) -> BOOL; fn StackWalkEx( MachineType: DWORD, hProcess: HANDLE, @@ -223,6 +248,31 @@ dbghelp! { pdwDisplacement: PDWORD, Line: PIMAGEHLP_LINEW64 ) -> BOOL; + fn SymAddrIncludeInlineTrace( + hProcess: HANDLE, + Address: DWORD64 + ) -> DWORD; + fn SymQueryInlineTrace( + hProcess: HANDLE, + StartAddress: DWORD64, + StartContext: DWORD, + StartRetAddress: DWORD64, + CurAddress: DWORD64, + CurContext: LPDWORD, + CurFrameIndex: LPDWORD + ) -> BOOL; + fn SymFromAddrW( + hProcess: HANDLE, + Address: DWORD64, + Displacement: PDWORD64, + Symbol: PSYMBOL_INFOW + ) -> BOOL; + fn SymGetLineFromAddrW64( + hProcess: HANDLE, + dwAddr: DWORD64, + pdwDisplacement: PDWORD, + Line: PIMAGEHLP_LINEW64 + ) -> BOOL; } } @@ -350,11 +400,133 @@ pub fn init() -> Result { // get to initialization first and the other will pick up that // initialization. DBGHELP.SymInitializeW().unwrap()(GetCurrentProcess(), ptr::null_mut(), TRUE); + + // The default search path for dbghelp will only look in the current working + // directory and (possibly) `_NT_SYMBOL_PATH` and `_NT_ALT_SYMBOL_PATH`. + // However, we also want to look in the directory of the executable + // and each DLL that is loaded. To do this, we need to update the search path + // to include these directories. + // + // See https://learn.microsoft.com/cpp/build/reference/pdbpath for an + // example of where symbols are usually searched for. + let mut search_path_buf = Vec::new(); + search_path_buf.resize(1024, 0); + + // Prefill the buffer with the current search path. + if DBGHELP.SymGetSearchPathW().unwrap()( + GetCurrentProcess(), + search_path_buf.as_mut_ptr(), + search_path_buf.len() as _, + ) == TRUE + { + // Trim the buffer to the actual length of the string. + let len = lstrlenW(search_path_buf.as_mut_ptr()); + assert!(len >= 0); + search_path_buf.truncate(len as usize); + } else { + // If getting the search path fails, at least include the current directory. + search_path_buf.clear(); + search_path_buf.push(utf16_char('.')); + search_path_buf.push(utf16_char(';')); + } + + let mut search_path = SearchPath::new(search_path_buf); + + // Update the search path to include the directory of the executable and each DLL. + DBGHELP.EnumerateLoadedModulesW64().unwrap()( + GetCurrentProcess(), + Some(enum_loaded_modules_callback), + ((&mut search_path) as *mut SearchPath) as *mut c_void, + ); + + let new_search_path = search_path.finalize(); + + // Set the new search path. + DBGHELP.SymSetSearchPathW().unwrap()(GetCurrentProcess(), new_search_path.as_ptr()); + INITIALIZED = true; Ok(ret) } } +struct SearchPath { + search_path_utf16: Vec, +} + +fn utf16_char(c: char) -> u16 { + let buf = &mut [0u16; 2]; + let buf = c.encode_utf16(buf); + assert!(buf.len() == 1); + buf[0] +} + +impl SearchPath { + fn new(initial_search_path: Vec) -> Self { + Self { + search_path_utf16: initial_search_path, + } + } + + /// Add a path to the search path if it is not already present. + fn add(&mut self, path: &[u16]) { + let sep = utf16_char(';'); + + // We could deduplicate in a case-insensitive way, but case-sensitivity + // can be configured by directory on Windows, so let's not do that. + // https://learn.microsoft.com/windows/wsl/case-sensitivity + if !self + .search_path_utf16 + .split(|&c| c == sep) + .any(|p| p == path) + { + if self.search_path_utf16.last() != Some(&sep) { + self.search_path_utf16.push(sep); + } + self.search_path_utf16.extend_from_slice(path); + } + } + + fn finalize(mut self) -> Vec { + // Add a null terminator. + self.search_path_utf16.push(0); + self.search_path_utf16 + } +} + +extern "system" fn enum_loaded_modules_callback( + module_name: PCWSTR, + _: DWORD64, + _: ULONG, + user_context: PVOID, +) -> BOOL { + // `module_name` is an absolute path like `C:\path\to\module.dll` + // or `C:\path\to\module.exe` + let len: usize = unsafe { lstrlenW(module_name).try_into().unwrap() }; + + if len == 0 { + // This should not happen, but if it does, we can just ignore it. + return TRUE; + } + + let module_name = unsafe { slice::from_raw_parts(module_name, len) }; + let path_sep = utf16_char('\\'); + let alt_path_sep = utf16_char('/'); + + let Some(end_of_directory) = module_name + .iter() + .rposition(|&c| c == path_sep || c == alt_path_sep) + else { + // `module_name` being an absolute path, it should always contain at least one + // path separator. If not, there is nothing we can do. + return TRUE; + }; + + let search_path = unsafe { &mut *(user_context as *mut SearchPath) }; + search_path.add(&module_name[..end_of_directory]); + + TRUE +} + impl Drop for Init { fn drop(&mut self) { unsafe { diff --git a/src/lib.rs b/src/lib.rs index 4615e1f96..5b97281a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,6 +134,12 @@ cfg_if::cfg_if! { } } +cfg_if::cfg_if! { + if #[cfg(all(target_env = "sgx", target_vendor = "fortanix", not(feature = "std")))] { + pub use self::backtrace::set_image_base; + } +} + #[allow(dead_code)] struct Bomb { enabled: bool, @@ -153,11 +159,12 @@ impl Drop for Bomb { mod lock { use std::boxed::Box; use std::cell::Cell; + use std::ptr; use std::sync::{Mutex, MutexGuard, Once}; pub struct LockGuard(Option>); - static mut LOCK: *mut Mutex<()> = 0 as *mut _; + static mut LOCK: *mut Mutex<()> = ptr::null_mut(); static INIT: Once = Once::new(); thread_local!(static LOCK_HELD: Cell = Cell::new(false)); @@ -186,7 +193,14 @@ mod lock { } } -#[cfg(all(windows, not(target_vendor = "uwp")))] +#[cfg(all( + windows, + any( + target_env = "msvc", + all(target_env = "gnu", any(target_arch = "x86", target_arch = "arm")) + ), + not(target_vendor = "uwp") +))] mod dbghelp; #[cfg(windows)] mod windows; diff --git a/src/print.rs b/src/print.rs index 395328a0a..6e38a0f13 100644 --- a/src/print.rs +++ b/src/print.rs @@ -219,7 +219,7 @@ impl BacktraceFrameFmt<'_, '_, '_> { #[allow(unused_mut)] fn print_raw_generic( &mut self, - mut frame_ip: *mut c_void, + frame_ip: *mut c_void, symbol_name: Option>, filename: Option>, lineno: Option, @@ -233,22 +233,13 @@ impl BacktraceFrameFmt<'_, '_, '_> { } } - // To reduce TCB size in Sgx enclave, we do not want to implement symbol - // resolution functionality. Rather, we can print the offset of the - // address here, which could be later mapped to correct function. - #[cfg(all(feature = "std", target_env = "sgx", target_vendor = "fortanix"))] - { - let image_base = std::os::fortanix_sgx::mem::image_base(); - frame_ip = usize::wrapping_sub(frame_ip as usize, image_base as _) as _; - } - // Print the index of the frame as well as the optional instruction // pointer of the frame. If we're beyond the first symbol of this frame // though we just print appropriate whitespace. if self.symbol_index == 0 { write!(self.fmt.fmt, "{:4}: ", self.fmt.frame_index)?; if let PrintFmt::Full = self.fmt.format { - write!(self.fmt.fmt, "{:1$?} - ", frame_ip, HEX_WIDTH)?; + write!(self.fmt.fmt, "{frame_ip:HEX_WIDTH$?} - ")?; } } else { write!(self.fmt.fmt, " ")?; @@ -261,8 +252,8 @@ impl BacktraceFrameFmt<'_, '_, '_> { // more information if we're a full backtrace. Here we also handle // symbols which don't have a name, match (symbol_name, &self.fmt.format) { - (Some(name), PrintFmt::Short) => write!(self.fmt.fmt, "{:#}", name)?, - (Some(name), PrintFmt::Full) => write!(self.fmt.fmt, "{}", name)?, + (Some(name), PrintFmt::Short) => write!(self.fmt.fmt, "{name:#}")?, + (Some(name), PrintFmt::Full) => write!(self.fmt.fmt, "{name}")?, (None, _) | (_, PrintFmt::__Nonexhaustive) => write!(self.fmt.fmt, "")?, } self.fmt.fmt.write_str("\n")?; @@ -291,11 +282,11 @@ impl BacktraceFrameFmt<'_, '_, '_> { // Delegate to our internal callback to print the filename and then // print out the line number. (self.fmt.print_path)(self.fmt.fmt, file)?; - write!(self.fmt.fmt, ":{}", line)?; + write!(self.fmt.fmt, ":{line}")?; // Add column number, if available. if let Some(colno) = colno { - write!(self.fmt.fmt, ":{}", colno)?; + write!(self.fmt.fmt, ":{colno}")?; } write!(self.fmt.fmt, "\n")?; diff --git a/src/print/fuchsia.rs b/src/print/fuchsia.rs index cb872697d..5a5aae530 100644 --- a/src/print/fuchsia.rs +++ b/src/print/fuchsia.rs @@ -312,7 +312,7 @@ struct HexSlice<'a> { impl fmt::Display for HexSlice<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for byte in self.bytes { - write!(f, "{:02x}", byte)?; + write!(f, "{byte:02x}")?; } Ok(()) } @@ -336,7 +336,7 @@ fn get_build_id<'a>(info: &'a dl_phdr_info) -> Option<&'a [u8]> { enum Error { /// NameError means that an error occurred while converting a C style string /// into a rust string. - NameError(core::str::Utf8Error), + NameError, /// BuildIDError means that we didn't find a build ID. This could either be /// because the DSO had no build ID or because the segment containing the /// build ID was malformed. @@ -359,11 +359,11 @@ fn for_each_dso(mut visitor: &mut DsoPrinter<'_, '_>) { // location. let name_len = unsafe { libc::strlen(info.name) }; let name_slice: &[u8] = - unsafe { core::slice::from_raw_parts(info.name as *const u8, name_len) }; + unsafe { core::slice::from_raw_parts(info.name.cast::(), name_len) }; let name = match core::str::from_utf8(name_slice) { Ok(name) => name, - Err(err) => { - return visitor.error(Error::NameError(err)) as i32; + Err(_) => { + return visitor.error(Error::NameError) as i32; } }; let build_id = match get_build_id(info) { diff --git a/src/symbolize/dbghelp.rs b/src/symbolize/dbghelp.rs index 181dba731..7969f9ff2 100644 --- a/src/symbolize/dbghelp.rs +++ b/src/symbolize/dbghelp.rs @@ -17,12 +17,13 @@ #![allow(bad_style)] -use super::super::{backtrace::StackFrame, dbghelp, windows::*}; +use super::super::{dbghelp, windows::*}; use super::{BytesOrWideString, ResolveWhat, SymbolName}; use core::char; use core::ffi::c_void; use core::marker; use core::mem; +use core::ptr; use core::slice; // Store an OsString on std so we can provide the symbol name and filename. @@ -44,7 +45,7 @@ impl Symbol<'_> { } pub fn addr(&self) -> Option<*mut c_void> { - Some(self.addr as *mut _) + Some(self.addr) } pub fn filename_raw(&self) -> Option> { @@ -71,61 +72,127 @@ impl Symbol<'_> { #[repr(C, align(8))] struct Aligned8(T); +#[cfg(not(target_vendor = "win7"))] pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) { // Ensure this process's symbols are initialized let dbghelp = match dbghelp::init() { Ok(dbghelp) => dbghelp, Err(()) => return, // oh well... }; + match what { + ResolveWhat::Address(_) => resolve_with_inline(&dbghelp, what.address_or_ip(), None, cb), + ResolveWhat::Frame(frame) => { + resolve_with_inline(&dbghelp, frame.ip(), frame.inner.inline_context(), cb) + } + } +} +#[cfg(target_vendor = "win7")] +pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) { + // Ensure this process's symbols are initialized + let dbghelp = match dbghelp::init() { + Ok(dbghelp) => dbghelp, + Err(()) => return, // oh well... + }; + + let resolve_inner = if (*dbghelp.dbghelp()).SymAddrIncludeInlineTrace().is_some() { + // We are on a version of dbghelp 6.2+, which contains the more modern + // Inline APIs. + resolve_with_inline + } else { + // We are on an older version of dbghelp which doesn't contain the Inline + // APIs. + resolve_legacy + }; match what { - ResolveWhat::Address(_) => resolve_without_inline(&dbghelp, what.address_or_ip(), cb), - ResolveWhat::Frame(frame) => match &frame.inner.stack_frame { - StackFrame::New(frame) => resolve_with_inline(&dbghelp, frame, cb), - StackFrame::Old(_) => resolve_without_inline(&dbghelp, frame.ip(), cb), - }, + ResolveWhat::Address(_) => resolve_inner(&dbghelp, what.address_or_ip(), None, cb), + ResolveWhat::Frame(frame) => { + resolve_inner(&dbghelp, frame.ip(), frame.inner.inline_context(), cb) + } } } -unsafe fn resolve_with_inline( +/// Resolve the address using the legacy dbghelp API. +/// +/// This should work all the way down to Windows XP. The inline context is +/// ignored, since this concept was only introduced in dbghelp 6.2+. +#[cfg(target_vendor = "win7")] +unsafe fn resolve_legacy( dbghelp: &dbghelp::Init, - frame: &STACKFRAME_EX, + addr: *mut c_void, + _inline_context: Option, cb: &mut dyn FnMut(&super::Symbol), ) { + let addr = super::adjust_ip(addr) as DWORD64; do_resolve( - |info| { - dbghelp.SymFromInlineContextW()( - GetCurrentProcess(), - super::adjust_ip(frame.AddrPC.Offset as *mut _) as u64, - frame.InlineFrameContext, - &mut 0, - info, - ) - }, - |line| { - dbghelp.SymGetLineFromInlineContextW()( - GetCurrentProcess(), - super::adjust_ip(frame.AddrPC.Offset as *mut _) as u64, - frame.InlineFrameContext, - 0, - &mut 0, - line, - ) - }, + |info| dbghelp.SymFromAddrW()(GetCurrentProcess(), addr, &mut 0, info), + |line| dbghelp.SymGetLineFromAddrW64()(GetCurrentProcess(), addr, &mut 0, line), cb, ) } -unsafe fn resolve_without_inline( +/// Resolve the address using the modern dbghelp APIs. +/// +/// Note that calling this function requires having dbghelp 6.2+ loaded - and +/// will panic otherwise. +unsafe fn resolve_with_inline( dbghelp: &dbghelp::Init, addr: *mut c_void, + inline_context: Option, cb: &mut dyn FnMut(&super::Symbol), ) { - do_resolve( - |info| dbghelp.SymFromAddrW()(GetCurrentProcess(), addr as DWORD64, &mut 0, info), - |line| dbghelp.SymGetLineFromAddrW64()(GetCurrentProcess(), addr as DWORD64, &mut 0, line), - cb, - ) + let current_process = GetCurrentProcess(); + + let addr = super::adjust_ip(addr) as DWORD64; + + let (inlined_frame_count, inline_context) = if let Some(ic) = inline_context { + (0, ic) + } else { + let mut inlined_frame_count = dbghelp.SymAddrIncludeInlineTrace()(current_process, addr); + + let mut inline_context = 0; + + // If there is are inlined frames but we can't load them for some reason OR if there are no + // inlined frames, then we disregard inlined_frame_count and inline_context. + if (inlined_frame_count > 0 + && dbghelp.SymQueryInlineTrace()( + current_process, + addr, + 0, + addr, + addr, + &mut inline_context, + &mut 0, + ) != TRUE) + || inlined_frame_count == 0 + { + inlined_frame_count = 0; + inline_context = 0; + } + + (inlined_frame_count, inline_context) + }; + + let last_inline_context = inline_context + 1 + inlined_frame_count; + + for inline_context in inline_context..last_inline_context { + do_resolve( + |info| { + dbghelp.SymFromInlineContextW()(current_process, addr, inline_context, &mut 0, info) + }, + |line| { + dbghelp.SymGetLineFromInlineContextW()( + current_process, + addr, + inline_context, + 0, + &mut 0, + line, + ) + }, + cb, + ); + } } unsafe fn do_resolve( @@ -135,8 +202,7 @@ unsafe fn do_resolve( ) { const SIZE: usize = 2 * MAX_SYM_NAME + mem::size_of::(); let mut data = Aligned8([0u8; SIZE]); - let data = &mut data.0; - let info = &mut *(data.as_mut_ptr() as *mut SYMBOL_INFOW); + let info = &mut *data.0.as_mut_ptr().cast::(); info.MaxNameLen = MAX_SYM_NAME as ULONG; // the struct size in C. the value is different to // `size_of::() - MAX_SYM_NAME + 1` (== 81) @@ -151,7 +217,7 @@ unsafe fn do_resolve( // give a buffer of (MaxNameLen - 1) characters and set NameLen to // the real value. let name_len = ::core::cmp::min(info.NameLen as usize, info.MaxNameLen as usize - 1); - let name_ptr = info.Name.as_ptr() as *const u16; + let name_ptr = info.Name.as_ptr().cast::(); let name = slice::from_raw_parts(name_ptr, name_len); // Reencode the utf-16 symbol to utf-8 so we can use `SymbolName::new` like @@ -173,7 +239,7 @@ unsafe fn do_resolve( } } } - let name = &name_buffer[..name_len] as *const [u8]; + let name = ptr::addr_of!(name_buffer[..name_len]); let mut line = mem::zeroed::(); line.SizeOfStruct = mem::size_of::() as DWORD; @@ -191,7 +257,7 @@ unsafe fn do_resolve( let len = len as usize; - filename = Some(slice::from_raw_parts(base, len) as *const [u16]); + filename = Some(ptr::from_ref(slice::from_raw_parts(base, len))); } cb(&super::Symbol { diff --git a/src/symbolize/gimli.rs b/src/symbolize/gimli.rs index 7f1c6a528..fa8e59723 100644 --- a/src/symbolize/gimli.rs +++ b/src/symbolize/gimli.rs @@ -35,12 +35,14 @@ cfg_if::cfg_if! { target_os = "freebsd", target_os = "fuchsia", target_os = "haiku", + target_os = "hurd", target_os = "ios", target_os = "linux", target_os = "macos", target_os = "openbsd", target_os = "solaris", target_os = "illumos", + target_os = "aix", ))] { #[path = "gimli/mmap_unix.rs"] mod mmap; @@ -116,8 +118,17 @@ impl<'data> Context<'data> { dwp: Option>, ) -> Option> { let mut sections = gimli::Dwarf::load(|id| -> Result<_, ()> { - let data = object.section(stash, id.name()).unwrap_or(&[]); - Ok(EndianSlice::new(data, Endian)) + if cfg!(not(target_os = "aix")) { + let data = object.section(stash, id.name()).unwrap_or(&[]); + Ok(EndianSlice::new(data, Endian)) + } else { + if let Some(name) = id.xcoff_name() { + let data = object.section(stash, name).unwrap_or(&[]); + Ok(EndianSlice::new(data, Endian)) + } else { + Ok(EndianSlice::new(&[], Endian)) + } + } }) .ok()?; @@ -192,6 +203,9 @@ cfg_if::cfg_if! { ))] { mod macho; use self::macho::{handle_split_dwarf, Object}; + } else if #[cfg(target_os = "aix")] { + mod xcoff; + use self::xcoff::{handle_split_dwarf, Object}; } else { mod elf; use self::elf::{handle_split_dwarf, Object}; @@ -218,6 +232,7 @@ cfg_if::cfg_if! { target_os = "linux", target_os = "fuchsia", target_os = "freebsd", + target_os = "hurd", target_os = "openbsd", target_os = "netbsd", all(target_os = "android", feature = "dl_iterate_phdr"), @@ -234,6 +249,9 @@ cfg_if::cfg_if! { } else if #[cfg(target_os = "haiku")] { mod libs_haiku; use libs_haiku::native_libraries; + } else if #[cfg(target_os = "aix")] { + mod libs_aix; + use libs_aix::native_libraries; } else { // Everything else should doesn't know how to load native libraries. fn native_libraries() -> Vec { @@ -261,6 +279,13 @@ struct Cache { struct Library { name: OsString, + #[cfg(target_os = "aix")] + /// On AIX, the library mmapped can be a member of a big-archive file. + /// For example, with a big-archive named libfoo.a containing libbar.so, + /// one can use `dlopen("libfoo.a(libbar.so)", RTLD_MEMBER | RTLD_LAZY)` + /// to use the `libbar.so` library. In this case, only `libbar.so` is + /// mmapped, not the whole `libfoo.a`. + member_name: OsString, /// Segments of this library loaded into memory, and where they're loaded. segments: Vec, /// The "bias" of this library, typically where it's loaded into memory. @@ -280,6 +305,19 @@ struct LibrarySegment { len: usize, } +#[cfg(target_os = "aix")] +fn create_mapping(lib: &Library) -> Option { + let name = &lib.name; + let member_name = &lib.member_name; + Mapping::new(name.as_ref(), member_name) +} + +#[cfg(not(target_os = "aix"))] +fn create_mapping(lib: &Library) -> Option { + let name = &lib.name; + Mapping::new(name.as_ref()) +} + // unsafe because this is required to be externally synchronized pub unsafe fn clear_symbol_cache() { Cache::with_global(|cache| cache.mappings.clear()); @@ -360,8 +398,7 @@ impl Cache { // When the mapping is not in the cache, create a new mapping, // insert it into the front of the cache, and evict the oldest cache // entry if necessary. - let name = &self.libraries[lib].name; - let mapping = Mapping::new(name.as_ref())?; + let mapping = create_mapping(&self.libraries[lib])?; if self.mappings.len() == MAPPINGS_CACHE_SIZE { self.mappings.pop(); @@ -393,7 +430,7 @@ pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) }; Cache::with_global(|cache| { - let (lib, addr) = match cache.avma_to_svma(addr as *const u8) { + let (lib, addr) = match cache.avma_to_svma(addr.cast_const().cast::()) { Some(pair) => pair, None => return, }; diff --git a/src/symbolize/gimli/elf.rs b/src/symbolize/gimli/elf.rs index b0eec0762..906a30054 100644 --- a/src/symbolize/gimli/elf.rs +++ b/src/symbolize/gimli/elf.rs @@ -308,7 +308,7 @@ const DEBUG_PATH: &[u8] = b"/usr/lib/debug"; fn debug_path_exists() -> bool { cfg_if::cfg_if! { - if #[cfg(any(target_os = "freebsd", target_os = "linux"))] { + if #[cfg(any(target_os = "freebsd", target_os = "hurd", target_os = "linux"))] { use core::sync::atomic::{AtomicU8, Ordering}; static DEBUG_PATH_EXISTS: AtomicU8 = AtomicU8::new(0); diff --git a/src/symbolize/gimli/libs_aix.rs b/src/symbolize/gimli/libs_aix.rs new file mode 100644 index 000000000..aa4eaff89 --- /dev/null +++ b/src/symbolize/gimli/libs_aix.rs @@ -0,0 +1,76 @@ +use super::mystd::borrow::ToOwned; +use super::mystd::env; +use super::mystd::ffi::{CStr, OsStr}; +use super::mystd::io::Error; +use super::mystd::os::unix::prelude::*; +use super::xcoff; +use super::{Library, LibrarySegment, Vec}; +use alloc::vec; +use core::mem; + +const EXE_IMAGE_BASE: u64 = 0x100000000; + +/// On AIX, we use `loadquery` with `L_GETINFO` flag to query libraries mmapped. +/// See https://www.ibm.com/docs/en/aix/7.2?topic=l-loadquery-subroutine for +/// detailed information of `loadquery`. +pub(super) fn native_libraries() -> Vec { + let mut ret = Vec::new(); + unsafe { + let mut buffer = vec![mem::zeroed::(); 64]; + loop { + if libc::loadquery( + libc::L_GETINFO, + buffer.as_mut_ptr().cast::(), + (mem::size_of::() * buffer.len()) as u32, + ) != -1 + { + break; + } else { + match Error::last_os_error().raw_os_error() { + Some(libc::ENOMEM) => { + buffer.resize(buffer.len() * 2, mem::zeroed::()); + } + Some(_) => { + // If other error occurs, return empty libraries. + return Vec::new(); + } + _ => unreachable!(), + } + } + } + let mut current = buffer.as_mut_ptr(); + loop { + let text_base = (*current).ldinfo_textorg as usize; + let filename_ptr: *const libc::c_char = &(*current).ldinfo_filename[0]; + let bytes = CStr::from_ptr(filename_ptr).to_bytes(); + let member_name_ptr = filename_ptr.offset((bytes.len() + 1) as isize); + let mut filename = OsStr::from_bytes(bytes).to_owned(); + if text_base == EXE_IMAGE_BASE as usize { + if let Ok(exe) = env::current_exe() { + filename = exe.into_os_string(); + } + } + let bytes = CStr::from_ptr(member_name_ptr).to_bytes(); + let member_name = OsStr::from_bytes(bytes).to_owned(); + if let Some(image) = xcoff::parse_image(filename.as_ref(), &member_name) { + ret.push(Library { + name: filename, + member_name, + segments: vec![LibrarySegment { + stated_virtual_memory_address: image.base as usize, + len: image.size, + }], + bias: (text_base + image.offset).wrapping_sub(image.base as usize), + }); + } + if (*current).ldinfo_next == 0 { + break; + } + current = current + .cast::() + .offset((*current).ldinfo_next as isize) + .cast::(); + } + } + return ret; +} diff --git a/src/symbolize/gimli/libs_dl_iterate_phdr.rs b/src/symbolize/gimli/libs_dl_iterate_phdr.rs index 9f0304ce8..c8558e626 100644 --- a/src/symbolize/gimli/libs_dl_iterate_phdr.rs +++ b/src/symbolize/gimli/libs_dl_iterate_phdr.rs @@ -12,20 +12,24 @@ use core::slice; pub(super) fn native_libraries() -> Vec { let mut ret = Vec::new(); unsafe { - libc::dl_iterate_phdr(Some(callback), &mut ret as *mut Vec<_> as *mut _); + libc::dl_iterate_phdr(Some(callback), core::ptr::addr_of_mut!(ret).cast()); } return ret; } fn infer_current_exe(base_addr: usize) -> OsString { - if let Ok(entries) = super::parse_running_mmaps::parse_maps() { - let opt_path = entries - .iter() - .find(|e| e.ip_matches(base_addr) && e.pathname().len() > 0) - .map(|e| e.pathname()) - .cloned(); - if let Some(path) = opt_path { - return path; + cfg_if::cfg_if! { + if #[cfg(not(target_os = "hurd"))] { + if let Ok(entries) = super::parse_running_mmaps::parse_maps() { + let opt_path = entries + .iter() + .find(|e| e.ip_matches(base_addr) && e.pathname().len() > 0) + .map(|e| e.pathname()) + .cloned(); + if let Some(path) = opt_path { + return path; + } + } } } env::current_exe().map(|e| e.into()).unwrap_or_default() @@ -39,7 +43,7 @@ unsafe extern "C" fn callback( vec: *mut libc::c_void, ) -> libc::c_int { let info = &*info; - let libs = &mut *(vec as *mut Vec); + let libs = &mut *vec.cast::>(); let is_main_prog = info.dlpi_name.is_null() || *info.dlpi_name == 0; let name = if is_main_prog { // The man page for dl_iterate_phdr says that the first object visited by diff --git a/src/symbolize/gimli/libs_illumos.rs b/src/symbolize/gimli/libs_illumos.rs index e64975e0c..4fc510cf5 100644 --- a/src/symbolize/gimli/libs_illumos.rs +++ b/src/symbolize/gimli/libs_illumos.rs @@ -41,7 +41,7 @@ pub(super) fn native_libraries() -> Vec { if dlinfo( RTLD_SELF, RTLD_DI_LINKMAP, - (&mut map) as *mut *const LinkMap as *mut libc::c_void, + core::ptr::addr_of_mut!(map).cast::(), ) != 0 { return libs; diff --git a/src/symbolize/gimli/libs_libnx.rs b/src/symbolize/gimli/libs_libnx.rs index 93b5ba17e..803008ef3 100644 --- a/src/symbolize/gimli/libs_libnx.rs +++ b/src/symbolize/gimli/libs_libnx.rs @@ -7,7 +7,7 @@ pub(super) fn native_libraries() -> Vec { static __start__: u8; } - let bias = unsafe { &__start__ } as *const u8 as usize; + let bias = core::ptr::addr_of!(__start__) as usize; let mut ret = Vec::new(); let mut segments = Vec::new(); diff --git a/src/symbolize/gimli/libs_macos.rs b/src/symbolize/gimli/libs_macos.rs index 438bbff6f..671505a13 100644 --- a/src/symbolize/gimli/libs_macos.rs +++ b/src/symbolize/gimli/libs_macos.rs @@ -7,6 +7,13 @@ use super::{Library, LibrarySegment}; use core::convert::TryInto; use core::mem; +// FIXME: replace with ptr::from_ref once MSRV is high enough +#[inline(always)] +#[must_use] +const fn ptr_from_ref(r: &T) -> *const T { + r +} + pub(super) fn native_libraries() -> Vec { let mut ret = Vec::new(); let images = unsafe { libc::_dyld_image_count() }; @@ -42,18 +49,18 @@ fn native_library(i: u32) -> Option { match (*header).magic { macho::MH_MAGIC => { let endian = NativeEndian; - let header = &*(header as *const macho::MachHeader32); + let header = &*header.cast::>(); let data = core::slice::from_raw_parts( - header as *const _ as *const u8, + ptr_from_ref(header).cast::(), mem::size_of_val(header) + header.sizeofcmds.get(endian) as usize, ); (header.load_commands(endian, data, 0).ok()?, endian) } macho::MH_MAGIC_64 => { let endian = NativeEndian; - let header = &*(header as *const macho::MachHeader64); + let header = &*header.cast::>(); let data = core::slice::from_raw_parts( - header as *const _ as *const u8, + ptr_from_ref(header).cast::(), mem::size_of_val(header) + header.sizeofcmds.get(endian) as usize, ); (header.load_commands(endian, data, 0).ok()?, endian) diff --git a/src/symbolize/gimli/mmap_windows.rs b/src/symbolize/gimli/mmap_windows.rs index b39509d8c..c49ab083c 100644 --- a/src/symbolize/gimli/mmap_windows.rs +++ b/src/symbolize/gimli/mmap_windows.rs @@ -17,7 +17,7 @@ impl Mmap { pub unsafe fn map(file: &File, len: usize) -> Option { let file = file.try_clone().ok()?; let mapping = CreateFileMappingA( - file.as_raw_handle() as *mut _, + file.as_raw_handle().cast(), ptr::null_mut(), PAGE_READONLY, 0, @@ -43,7 +43,7 @@ impl Deref for Mmap { type Target = [u8]; fn deref(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.ptr as *const u8, self.len) } + unsafe { slice::from_raw_parts(self.ptr.cast_const().cast::(), self.len) } } } diff --git a/src/symbolize/gimli/stash.rs b/src/symbolize/gimli/stash.rs index 792f9a6f8..5ec06e240 100644 --- a/src/symbolize/gimli/stash.rs +++ b/src/symbolize/gimli/stash.rs @@ -1,3 +1,4 @@ +#![allow(clippy::all)] // only used on Linux right now, so allow dead code elsewhere #![cfg_attr(not(target_os = "linux"), allow(dead_code))] diff --git a/src/symbolize/gimli/xcoff.rs b/src/symbolize/gimli/xcoff.rs new file mode 100644 index 000000000..dd308840f --- /dev/null +++ b/src/symbolize/gimli/xcoff.rs @@ -0,0 +1,186 @@ +use super::mystd::ffi::{OsStr, OsString}; +use super::mystd::os::unix::ffi::OsStrExt; +use super::mystd::str; +use super::{gimli, Context, Endian, EndianSlice, Mapping, Path, Stash, Vec}; +use alloc::sync::Arc; +use core::ops::Deref; +use object::read::archive::ArchiveFile; +use object::read::xcoff::{FileHeader, SectionHeader, XcoffFile, XcoffSymbol}; +use object::Object as _; +use object::ObjectSection as _; +use object::ObjectSymbol as _; +use object::SymbolFlags; + +#[cfg(target_pointer_width = "32")] +type Xcoff = object::xcoff::FileHeader32; +#[cfg(target_pointer_width = "64")] +type Xcoff = object::xcoff::FileHeader64; + +impl Mapping { + pub fn new(path: &Path, member_name: &OsString) -> Option { + let map = super::mmap(path)?; + Mapping::mk(map, |data, stash| { + if member_name.is_empty() { + Context::new(stash, Object::parse(data)?, None, None) + } else { + let archive = ArchiveFile::parse(data).ok()?; + for member in archive + .members() + .filter_map(|m| m.ok()) + .filter(|m| OsStr::from_bytes(m.name()) == member_name) + { + let member_data = member.data(data).ok()?; + if let Some(obj) = Object::parse(member_data) { + return Context::new(stash, obj, None, None); + } + } + None + } + }) + } +} + +struct ParsedSym<'a> { + address: u64, + size: u64, + name: &'a str, +} + +pub struct Object<'a> { + syms: Vec>, + file: XcoffFile<'a, Xcoff>, +} + +pub struct Image { + pub offset: usize, + pub base: u64, + pub size: usize, +} + +pub fn parse_xcoff(data: &[u8]) -> Option { + let mut offset = 0; + let header = Xcoff::parse(data, &mut offset).ok()?; + let _ = header.aux_header(data, &mut offset).ok()?; + let sections = header.sections(data, &mut offset).ok()?; + if let Some(section) = sections.iter().find(|s| { + if let Ok(name) = str::from_utf8(&s.s_name()[0..5]) { + name == ".text" + } else { + false + } + }) { + Some(Image { + offset: section.s_scnptr() as usize, + base: section.s_paddr() as u64, + size: section.s_size() as usize, + }) + } else { + None + } +} + +pub fn parse_image(path: &Path, member_name: &OsString) -> Option { + let map = super::mmap(path)?; + let data = map.deref(); + if member_name.is_empty() { + return parse_xcoff(data); + } else { + let archive = ArchiveFile::parse(data).ok()?; + for member in archive + .members() + .filter_map(|m| m.ok()) + .filter(|m| OsStr::from_bytes(m.name()) == member_name) + { + let member_data = member.data(data).ok()?; + if let Some(image) = parse_xcoff(member_data) { + return Some(image); + } + } + None + } +} + +impl<'a> Object<'a> { + fn get_concrete_size(file: &XcoffFile<'a, Xcoff>, sym: &XcoffSymbol<'a, '_, Xcoff>) -> u64 { + match sym.flags() { + SymbolFlags::Xcoff { + n_sclass: _, + x_smtyp: _, + x_smclas: _, + containing_csect: Some(index), + } => { + if let Ok(tgt_sym) = file.symbol_by_index(index) { + Self::get_concrete_size(file, &tgt_sym) + } else { + 0 + } + } + _ => sym.size(), + } + } + + fn parse(data: &'a [u8]) -> Option> { + let file = XcoffFile::parse(data).ok()?; + let mut syms = file + .symbols() + .filter_map(|sym| { + let name = sym.name().map_or("", |v| v); + let address = sym.address(); + let size = Self::get_concrete_size(&file, &sym); + if name == ".text" || name == ".data" { + // We don't want to include ".text" and ".data" symbols. + // If they are included, since their ranges cover other + // symbols, when searching a symbol for a given address, + // ".text" or ".data" is returned. That's not what we expect. + None + } else { + Some(ParsedSym { + address, + size, + name, + }) + } + }) + .collect::>(); + syms.sort_by_key(|s| s.address); + Some(Object { syms, file }) + } + + pub fn section(&self, _: &Stash, name: &str) -> Option<&'a [u8]> { + Some(self.file.section_by_name(name)?.data().ok()?) + } + + pub fn search_symtab<'b>(&'b self, addr: u64) -> Option<&'b [u8]> { + // Symbols, except ".text" and ".data", are sorted and are not overlapped each other, + // so we can just perform a binary search here. + let i = match self.syms.binary_search_by_key(&addr, |sym| sym.address) { + Ok(i) => i, + Err(i) => i.checked_sub(1)?, + }; + let sym = self.syms.get(i)?; + if (sym.address..sym.address + sym.size).contains(&addr) { + // On AIX, for a function call, for example, `foo()`, we have + // two symbols `foo` and `.foo`. `foo` references the function + // descriptor and `.foo` references the function entry. + // See https://www.ibm.com/docs/en/xl-fortran-aix/16.1.0?topic=calls-linkage-convention-function + // for more information. + // We trim the prefix `.` here, so that the rust demangler can work + // properly. + Some(sym.name.trim_start_matches(".").as_bytes()) + } else { + None + } + } + + pub(super) fn search_object_map(&self, _addr: u64) -> Option<(&Context<'_>, u64)> { + None + } +} + +pub(super) fn handle_split_dwarf<'data>( + _package: Option<&gimli::DwarfPackage>>, + _stash: &'data Stash, + _load: addr2line::SplitDwarfLoad>, +) -> Option>>> { + None +} diff --git a/src/symbolize/mod.rs b/src/symbolize/mod.rs index a7c199506..b01fe86d6 100644 --- a/src/symbolize/mod.rs +++ b/src/symbolize/mod.rs @@ -210,7 +210,7 @@ impl Symbol { /// Returns the starting address of this function. pub fn addr(&self) -> Option<*mut c_void> { - self.inner.addr().map(|p| p as *mut _) + self.inner.addr() } /// Returns the raw filename as a slice. This is mainly useful for `no_std` @@ -421,7 +421,7 @@ cfg_if::cfg_if! { // it outwards. if let Some(ref cpp) = self.cpp_demangled.0 { let mut s = String::new(); - if write!(s, "{}", cpp).is_ok() { + if write!(s, "{cpp}").is_ok() { return s.fmt(f) } } diff --git a/src/windows.rs b/src/windows.rs index 92c2b2e66..a95762d35 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -7,7 +7,7 @@ //! This module largely exists to integrate into libstd itself where winapi is //! not currently available. -#![allow(bad_style, dead_code)] +#![allow(bad_style, dead_code, unused)] cfg_if::cfg_if! { if #[cfg(feature = "verify-winapi")] { @@ -19,6 +19,9 @@ cfg_if::cfg_if! { pub use self::winapi::PUNWIND_HISTORY_TABLE; #[cfg(target_pointer_width = "64")] pub use self::winapi::PRUNTIME_FUNCTION; + pub use self::winapi::PEXCEPTION_ROUTINE; + #[cfg(target_pointer_width = "64")] + pub use self::winapi::PKNONVOLATILE_CONTEXT_POINTERS; mod winapi { pub use winapi::ctypes::*; @@ -35,6 +38,32 @@ cfg_if::cfg_if! { pub use winapi::um::tlhelp32::*; pub use winapi::um::winbase::*; pub use winapi::um::winnt::*; + + // Work around winapi not having this function on aarch64. + #[cfg(target_arch = "aarch64")] + #[link(name = "kernel32")] + extern "system" { + pub fn RtlVirtualUnwind( + HandlerType: ULONG, + ImageBase: ULONG64, + ControlPc: ULONG64, + FunctionEntry: PRUNTIME_FUNCTION, + ContextRecord: PCONTEXT, + HandlerData: *mut PVOID, + EstablisherFrame: PULONG64, + ContextPointers: PKNONVOLATILE_CONTEXT_POINTERS + ) -> PEXCEPTION_ROUTINE; + } + + // winapi doesn't have this type + pub type PENUMLOADED_MODULES_CALLBACKW64 = Option< + unsafe extern "system" fn( + modulename: PCWSTR, + modulebase: DWORD64, + modulesize: ULONG, + usercontext: PVOID, + ) -> BOOL, + >; } } else { pub use core::ffi::c_void; @@ -45,6 +74,9 @@ cfg_if::cfg_if! { pub type PRUNTIME_FUNCTION = *mut c_void; #[cfg(target_pointer_width = "64")] pub type PUNWIND_HISTORY_TABLE = *mut c_void; + pub type PEXCEPTION_ROUTINE = *mut c_void; + #[cfg(target_pointer_width = "64")] + pub type PKNONVOLATILE_CONTEXT_POINTERS = *mut c_void; } } @@ -269,6 +301,7 @@ ffi! { pub type PTRANSLATE_ADDRESS_ROUTINE64 = Option< unsafe extern "system" fn(hProcess: HANDLE, hThread: HANDLE, lpaddr: LPADDRESS64) -> DWORD64, >; + pub type PENUMLOADED_MODULES_CALLBACKW64 = Option BOOL>; pub type PGET_MODULE_BASE_ROUTINE64 = Option DWORD64>; pub type PFUNCTION_TABLE_ACCESS_ROUTINE64 = @@ -359,6 +392,7 @@ ffi! { pub type LPCSTR = *const i8; pub type PWSTR = *mut u16; pub type WORD = u16; + pub type USHORT = u16; pub type ULONG = u32; pub type ULONG64 = u64; pub type WCHAR = u16; @@ -370,6 +404,8 @@ ffi! { pub type LPVOID = *mut c_void; pub type LPCVOID = *const c_void; pub type LPMODULEENTRY32W = *mut MODULEENTRY32W; + pub type PULONG = *mut ULONG; + pub type PULONG64 = *mut ULONG64; #[link(name = "kernel32")] extern "system" { @@ -378,23 +414,8 @@ ffi! { pub fn RtlCaptureContext(ContextRecord: PCONTEXT) -> (); pub fn LoadLibraryA(a: *const i8) -> HMODULE; pub fn GetProcAddress(h: HMODULE, name: *const i8) -> FARPROC; - pub fn GetModuleHandleA(name: *const i8) -> HMODULE; - pub fn OpenProcess( - dwDesiredAccess: DWORD, - bInheitHandle: BOOL, - dwProcessId: DWORD, - ) -> HANDLE; pub fn GetCurrentProcessId() -> DWORD; pub fn CloseHandle(h: HANDLE) -> BOOL; - pub fn CreateFileA( - lpFileName: LPCSTR, - dwDesiredAccess: DWORD, - dwShareMode: DWORD, - lpSecurityAttributes: LPSECURITY_ATTRIBUTES, - dwCreationDisposition: DWORD, - dwFlagsAndAttributes: DWORD, - hTemplateFile: HANDLE, - ) -> HANDLE; pub fn CreateMutexA( attrs: LPSECURITY_ATTRIBUTES, initial: BOOL, @@ -434,6 +455,28 @@ ffi! { hSnapshot: HANDLE, lpme: LPMODULEENTRY32W, ) -> BOOL; + pub fn lstrlenW(lpstring: PCWSTR) -> i32; + } +} + +#[cfg(any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "arm64ec" +))] +ffi! { + #[link(name = "kernel32")] + extern "system" { + pub fn RtlVirtualUnwind( + HandlerType: ULONG, + ImageBase: ULONG64, + ControlPc: ULONG64, + FunctionEntry: PRUNTIME_FUNCTION, + ContextRecord: PCONTEXT, + HandlerData: *mut PVOID, + EstablisherFrame: PULONG64, + ContextPointers: PKNONVOLATILE_CONTEXT_POINTERS + ) -> PEXCEPTION_ROUTINE; } } @@ -567,7 +610,7 @@ ffi! { } } -#[cfg(target_arch = "x86_64")] +#[cfg(any(target_arch = "x86_64", target_arch = "arm64ec"))] ffi! { #[repr(C, align(8))] pub struct CONTEXT { @@ -635,7 +678,7 @@ ffi! { } #[repr(C)] -#[cfg(target_arch = "x86_64")] +#[cfg(any(target_arch = "x86_64", target_arch = "arm64ec"))] #[derive(Copy, Clone)] pub struct FLOATING_SAVE_AREA { _Dummy: [u8; 512], diff --git a/tests/accuracy/main.rs b/tests/accuracy/main.rs index 149203a1b..568acab84 100644 --- a/tests/accuracy/main.rs +++ b/tests/accuracy/main.rs @@ -31,6 +31,8 @@ fn doit() { dir.push("dylib_dep.dll"); } else if cfg!(target_os = "macos") { dir.push("libdylib_dep.dylib"); + } else if cfg!(target_os = "aix") { + dir.push("libdylib_dep.a"); } else { dir.push("libdylib_dep.so"); } @@ -94,16 +96,16 @@ fn verify(filelines: &[Pos]) { println!("-----------------------------------"); println!("looking for:"); for (file, line) in filelines.iter().rev() { - println!("\t{}:{}", file, line); + println!("\t{file}:{line}"); } - println!("found:\n{:?}", trace); + println!("found:\n{trace:?}"); let mut symbols = trace.frames().iter().flat_map(|frame| frame.symbols()); let mut iter = filelines.iter().rev(); while let Some((file, line)) = iter.next() { loop { let sym = match symbols.next() { Some(sym) => sym, - None => panic!("failed to find {}:{}", file, line), + None => panic!("failed to find {file}:{line}"), }; if let Some(filename) = sym.filename() { if let Some(lineno) = sym.lineno() { diff --git a/tests/current-exe-mismatch.rs b/tests/current-exe-mismatch.rs index b655827fb..e119adfcb 100644 --- a/tests/current-exe-mismatch.rs +++ b/tests/current-exe-mismatch.rs @@ -16,11 +16,11 @@ fn main() { Ok(()) => { println!("test result: ok"); } - Err(EarlyExit::IgnoreTest(_)) => { + Err(EarlyExit::IgnoreTest) => { println!("test result: ignored"); } Err(EarlyExit::IoError(e)) => { - println!("{} parent encoutered IoError: {:?}", file!(), e); + println!("{} parent encountered IoError: {:?}", file!(), e); panic!(); } } @@ -34,7 +34,7 @@ const VAR: &str = "__THE_TEST_YOU_ARE_LUKE"; #[derive(Debug)] enum EarlyExit { - IgnoreTest(String), + IgnoreTest, IoError(std::io::Error), } @@ -47,7 +47,7 @@ impl From for EarlyExit { fn parent() -> Result<(), EarlyExit> { // If we cannot re-exec this test, there's no point in trying to do it. if common::cannot_reexec_the_test() { - return Err(EarlyExit::IgnoreTest("(cannot reexec)".into())); + return Err(EarlyExit::IgnoreTest); } let me = std::env::current_exe().unwrap(); @@ -74,7 +74,7 @@ fn parent() -> Result<(), EarlyExit> { fn child() -> Result<(), EarlyExit> { let bt = backtrace::Backtrace::new(); - println!("{:?}", bt); + println!("{bt:?}"); let mut found_my_name = false; @@ -111,7 +111,7 @@ fn find_interpreter(me: &Path) -> Result { .arg("-l") .arg(me) .output() - .map_err(|_err| EarlyExit::IgnoreTest("readelf invocation failed".into()))?; + .map_err(|_| EarlyExit::IgnoreTest)?; if result.status.success() { let r = BufReader::new(&result.stdout[..]); for line in r.lines() { @@ -124,11 +124,6 @@ fn find_interpreter(me: &Path) -> Result { } } } - - Err(EarlyExit::IgnoreTest( - "could not find interpreter from readelf output".into(), - )) - } else { - Err(EarlyExit::IgnoreTest("readelf returned non-success".into())) } + Err(EarlyExit::IgnoreTest) } diff --git a/tests/long_fn_name.rs b/tests/long_fn_name.rs index fa4cfda15..4a03825b6 100644 --- a/tests/long_fn_name.rs +++ b/tests/long_fn_name.rs @@ -25,7 +25,7 @@ fn test_long_fn_name() { // It's actually longer since it also includes `::`, `<>` and the // name of the current module let bt = S::>>>>>>>>>::new(); - println!("{:?}", bt); + println!("{bt:?}"); let mut found_long_name_frame = false; diff --git a/tests/sgx-image-base.rs b/tests/sgx-image-base.rs new file mode 100644 index 000000000..c29a8b67a --- /dev/null +++ b/tests/sgx-image-base.rs @@ -0,0 +1,56 @@ +#![cfg(all(target_env = "sgx", target_vendor = "fortanix"))] +#![feature(sgx_platform)] + +#[cfg(feature = "std")] +#[test] +fn sgx_image_base_with_std() { + use backtrace::trace; + + let image_base = std::os::fortanix_sgx::mem::image_base(); + + let mut frame_ips = Vec::new(); + trace(|frame| { + frame_ips.push(frame.ip()); + true + }); + + assert!(frame_ips.len() > 0); + for ip in frame_ips { + let ip: u64 = ip as _; + assert!(ip < image_base); + } +} + +#[cfg(not(feature = "std"))] +#[test] +fn sgx_image_base_no_std() { + use backtrace::trace_unsynchronized; + + fn guess_image_base() -> u64 { + let mut top_frame_ip = None; + unsafe { + trace_unsynchronized(|frame| { + top_frame_ip = Some(frame.ip()); + false + }); + } + top_frame_ip.unwrap() as u64 & 0xFFFFFF000000 + } + + let image_base = guess_image_base(); + backtrace::set_image_base(image_base as _); + + let mut frame_ips = Vec::new(); + unsafe { + trace_unsynchronized(|frame| { + frame_ips.push(frame.ip()); + true + }); + } + + assert!(frame_ips.len() > 0); + for ip in frame_ips { + let ip: u64 = ip as _; + assert!(ip < image_base); + } +} diff --git a/tests/skip_inner_frames.rs b/tests/skip_inner_frames.rs index 60bba35e6..4a370fbc1 100644 --- a/tests/skip_inner_frames.rs +++ b/tests/skip_inner_frames.rs @@ -18,7 +18,7 @@ fn backtrace_new_unresolved_should_start_with_call_site_trace() { } let mut b = Backtrace::new_unresolved(); b.resolve(); - println!("{:?}", b); + println!("{b:?}"); assert!(!b.frames().is_empty()); @@ -34,7 +34,7 @@ fn backtrace_new_should_start_with_call_site_trace() { return; } let b = Backtrace::new(); - println!("{:?}", b); + println!("{b:?}"); assert!(!b.frames().is_empty()); diff --git a/tests/smoke.rs b/tests/smoke.rs index 683a6f0db..b47f02323 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -1,6 +1,28 @@ use backtrace::Frame; +use std::ptr; use std::thread; +fn get_actual_fn_pointer(fp: usize) -> usize { + // On AIX, the function name references a function descriptor. + // A function descriptor consists of (See https://reviews.llvm.org/D62532) + // * The address of the entry point of the function. + // * The TOC base address for the function. + // * The environment pointer. + // Deref `fp` directly so that we can get the address of `fp`'s + // entry point in text section. + // + // For TOC, one can find more information in + // https://www.ibm.com/docs/en/aix/7.2?topic=program-understanding-programming-toc + if cfg!(target_os = "aix") { + unsafe { + let actual_fn_entry = *(fp as *const usize); + actual_fn_entry + } + } else { + fp + } +} + #[test] // FIXME: shouldn't ignore this test on i686-msvc, unsure why it's failing #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] @@ -20,7 +42,7 @@ fn smoke_test_frames() { // Various platforms have various bits of weirdness about their // backtraces. To find a good starting spot let's search through the // frames - let target = frame_4 as usize; + let target = get_actual_fn_pointer(frame_4 as usize); let offset = v .iter() .map(|frame| frame.symbol_address() as usize) @@ -39,7 +61,7 @@ fn smoke_test_frames() { assert_frame( frames.next().unwrap(), - frame_4 as usize, + get_actual_fn_pointer(frame_4 as usize), "frame_4", "tests/smoke.rs", start_line + 6, @@ -47,7 +69,7 @@ fn smoke_test_frames() { ); assert_frame( frames.next().unwrap(), - frame_3 as usize, + get_actual_fn_pointer(frame_3 as usize), "frame_3", "tests/smoke.rs", start_line + 3, @@ -55,7 +77,7 @@ fn smoke_test_frames() { ); assert_frame( frames.next().unwrap(), - frame_2 as usize, + get_actual_fn_pointer(frame_2 as usize), "frame_2", "tests/smoke.rs", start_line + 2, @@ -63,7 +85,7 @@ fn smoke_test_frames() { ); assert_frame( frames.next().unwrap(), - frame_1 as usize, + get_actual_fn_pointer(frame_1 as usize), "frame_1", "tests/smoke.rs", start_line + 1, @@ -71,7 +93,7 @@ fn smoke_test_frames() { ); assert_frame( frames.next().unwrap(), - smoke_test_frames as usize, + get_actual_fn_pointer(smoke_test_frames as usize), "smoke_test_frames", "", 0, @@ -90,16 +112,16 @@ fn smoke_test_frames() { backtrace::resolve_frame(frame, |sym| { print!("symbol ip:{:?} address:{:?} ", frame.ip(), frame.symbol_address()); if let Some(name) = sym.name() { - print!("name:{} ", name); + print!("name:{name} "); } if let Some(file) = sym.filename() { print!("file:{} ", file.display()); } if let Some(lineno) = sym.lineno() { - print!("lineno:{} ", lineno); + print!("lineno:{lineno} "); } if let Some(colno) = sym.colno() { - print!("colno:{} ", colno); + print!("colno:{colno} "); } println!(); }); @@ -150,9 +172,7 @@ fn smoke_test_frames() { if cfg!(debug_assertions) { assert!( name.contains(expected_name), - "didn't find `{}` in `{}`", - expected_name, - name + "didn't find `{expected_name}` in `{name}`" ); } @@ -164,18 +184,13 @@ fn smoke_test_frames() { if !expected_file.is_empty() { assert!( file.ends_with(expected_file), - "{:?} didn't end with {:?}", - file, - expected_file + "{file:?} didn't end with {expected_file:?}" ); } if expected_line != 0 { assert!( line == expected_line, - "bad line number on frame for `{}`: {} != {}", - expected_name, - line, - expected_line + "bad line number on frame for `{expected_name}`: {line} != {expected_line}" ); } @@ -185,10 +200,7 @@ fn smoke_test_frames() { if expected_col != 0 { assert!( col == expected_col, - "bad column number on frame for `{}`: {} != {}", - expected_name, - col, - expected_col + "bad column number on frame for `{expected_name}`: {col} != {expected_col}", ); } } @@ -253,16 +265,16 @@ fn sp_smoke_test() { assert!(refs.len() < 5); let x = refs.len(); - refs.push(&x as *const _ as usize); + refs.push(ptr::addr_of!(x) as usize); if refs.len() < 5 { recursive_stack_references(refs); - eprintln!("exiting: {}", x); + eprintln!("exiting: {x}"); return; } backtrace::trace(make_trace_closure(refs)); - eprintln!("exiting: {}", x); + eprintln!("exiting: {x}"); } // NB: the following `make_*` functions are pulled out of line, rather than @@ -284,7 +296,7 @@ fn sp_smoke_test() { sym.name() .and_then(|name| name.as_str()) .map_or(false, |name| { - eprintln!("name = {}", name); + eprintln!("name = {name}"); name.contains("recursive_stack_references") }) });