From ef24de24a82fe681581cc130f342363c47c0969a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 7 Jun 2024 07:48:48 +0200 Subject: Merging upstream version 1.75.0+dfsg1. Signed-off-by: Daniel Baumann --- .../actions/build-with-patched-std/action.yml | 48 +++ .../actions/report-code-size-changes/action.yml | 111 +++++++ .../.github/workflows/check-binary-size.yml | 172 +++++++---- library/backtrace/Cargo.lock | 221 ++++++++++++++ library/backtrace/Cargo.toml | 9 +- library/backtrace/build.rs | 31 +- library/backtrace/crates/as-if-std/Cargo.toml | 4 +- library/backtrace/crates/debuglink/Cargo.toml | 2 +- library/backtrace/crates/dylib-dep/Cargo.toml | 2 +- .../backtrace/crates/line-tables-only/Cargo.toml | 2 +- .../backtrace/crates/line-tables-only/src/lib.rs | 15 +- .../backtrace/crates/macos_frames_test/Cargo.toml | 2 +- .../backtrace/crates/without_debuginfo/Cargo.toml | 2 +- library/backtrace/src/android-api.c | 4 - library/backtrace/src/backtrace/dbghelp.rs | 327 +++++++++++---------- library/backtrace/src/backtrace/libunwind.rs | 13 +- library/backtrace/src/backtrace/mod.rs | 35 ++- library/backtrace/src/dbghelp.rs | 52 +++- library/backtrace/src/lib.rs | 15 +- library/backtrace/src/print.rs | 11 +- library/backtrace/src/symbolize/dbghelp.rs | 115 +++++--- library/backtrace/src/symbolize/gimli.rs | 45 ++- library/backtrace/src/symbolize/gimli/elf.rs | 2 +- library/backtrace/src/symbolize/gimli/libs_aix.rs | 74 +++++ .../src/symbolize/gimli/libs_dl_iterate_phdr.rs | 20 +- library/backtrace/src/symbolize/gimli/xcoff.rs | 186 ++++++++++++ library/backtrace/src/windows.rs | 52 ++++ library/backtrace/tests/accuracy/main.rs | 4 +- library/backtrace/tests/sgx-image-base.rs | 56 ++++ library/backtrace/tests/smoke.rs | 51 ++-- 30 files changed, 1350 insertions(+), 333 deletions(-) create mode 100644 library/backtrace/.github/actions/build-with-patched-std/action.yml create mode 100644 library/backtrace/.github/actions/report-code-size-changes/action.yml create mode 100644 library/backtrace/Cargo.lock delete mode 100644 library/backtrace/src/android-api.c create mode 100644 library/backtrace/src/symbolize/gimli/libs_aix.rs create mode 100644 library/backtrace/src/symbolize/gimli/xcoff.rs create mode 100644 library/backtrace/tests/sgx-image-base.rs (limited to 'library/backtrace') diff --git a/library/backtrace/.github/actions/build-with-patched-std/action.yml b/library/backtrace/.github/actions/build-with-patched-std/action.yml new file mode 100644 index 000000000..5466a2289 --- /dev/null +++ b/library/backtrace/.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/library/backtrace/.github/actions/report-code-size-changes/action.yml b/library/backtrace/.github/actions/report-code-size-changes/action.yml new file mode 100644 index 000000000..ede26975a --- /dev/null +++ b/library/backtrace/.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/library/backtrace/.github/workflows/check-binary-size.yml b/library/backtrace/.github/workflows/check-binary-size.yml index 0beae1da9..d045fb7b3 100644 --- a/library/backtrace/.github/workflows/check-binary-size.yml +++ b/library/backtrace/.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/library/backtrace/Cargo.lock b/library/backtrace/Cargo.lock new file mode 100644 index 000000000..619a1392f --- /dev/null +++ b/library/backtrace/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.69" +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.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" + +[[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/library/backtrace/Cargo.toml b/library/backtrace/Cargo.toml index 6714b3b7d..932310bcf 100644 --- a/library/backtrace/Cargo.toml +++ b/library/backtrace/Cargo.toml @@ -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/library/backtrace/build.rs b/library/backtrace/build.rs index 9bd3abd16..ed4e07a85 100644 --- a/library/backtrace/build.rs +++ b/library/backtrace/build.rs @@ -11,17 +11,27 @@ 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 +39,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/library/backtrace/crates/as-if-std/Cargo.toml b/library/backtrace/crates/as-if-std/Cargo.toml index bcbcfe159..7f12cfb56 100644 --- a/library/backtrace/crates/as-if-std/Cargo.toml +++ b/library/backtrace/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/library/backtrace/crates/debuglink/Cargo.toml b/library/backtrace/crates/debuglink/Cargo.toml index 6b55b1394..5e62abd37 100644 --- a/library/backtrace/crates/debuglink/Cargo.toml +++ b/library/backtrace/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/library/backtrace/crates/dylib-dep/Cargo.toml b/library/backtrace/crates/dylib-dep/Cargo.toml index c3d4a8c2f..e6cc9c23b 100644 --- a/library/backtrace/crates/dylib-dep/Cargo.toml +++ b/library/backtrace/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/library/backtrace/crates/line-tables-only/Cargo.toml b/library/backtrace/crates/line-tables-only/Cargo.toml index e2967d3d3..8d17db58c 100644 --- a/library/backtrace/crates/line-tables-only/Cargo.toml +++ b/library/backtrace/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/library/backtrace/crates/line-tables-only/src/lib.rs b/library/backtrace/crates/line-tables-only/src/lib.rs index bd5afcb3a..b292b8441 100644 --- a/library/backtrace/crates/line-tables-only/src/lib.rs +++ b/library/backtrace/crates/line-tables-only/src/lib.rs @@ -1,8 +1,8 @@ #[cfg(test)] mod tests { - use std::path::Path; use backtrace::Backtrace; use libc::c_void; + use std::path::Path; pub type Callback = extern "C" fn(data: *mut c_void); @@ -15,11 +15,12 @@ mod tests { unsafe { *(data as *mut Option) = 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 +35,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 diff --git a/library/backtrace/crates/macos_frames_test/Cargo.toml b/library/backtrace/crates/macos_frames_test/Cargo.toml index 278d51e79..849e76414 100644 --- a/library/backtrace/crates/macos_frames_test/Cargo.toml +++ b/library/backtrace/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/library/backtrace/crates/without_debuginfo/Cargo.toml b/library/backtrace/crates/without_debuginfo/Cargo.toml index 19d76cbec..38e559971 100644 --- a/library/backtrace/crates/without_debuginfo/Cargo.toml +++ b/library/backtrace/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/library/backtrace/src/android-api.c b/library/backtrace/src/android-api.c deleted file mode 100644 index 1bfeadf5b..000000000 --- a/library/backtrace/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/library/backtrace/src/backtrace/dbghelp.rs b/library/backtrace/src/backtrace/dbghelp.rs index ba0f05f3b..d1b76e281 100644 --- a/library/backtrace/src/backtrace/dbghelp.rs +++ b/library/backtrace/src/backtrace/dbghelp.rs @@ -1,30 +1,32 @@ //! Backtrace strategy for MSVC platforms. //! -//! This module contains the ability to generate a backtrace on MSVC using one -//! of two possible methods. The `StackWalkEx` function is primarily 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. +//! 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::{dbghelp, windows::*}; +use super::super::windows::*; use core::ffi::c_void; -use core::mem; - -#[derive(Clone, Copy)] -pub enum StackFrame { - New(STACKFRAME_EX), - Old(STACKFRAME64), -} #[derive(Clone, Copy)] pub struct Frame { - pub(crate) stack_frame: StackFrame, 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 @@ -34,62 +36,144 @@ unsafe impl Sync for Frame {} impl Frame { pub fn ip(&self) -> *mut c_void { - self.addr_pc().Offset as *mut _ + self.ip } pub fn sp(&self) -> *mut c_void { - self.addr_stack().Offset as *mut _ + self.sp } pub fn symbol_address(&self) -> *mut c_void { - self.ip() + self.ip } pub fn module_base_address(&self) -> Option<*mut c_void> { Some(self.base_address) } - fn addr_pc(&self) -> &ADDRESS64 { - match self.stack_frame { - StackFrame::New(ref new) => &new.AddrPC, - StackFrame::Old(ref old) => &old.AddrPC, - } + #[cfg(not(target_env = "gnu"))] + pub fn inline_context(&self) -> Option { + self.inline_context } +} - fn addr_pc_mut(&mut self) -> &mut ADDRESS64 { - match self.stack_frame { - StackFrame::New(ref mut new) => &mut new.AddrPC, - StackFrame::Old(ref mut old) => &mut old.AddrPC, - } +#[repr(C, align(16))] // required by `CONTEXT`, is a FIXME in winapi right now +struct MyContext(CONTEXT); + +#[cfg(target_arch = "x86_64")] +impl MyContext { + #[inline(always)] + fn ip(&self) -> DWORD64 { + self.0.Rip } - fn addr_frame_mut(&mut self) -> &mut ADDRESS64 { - match self.stack_frame { - StackFrame::New(ref mut new) => &mut new.AddrFrame, - StackFrame::Old(ref mut old) => &mut old.AddrFrame, - } + #[inline(always)] + fn sp(&self) -> DWORD64 { + self.0.Rsp } +} - fn addr_stack(&self) -> &ADDRESS64 { - match self.stack_frame { - StackFrame::New(ref new) => &new.AddrStack, - StackFrame::Old(ref old) => &old.AddrStack, - } +#[cfg(target_arch = "aarch64")] +impl MyContext { + #[inline(always)] + fn ip(&self) -> DWORD64 { + self.0.Pc } - fn addr_stack_mut(&mut self) -> &mut ADDRESS64 { - match self.stack_frame { - StackFrame::New(ref mut new) => &mut new.AddrStack, - StackFrame::Old(ref mut old) => &mut old.AddrStack, - } + #[inline(always)] + fn sp(&self) -> DWORD64 { + self.0.Sp } } -#[repr(C, align(16))] // required by `CONTEXT`, is a FIXME in winapi right now -struct MyContext(CONTEXT); +#[cfg(target_arch = "x86")] +impl MyContext { + #[inline(always)] + fn ip(&self) -> DWORD { + self.0.Eip + } + + #[inline(always)] + fn sp(&self) -> DWORD { + self.0.Esp + } + #[inline(always)] + fn fp(&self) -> DWORD { + self.0.Ebp + } +} + +#[cfg(target_arch = "arm")] +impl MyContext { + #[inline(always)] + fn ip(&self) -> DWORD { + self.0.Pc + } + + #[inline(always)] + fn sp(&self) -> DWORD { + self.0.Sp + } + + #[inline(always)] + fn fp(&self) -> DWORD { + self.0.R11 + } +} + +#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] #[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 as *mut c_void, + 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, + &mut handler_data as *mut usize as *mut PVOID, + &mut establisher_frame, + ptr::null_mut(), + ); + } +} + +#[cfg(any(target_arch = "x86", target_arch = "arm"))] +#[inline(always)] +pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { + use core::mem; + // Allocate necessary structures for doing the stack walk let process = GetCurrentProcess(); let thread = GetCurrentThread(); @@ -98,65 +182,40 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { RtlCaptureContext(&mut context.0); // Ensure this process's symbols are initialized - let dbghelp = match dbghelp::init() { + let dbghelp = match super::super::dbghelp::init() { Ok(dbghelp) => dbghelp, Err(()) => return, // oh well... }; - // On x86_64 and ARM64 we opt to not use the default `Sym*` functions from - // dbghelp for getting the function table and module base. Instead we use - // the `RtlLookupFunctionEntry` function in kernel32 which will account for - // JIT compiler frames as well. These should be equivalent, but using - // `Rtl*` allows us to backtrace through JIT frames. - // - // 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(); + #[cfg(target_arch = "x86")] + let image = IMAGE_FILE_MACHINE_I386; + #[cfg(target_arch = "arm")] + let image = IMAGE_FILE_MACHINE_ARMNT; + // Attempt to use `StackWalkEx` if we can, but fall back to `StackWalk64` // since it's in theory supported on more systems. match (*dbghelp.dbghelp()).StackWalkEx() { Some(StackWalkEx) => { - let mut inner: STACKFRAME_EX = mem::zeroed(); - inner.StackFrameSize = mem::size_of::() as DWORD; - let mut frame = super::Frame { - inner: Frame { - stack_frame: StackFrame::New(inner), - base_address: 0 as _, - }, - }; - let image = init_frame(&mut frame.inner, &context.0); - let frame_ptr = match &mut frame.inner.stack_frame { - StackFrame::New(ptr) => ptr as *mut STACKFRAME_EX, - _ => unreachable!(), - }; + let mut stack_frame_ex: STACKFRAME_EX = mem::zeroed(); + stack_frame_ex.StackFrameSize = mem::size_of::() as DWORD; + stack_frame_ex.AddrPC.Offset = context.ip() as u64; + stack_frame_ex.AddrPC.Mode = AddrModeFlat; + stack_frame_ex.AddrStack.Offset = context.sp() as u64; + stack_frame_ex.AddrStack.Mode = AddrModeFlat; + stack_frame_ex.AddrFrame.Offset = context.fp() as u64; + stack_frame_ex.AddrFrame.Mode = AddrModeFlat; while StackWalkEx( image as DWORD, process, thread, - frame_ptr, - &mut context.0 as *mut CONTEXT as *mut _, + &mut stack_frame_ex, + &mut context.0 as *mut CONTEXT as PVOID, None, Some(function_table_access), Some(get_module_base), @@ -164,7 +223,16 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { 0, ) == TRUE { - frame.inner.base_address = get_module_base(process_handle, frame.ip() as _) as _; + let frame = super::Frame { + inner: Frame { + base_address: get_module_base(process_handle, stack_frame_ex.AddrPC.Offset) + as *mut c_void, + ip: stack_frame_ex.AddrPC.Offset as *mut c_void, + sp: stack_frame_ex.AddrStack.Offset as *mut c_void, + #[cfg(not(target_env = "gnu"))] + inline_context: Some(stack_frame_ex.InlineFrameContext), + }, + }; if !cb(&frame) { break; @@ -172,31 +240,36 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { } } None => { - let mut frame = super::Frame { - inner: Frame { - stack_frame: StackFrame::Old(mem::zeroed()), - base_address: 0 as _, - }, - }; - let image = init_frame(&mut frame.inner, &context.0); - let frame_ptr = match &mut frame.inner.stack_frame { - StackFrame::Old(ptr) => ptr as *mut STACKFRAME64, - _ => unreachable!(), - }; + let mut stack_frame64: STACKFRAME64 = mem::zeroed(); + stack_frame64.AddrPC.Offset = context.ip() as u64; + stack_frame64.AddrPC.Mode = AddrModeFlat; + stack_frame64.AddrStack.Offset = context.sp() as u64; + stack_frame64.AddrStack.Mode = AddrModeFlat; + stack_frame64.AddrFrame.Offset = context.fp() as u64; + stack_frame64.AddrFrame.Mode = AddrModeFlat; while dbghelp.StackWalk64()( image as DWORD, process, thread, - frame_ptr, - &mut context.0 as *mut CONTEXT as *mut _, + &mut stack_frame64, + &mut context.0 as *mut CONTEXT as PVOID, None, Some(function_table_access), Some(get_module_base), None, ) == TRUE { - frame.inner.base_address = get_module_base(process_handle, frame.ip() as _) as _; + let frame = super::Frame { + inner: Frame { + base_address: get_module_base(process_handle, stack_frame64.AddrPC.Offset) + as *mut c_void, + ip: stack_frame64.AddrPC.Offset as *mut c_void, + sp: stack_frame64.AddrStack.Offset as *mut c_void, + #[cfg(not(target_env = "gnu"))] + inline_context: None, + }, + }; if !cb(&frame) { break; @@ -205,53 +278,3 @@ 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; - frame.addr_pc_mut().Mode = AddrModeFlat; - frame.addr_stack_mut().Offset = ctx.Esp as u64; - frame.addr_stack_mut().Mode = AddrModeFlat; - frame.addr_frame_mut().Offset = ctx.Ebp as u64; - frame.addr_frame_mut().Mode = AddrModeFlat; - - 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; - 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.R11 as u64; - } - frame.addr_frame_mut().Mode = AddrModeFlat; - IMAGE_FILE_MACHINE_ARMNT -} diff --git a/library/backtrace/src/backtrace/libunwind.rs b/library/backtrace/src/backtrace/libunwind.rs index aefa8b094..0cf6365f7 100644 --- a/library/backtrace/src/backtrace/libunwind.rs +++ b/library/backtrace/src/backtrace/libunwind.rs @@ -40,7 +40,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::get_image_base(); + ip = usize::wrapping_sub(ip as usize, image_base as _) as _; + } + ip } pub fn sp(&self) -> *mut c_void { diff --git a/library/backtrace/src/backtrace/mod.rs b/library/backtrace/src/backtrace/mod.rs index 6ca1080c4..1b812d84e 100644 --- a/library/backtrace/src/backtrace/mod.rs +++ b/library/backtrace/src/backtrace/mod.rs @@ -125,6 +125,39 @@ impl fmt::Debug for Frame { } } +#[cfg(all(target_env = "sgx", target_vendor = "fortanix", not(feature = "std")))] +mod sgx_no_std_image_base { + 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(all(target_env = "sgx", target_vendor = "fortanix", not(feature = "std")))] +pub use self::sgx_no_std_image_base::set_image_base; + +#[cfg(all(target_env = "sgx", target_vendor = "fortanix", not(feature = "std")))] +#[deny(unused)] +pub(crate) use self::sgx_no_std_image_base::get_image_base; + +#[cfg(all(target_env = "sgx", target_vendor = "fortanix", feature = "std"))] +#[deny(unused)] +pub(crate) fn get_image_base() -> *mut c_void { + std::os::fortanix_sgx::mem::image_base() as _ +} + cfg_if::cfg_if! { // This needs to come first, to ensure that // Miri takes priority over the host platform @@ -153,8 +186,6 @@ cfg_if::cfg_if! { mod 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/library/backtrace/src/dbghelp.rs b/library/backtrace/src/dbghelp.rs index c81766bae..e456dd45d 100644 --- a/library/backtrace/src/dbghelp.rs +++ b/library/backtrace/src/dbghelp.rs @@ -34,8 +34,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 +55,16 @@ 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 assert_equal_types(a: T, _b: T) -> T { @@ -164,7 +174,6 @@ dbghelp! { path: PCWSTR, invade: BOOL ) -> BOOL; - fn SymCleanup(handle: HANDLE) -> BOOL; fn StackWalk64( MachineType: DWORD, hProcess: HANDLE, @@ -184,18 +193,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 +220,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; } } diff --git a/library/backtrace/src/lib.rs b/library/backtrace/src/lib.rs index 4615e1f96..44a0bc64e 100644 --- a/library/backtrace/src/lib.rs +++ b/library/backtrace/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, @@ -186,7 +192,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/library/backtrace/src/print.rs b/library/backtrace/src/print.rs index 395328a0a..de8569182 100644 --- a/library/backtrace/src/print.rs +++ b/library/backtrace/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,15 +233,6 @@ 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. diff --git a/library/backtrace/src/symbolize/dbghelp.rs b/library/backtrace/src/symbolize/dbghelp.rs index 181dba731..8c47d58e8 100644 --- a/library/backtrace/src/symbolize/dbghelp.rs +++ b/library/backtrace/src/symbolize/dbghelp.rs @@ -17,7 +17,7 @@ #![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; @@ -78,54 +78,103 @@ pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) 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+. +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( diff --git a/library/backtrace/src/symbolize/gimli.rs b/library/backtrace/src/symbolize/gimli.rs index 7f1c6a528..3b28bf741 100644 --- a/library/backtrace/src/symbolize/gimli.rs +++ b/library/backtrace/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(); diff --git a/library/backtrace/src/symbolize/gimli/elf.rs b/library/backtrace/src/symbolize/gimli/elf.rs index b0eec0762..906a30054 100644 --- a/library/backtrace/src/symbolize/gimli/elf.rs +++ b/library/backtrace/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/library/backtrace/src/symbolize/gimli/libs_aix.rs b/library/backtrace/src/symbolize/gimli/libs_aix.rs new file mode 100644 index 000000000..8cac11d4d --- /dev/null +++ b/library/backtrace/src/symbolize/gimli/libs_aix.rs @@ -0,0 +1,74 @@ +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() as *mut libc::c_char, + (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 as *mut libc::c_char).offset((*current).ldinfo_next as isize) + as *mut libc::ld_info; + } + } + return ret; +} diff --git a/library/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs b/library/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs index 9f0304ce8..518512fff 100644 --- a/library/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs +++ b/library/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs @@ -18,14 +18,18 @@ pub(super) fn native_libraries() -> Vec { } 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() diff --git a/library/backtrace/src/symbolize/gimli/xcoff.rs b/library/backtrace/src/symbolize/gimli/xcoff.rs new file mode 100644 index 000000000..dd308840f --- /dev/null +++ b/library/backtrace/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/library/backtrace/src/windows.rs b/library/backtrace/src/windows.rs index 92c2b2e66..13287f7c3 100644 --- a/library/backtrace/src/windows.rs +++ b/library/backtrace/src/windows.rs @@ -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,22 @@ 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; + } } } else { pub use core::ffi::c_void; @@ -45,6 +64,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; } } @@ -359,6 +381,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 +393,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" { @@ -435,6 +460,33 @@ ffi! { lpme: LPMODULEENTRY32W, ) -> BOOL; } + + #[link(name = "ntdll")] + extern "system" { + pub fn RtlCaptureStackBackTrace( + FramesToSkip: ULONG, + FramesToCapture: ULONG, + BackTrace: *mut PVOID, + BackTraceHash: PULONG, + ) -> USHORT; + } +} + +#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] +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; + } } #[cfg(target_pointer_width = "64")] diff --git a/library/backtrace/tests/accuracy/main.rs b/library/backtrace/tests/accuracy/main.rs index 149203a1b..79b2d3797 100644 --- a/library/backtrace/tests/accuracy/main.rs +++ b/library/backtrace/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"); } @@ -103,7 +105,7 @@ fn verify(filelines: &[Pos]) { 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/library/backtrace/tests/sgx-image-base.rs b/library/backtrace/tests/sgx-image-base.rs new file mode 100644 index 000000000..c29a8b67a --- /dev/null +++ b/library/backtrace/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/library/backtrace/tests/smoke.rs b/library/backtrace/tests/smoke.rs index 683a6f0db..715f567f3 100644 --- a/library/backtrace/tests/smoke.rs +++ b/library/backtrace/tests/smoke.rs @@ -1,6 +1,27 @@ use backtrace::Frame; 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 +41,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 +60,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 +68,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 +76,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 +84,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 +92,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, @@ -150,9 +171,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 +183,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 +199,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}", ); } } -- cgit v1.2.3