summaryrefslogtreecommitdiffstats
path: root/library/backtrace
diff options
context:
space:
mode:
Diffstat (limited to 'library/backtrace')
-rw-r--r--library/backtrace/.github/workflows/main.yml247
-rw-r--r--library/backtrace/Cargo.toml132
-rw-r--r--library/backtrace/LICENSE-APACHE201
-rw-r--r--library/backtrace/LICENSE-MIT25
-rw-r--r--library/backtrace/README.md73
-rw-r--r--library/backtrace/benches/benchmarks.rs92
-rw-r--r--library/backtrace/build.rs41
-rwxr-xr-xlibrary/backtrace/ci/android-ndk.sh23
-rwxr-xr-xlibrary/backtrace/ci/android-sdk.sh65
-rwxr-xr-xlibrary/backtrace/ci/debuglink-docker.sh29
-rwxr-xr-xlibrary/backtrace/ci/debuglink.sh75
-rw-r--r--library/backtrace/ci/docker/aarch64-linux-android/Dockerfile18
-rw-r--r--library/backtrace/ci/docker/aarch64-unknown-linux-gnu/Dockerfile11
-rw-r--r--library/backtrace/ci/docker/arm-linux-androideabi/Dockerfile18
-rw-r--r--library/backtrace/ci/docker/arm-unknown-linux-gnueabihf/Dockerfile10
-rw-r--r--library/backtrace/ci/docker/armv7-linux-androideabi/Dockerfile18
-rw-r--r--library/backtrace/ci/docker/armv7-unknown-linux-gnueabihf/Dockerfile10
-rw-r--r--library/backtrace/ci/docker/i586-unknown-linux-gnu/Dockerfile5
-rw-r--r--library/backtrace/ci/docker/i686-linux-android/Dockerfile18
-rw-r--r--library/backtrace/ci/docker/i686-unknown-linux-gnu/Dockerfile5
-rw-r--r--library/backtrace/ci/docker/powerpc64-unknown-linux-gnu/Dockerfile16
-rw-r--r--library/backtrace/ci/docker/s390x-unknown-linux-gnu/Dockerfile17
-rw-r--r--library/backtrace/ci/docker/x86_64-linux-android/Dockerfile18
-rw-r--r--library/backtrace/ci/docker/x86_64-pc-windows-gnu/Dockerfile10
-rw-r--r--library/backtrace/ci/docker/x86_64-unknown-linux-gnu/Dockerfile6
-rw-r--r--library/backtrace/ci/docker/x86_64-unknown-linux-musl/Dockerfile6
-rwxr-xr-xlibrary/backtrace/ci/run-docker.sh33
-rwxr-xr-xlibrary/backtrace/ci/run.sh6
-rw-r--r--library/backtrace/ci/runtest-android.rs50
-rw-r--r--library/backtrace/crates/as-if-std/Cargo.toml29
-rw-r--r--library/backtrace/crates/as-if-std/build.rs3
-rw-r--r--library/backtrace/crates/as-if-std/src/lib.rs21
-rw-r--r--library/backtrace/crates/cpp_smoke_test/Cargo.toml11
-rw-r--r--library/backtrace/crates/cpp_smoke_test/build.rs14
-rw-r--r--library/backtrace/crates/cpp_smoke_test/cpp/trampoline.cpp14
-rw-r--r--library/backtrace/crates/cpp_smoke_test/src/lib.rs2
-rw-r--r--library/backtrace/crates/cpp_smoke_test/tests/smoke.rs73
-rw-r--r--library/backtrace/crates/debuglink/Cargo.toml7
-rw-r--r--library/backtrace/crates/debuglink/src/main.rs34
-rw-r--r--library/backtrace/crates/dylib-dep/Cargo.toml10
-rw-r--r--library/backtrace/crates/dylib-dep/src/lib.rs14
-rw-r--r--library/backtrace/crates/line-tables-only/Cargo.toml21
-rw-r--r--library/backtrace/crates/line-tables-only/build.rs10
-rw-r--r--library/backtrace/crates/line-tables-only/src/callback.c14
-rw-r--r--library/backtrace/crates/line-tables-only/src/lib.rs57
-rw-r--r--library/backtrace/crates/macos_frames_test/Cargo.toml8
-rw-r--r--library/backtrace/crates/macos_frames_test/src/lib.rs1
-rw-r--r--library/backtrace/crates/macos_frames_test/tests/main.rs30
-rw-r--r--library/backtrace/crates/without_debuginfo/Cargo.toml20
-rw-r--r--library/backtrace/crates/without_debuginfo/src/lib.rs1
-rw-r--r--library/backtrace/crates/without_debuginfo/tests/smoke.rs44
-rw-r--r--library/backtrace/examples/backtrace.rs5
-rw-r--r--library/backtrace/examples/raw.rs52
-rw-r--r--library/backtrace/src/android-api.c4
-rw-r--r--library/backtrace/src/backtrace/dbghelp.rs257
-rw-r--r--library/backtrace/src/backtrace/libunwind.rs267
-rw-r--r--library/backtrace/src/backtrace/miri.rs107
-rw-r--r--library/backtrace/src/backtrace/mod.rs162
-rw-r--r--library/backtrace/src/backtrace/noop.rs28
-rw-r--r--library/backtrace/src/capture.rs555
-rw-r--r--library/backtrace/src/dbghelp.rs351
-rw-r--r--library/backtrace/src/lib.rs193
-rw-r--r--library/backtrace/src/print.rs302
-rw-r--r--library/backtrace/src/print/fuchsia.rs436
-rw-r--r--library/backtrace/src/symbolize/dbghelp.rs218
-rw-r--r--library/backtrace/src/symbolize/gimli.rs462
-rw-r--r--library/backtrace/src/symbolize/gimli/coff.rs108
-rw-r--r--library/backtrace/src/symbolize/gimli/elf.rs423
-rw-r--r--library/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs53
-rw-r--r--library/backtrace/src/symbolize/gimli/libs_haiku.rs48
-rw-r--r--library/backtrace/src/symbolize/gimli/libs_illumos.rs99
-rw-r--r--library/backtrace/src/symbolize/gimli/libs_libnx.rs27
-rw-r--r--library/backtrace/src/symbolize/gimli/libs_macos.rs146
-rw-r--r--library/backtrace/src/symbolize/gimli/libs_windows.rs89
-rw-r--r--library/backtrace/src/symbolize/gimli/macho.rs324
-rw-r--r--library/backtrace/src/symbolize/gimli/mmap_fake.rs25
-rw-r--r--library/backtrace/src/symbolize/gimli/mmap_unix.rs44
-rw-r--r--library/backtrace/src/symbolize/gimli/mmap_windows.rs57
-rw-r--r--library/backtrace/src/symbolize/gimli/stash.rs52
-rw-r--r--library/backtrace/src/symbolize/miri.rs56
-rw-r--r--library/backtrace/src/symbolize/mod.rs485
-rw-r--r--library/backtrace/src/symbolize/noop.rs41
-rw-r--r--library/backtrace/src/types.rs83
-rw-r--r--library/backtrace/src/windows.rs691
-rw-r--r--library/backtrace/tests/accuracy/auxiliary.rs15
-rw-r--r--library/backtrace/tests/accuracy/main.rs117
-rw-r--r--library/backtrace/tests/concurrent-panics.rs78
-rw-r--r--library/backtrace/tests/long_fn_name.rs48
-rw-r--r--library/backtrace/tests/skip_inner_frames.rs44
-rw-r--r--library/backtrace/tests/smoke.rs323
90 files changed, 8591 insertions, 0 deletions
diff --git a/library/backtrace/.github/workflows/main.yml b/library/backtrace/.github/workflows/main.yml
new file mode 100644
index 000000000..5f4bd505b
--- /dev/null
+++ b/library/backtrace/.github/workflows/main.yml
@@ -0,0 +1,247 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ test:
+ name: Test
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ include:
+ - os: ubuntu-latest
+ rust: stable
+ - os: ubuntu-latest
+ rust: beta
+ - os: ubuntu-latest
+ rust: nightly
+ - os: macos-latest
+ rust: stable
+ - os: macos-latest
+ rust: nightly
+ # Note that these are on nightly due to rust-lang/rust#63700 not being
+ # on stable yet
+ - os: windows-latest
+ rust: stable-x86_64-msvc
+ - os: windows-latest
+ rust: stable-i686-msvc
+ - os: windows-latest
+ rust: stable-x86_64-gnu
+ - os: windows-latest
+ rust: stable-i686-gnu
+ steps:
+ - uses: actions/checkout@v1
+ with:
+ submodules: true
+ - name: Install Rust (rustup)
+ run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }}
+ shell: bash
+ - run: echo RUSTFLAGS=-Dwarnings >> $GITHUB_ENV
+ shell: bash
+
+ # full fidelity of backtraces on 32-bit msvc requires frame pointers, so
+ # enable that for our tests
+ - name: Force frame pointers
+ run: echo RUSTFLAGS="-Cforce-frame-pointers $RUSTFLAGS" >> $GITHUB_ENV
+ shell: bash
+ if: matrix.rust == 'stable-i686-msvc'
+
+ - run: cargo build
+ - run: cargo test
+ - run: cargo test --features "serialize-rustc"
+ - run: cargo test --features "serialize-serde"
+ - run: cargo test --features "verify-winapi"
+ - run: cargo test --features "cpp_demangle"
+ - run: cargo test --no-default-features
+ - run: cargo test --no-default-features --features "std"
+ - run: cargo test --manifest-path crates/cpp_smoke_test/Cargo.toml
+ # This test is specifically about packed debuginfo with `*.dSYM` files
+ - run: cargo test --manifest-path crates/macos_frames_test/Cargo.toml
+ env:
+ CARGO_PROFILE_DEV_SPLIT_DEBUGINFO: packed
+ CARGO_PROFILE_TEST_SPLIT_DEBUGINFO: packed
+ - run: cargo test --features gimli-symbolize --manifest-path crates/without_debuginfo/Cargo.toml
+ - run: cargo test --manifest-path crates/line-tables-only/Cargo.toml --features gimli-symbolize
+
+ # Test debuginfo compression still works
+ - run: cargo test
+ if: contains(matrix.os, 'ubuntu')
+ env:
+ RUSTFLAGS: "-C link-arg=-Wl,--compress-debug-sections=zlib-gabi"
+ - run: cargo test
+ if: contains(matrix.os, 'ubuntu')
+ env:
+ RUSTFLAGS: "-C link-arg=-Wl,--compress-debug-sections=zlib-gnu"
+
+ # Test that, on macOS, packed/unpacked debuginfo both work
+ - run: cargo clean && cargo test
+ # Test that, on macOS, packed/unpacked debuginfo both work
+ if: matrix.os == 'macos-latest'
+ env:
+ CARGO_PROFILE_DEV_SPLIT_DEBUGINFO: unpacked
+ CARGO_PROFILE_TEST_SPLIT_DEBUGINFO: unpacked
+ - run: cargo clean && cargo test
+ if: matrix.os == 'macos-latest'
+ env:
+ CARGO_PROFILE_DEV_SPLIT_DEBUGINFO: packed
+ CARGO_PROFILE_TEST_SPLIT_DEBUGINFO: packed
+ # Test that, on macOS, binaries with no UUID work
+ - run: cargo clean && cargo test
+ if: matrix.os == 'macos-latest'
+ env:
+ RUSTFLAGS: "-C link-arg=-Wl,-no_uuid"
+
+ # Test that, on Linux, packed/unpacked debuginfo both work
+ - run: cargo clean && cargo test
+ if: matrix.rust == 'nightly'
+ env:
+ RUSTFLAGS: "-C split-debuginfo=unpacked -Zunstable-options"
+ - run: cargo clean && cargo test
+ if: matrix.rust == 'nightly'
+ env:
+ RUSTFLAGS: "-C split-debuginfo=packed -Zunstable-options"
+
+ # Test that separate debug info works
+ - run: ./ci/debuglink-docker.sh
+ if: contains(matrix.os, 'ubuntu')
+
+ # 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
+ - run: cargo build --manifest-path crates/as-if-std/Cargo.toml --no-default-features
+
+ windows_arm64:
+ name: Windows AArch64
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v1
+ with:
+ submodules: true
+ - name: Install Rust
+ run: rustup update stable --no-self-update && rustup default stable
+ shell: bash
+ - run: echo RUSTFLAGS=-Dwarnings >> $GITHUB_ENV
+ shell: bash
+ - run: rustup target add aarch64-pc-windows-msvc
+ - run: cargo test --no-run --target aarch64-pc-windows-msvc
+ - run: cargo test --no-run --target aarch64-pc-windows-msvc --features verify-winapi
+
+ ios:
+ name: iOS
+ runs-on: macos-latest
+ strategy:
+ matrix:
+ include:
+ - target: aarch64-apple-ios
+ sdk: iphoneos
+ - target: x86_64-apple-ios
+ sdk: iphonesimulator
+ steps:
+ - uses: actions/checkout@v1
+ with:
+ submodules: true
+ - run: rustup target add ${{ matrix.target }}
+ - run: |
+ export RUSTFLAGS=-Dwarnings
+ export SDK_PATH=`xcrun --show-sdk-path --sdk ${{ matrix.sdk }}`
+ export RUSTFLAGS="-C link-arg=-isysroot -C link-arg=$SDK_PATH"
+ cargo test --no-run --target ${{ matrix.target }}
+ name: Build tests
+
+ docker:
+ name: Docker
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ target:
+ - aarch64-unknown-linux-gnu
+ - arm-unknown-linux-gnueabihf
+ - armv7-unknown-linux-gnueabihf
+ - i586-unknown-linux-gnu
+ - i686-unknown-linux-gnu
+ - powerpc64-unknown-linux-gnu
+ - s390x-unknown-linux-gnu
+ - x86_64-pc-windows-gnu
+ - x86_64-unknown-linux-gnu
+ - x86_64-unknown-linux-musl
+ - arm-linux-androideabi
+ - armv7-linux-androideabi
+ - aarch64-linux-android
+ - i686-linux-android
+ - x86_64-linux-android
+ steps:
+ - uses: actions/checkout@v1
+ with:
+ submodules: true
+ - name: Install Rust
+ run: rustup update stable && rustup default stable
+ - run: rustup target add ${{ matrix.target }}
+ - run: cargo generate-lockfile
+ - run: echo RUSTFLAGS=-Dwarnings >> $GITHUB_ENV
+ shell: bash
+ - run: ./ci/run-docker.sh ${{ matrix.target }}
+
+ rustfmt:
+ name: Rustfmt
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+ with:
+ submodules: true
+ - name: Install Rust
+ run: rustup update stable && rustup default stable && rustup component add rustfmt
+ - run: cargo fmt --all -- --check
+
+ build:
+ name: Build Targets
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ target:
+ - wasm32-unknown-unknown
+ - wasm32-wasi
+ - x86_64-fuchsia
+ - x86_64-fortanix-unknown-sgx
+ - x86_64-unknown-illumos
+ steps:
+ - uses: actions/checkout@v1
+ with:
+ submodules: true
+ - name: Install Rust
+ run: rustup update nightly && rustup default nightly
+ - run: rustup target add ${{ matrix.target }}
+ - run: echo RUSTFLAGS=-Dwarnings >> $GITHUB_ENV
+ shell: bash
+ - run: cargo build --target ${{ matrix.target }}
+ - run: cargo build --manifest-path crates/as-if-std/Cargo.toml --target ${{ matrix.target }}
+
+ msrv:
+ name: MSRV
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+ with:
+ submodules: true
+ - name: Install Rust
+ run: rustup update 1.42.0 && rustup default 1.42.0
+ - run: cargo build
+
+ miri:
+ name: Miri
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+ with:
+ submodules: true
+ - name: Install Miri
+ run: |
+ rustup toolchain install nightly --component miri
+ rustup override set nightly
+ cargo miri setup
+ - run: MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-strict-provenance" cargo miri test
diff --git a/library/backtrace/Cargo.toml b/library/backtrace/Cargo.toml
new file mode 100644
index 000000000..2881c8e8f
--- /dev/null
+++ b/library/backtrace/Cargo.toml
@@ -0,0 +1,132 @@
+[package]
+name = "backtrace"
+version = "0.3.65"
+authors = ["The Rust Project Developers"]
+build = "build.rs"
+license = "MIT/Apache-2.0"
+readme = "README.md"
+repository = "https://github.com/rust-lang/backtrace-rs"
+homepage = "https://github.com/rust-lang/backtrace-rs"
+documentation = "https://docs.rs/backtrace"
+description = """
+A library to acquire a stack trace (backtrace) at runtime in a Rust program.
+"""
+autoexamples = true
+autotests = true
+edition = "2018"
+
+[workspace]
+members = ['crates/cpp_smoke_test', 'crates/as-if-std']
+exclude = [
+ 'crates/without_debuginfo',
+ 'crates/macos_frames_test',
+ 'crates/line-tables-only',
+ 'crates/debuglink',
+]
+
+[dependencies]
+cfg-if = "1.0"
+rustc-demangle = "0.1.4"
+libc = { version = "0.2.94", default-features = false }
+
+# Optionally enable the ability to serialize a `Backtrace`, controlled through
+# the `serialize-*` features below.
+serde = { version = "1.0", optional = true, features = ['derive'] }
+rustc-serialize = { version = "0.3", optional = true }
+
+# Optionally demangle C++ frames' symbols in backtraces.
+cpp_demangle = { default-features = false, version = "0.3.0", optional = true }
+
+
+# Optional dependencies enabled through the `gimli-symbolize` feature, do not
+# use these features directly.
+addr2line = { version = "0.17.0", default-features = false }
+miniz_oxide = { version = "0.5.0", default-features = false }
+
+[dependencies.object]
+version = "0.28.0"
+default-features = false
+features = ['read_core', 'elf', 'macho', 'pe', 'unaligned', 'archive']
+
+[target.'cfg(windows)'.dependencies]
+winapi = { version = "0.3.9", optional = true }
+
+[build-dependencies]
+# Only needed for Android, but cannot be target dependent
+# https://github.com/rust-lang/cargo/issues/4932
+cc = "1.0.67"
+
+[dev-dependencies]
+dylib-dep = { path = "crates/dylib-dep" }
+libloading = "0.7"
+
+[features]
+# By default libstd support and gimli-symbolize is used to symbolize addresses.
+default = ["std"]
+
+# Include std support. This enables types like `Backtrace`.
+std = []
+
+#=======================================
+# Methods of serialization
+#
+# Various features used for enabling rustc-serialize or syntex codegen.
+serialize-rustc = ["rustc-serialize"]
+serialize-serde = ["serde"]
+
+#=======================================
+# Deprecated/internal features
+#
+# Only here for backwards compatibility purposes or for internal testing
+# purposes. New code should use none of these features.
+coresymbolication = []
+dbghelp = []
+dladdr = []
+gimli-symbolize = []
+kernel32 = []
+libbacktrace = []
+libunwind = []
+unix-backtrace = []
+verify-winapi = [
+ 'winapi/dbghelp',
+ 'winapi/handleapi',
+ 'winapi/libloaderapi',
+ 'winapi/memoryapi',
+ 'winapi/minwindef',
+ 'winapi/processthreadsapi',
+ 'winapi/synchapi',
+ 'winapi/tlhelp32',
+ 'winapi/winbase',
+ 'winapi/winnt',
+]
+
+[[example]]
+name = "backtrace"
+required-features = ["std"]
+
+[[example]]
+name = "raw"
+required-features = ["std"]
+
+[[test]]
+name = "skip_inner_frames"
+required-features = ["std"]
+
+[[test]]
+name = "long_fn_name"
+required-features = ["std"]
+
+[[test]]
+name = "smoke"
+required-features = ["std"]
+edition = '2018'
+
+[[test]]
+name = "accuracy"
+required-features = ["std"]
+edition = '2018'
+
+[[test]]
+name = "concurrent-panics"
+required-features = ["std"]
+harness = false
diff --git a/library/backtrace/LICENSE-APACHE b/library/backtrace/LICENSE-APACHE
new file mode 100644
index 000000000..16fe87b06
--- /dev/null
+++ b/library/backtrace/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/library/backtrace/LICENSE-MIT b/library/backtrace/LICENSE-MIT
new file mode 100644
index 000000000..39e0ed660
--- /dev/null
+++ b/library/backtrace/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2014 Alex Crichton
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/library/backtrace/README.md b/library/backtrace/README.md
new file mode 100644
index 000000000..cd80a697c
--- /dev/null
+++ b/library/backtrace/README.md
@@ -0,0 +1,73 @@
+# backtrace-rs
+
+[Documentation](https://docs.rs/backtrace)
+
+A library for acquiring backtraces at runtime for Rust. This library aims to
+enhance the support of the standard library by providing a programmatic
+interface to work with, but it also supports simply easily printing the current
+backtrace like libstd's panics.
+
+## Install
+
+```toml
+[dependencies]
+backtrace = "0.3"
+```
+
+## Usage
+
+To simply capture a backtrace and defer dealing with it until a later time,
+you can use the top-level `Backtrace` type.
+
+```rust
+use backtrace::Backtrace;
+
+fn main() {
+ let bt = Backtrace::new();
+
+ // do_some_work();
+
+ println!("{:?}", bt);
+}
+```
+
+If, however, you'd like more raw access to the actual tracing functionality, you
+can use the `trace` and `resolve` functions directly.
+
+```rust
+fn main() {
+ backtrace::trace(|frame| {
+ let ip = frame.ip();
+ let symbol_address = frame.symbol_address();
+
+ // Resolve this instruction pointer to a symbol name
+ backtrace::resolve_frame(frame, |symbol| {
+ if let Some(name) = symbol.name() {
+ // ...
+ }
+ if let Some(filename) = symbol.filename() {
+ // ...
+ }
+ });
+
+ true // keep going to the next frame
+ });
+}
+```
+
+# License
+
+This project is licensed under either of
+
+ * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
+ https://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or
+ https://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in backtrace-rs by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
diff --git a/library/backtrace/benches/benchmarks.rs b/library/backtrace/benches/benchmarks.rs
new file mode 100644
index 000000000..e14e733b8
--- /dev/null
+++ b/library/backtrace/benches/benchmarks.rs
@@ -0,0 +1,92 @@
+#![feature(test)]
+
+extern crate test;
+
+#[cfg(feature = "std")]
+use backtrace::Backtrace;
+
+#[bench]
+#[cfg(feature = "std")]
+fn trace(b: &mut test::Bencher) {
+ #[inline(never)]
+ fn the_function() {
+ backtrace::trace(|frame| {
+ let ip = frame.ip();
+ test::black_box(ip);
+ true
+ });
+ }
+ b.iter(the_function);
+}
+
+#[bench]
+#[cfg(feature = "std")]
+fn trace_and_resolve_callback(b: &mut test::Bencher) {
+ #[inline(never)]
+ fn the_function() {
+ backtrace::trace(|frame| {
+ backtrace::resolve(frame.ip(), |symbol| {
+ let addr = symbol.addr();
+ test::black_box(addr);
+ });
+ true
+ });
+ }
+ b.iter(the_function);
+}
+
+#[bench]
+#[cfg(feature = "std")]
+fn trace_and_resolve_separate(b: &mut test::Bencher) {
+ #[inline(never)]
+ fn the_function(frames: &mut Vec<*mut std::ffi::c_void>) {
+ backtrace::trace(|frame| {
+ frames.push(frame.ip());
+ true
+ });
+ frames.iter().for_each(|frame_ip| {
+ backtrace::resolve(*frame_ip, |symbol| {
+ test::black_box(symbol);
+ });
+ });
+ }
+ let mut frames = Vec::with_capacity(1024);
+ b.iter(|| {
+ the_function(&mut frames);
+ frames.clear();
+ });
+}
+
+#[bench]
+#[cfg(feature = "std")]
+fn new_unresolved(b: &mut test::Bencher) {
+ #[inline(never)]
+ fn the_function() {
+ let bt = Backtrace::new_unresolved();
+ test::black_box(bt);
+ }
+ b.iter(the_function);
+}
+
+#[bench]
+#[cfg(feature = "std")]
+fn new(b: &mut test::Bencher) {
+ #[inline(never)]
+ fn the_function() {
+ let bt = Backtrace::new();
+ test::black_box(bt);
+ }
+ b.iter(the_function);
+}
+
+#[bench]
+#[cfg(feature = "std")]
+fn new_unresolved_and_resolve_separate(b: &mut test::Bencher) {
+ #[inline(never)]
+ fn the_function() {
+ let mut bt = Backtrace::new_unresolved();
+ bt.resolve();
+ test::black_box(bt);
+ }
+ b.iter(the_function);
+}
diff --git a/library/backtrace/build.rs b/library/backtrace/build.rs
new file mode 100644
index 000000000..812fbb1fe
--- /dev/null
+++ b/library/backtrace/build.rs
@@ -0,0 +1,41 @@
+extern crate cc;
+
+use std::env;
+
+fn main() {
+ match env::var("CARGO_CFG_TARGET_OS").unwrap_or_default().as_str() {
+ "android" => build_android(),
+ _ => {}
+ }
+}
+
+fn build_android() {
+ let expansion = match cc::Build::new().file("src/android-api.c").try_expand() {
+ Ok(result) => result,
+ Err(e) => {
+ println!("failed to run C compiler: {}", e);
+ return;
+ }
+ };
+ let expansion = match std::str::from_utf8(&expansion) {
+ Ok(s) => s,
+ Err(_) => return,
+ };
+ println!("expanded android version detection:\n{}", expansion);
+ let marker = "APIVERSION";
+ let i = match expansion.find(marker) {
+ Some(i) => i,
+ None => return,
+ };
+ let version = match expansion[i + marker.len() + 1..].split_whitespace().next() {
+ Some(s) => s,
+ None => return,
+ };
+ let version = match version.parse::<u32>() {
+ Ok(n) => n,
+ Err(_) => return,
+ };
+ if version >= 21 {
+ println!("cargo:rustc-cfg=feature=\"dl_iterate_phdr\"");
+ }
+}
diff --git a/library/backtrace/ci/android-ndk.sh b/library/backtrace/ci/android-ndk.sh
new file mode 100755
index 000000000..b5df62b6f
--- /dev/null
+++ b/library/backtrace/ci/android-ndk.sh
@@ -0,0 +1,23 @@
+set -ex
+
+ANDROID_ARCH=$1
+ANDROID_SDK_VERSION=4333796
+
+mkdir /tmp/android
+cd /tmp/android
+
+curl -o android-sdk.zip \
+ "https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_VERSION}.zip"
+unzip -q android-sdk.zip
+
+yes | ./tools/bin/sdkmanager --licenses > /dev/null
+./tools/bin/sdkmanager ndk-bundle > /dev/null
+
+./ndk-bundle/build/tools/make_standalone_toolchain.py \
+ --arch $ANDROID_ARCH \
+ --stl=libc++ \
+ --api 21 \
+ --install-dir /android-toolchain
+
+cd /tmp
+rm -rf android
diff --git a/library/backtrace/ci/android-sdk.sh b/library/backtrace/ci/android-sdk.sh
new file mode 100755
index 000000000..7fde9a97f
--- /dev/null
+++ b/library/backtrace/ci/android-sdk.sh
@@ -0,0 +1,65 @@
+#!/usr/bin/env sh
+
+set -ex
+
+# Prep the SDK and emulator
+#
+# Note that the update process requires that we accept a bunch of licenses, and
+# we can't just pipe `yes` into it for some reason, so we take the same strategy
+# located in https://github.com/appunite/docker by just wrapping it in a script
+# which apparently magically accepts the licenses.
+
+SDK=4333796
+mkdir sdk
+curl --retry 20 https://dl.google.com/android/repository/sdk-tools-linux-${SDK}.zip -O
+unzip -q -d sdk sdk-tools-linux-${SDK}.zip
+
+case "$1" in
+ arm | armv7)
+ api=24
+ image="system-images;android-${api};google_apis;armeabi-v7a"
+ ;;
+ aarch64)
+ api=24
+ image="system-images;android-${api};google_apis;arm64-v8a"
+ ;;
+ i686)
+ api=28
+ image="system-images;android-${api};default;x86"
+ ;;
+ x86_64)
+ api=28
+ image="system-images;android-${api};default;x86_64"
+ ;;
+ *)
+ echo "invalid arch: $1"
+ exit 1
+ ;;
+esac;
+
+# Try to fix warning about missing file.
+# See https://askubuntu.com/a/1078784
+mkdir -p /root/.android/
+echo '### User Sources for Android SDK Manager' >> /root/.android/repositories.cfg
+echo '#Fri Nov 03 10:11:27 CET 2017 count=0' >> /root/.android/repositories.cfg
+
+# Print all available packages
+# yes | ./sdk/tools/bin/sdkmanager --list --verbose
+
+# --no_https avoids
+# javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found
+#
+# | grep -v = || true removes the progress bar output from the sdkmanager
+# which produces an insane amount of output.
+yes | ./sdk/tools/bin/sdkmanager --licenses --no_https | grep -v = || true
+yes | ./sdk/tools/bin/sdkmanager --no_https \
+ "emulator" \
+ "platform-tools" \
+ "platforms;android-${api}" \
+ "${image}" | grep -v = || true
+
+echo "no" |
+ ./sdk/tools/bin/avdmanager create avd \
+ --name "${1}" \
+ --package "${image}" | grep -v = || true
+
diff --git a/library/backtrace/ci/debuglink-docker.sh b/library/backtrace/ci/debuglink-docker.sh
new file mode 100755
index 000000000..acb19e98b
--- /dev/null
+++ b/library/backtrace/ci/debuglink-docker.sh
@@ -0,0 +1,29 @@
+# Small script to run debuglink tests inside a docker image.
+# Creates a writable mount on /usr/lib/debug.
+
+set -ex
+
+run() {
+ cargo generate-lockfile --manifest-path crates/debuglink/Cargo.toml
+ mkdir -p target crates/debuglink/target debug
+ docker build -t backtrace -f ci/docker/$1/Dockerfile ci
+ docker run \
+ --user `id -u`:`id -g` \
+ --rm \
+ --init \
+ --volume $(dirname $(dirname `which cargo`)):/cargo \
+ --env CARGO_HOME=/cargo \
+ --volume `rustc --print sysroot`:/rust:ro \
+ --env TARGET=$1 \
+ --volume `pwd`:/checkout:ro \
+ --volume `pwd`/target:/checkout/crates/debuglink/target \
+ --workdir /checkout \
+ --volume `pwd`/debug:/usr/lib/debug \
+ --privileged \
+ --env RUSTFLAGS \
+ backtrace \
+ bash \
+ -c 'PATH=$PATH:/rust/bin exec ci/debuglink.sh'
+}
+
+run x86_64-unknown-linux-gnu
diff --git a/library/backtrace/ci/debuglink.sh b/library/backtrace/ci/debuglink.sh
new file mode 100755
index 000000000..b2da2013d
--- /dev/null
+++ b/library/backtrace/ci/debuglink.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+
+# Debuglink tests.
+# We build crates/debuglink and then move its debuginfo around
+# and test that it can still find the debuginfo.
+
+set -ex
+
+cratedir=`pwd`/crates/debuglink
+exefile=crates/debuglink/target/debug/debuglink
+
+# Baseline; no separate debug
+cargo build --manifest-path crates/debuglink/Cargo.toml
+$exefile $cratedir
+
+# Separate debug in same dir
+debugfile1=`dirname $exefile`/debuglink.debug
+objcopy --only-keep-debug $exefile $debugfile1
+strip -g $exefile
+(cd `dirname $exefile` && objcopy --add-gnu-debuglink=debuglink.debug debuglink)
+$exefile $cratedir
+
+# Separate debug in .debug subdir
+debugfile2=`dirname $exefile`/.debug/debuglink.debug
+mkdir -p `dirname $debugfile2`
+mv $debugfile1 $debugfile2
+$exefile $cratedir
+
+# Separate debug in /usr/lib/debug subdir
+debugfile3="/usr/lib/debug/$cratedir/target/debug/debuglink.debug"
+mkdir -p `dirname $debugfile3`
+mv $debugfile2 $debugfile3
+$exefile $cratedir
+
+# Separate debug in /usr/lib/debug/.build-id subdir
+id=`readelf -n $exefile | grep '^ Build ID: [0-9a-f]' | cut -b 15-`
+idfile="/usr/lib/debug/.build-id/${id:0:2}/${id:2}.debug"
+mkdir -p `dirname $idfile`
+mv $debugfile3 $idfile
+$exefile $cratedir
+
+# Replace idfile with a symlink (this is the usual arrangement)
+mv $idfile $debugfile3
+ln -s $debugfile3 $idfile
+$exefile $cratedir
+
+# Supplementary object file using relative path
+dwzfile="/usr/lib/debug/.dwz/debuglink.debug"
+mkdir -p `dirname $dwzfile`
+cp $debugfile3 $debugfile3.copy
+dwz -m $dwzfile -rh $debugfile3 $debugfile3.copy
+rm $debugfile3.copy
+$exefile $cratedir
+
+# Supplementary object file using build ID
+dwzid=`readelf -n $dwzfile | grep '^ Build ID: [0-9a-f]' | cut -b 15-`
+dwzidfile="/usr/lib/debug/.build-id/${dwzid:0:2}/${dwzid:2}.debug"
+mkdir -p `dirname $dwzidfile`
+mv $dwzfile $dwzidfile
+$exefile $cratedir
+mv $dwzidfile $dwzfile
+
+# Missing debug should fail
+mv $debugfile3 $debugfile3.tmp
+! $exefile $cratedir
+mv $debugfile3.tmp $debugfile3
+
+# Missing dwz should fail
+mv $dwzfile $dwzfile.tmp
+! $exefile $cratedir
+mv $dwzfile.tmp $dwzfile
+
+# Cleanup
+rm $idfile $debugfile3 $dwzfile
+echo Success
diff --git a/library/backtrace/ci/docker/aarch64-linux-android/Dockerfile b/library/backtrace/ci/docker/aarch64-linux-android/Dockerfile
new file mode 100644
index 000000000..c5655ed5e
--- /dev/null
+++ b/library/backtrace/ci/docker/aarch64-linux-android/Dockerfile
@@ -0,0 +1,18 @@
+FROM ubuntu:20.04
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ curl \
+ ca-certificates \
+ unzip \
+ openjdk-8-jre \
+ python \
+ gcc \
+ libc6-dev
+
+COPY android-ndk.sh /
+RUN /android-ndk.sh arm64
+ENV PATH=$PATH:/android-toolchain/bin
+
+# TODO: run tests in an emulator eventually
+ENV CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android-gcc \
+ CARGO_TARGET_AARCH64_LINUX_ANDROID_RUNNER=echo
diff --git a/library/backtrace/ci/docker/aarch64-unknown-linux-gnu/Dockerfile b/library/backtrace/ci/docker/aarch64-unknown-linux-gnu/Dockerfile
new file mode 100644
index 000000000..871b353c0
--- /dev/null
+++ b/library/backtrace/ci/docker/aarch64-unknown-linux-gnu/Dockerfile
@@ -0,0 +1,11 @@
+FROM ubuntu:20.04
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ gcc \
+ ca-certificates \
+ libc6-dev \
+ gcc-aarch64-linux-gnu \
+ libc6-dev-arm64-cross \
+ qemu-user
+
+ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
+ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER="qemu-aarch64 -L /usr/aarch64-linux-gnu"
diff --git a/library/backtrace/ci/docker/arm-linux-androideabi/Dockerfile b/library/backtrace/ci/docker/arm-linux-androideabi/Dockerfile
new file mode 100644
index 000000000..446a64cc0
--- /dev/null
+++ b/library/backtrace/ci/docker/arm-linux-androideabi/Dockerfile
@@ -0,0 +1,18 @@
+FROM ubuntu:20.04
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ curl \
+ ca-certificates \
+ unzip \
+ openjdk-8-jre \
+ python \
+ gcc \
+ libc6-dev
+
+COPY android-ndk.sh /
+RUN /android-ndk.sh arm
+ENV PATH=$PATH:/android-toolchain/bin
+
+# TODO: run tests in an emulator eventually
+ENV CARGO_TARGET_ARM_LINUX_ANDROIDEABI_LINKER=arm-linux-androideabi-gcc \
+ CARGO_TARGET_ARM_LINUX_ANDROIDEABI_RUNNER=echo
diff --git a/library/backtrace/ci/docker/arm-unknown-linux-gnueabihf/Dockerfile b/library/backtrace/ci/docker/arm-unknown-linux-gnueabihf/Dockerfile
new file mode 100644
index 000000000..24665972c
--- /dev/null
+++ b/library/backtrace/ci/docker/arm-unknown-linux-gnueabihf/Dockerfile
@@ -0,0 +1,10 @@
+FROM ubuntu:20.04
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ gcc \
+ ca-certificates \
+ libc6-dev \
+ gcc-arm-linux-gnueabihf \
+ libc6-dev-armhf-cross \
+ qemu-user
+ENV CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc \
+ CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_RUNNER="qemu-arm -L /usr/arm-linux-gnueabihf"
diff --git a/library/backtrace/ci/docker/armv7-linux-androideabi/Dockerfile b/library/backtrace/ci/docker/armv7-linux-androideabi/Dockerfile
new file mode 100644
index 000000000..539bbc494
--- /dev/null
+++ b/library/backtrace/ci/docker/armv7-linux-androideabi/Dockerfile
@@ -0,0 +1,18 @@
+FROM ubuntu:20.04
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ curl \
+ ca-certificates \
+ unzip \
+ openjdk-8-jre \
+ python \
+ gcc \
+ libc6-dev
+
+COPY android-ndk.sh /
+RUN /android-ndk.sh arm
+ENV PATH=$PATH:/android-toolchain/bin
+
+# TODO: run tests in an emulator eventually
+ENV CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=arm-linux-androideabi-gcc \
+ CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_RUNNER=echo
diff --git a/library/backtrace/ci/docker/armv7-unknown-linux-gnueabihf/Dockerfile b/library/backtrace/ci/docker/armv7-unknown-linux-gnueabihf/Dockerfile
new file mode 100644
index 000000000..6f7d0fd36
--- /dev/null
+++ b/library/backtrace/ci/docker/armv7-unknown-linux-gnueabihf/Dockerfile
@@ -0,0 +1,10 @@
+FROM ubuntu:20.04
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ gcc \
+ ca-certificates \
+ libc6-dev \
+ gcc-arm-linux-gnueabihf \
+ libc6-dev-armhf-cross \
+ qemu-user
+ENV CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc \
+ CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_RUNNER="qemu-arm -L /usr/arm-linux-gnueabihf"
diff --git a/library/backtrace/ci/docker/i586-unknown-linux-gnu/Dockerfile b/library/backtrace/ci/docker/i586-unknown-linux-gnu/Dockerfile
new file mode 100644
index 000000000..316a233e3
--- /dev/null
+++ b/library/backtrace/ci/docker/i586-unknown-linux-gnu/Dockerfile
@@ -0,0 +1,5 @@
+FROM ubuntu:20.04
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ gcc-multilib \
+ libc6-dev \
+ ca-certificates
diff --git a/library/backtrace/ci/docker/i686-linux-android/Dockerfile b/library/backtrace/ci/docker/i686-linux-android/Dockerfile
new file mode 100644
index 000000000..83ccb2948
--- /dev/null
+++ b/library/backtrace/ci/docker/i686-linux-android/Dockerfile
@@ -0,0 +1,18 @@
+FROM ubuntu:20.04
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ curl \
+ ca-certificates \
+ unzip \
+ openjdk-8-jre \
+ python \
+ gcc \
+ libc6-dev
+
+COPY android-ndk.sh /
+RUN /android-ndk.sh x86
+ENV PATH=$PATH:/android-toolchain/bin
+
+# TODO: run tests in an emulator eventually
+ENV CARGO_TARGET_I686_LINUX_ANDROID_LINKER=i686-linux-android-gcc \
+ CARGO_TARGET_I686_LINUX_ANDROID_RUNNER=echo
diff --git a/library/backtrace/ci/docker/i686-unknown-linux-gnu/Dockerfile b/library/backtrace/ci/docker/i686-unknown-linux-gnu/Dockerfile
new file mode 100644
index 000000000..316a233e3
--- /dev/null
+++ b/library/backtrace/ci/docker/i686-unknown-linux-gnu/Dockerfile
@@ -0,0 +1,5 @@
+FROM ubuntu:20.04
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ gcc-multilib \
+ libc6-dev \
+ ca-certificates
diff --git a/library/backtrace/ci/docker/powerpc64-unknown-linux-gnu/Dockerfile b/library/backtrace/ci/docker/powerpc64-unknown-linux-gnu/Dockerfile
new file mode 100644
index 000000000..7ca5a64bf
--- /dev/null
+++ b/library/backtrace/ci/docker/powerpc64-unknown-linux-gnu/Dockerfile
@@ -0,0 +1,16 @@
+FROM ubuntu:20.04
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ gcc \
+ ca-certificates \
+ libc6-dev \
+ gcc-powerpc64-linux-gnu \
+ libc6-dev-ppc64-cross \
+ qemu-user \
+ qemu-system-ppc
+
+ENV CARGO_TARGET_POWERPC64_UNKNOWN_LINUX_GNU_LINKER=powerpc64-linux-gnu-gcc \
+ # TODO: should actually run these tests
+ #CARGO_TARGET_POWERPC64_UNKNOWN_LINUX_GNU_RUNNER="qemu-ppc64 -L /usr/powerpc64-linux-gnu" \
+ CARGO_TARGET_POWERPC64_UNKNOWN_LINUX_GNU_RUNNER=echo \
+ CC=powerpc64-linux-gnu-gcc
diff --git a/library/backtrace/ci/docker/s390x-unknown-linux-gnu/Dockerfile b/library/backtrace/ci/docker/s390x-unknown-linux-gnu/Dockerfile
new file mode 100644
index 000000000..7c19dcbb4
--- /dev/null
+++ b/library/backtrace/ci/docker/s390x-unknown-linux-gnu/Dockerfile
@@ -0,0 +1,17 @@
+FROM ubuntu:20.04
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ gcc \
+ ca-certificates \
+ libc6-dev \
+ gcc-s390x-linux-gnu \
+ libc6-dev-s390x-cross \
+ qemu-user \
+ # There seems to be a bug in processing mixed-architecture
+ # ld.so.cache files that causes crashes in some cases. Work
+ # around this by simply deleting the cache for now.
+ && rm /etc/ld.so.cache
+
+ENV CARGO_TARGET_S390X_UNKNOWN_LINUX_GNU_LINKER=s390x-linux-gnu-gcc \
+ CARGO_TARGET_S390X_UNKNOWN_LINUX_GNU_RUNNER="qemu-s390x -L /usr/s390x-linux-gnu" \
+ CC=s390x-linux-gnu-gcc
diff --git a/library/backtrace/ci/docker/x86_64-linux-android/Dockerfile b/library/backtrace/ci/docker/x86_64-linux-android/Dockerfile
new file mode 100644
index 000000000..88a22ce6c
--- /dev/null
+++ b/library/backtrace/ci/docker/x86_64-linux-android/Dockerfile
@@ -0,0 +1,18 @@
+FROM ubuntu:20.04
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ curl \
+ ca-certificates \
+ unzip \
+ openjdk-8-jre \
+ python \
+ gcc \
+ libc6-dev
+
+COPY android-ndk.sh /
+RUN /android-ndk.sh x86_64
+ENV PATH=$PATH:/android-toolchain/bin
+
+# TODO: run tests in an emulator eventually
+ENV CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER=x86_64-linux-android-gcc \
+ CARGO_TARGET_X86_64_LINUX_ANDROID_RUNNER=echo
diff --git a/library/backtrace/ci/docker/x86_64-pc-windows-gnu/Dockerfile b/library/backtrace/ci/docker/x86_64-pc-windows-gnu/Dockerfile
new file mode 100644
index 000000000..a8e859e67
--- /dev/null
+++ b/library/backtrace/ci/docker/x86_64-pc-windows-gnu/Dockerfile
@@ -0,0 +1,10 @@
+FROM ubuntu:20.04
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ gcc \
+ libc6-dev \
+ ca-certificates \
+ gcc-mingw-w64-x86-64
+
+# No need to run tests, we're just testing that it compiles
+ENV CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUNNER=echo \
+ CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=x86_64-w64-mingw32-gcc
diff --git a/library/backtrace/ci/docker/x86_64-unknown-linux-gnu/Dockerfile b/library/backtrace/ci/docker/x86_64-unknown-linux-gnu/Dockerfile
new file mode 100644
index 000000000..551ab1378
--- /dev/null
+++ b/library/backtrace/ci/docker/x86_64-unknown-linux-gnu/Dockerfile
@@ -0,0 +1,6 @@
+FROM ubuntu:20.04
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ gcc \
+ libc6-dev \
+ ca-certificates \
+ dwz
diff --git a/library/backtrace/ci/docker/x86_64-unknown-linux-musl/Dockerfile b/library/backtrace/ci/docker/x86_64-unknown-linux-musl/Dockerfile
new file mode 100644
index 000000000..e77e41f5b
--- /dev/null
+++ b/library/backtrace/ci/docker/x86_64-unknown-linux-musl/Dockerfile
@@ -0,0 +1,6 @@
+FROM ubuntu:20.04
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ gcc \
+ libc6-dev \
+ ca-certificates \
+ musl-tools
diff --git a/library/backtrace/ci/run-docker.sh b/library/backtrace/ci/run-docker.sh
new file mode 100755
index 000000000..8aa6d84a4
--- /dev/null
+++ b/library/backtrace/ci/run-docker.sh
@@ -0,0 +1,33 @@
+# Small script to run tests for a target (or all targets) inside all the
+# respective docker images.
+
+set -ex
+
+run() {
+ docker build -t backtrace -f ci/docker/$1/Dockerfile ci
+ mkdir -p target
+ docker run \
+ --user `id -u`:`id -g` \
+ --rm \
+ --init \
+ --volume $(dirname $(dirname `which cargo`)):/cargo \
+ --env CARGO_HOME=/cargo \
+ --volume `rustc --print sysroot`:/rust:ro \
+ --env TARGET=$1 \
+ --volume `pwd`:/checkout:ro \
+ --volume `pwd`/target:/checkout/target \
+ --workdir /checkout \
+ --privileged \
+ --env RUSTFLAGS \
+ backtrace \
+ bash \
+ -c 'PATH=$PATH:/rust/bin exec ci/run.sh'
+}
+
+if [ -z "$1" ]; then
+ for d in `ls ci/docker/`; do
+ run $d
+ done
+else
+ run $1
+fi
diff --git a/library/backtrace/ci/run.sh b/library/backtrace/ci/run.sh
new file mode 100755
index 000000000..166b387e4
--- /dev/null
+++ b/library/backtrace/ci/run.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+set -ex
+
+cargo test --target $TARGET
+cargo build --target $TARGET --manifest-path crates/as-if-std/Cargo.toml
diff --git a/library/backtrace/ci/runtest-android.rs b/library/backtrace/ci/runtest-android.rs
new file mode 100644
index 000000000..dc70121dc
--- /dev/null
+++ b/library/backtrace/ci/runtest-android.rs
@@ -0,0 +1,50 @@
+use std::env;
+use std::process::Command;
+use std::path::{Path, PathBuf};
+
+fn main() {
+ let args = env::args_os()
+ .skip(1)
+ .filter(|arg| arg != "--quiet")
+ .collect::<Vec<_>>();
+ assert_eq!(args.len(), 1);
+ let test = PathBuf::from(&args[0]);
+ let dst = Path::new("/data/local/tmp").join(test.file_name().unwrap());
+
+ println!("waiting for device to come online...");
+ let status = Command::new("adb")
+ .arg("wait-for-device")
+ .status()
+ .expect("failed to run: adb wait-for-device");
+ assert!(status.success());
+
+ println!("pushing executable...");
+ let status = Command::new("adb")
+ .arg("push")
+ .arg(&test)
+ .arg(&dst)
+ .status()
+ .expect("failed to run: adb pushr");
+ assert!(status.success());
+
+ println!("executing tests...");
+ let output = Command::new("adb")
+ .arg("shell")
+ .arg(&dst)
+ .output()
+ .expect("failed to run: adb shell");
+ assert!(status.success());
+
+ println!("status: {}\nstdout ---\n{}\nstderr ---\n{}",
+ output.status,
+ String::from_utf8_lossy(&output.stdout),
+ String::from_utf8_lossy(&output.stderr));
+
+ let stdout = String::from_utf8_lossy(&output.stdout);
+ stdout.lines().find(|l|
+ (l.starts_with("PASSED ") && l.contains(" tests")) ||
+ l.starts_with("test result: ok")
+ ).unwrap_or_else(|| {
+ panic!("failed to find successful test run");
+ });
+}
diff --git a/library/backtrace/crates/as-if-std/Cargo.toml b/library/backtrace/crates/as-if-std/Cargo.toml
new file mode 100644
index 000000000..c763227f2
--- /dev/null
+++ b/library/backtrace/crates/as-if-std/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+name = "as-if-std"
+version = "0.1.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+edition = "2018"
+publish = false
+
+[lib]
+test = false
+doc = false
+doctest = false
+bench = false
+
+[dependencies]
+cfg-if = "1.0"
+rustc-demangle = "0.1.4"
+libc = { version = "0.2.45", default-features = false }
+addr2line = { version = "0.16.0", default-features = false, optional = true }
+miniz_oxide = { version = "0.4.0", default-features = false }
+
+[dependencies.object]
+version = "0.28"
+default-features = false
+optional = true
+features = ['read_core', 'elf', 'macho', 'pe', 'unaligned', 'archive']
+
+[features]
+default = ['backtrace']
+backtrace = ['addr2line', 'object']
diff --git a/library/backtrace/crates/as-if-std/build.rs b/library/backtrace/crates/as-if-std/build.rs
new file mode 100644
index 000000000..7018b1017
--- /dev/null
+++ b/library/backtrace/crates/as-if-std/build.rs
@@ -0,0 +1,3 @@
+fn main() {
+ println!("cargo:rustc-cfg=backtrace_in_libstd");
+}
diff --git a/library/backtrace/crates/as-if-std/src/lib.rs b/library/backtrace/crates/as-if-std/src/lib.rs
new file mode 100644
index 000000000..c0f49b77d
--- /dev/null
+++ b/library/backtrace/crates/as-if-std/src/lib.rs
@@ -0,0 +1,21 @@
+// A crate which builds the `backtrace` crate as-if it's included as a
+// submodule into the standard library. We try to set this crate up similarly
+// to the standard library itself to minimize the likelihood of issues when
+// updating the `backtrace` crate.
+
+#![no_std]
+
+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.
+mod __internal {
+ extern crate std;
+ pub use std::*;
+}
+
+pub use __internal::*;
+
+// This is the magical part which we hope works.
+#[path = "../../../src/lib.rs"]
+mod the_backtrace_crate;
diff --git a/library/backtrace/crates/cpp_smoke_test/Cargo.toml b/library/backtrace/crates/cpp_smoke_test/Cargo.toml
new file mode 100644
index 000000000..1325d7c04
--- /dev/null
+++ b/library/backtrace/crates/cpp_smoke_test/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "cpp_smoke_test"
+version = "0.1.0"
+authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
+build = "build.rs"
+
+[build-dependencies]
+cc = "1.0"
+
+[dependencies]
+backtrace = { path = "../..", features = ["cpp_demangle"] }
diff --git a/library/backtrace/crates/cpp_smoke_test/build.rs b/library/backtrace/crates/cpp_smoke_test/build.rs
new file mode 100644
index 000000000..5ddc63d82
--- /dev/null
+++ b/library/backtrace/crates/cpp_smoke_test/build.rs
@@ -0,0 +1,14 @@
+fn main() {
+ compile_cpp();
+}
+
+fn compile_cpp() {
+ println!("cargo:rerun-if-changed=cpp/trampoline.cpp");
+
+ cc::Build::new()
+ .cpp(true)
+ .debug(true)
+ .opt_level(0)
+ .file("cpp/trampoline.cpp")
+ .compile("libcpptrampoline.a");
+}
diff --git a/library/backtrace/crates/cpp_smoke_test/cpp/trampoline.cpp b/library/backtrace/crates/cpp_smoke_test/cpp/trampoline.cpp
new file mode 100644
index 000000000..61e09604c
--- /dev/null
+++ b/library/backtrace/crates/cpp_smoke_test/cpp/trampoline.cpp
@@ -0,0 +1,14 @@
+#include <stdio.h>
+
+namespace space {
+ template <typename FuncT>
+ void templated_trampoline(FuncT func) {
+ func();
+ }
+}
+
+typedef void (*FuncPtr)();
+
+extern "C" void cpp_trampoline(FuncPtr func) {
+ space::templated_trampoline(func);
+}
diff --git a/library/backtrace/crates/cpp_smoke_test/src/lib.rs b/library/backtrace/crates/cpp_smoke_test/src/lib.rs
new file mode 100644
index 000000000..2002ef17d
--- /dev/null
+++ b/library/backtrace/crates/cpp_smoke_test/src/lib.rs
@@ -0,0 +1,2 @@
+#[test]
+fn it_works() {}
diff --git a/library/backtrace/crates/cpp_smoke_test/tests/smoke.rs b/library/backtrace/crates/cpp_smoke_test/tests/smoke.rs
new file mode 100644
index 000000000..b9aef47f5
--- /dev/null
+++ b/library/backtrace/crates/cpp_smoke_test/tests/smoke.rs
@@ -0,0 +1,73 @@
+extern crate backtrace;
+extern crate cpp_smoke_test;
+
+use std::sync::atomic::{AtomicBool, Ordering};
+
+extern "C" {
+ fn cpp_trampoline(func: extern "C" fn()) -> ();
+}
+
+#[test]
+#[ignore] // fixme(fitzgen/cpp_demangle#73)
+fn smoke_test_cpp() {
+ static RAN_ASSERTS: AtomicBool = AtomicBool::new(false);
+
+ extern "C" fn assert_cpp_frames() {
+ let mut physical_frames = Vec::new();
+ backtrace::trace(|cx| {
+ physical_frames.push(cx.ip());
+
+ // We only want to capture this closure's frame, assert_cpp_frames,
+ // space::templated_trampoline, and cpp_trampoline. Those are
+ // logical frames, which might be inlined into fewer physical
+ // frames, so we may end up with extra logical frames after
+ // resolving these.
+ physical_frames.len() < 4
+ });
+
+ let names: Vec<_> = physical_frames
+ .into_iter()
+ .flat_map(|ip| {
+ let mut logical_frame_names = vec![];
+
+ backtrace::resolve(ip, |sym| {
+ let sym_name = sym.name().expect("Should have a symbol name");
+ let demangled = sym_name.to_string();
+ logical_frame_names.push(demangled);
+ });
+
+ assert!(
+ !logical_frame_names.is_empty(),
+ "Should have resolved at least one symbol for the physical frame"
+ );
+
+ logical_frame_names
+ })
+ // Skip the backtrace::trace closure and assert_cpp_frames, and then
+ // take the two C++ frame names.
+ .skip_while(|name| !name.contains("trampoline"))
+ .take(2)
+ .collect();
+
+ println!("actual names = {:#?}", names);
+
+ let expected = [
+ "void space::templated_trampoline<void (*)()>(void (*)())",
+ "cpp_trampoline",
+ ];
+ println!("expected names = {:#?}", expected);
+
+ assert_eq!(names.len(), expected.len());
+ for (actual, expected) in names.iter().zip(expected.iter()) {
+ assert_eq!(actual, expected);
+ }
+
+ RAN_ASSERTS.store(true, Ordering::SeqCst);
+ }
+
+ assert!(!RAN_ASSERTS.load(Ordering::SeqCst));
+ unsafe {
+ cpp_trampoline(assert_cpp_frames);
+ }
+ assert!(RAN_ASSERTS.load(Ordering::SeqCst));
+}
diff --git a/library/backtrace/crates/debuglink/Cargo.toml b/library/backtrace/crates/debuglink/Cargo.toml
new file mode 100644
index 000000000..6b55b1394
--- /dev/null
+++ b/library/backtrace/crates/debuglink/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "debuglink"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+backtrace = { path = "../.." }
diff --git a/library/backtrace/crates/debuglink/src/main.rs b/library/backtrace/crates/debuglink/src/main.rs
new file mode 100644
index 000000000..99265ae9a
--- /dev/null
+++ b/library/backtrace/crates/debuglink/src/main.rs
@@ -0,0 +1,34 @@
+// Test that the debuginfo is being found by checking that the
+// backtrace contains `main` and that the source filename uses
+// the path given in the command line arguments.
+//
+// For dwz tests, this assumes that the path string will be moved into
+// the dwz file.
+fn main() {
+ let crate_dir = std::env::args().skip(1).next().unwrap();
+ let expect = std::path::Path::new(&crate_dir).join("src/main.rs");
+
+ let bt = backtrace::Backtrace::new();
+ println!("{:?}", bt);
+
+ let mut found_main = false;
+
+ for frame in bt.frames() {
+ let symbols = frame.symbols();
+ if symbols.is_empty() {
+ continue;
+ }
+
+ if let Some(name) = symbols[0].name() {
+ let name = format!("{:#}", name);
+ if name == "debuglink::main" {
+ found_main = true;
+ let filename = symbols[0].filename().unwrap();
+ assert_eq!(filename, expect);
+ break;
+ }
+ }
+ }
+
+ assert!(found_main);
+}
diff --git a/library/backtrace/crates/dylib-dep/Cargo.toml b/library/backtrace/crates/dylib-dep/Cargo.toml
new file mode 100644
index 000000000..c3d4a8c2f
--- /dev/null
+++ b/library/backtrace/crates/dylib-dep/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "dylib-dep"
+version = "0.1.0"
+edition = "2018"
+authors = []
+publish = false
+
+[lib]
+name = "dylib_dep"
+crate-type = ["cdylib", "rlib"]
diff --git a/library/backtrace/crates/dylib-dep/src/lib.rs b/library/backtrace/crates/dylib-dep/src/lib.rs
new file mode 100644
index 000000000..201807797
--- /dev/null
+++ b/library/backtrace/crates/dylib-dep/src/lib.rs
@@ -0,0 +1,14 @@
+#![allow(improper_ctypes_definitions)]
+
+type Pos = (&'static str, u32);
+
+macro_rules! pos {
+ () => {
+ (file!(), line!())
+ };
+}
+
+#[no_mangle]
+pub extern "C" fn foo(outer: Pos, inner: fn(Pos, Pos)) {
+ inner(outer, pos!());
+}
diff --git a/library/backtrace/crates/line-tables-only/Cargo.toml b/library/backtrace/crates/line-tables-only/Cargo.toml
new file mode 100644
index 000000000..e2967d3d3
--- /dev/null
+++ b/library/backtrace/crates/line-tables-only/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "line-tables-only"
+version = "0.1.0"
+edition = "2018"
+
+[build-dependencies]
+cc = "1.0"
+
+[dependencies]
+libc = { version = "0.2", default-features = false }
+
+[dependencies.backtrace]
+path = "../.."
+features = [
+ 'libunwind',
+ 'std',
+]
+
+[features]
+libbacktrace = ['backtrace/libbacktrace']
+gimli-symbolize = ['backtrace/gimli-symbolize']
diff --git a/library/backtrace/crates/line-tables-only/build.rs b/library/backtrace/crates/line-tables-only/build.rs
new file mode 100644
index 000000000..125fb7645
--- /dev/null
+++ b/library/backtrace/crates/line-tables-only/build.rs
@@ -0,0 +1,10 @@
+fn main() {
+ println!("cargo:rerun-if-changed=src/callback.c");
+
+ cc::Build::new()
+ .opt_level(0)
+ .debug(false)
+ .flag("-g1")
+ .file("src/callback.c")
+ .compile("libcallback.a");
+}
diff --git a/library/backtrace/crates/line-tables-only/src/callback.c b/library/backtrace/crates/line-tables-only/src/callback.c
new file mode 100644
index 000000000..c9d5d072a
--- /dev/null
+++ b/library/backtrace/crates/line-tables-only/src/callback.c
@@ -0,0 +1,14 @@
+
+typedef void (*callback) (void *data);
+
+void baz(callback cb, void *data) {
+ cb(data);
+}
+
+void bar(callback cb, void *data) {
+ baz(cb, data);
+}
+
+void foo(callback cb, void *data) {
+ bar(cb, data);
+}
diff --git a/library/backtrace/crates/line-tables-only/src/lib.rs b/library/backtrace/crates/line-tables-only/src/lib.rs
new file mode 100644
index 000000000..bd5afcb3a
--- /dev/null
+++ b/library/backtrace/crates/line-tables-only/src/lib.rs
@@ -0,0 +1,57 @@
+#[cfg(test)]
+mod tests {
+ use std::path::Path;
+ use backtrace::Backtrace;
+ use libc::c_void;
+
+ pub type Callback = extern "C" fn(data: *mut c_void);
+
+ extern "C" {
+ fn foo(cb: Callback, data: *mut c_void);
+ }
+
+ extern "C" fn store_backtrace(data: *mut c_void) {
+ let bt = backtrace::Backtrace::new();
+ unsafe { *(data as *mut Option<Backtrace>) = Some(bt) };
+ }
+
+ 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() {
+ for symbol in frame.symbols() {
+ if let Some(name) = symbol.name() {
+ if name.as_bytes() == expected_name.as_bytes() {
+ assert!(symbol.filename().unwrap().ends_with(expected_file));
+ assert_eq!(symbol.lineno(), Some(expected_line));
+ return;
+ }
+ }
+ }
+ }
+
+ panic!("symbol {:?} not found in backtrace: {:?}", expected_name, backtrace);
+ }
+
+ /// Verifies that when debug info includes only lines tables the generated
+ /// backtrace is still generated successfully. The test exercises behaviour
+ /// that failed previously when compiling with clang -g1.
+ ///
+ /// The test case uses C rather than rust, since at that time when it was
+ /// written the debug info generated at level 1 in rustc was essentially
+ /// the same as at level 2.
+ #[test]
+ #[cfg_attr(windows, ignore)]
+ fn backtrace_works_with_line_tables_only() {
+ let mut backtrace: Option<Backtrace> = None;
+ unsafe { foo(store_backtrace, &mut backtrace as *mut _ as *mut c_void) };
+ let backtrace = backtrace.expect("backtrace");
+ assert_contains(&backtrace, "foo", "src/callback.c", 13);
+ assert_contains(&backtrace, "bar", "src/callback.c", 9);
+ assert_contains(&backtrace, "baz", "src/callback.c", 5);
+ }
+}
diff --git a/library/backtrace/crates/macos_frames_test/Cargo.toml b/library/backtrace/crates/macos_frames_test/Cargo.toml
new file mode 100644
index 000000000..278d51e79
--- /dev/null
+++ b/library/backtrace/crates/macos_frames_test/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "macos_frames_test"
+version = "0.1.0"
+authors = ["Aaron Hill <aa1ronham@gmail.com>"]
+edition = "2018"
+
+[dependencies.backtrace]
+path = "../.."
diff --git a/library/backtrace/crates/macos_frames_test/src/lib.rs b/library/backtrace/crates/macos_frames_test/src/lib.rs
new file mode 100644
index 000000000..65e2cc340
--- /dev/null
+++ b/library/backtrace/crates/macos_frames_test/src/lib.rs
@@ -0,0 +1 @@
+// intentionally blank
diff --git a/library/backtrace/crates/macos_frames_test/tests/main.rs b/library/backtrace/crates/macos_frames_test/tests/main.rs
new file mode 100644
index 000000000..f0e905b24
--- /dev/null
+++ b/library/backtrace/crates/macos_frames_test/tests/main.rs
@@ -0,0 +1,30 @@
+// Based on from https://github.com/rust-lang/rust/blob/2cb0b8582ebbf9784db9cec06fff517badbf4553/src/test/ui/issues/issue-45731.rs
+// This needs to go in a crate by itself, since it modifies the dSYM for the entire test
+// output directory.
+//
+// Note that this crate is *not* part of the overall `backtrace-rs` workspace,
+// so that it gets its own 'target' directory. We manually invoke this test
+// in .github/workflows/main.yml by passing `--manifest-path` to Cargo
+#[test]
+#[cfg(target_os = "macos")]
+fn backtrace_no_dsym() {
+ use std::{env, fs};
+
+ // Find our dSYM and replace the DWARF binary with an empty file
+ let mut dsym_path = env::current_exe().unwrap();
+ 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
+ ));
+ let _ = fs::OpenOptions::new()
+ .read(false)
+ .write(true)
+ .truncate(true)
+ .create(false)
+ .open(&dsym_path)
+ .unwrap();
+
+ backtrace::Backtrace::new();
+}
diff --git a/library/backtrace/crates/without_debuginfo/Cargo.toml b/library/backtrace/crates/without_debuginfo/Cargo.toml
new file mode 100644
index 000000000..19d76cbec
--- /dev/null
+++ b/library/backtrace/crates/without_debuginfo/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "without_debuginfo"
+version = "0.1.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+edition = "2018"
+
+[dependencies.backtrace]
+path = "../.."
+default-features = false
+features = ['std']
+
+[profile.dev]
+debug = false
+
+[profile.test]
+debug = false
+
+[features]
+libbacktrace = ['backtrace/libbacktrace']
+gimli-symbolize = ['backtrace/gimli-symbolize']
diff --git a/library/backtrace/crates/without_debuginfo/src/lib.rs b/library/backtrace/crates/without_debuginfo/src/lib.rs
new file mode 100644
index 000000000..65e2cc340
--- /dev/null
+++ b/library/backtrace/crates/without_debuginfo/src/lib.rs
@@ -0,0 +1 @@
+// intentionally blank
diff --git a/library/backtrace/crates/without_debuginfo/tests/smoke.rs b/library/backtrace/crates/without_debuginfo/tests/smoke.rs
new file mode 100644
index 000000000..5a0dfea15
--- /dev/null
+++ b/library/backtrace/crates/without_debuginfo/tests/smoke.rs
@@ -0,0 +1,44 @@
+#[test]
+fn all_frames_have_symbols() {
+ println!("{:?}", backtrace::Backtrace::new());
+
+ let mut missing_symbols = 0;
+ let mut has_symbols = 0;
+ backtrace::trace(|frame| {
+ let mut any = false;
+ backtrace::resolve_frame(frame, |sym| {
+ if sym.name().is_some() {
+ any = true;
+ }
+ });
+ if any {
+ has_symbols += 1;
+ } else if !frame.ip().is_null() {
+ missing_symbols += 1;
+ }
+ true
+ });
+
+ // FIXME(#346) currently on MinGW we can't symbolize kernel32.dll and other
+ // system libraries, which means we miss the last few symbols.
+ if cfg!(windows) && cfg!(target_env = "gnu") {
+ assert!(missing_symbols < has_symbols && has_symbols > 4);
+ } else {
+ assert_eq!(missing_symbols, 0);
+ }
+}
+
+#[test]
+fn all_frames_have_module_base_address() {
+ let mut missing_base_addresses = 0;
+ backtrace::trace(|frame| {
+ if frame.module_base_address().is_none() {
+ missing_base_addresses += 1;
+ }
+ true
+ });
+
+ if cfg!(windows) {
+ assert_eq!(missing_base_addresses, 0);
+ }
+}
diff --git a/library/backtrace/examples/backtrace.rs b/library/backtrace/examples/backtrace.rs
new file mode 100644
index 000000000..7ff6cd39e
--- /dev/null
+++ b/library/backtrace/examples/backtrace.rs
@@ -0,0 +1,5 @@
+use backtrace::Backtrace;
+
+fn main() {
+ println!("{:?}", Backtrace::new());
+}
diff --git a/library/backtrace/examples/raw.rs b/library/backtrace/examples/raw.rs
new file mode 100644
index 000000000..d96a127a2
--- /dev/null
+++ b/library/backtrace/examples/raw.rs
@@ -0,0 +1,52 @@
+fn main() {
+ foo();
+}
+
+fn foo() {
+ bar()
+}
+fn bar() {
+ baz()
+}
+fn baz() {
+ print()
+}
+
+#[cfg(target_pointer_width = "32")]
+const HEX_WIDTH: usize = 10;
+#[cfg(target_pointer_width = "64")]
+const HEX_WIDTH: usize = 20;
+
+fn print() {
+ let mut cnt = 0;
+ backtrace::trace(|frame| {
+ let ip = frame.ip();
+ print!("frame #{:<2} - {:#02$x}", cnt, ip as usize, HEX_WIDTH);
+ cnt += 1;
+
+ let mut resolved = false;
+ backtrace::resolve(frame.ip(), |symbol| {
+ if !resolved {
+ resolved = true;
+ } else {
+ print!("{}", vec![" "; 7 + 2 + 3 + HEX_WIDTH].join(""));
+ }
+
+ if let Some(name) = symbol.name() {
+ print!(" - {}", name);
+ } else {
+ print!(" - <unknown>");
+ }
+ if let Some(file) = symbol.filename() {
+ if let Some(l) = symbol.lineno() {
+ print!("\n{:13}{:4$}@ {}:{}", "", "", file.display(), l, HEX_WIDTH);
+ }
+ }
+ println!("");
+ });
+ if !resolved {
+ println!(" - <no info>");
+ }
+ true // keep going
+ });
+}
diff --git a/library/backtrace/src/android-api.c b/library/backtrace/src/android-api.c
new file mode 100644
index 000000000..1bfeadf5b
--- /dev/null
+++ b/library/backtrace/src/android-api.c
@@ -0,0 +1,4 @@
+// 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
new file mode 100644
index 000000000..ba0f05f3b
--- /dev/null
+++ b/library/backtrace/src/backtrace/dbghelp.rs
@@ -0,0 +1,257 @@
+//! 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.
+//!
+//! 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 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,
+}
+
+// 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.addr_pc().Offset as *mut _
+ }
+
+ pub fn sp(&self) -> *mut c_void {
+ self.addr_stack().Offset as *mut _
+ }
+
+ pub fn symbol_address(&self) -> *mut c_void {
+ 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,
+ }
+ }
+
+ 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,
+ }
+ }
+
+ 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,
+ }
+ }
+
+ fn addr_stack(&self) -> &ADDRESS64 {
+ match self.stack_frame {
+ StackFrame::New(ref new) => &new.AddrStack,
+ StackFrame::Old(ref old) => &old.AddrStack,
+ }
+ }
+
+ 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,
+ }
+ }
+}
+
+#[repr(C, align(16))] // required by `CONTEXT`, is a FIXME in winapi right now
+struct MyContext(CONTEXT);
+
+#[inline(always)]
+pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) {
+ // Allocate necessary structures for doing the stack walk
+ let process = GetCurrentProcess();
+ let thread = GetCurrentThread();
+
+ let mut context = mem::zeroed::<MyContext>();
+ RtlCaptureContext(&mut context.0);
+
+ // Ensure this process's symbols are initialized
+ let dbghelp = match 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 process_handle = GetCurrentProcess();
+
+ // 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::<STACKFRAME_EX>() 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!(),
+ };
+
+ while StackWalkEx(
+ image as DWORD,
+ process,
+ thread,
+ frame_ptr,
+ &mut context.0 as *mut CONTEXT as *mut _,
+ None,
+ Some(function_table_access),
+ Some(get_module_base),
+ None,
+ 0,
+ ) == TRUE
+ {
+ frame.inner.base_address = get_module_base(process_handle, frame.ip() as _) as _;
+
+ if !cb(&frame) {
+ break;
+ }
+ }
+ }
+ 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!(),
+ };
+
+ while dbghelp.StackWalk64()(
+ image as DWORD,
+ process,
+ thread,
+ frame_ptr,
+ &mut context.0 as *mut CONTEXT as *mut _,
+ None,
+ Some(function_table_access),
+ Some(get_module_base),
+ None,
+ ) == TRUE
+ {
+ frame.inner.base_address = get_module_base(process_handle, frame.ip() as _) as _;
+
+ if !cb(&frame) {
+ break;
+ }
+ }
+ }
+ }
+}
+
+#[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
new file mode 100644
index 000000000..ef77edda5
--- /dev/null
+++ b/library/backtrace/src/backtrace/libunwind.rs
@@ -0,0 +1,267 @@
+//! Backtrace support using libunwind/gcc_s/etc APIs.
+//!
+//! This module contains the ability to unwind the stack using libunwind-style
+//! APIs. Note that there's a whole bunch of implementations of the
+//! libunwind-like API, and this is just trying to be compatible with most of
+//! them all at once instead of being picky.
+//!
+//! The libunwind API is powered by `_Unwind_Backtrace` and is in practice very
+//! reliable at generating a backtrace. It's not entirely clear how it does it
+//! (frame pointers? eh_frame info? both?) but it seems to work!
+//!
+//! Most of the complexity of this module is handling the various platform
+//! differences across libunwind implementations. Otherwise this is a pretty
+//! straightforward Rust binding to the libunwind APIs.
+//!
+//! This is the default unwinding API for all non-Windows platforms currently.
+
+use super::super::Bomb;
+use core::ffi::c_void;
+
+pub enum Frame {
+ Raw(*mut uw::_Unwind_Context),
+ Cloned {
+ ip: *mut c_void,
+ sp: *mut c_void,
+ symbol_address: *mut c_void,
+ },
+}
+
+// With a raw libunwind pointer it should only ever be access in a readonly
+// threadsafe fashion, so it's `Sync`. When sending to other threads via `Clone`
+// we always switch to a version which doesn't retain interior pointers, so we
+// should be `Send` as well.
+unsafe impl Send for Frame {}
+unsafe impl Sync for Frame {}
+
+impl Frame {
+ pub fn ip(&self) -> *mut c_void {
+ let ctx = match *self {
+ Frame::Raw(ctx) => ctx,
+ Frame::Cloned { ip, .. } => return ip,
+ };
+ unsafe { uw::_Unwind_GetIP(ctx) as *mut c_void }
+ }
+
+ pub fn sp(&self) -> *mut c_void {
+ match *self {
+ Frame::Raw(ctx) => unsafe { uw::get_sp(ctx) as *mut c_void },
+ Frame::Cloned { sp, .. } => sp,
+ }
+ }
+
+ pub fn symbol_address(&self) -> *mut c_void {
+ if let Frame::Cloned { symbol_address, .. } = *self {
+ return symbol_address;
+ }
+
+ // The macOS linker emits a "compact" unwind table that only includes an
+ // entry for a function if that function either has an LSDA or its
+ // encoding differs from that of the previous entry. Consequently, on
+ // macOS, `_Unwind_FindEnclosingFunction` is unreliable (it can return a
+ // pointer to some totally unrelated function). Instead, we just always
+ // return the ip.
+ //
+ // https://github.com/rust-lang/rust/issues/74771#issuecomment-664056788
+ //
+ // Note the `skip_inner_frames.rs` test is skipped on macOS due to this
+ // clause, and if this is fixed that test in theory can be run on macOS!
+ if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
+ self.ip()
+ } else {
+ unsafe { uw::_Unwind_FindEnclosingFunction(self.ip()) }
+ }
+ }
+
+ pub fn module_base_address(&self) -> Option<*mut c_void> {
+ None
+ }
+}
+
+impl Clone for Frame {
+ fn clone(&self) -> Frame {
+ Frame::Cloned {
+ ip: self.ip(),
+ sp: self.sp(),
+ symbol_address: self.symbol_address(),
+ }
+ }
+}
+
+#[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 _);
+
+ 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 cx = super::Frame {
+ inner: Frame::Raw(ctx),
+ };
+
+ let mut bomb = Bomb { enabled: true };
+ let keep_going = cb(&cx);
+ bomb.enabled = false;
+
+ if keep_going {
+ uw::_URC_NO_REASON
+ } else {
+ uw::_URC_FAILURE
+ }
+ }
+}
+
+/// Unwind library interface used for backtraces
+///
+/// Note that dead code is allowed as here are just bindings
+/// iOS doesn't use all of them it but adding more
+/// platform-specific configs pollutes the code too much
+#[allow(non_camel_case_types)]
+#[allow(non_snake_case)]
+#[allow(dead_code)]
+mod uw {
+ pub use self::_Unwind_Reason_Code::*;
+
+ use core::ffi::c_void;
+
+ #[repr(C)]
+ pub enum _Unwind_Reason_Code {
+ _URC_NO_REASON = 0,
+ _URC_FOREIGN_EXCEPTION_CAUGHT = 1,
+ _URC_FATAL_PHASE2_ERROR = 2,
+ _URC_FATAL_PHASE1_ERROR = 3,
+ _URC_NORMAL_STOP = 4,
+ _URC_END_OF_STACK = 5,
+ _URC_HANDLER_FOUND = 6,
+ _URC_INSTALL_CONTEXT = 7,
+ _URC_CONTINUE_UNWIND = 8,
+ _URC_FAILURE = 9, // used only by ARM EABI
+ }
+
+ pub enum _Unwind_Context {}
+
+ pub type _Unwind_Trace_Fn =
+ extern "C" fn(ctx: *mut _Unwind_Context, arg: *mut c_void) -> _Unwind_Reason_Code;
+
+ extern "C" {
+ pub fn _Unwind_Backtrace(
+ trace: _Unwind_Trace_Fn,
+ trace_argument: *mut c_void,
+ ) -> _Unwind_Reason_Code;
+ }
+
+ cfg_if::cfg_if! {
+ // available since GCC 4.2.0, should be fine for our purpose
+ if #[cfg(all(
+ not(all(target_os = "android", target_arch = "arm")),
+ not(all(target_os = "freebsd", target_arch = "arm")),
+ not(all(target_os = "linux", target_arch = "arm")),
+ not(all(target_os = "horizon", target_arch = "arm"))
+ ))] {
+ extern "C" {
+ pub fn _Unwind_GetIP(ctx: *mut _Unwind_Context) -> libc::uintptr_t;
+ pub fn _Unwind_FindEnclosingFunction(pc: *mut c_void) -> *mut c_void;
+
+ #[cfg(not(all(target_os = "linux", target_arch = "s390x")))]
+ // This function is a misnomer: rather than getting this frame's
+ // Canonical Frame Address (aka the caller frame's SP) it
+ // returns this frame's SP.
+ //
+ // https://github.com/libunwind/libunwind/blob/d32956507cf29d9b1a98a8bce53c78623908f4fe/src/unwind/GetCFA.c#L28-L35
+ #[link_name = "_Unwind_GetCFA"]
+ pub fn get_sp(ctx: *mut _Unwind_Context) -> libc::uintptr_t;
+
+ }
+
+ // s390x uses a biased CFA value, therefore we need to use
+ // _Unwind_GetGR to get the stack pointer register (%r15)
+ // instead of relying on _Unwind_GetCFA.
+ #[cfg(all(target_os = "linux", target_arch = "s390x"))]
+ pub unsafe fn get_sp(ctx: *mut _Unwind_Context) -> libc::uintptr_t {
+ extern "C" {
+ pub fn _Unwind_GetGR(ctx: *mut _Unwind_Context, index: libc::c_int) -> libc::uintptr_t;
+ }
+ _Unwind_GetGR(ctx, 15)
+ }
+ } else {
+ // 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.
+ //
+ // TODO: link to the header file that defines these macros, if you
+ // can find it. (I, fitzgen, cannot find the header file that some
+ // of these macro expansions were originally borrowed from.)
+ #[repr(C)]
+ enum _Unwind_VRS_Result {
+ _UVRSR_OK = 0,
+ _UVRSR_NOT_IMPLEMENTED = 1,
+ _UVRSR_FAILED = 2,
+ }
+ #[repr(C)]
+ enum _Unwind_VRS_RegClass {
+ _UVRSC_CORE = 0,
+ _UVRSC_VFP = 1,
+ _UVRSC_FPA = 2,
+ _UVRSC_WMMXD = 3,
+ _UVRSC_WMMXC = 4,
+ }
+ #[repr(C)]
+ enum _Unwind_VRS_DataRepresentation {
+ _UVRSD_UINT32 = 0,
+ _UVRSD_VFPX = 1,
+ _UVRSD_FPAX = 2,
+ _UVRSD_UINT64 = 3,
+ _UVRSD_FLOAT = 4,
+ _UVRSD_DOUBLE = 5,
+ }
+
+ type _Unwind_Word = libc::c_uint;
+ extern "C" {
+ fn _Unwind_VRS_Get(
+ ctx: *mut _Unwind_Context,
+ klass: _Unwind_VRS_RegClass,
+ word: _Unwind_Word,
+ repr: _Unwind_VRS_DataRepresentation,
+ data: *mut c_void,
+ ) -> _Unwind_VRS_Result;
+ }
+
+ 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 _ = _Unwind_VRS_Get(
+ ctx,
+ _Unwind_VRS_RegClass::_UVRSC_CORE,
+ 15,
+ _Unwind_VRS_DataRepresentation::_UVRSD_UINT32,
+ ptr as *mut c_void,
+ );
+ (val & !1) as libc::uintptr_t
+ }
+
+ // R13 is the stack pointer on arm.
+ const SP: _Unwind_Word = 13;
+
+ 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 _ = _Unwind_VRS_Get(
+ ctx,
+ _Unwind_VRS_RegClass::_UVRSC_CORE,
+ SP,
+ _Unwind_VRS_DataRepresentation::_UVRSD_UINT32,
+ ptr as *mut c_void,
+ );
+ val as libc::uintptr_t
+ }
+
+ // This function also doesn't exist on Android or ARM/Linux, so make it
+ // a no-op.
+ pub unsafe fn _Unwind_FindEnclosingFunction(pc: *mut c_void) -> *mut c_void {
+ pc
+ }
+ }
+ }
+}
diff --git a/library/backtrace/src/backtrace/miri.rs b/library/backtrace/src/backtrace/miri.rs
new file mode 100644
index 000000000..9a5f65b80
--- /dev/null
+++ b/library/backtrace/src/backtrace/miri.rs
@@ -0,0 +1,107 @@
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+use core::ffi::c_void;
+
+extern "Rust" {
+ fn miri_backtrace_size(flags: u64) -> usize;
+ fn miri_get_backtrace(flags: u64, buf: *mut *mut ());
+ fn miri_resolve_frame(ptr: *mut (), flags: u64) -> MiriFrame;
+ fn miri_resolve_frame_names(ptr: *mut (), flags: u64, name_buf: *mut u8, filename_buf: *mut u8);
+}
+
+#[repr(C)]
+pub struct MiriFrame {
+ pub name_len: usize,
+ pub filename_len: usize,
+ pub lineno: u32,
+ pub colno: u32,
+ pub fn_ptr: *mut c_void,
+}
+
+#[derive(Clone, Debug)]
+pub struct FullMiriFrame {
+ pub name: Box<[u8]>,
+ pub filename: Box<[u8]>,
+ pub lineno: u32,
+ pub colno: u32,
+ pub fn_ptr: *mut c_void,
+}
+
+#[derive(Debug, Clone)]
+pub struct Frame {
+ pub addr: *mut c_void,
+ pub inner: FullMiriFrame,
+}
+
+// SAFETY: Miri guarantees that the returned pointer
+// can be used from any thread.
+unsafe impl Send for Frame {}
+unsafe impl Sync for Frame {}
+
+impl Frame {
+ pub fn ip(&self) -> *mut c_void {
+ self.addr
+ }
+
+ pub fn sp(&self) -> *mut c_void {
+ core::ptr::null_mut()
+ }
+
+ pub fn symbol_address(&self) -> *mut c_void {
+ self.inner.fn_ptr
+ }
+
+ pub fn module_base_address(&self) -> Option<*mut c_void> {
+ None
+ }
+}
+
+pub fn trace<F: FnMut(&super::Frame) -> bool>(cb: F) {
+ // SAFETY: Miri guarantees that the backtrace API functions
+ // can be called from any thread.
+ unsafe { trace_unsynchronized(cb) };
+}
+
+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 mut name = Vec::with_capacity(frame.name_len);
+ let mut filename = Vec::with_capacity(frame.filename_len);
+
+ // 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());
+
+ name.set_len(frame.name_len);
+ filename.set_len(frame.filename_len);
+ }
+
+ Frame {
+ addr: ptr,
+ inner: FullMiriFrame {
+ name: name.into(),
+ filename: filename.into(),
+ lineno: frame.lineno,
+ colno: frame.colno,
+ fn_ptr: frame.fn_ptr,
+ },
+ }
+}
+
+pub unsafe fn trace_unsynchronized<F: FnMut(&super::Frame) -> bool>(mut cb: F) {
+ let len = miri_backtrace_size(0);
+
+ let mut frames = Vec::with_capacity(len);
+
+ miri_get_backtrace(1, frames.as_mut_ptr());
+
+ frames.set_len(len);
+
+ for ptr in frames.iter() {
+ let frame = resolve_addr(*ptr as *mut c_void);
+ cb(&super::Frame { inner: frame });
+ }
+}
diff --git a/library/backtrace/src/backtrace/mod.rs b/library/backtrace/src/backtrace/mod.rs
new file mode 100644
index 000000000..93355d744
--- /dev/null
+++ b/library/backtrace/src/backtrace/mod.rs
@@ -0,0 +1,162 @@
+use core::ffi::c_void;
+use core::fmt;
+
+/// Inspects the current call-stack, passing all active frames into the closure
+/// provided to calculate a stack trace.
+///
+/// This function is the workhorse of this library in calculating the stack
+/// traces for a program. The given closure `cb` is yielded instances of a
+/// `Frame` which represent information about that call frame on the stack. The
+/// closure is yielded frames in a top-down fashion (most recently called
+/// functions first).
+///
+/// The closure's return value is an indication of whether the backtrace should
+/// continue. A return value of `false` will terminate the backtrace and return
+/// immediately.
+///
+/// Once a `Frame` is acquired you will likely want to call `backtrace::resolve`
+/// to convert the `ip` (instruction pointer) or symbol address to a `Symbol`
+/// through which the name and/or filename/line number can be learned.
+///
+/// Note that this is a relatively low-level function and if you'd like to, for
+/// example, capture a backtrace to be inspected later, then the `Backtrace`
+/// type may be more appropriate.
+///
+/// # Required features
+///
+/// This function requires the `std` feature of the `backtrace` crate to be
+/// enabled, and the `std` feature is enabled by default.
+///
+/// # Panics
+///
+/// This function strives to never panic, but if the `cb` provided panics then
+/// some platforms will force a double panic to abort the process. Some
+/// platforms use a C library which internally uses callbacks which cannot be
+/// unwound through, so panicking from `cb` may trigger a process abort.
+///
+/// # Example
+///
+/// ```
+/// extern crate backtrace;
+///
+/// fn main() {
+/// backtrace::trace(|frame| {
+/// // ...
+///
+/// true // continue the backtrace
+/// });
+/// }
+/// ```
+#[cfg(feature = "std")]
+pub fn trace<F: FnMut(&Frame) -> bool>(cb: F) {
+ let _guard = crate::lock::lock();
+ unsafe { trace_unsynchronized(cb) }
+}
+
+/// Same as `trace`, only unsafe as it's unsynchronized.
+///
+/// This function does not have synchronization guarantees but is available
+/// when the `std` feature of this crate isn't compiled in. See the `trace`
+/// function for more documentation and examples.
+///
+/// # Panics
+///
+/// See information on `trace` for caveats on `cb` panicking.
+pub unsafe fn trace_unsynchronized<F: FnMut(&Frame) -> bool>(mut cb: F) {
+ trace_imp(&mut cb)
+}
+
+/// A trait representing one frame of a backtrace, yielded to the `trace`
+/// function of this crate.
+///
+/// The tracing function's closure will be yielded frames, and the frame is
+/// virtually dispatched as the underlying implementation is not always known
+/// until runtime.
+#[derive(Clone)]
+pub struct Frame {
+ pub(crate) inner: FrameImp,
+}
+
+impl Frame {
+ /// Returns the current instruction pointer of this frame.
+ ///
+ /// This is normally the next instruction to execute in the frame, but not
+ /// all implementations list this with 100% accuracy (but it's generally
+ /// pretty close).
+ ///
+ /// It is recommended to pass this value to `backtrace::resolve` to turn it
+ /// into a symbol name.
+ pub fn ip(&self) -> *mut c_void {
+ self.inner.ip()
+ }
+
+ /// Returns the current stack pointer of this frame.
+ ///
+ /// In the case that a backend cannot recover the stack pointer for this
+ /// frame, a null pointer is returned.
+ pub fn sp(&self) -> *mut c_void {
+ self.inner.sp()
+ }
+
+ /// Returns the starting symbol address of the frame of this function.
+ ///
+ /// This will attempt to rewind the instruction pointer returned by `ip` to
+ /// the start of the function, returning that value. In some cases, however,
+ /// backends will just return `ip` from this function.
+ ///
+ /// The returned value can sometimes be used if `backtrace::resolve` failed
+ /// on the `ip` given above.
+ pub fn symbol_address(&self) -> *mut c_void {
+ self.inner.symbol_address()
+ }
+
+ /// Returns the base address of the module to which the frame belongs.
+ pub fn module_base_address(&self) -> Option<*mut c_void> {
+ self.inner.module_base_address()
+ }
+}
+
+impl fmt::Debug for Frame {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("Frame")
+ .field("ip", &self.ip())
+ .field("symbol_address", &self.symbol_address())
+ .finish()
+ }
+}
+
+cfg_if::cfg_if! {
+ // This needs to come first, to ensure that
+ // Miri takes priority over the host platform
+ if #[cfg(miri)] {
+ pub(crate) mod miri;
+ use self::miri::trace as trace_imp;
+ pub(crate) use self::miri::Frame as FrameImp;
+ } else if #[cfg(
+ any(
+ all(
+ unix,
+ not(target_os = "emscripten"),
+ not(all(target_os = "ios", target_arch = "arm")),
+ ),
+ all(
+ target_env = "sgx",
+ target_vendor = "fortanix",
+ ),
+ )
+ )] {
+ mod libunwind;
+ 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;
+ 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;
+ pub(crate) use self::noop::Frame as FrameImp;
+ }
+}
diff --git a/library/backtrace/src/backtrace/noop.rs b/library/backtrace/src/backtrace/noop.rs
new file mode 100644
index 000000000..7bcea67aa
--- /dev/null
+++ b/library/backtrace/src/backtrace/noop.rs
@@ -0,0 +1,28 @@
+//! Empty implementation of unwinding used when no other implementation is
+//! appropriate.
+
+use core::ffi::c_void;
+
+#[inline(always)]
+pub fn trace(_cb: &mut dyn FnMut(&super::Frame) -> bool) {}
+
+#[derive(Clone)]
+pub struct Frame;
+
+impl Frame {
+ pub fn ip(&self) -> *mut c_void {
+ 0 as *mut _
+ }
+
+ pub fn sp(&self) -> *mut c_void {
+ 0 as *mut _
+ }
+
+ pub fn symbol_address(&self) -> *mut c_void {
+ 0 as *mut _
+ }
+
+ pub fn module_base_address(&self) -> Option<*mut c_void> {
+ None
+ }
+}
diff --git a/library/backtrace/src/capture.rs b/library/backtrace/src/capture.rs
new file mode 100644
index 000000000..e0dd9c474
--- /dev/null
+++ b/library/backtrace/src/capture.rs
@@ -0,0 +1,555 @@
+use crate::PrintFmt;
+use crate::{resolve, resolve_frame, trace, BacktraceFmt, Symbol, SymbolName};
+use std::ffi::c_void;
+use std::fmt;
+use std::path::{Path, PathBuf};
+use std::prelude::v1::*;
+
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
+/// Representation of an owned and self-contained backtrace.
+///
+/// This structure can be used to capture a backtrace at various points in a
+/// program and later used to inspect what the backtrace was at that time.
+///
+/// `Backtrace` supports pretty-printing of backtraces through its `Debug`
+/// implementation.
+///
+/// # Required features
+///
+/// This function requires the `std` feature of the `backtrace` crate to be
+/// enabled, and the `std` feature is enabled by default.
+#[derive(Clone)]
+#[cfg_attr(feature = "serialize-rustc", derive(RustcDecodable, RustcEncodable))]
+#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
+pub struct Backtrace {
+ // Frames here are listed from top-to-bottom of the stack
+ frames: Vec<BacktraceFrame>,
+ // 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() {
+ fn _assert<T: Send + Sync>() {}
+ _assert::<Backtrace>();
+}
+
+/// Captured version of a frame in a backtrace.
+///
+/// This type is returned as a list from `Backtrace::frames` and represents one
+/// stack frame in a captured backtrace.
+///
+/// # Required features
+///
+/// This function requires the `std` feature of the `backtrace` crate to be
+/// enabled, and the `std` feature is enabled by default.
+#[derive(Clone)]
+pub struct BacktraceFrame {
+ frame: Frame,
+ symbols: Option<Vec<BacktraceSymbol>>,
+}
+
+#[derive(Clone)]
+enum Frame {
+ Raw(crate::Frame),
+ #[allow(dead_code)]
+ Deserialized {
+ ip: usize,
+ symbol_address: usize,
+ module_base_address: Option<usize>,
+ },
+}
+
+impl Frame {
+ fn ip(&self) -> *mut c_void {
+ match *self {
+ Frame::Raw(ref f) => f.ip(),
+ Frame::Deserialized { ip, .. } => ip as *mut c_void,
+ }
+ }
+
+ fn symbol_address(&self) -> *mut c_void {
+ match *self {
+ Frame::Raw(ref f) => f.symbol_address(),
+ Frame::Deserialized { symbol_address, .. } => symbol_address as *mut c_void,
+ }
+ }
+
+ fn module_base_address(&self) -> Option<*mut c_void> {
+ match *self {
+ Frame::Raw(ref f) => f.module_base_address(),
+ Frame::Deserialized {
+ module_base_address,
+ ..
+ } => module_base_address.map(|addr| addr as *mut c_void),
+ }
+ }
+}
+
+/// Captured version of a symbol in a backtrace.
+///
+/// This type is returned as a list from `BacktraceFrame::symbols` and
+/// represents the metadata for a symbol in a backtrace.
+///
+/// # Required features
+///
+/// This function requires the `std` feature of the `backtrace` crate to be
+/// enabled, and the `std` feature is enabled by default.
+#[derive(Clone)]
+#[cfg_attr(feature = "serialize-rustc", derive(RustcDecodable, RustcEncodable))]
+#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
+pub struct BacktraceSymbol {
+ name: Option<Vec<u8>>,
+ addr: Option<usize>,
+ filename: Option<PathBuf>,
+ lineno: Option<u32>,
+ colno: Option<u32>,
+}
+
+impl Backtrace {
+ /// Captures a backtrace at the callsite of this function, returning an
+ /// owned representation.
+ ///
+ /// This function is useful for representing a backtrace as an object in
+ /// Rust. This returned value can be sent across threads and printed
+ /// elsewhere, and the purpose of this value is to be entirely self
+ /// contained.
+ ///
+ /// Note that on some platforms acquiring a full backtrace and resolving it
+ /// can be extremely expensive. If the cost is too much for your application
+ /// it's recommended to instead use `Backtrace::new_unresolved()` which
+ /// avoids the symbol resolution step (which typically takes the longest)
+ /// and allows deferring that to a later date.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use backtrace::Backtrace;
+ ///
+ /// let current_backtrace = Backtrace::new();
+ /// ```
+ ///
+ /// # Required features
+ ///
+ /// This function requires the `std` feature of the `backtrace` crate to be
+ /// enabled, and the `std` feature is enabled by default.
+ #[inline(never)] // want to make sure there's a frame here to remove
+ pub fn new() -> Backtrace {
+ let mut bt = Self::create(Self::new as usize);
+ bt.resolve();
+ bt
+ }
+
+ /// Similar to `new` except that this does not resolve any symbols, this
+ /// simply captures the backtrace as a list of addresses.
+ ///
+ /// At a later time the `resolve` function can be called to resolve this
+ /// backtrace's symbols into readable names. This function exists because
+ /// the resolution process can sometimes take a significant amount of time
+ /// whereas any one backtrace may only be rarely printed.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use backtrace::Backtrace;
+ ///
+ /// let mut current_backtrace = Backtrace::new_unresolved();
+ /// println!("{:?}", current_backtrace); // no symbol names
+ /// current_backtrace.resolve();
+ /// println!("{:?}", current_backtrace); // symbol names now present
+ /// ```
+ ///
+ /// # Required features
+ ///
+ /// This function requires the `std` feature of the `backtrace` crate to be
+ /// enabled, and the `std` feature is enabled by default.
+ #[inline(never)] // want to make sure there's a frame here to remove
+ pub fn new_unresolved() -> Backtrace {
+ Self::create(Self::new_unresolved as usize)
+ }
+
+ 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());
+ }
+ true
+ });
+
+ Backtrace {
+ frames,
+ actual_start_index: actual_start_index.unwrap_or(0),
+ }
+ }
+
+ /// Returns the frames from when this backtrace was captured.
+ ///
+ /// The first entry of this slice is likely the function `Backtrace::new`,
+ /// and the last frame is likely something about how this thread or the main
+ /// function started.
+ ///
+ /// # 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 frames(&self) -> &[BacktraceFrame] {
+ &self.frames[self.actual_start_index..]
+ }
+
+ /// If this backtrace was created from `new_unresolved` then this function
+ /// will resolve all addresses in the backtrace to their symbolic names.
+ ///
+ /// If this backtrace has been previously resolved or was created through
+ /// `new`, 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) {
+ 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);
+ }
+ }
+}
+
+impl From<Vec<BacktraceFrame>> for Backtrace {
+ fn from(frames: Vec<BacktraceFrame>) -> Self {
+ Backtrace {
+ frames,
+ actual_start_index: 0,
+ }
+ }
+}
+
+impl From<crate::Frame> for BacktraceFrame {
+ fn from(frame: crate::Frame) -> BacktraceFrame {
+ BacktraceFrame {
+ frame: Frame::Raw(frame),
+ symbols: None,
+ }
+ }
+}
+
+impl Into<Vec<BacktraceFrame>> for Backtrace {
+ fn into(self) -> Vec<BacktraceFrame> {
+ self.frames
+ }
+}
+
+impl BacktraceFrame {
+ /// Same as `Frame::ip`
+ ///
+ /// # 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 ip(&self) -> *mut c_void {
+ self.frame.ip() as *mut c_void
+ }
+
+ /// Same as `Frame::symbol_address`
+ ///
+ /// # 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 symbol_address(&self) -> *mut c_void {
+ self.frame.symbol_address() as *mut c_void
+ }
+
+ /// Same as `Frame::module_base_address`
+ ///
+ /// # 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 module_base_address(&self) -> Option<*mut c_void> {
+ self.frame
+ .module_base_address()
+ .map(|addr| addr as *mut c_void)
+ }
+
+ /// Returns the list of symbols that this frame corresponds to.
+ ///
+ /// Normally there is only one symbol per frame, but sometimes if a number
+ /// of functions are inlined into one frame then multiple symbols will be
+ /// returned. The first symbol listed is the "innermost function", whereas
+ /// the last symbol is the outermost (last caller).
+ ///
+ /// Note that if this frame came from an unresolved backtrace then this will
+ /// return an empty list.
+ ///
+ /// # 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 symbols(&self) -> &[BacktraceSymbol] {
+ self.symbols.as_ref().map(|s| &s[..]).unwrap_or(&[])
+ }
+}
+
+impl BacktraceSymbol {
+ /// Same as `Symbol::name`
+ ///
+ /// # 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 name(&self) -> Option<SymbolName<'_>> {
+ self.name.as_ref().map(|s| SymbolName::new(s))
+ }
+
+ /// Same as `Symbol::addr`
+ ///
+ /// # 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 addr(&self) -> Option<*mut c_void> {
+ self.addr.map(|s| s as *mut c_void)
+ }
+
+ /// Same as `Symbol::filename`
+ ///
+ /// # 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 filename(&self) -> Option<&Path> {
+ self.filename.as_ref().map(|p| &**p)
+ }
+
+ /// Same as `Symbol::lineno`
+ ///
+ /// # 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 lineno(&self) -> Option<u32> {
+ self.lineno
+ }
+
+ /// Same as `Symbol::colno`
+ ///
+ /// # 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 colno(&self) -> Option<u32> {
+ self.colno
+ }
+}
+
+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)
+ } else {
+ (&self.frames[self.actual_start_index..], PrintFmt::Short)
+ };
+
+ // When printing paths we try to strip the cwd if it exists, otherwise
+ // we just print the path as-is. Note that we also only do this for the
+ // short format, because if it's full we presumably want to print
+ // everything.
+ let cwd = std::env::current_dir();
+ let mut print_path =
+ move |fmt: &mut fmt::Formatter<'_>, path: crate::BytesOrWideString<'_>| {
+ let path = path.into_path_buf();
+ if !full {
+ if let Ok(cwd) = &cwd {
+ if let Ok(suffix) = path.strip_prefix(cwd) {
+ return fmt::Display::fmt(&suffix.display(), fmt);
+ }
+ }
+ }
+ fmt::Display::fmt(&path.display(), fmt)
+ };
+
+ let mut f = BacktraceFmt::new(fmt, style, &mut print_path);
+ f.add_context()?;
+ for frame in frames {
+ f.frame().backtrace_frame(frame)?;
+ }
+ f.finish()?;
+ Ok(())
+ }
+}
+
+impl Default for Backtrace {
+ fn default() -> Backtrace {
+ Backtrace::new()
+ }
+}
+
+impl fmt::Debug for BacktraceFrame {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt.debug_struct("BacktraceFrame")
+ .field("ip", &self.ip())
+ .field("symbol_address", &self.symbol_address())
+ .finish()
+ }
+}
+
+impl fmt::Debug for BacktraceSymbol {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt.debug_struct("BacktraceSymbol")
+ .field("name", &self.name())
+ .field("addr", &self.addr())
+ .field("filename", &self.filename())
+ .field("lineno", &self.lineno())
+ .field("colno", &self.colno())
+ .finish()
+ }
+}
+
+#[cfg(feature = "serialize-rustc")]
+mod rustc_serialize_impls {
+ use super::*;
+ use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
+
+ #[derive(RustcEncodable, RustcDecodable)]
+ struct SerializedFrame {
+ ip: usize,
+ symbol_address: usize,
+ module_base_address: Option<usize>,
+ symbols: Option<Vec<BacktraceSymbol>>,
+ }
+
+ impl Decodable for BacktraceFrame {
+ fn decode<D>(d: &mut D) -> Result<Self, D::Error>
+ where
+ D: Decoder,
+ {
+ let frame: SerializedFrame = SerializedFrame::decode(d)?;
+ Ok(BacktraceFrame {
+ frame: Frame::Deserialized {
+ ip: frame.ip,
+ symbol_address: frame.symbol_address,
+ module_base_address: frame.module_base_address,
+ },
+ symbols: frame.symbols,
+ })
+ }
+ }
+
+ impl Encodable for BacktraceFrame {
+ fn encode<E>(&self, e: &mut E) -> Result<(), E::Error>
+ where
+ E: Encoder,
+ {
+ let BacktraceFrame { frame, symbols } = self;
+ SerializedFrame {
+ ip: frame.ip() as usize,
+ symbol_address: frame.symbol_address() as usize,
+ module_base_address: frame.module_base_address().map(|addr| addr as usize),
+ symbols: symbols.clone(),
+ }
+ .encode(e)
+ }
+ }
+}
+
+#[cfg(feature = "serde")]
+mod serde_impls {
+ use super::*;
+ use serde::de::Deserializer;
+ use serde::ser::Serializer;
+ use serde::{Deserialize, Serialize};
+
+ #[derive(Serialize, Deserialize)]
+ struct SerializedFrame {
+ ip: usize,
+ symbol_address: usize,
+ module_base_address: Option<usize>,
+ symbols: Option<Vec<BacktraceSymbol>>,
+ }
+
+ impl Serialize for BacktraceFrame {
+ fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let BacktraceFrame { frame, symbols } = self;
+ SerializedFrame {
+ ip: frame.ip() as usize,
+ symbol_address: frame.symbol_address() as usize,
+ module_base_address: frame.module_base_address().map(|addr| addr as usize),
+ symbols: symbols.clone(),
+ }
+ .serialize(s)
+ }
+ }
+
+ impl<'a> Deserialize<'a> for BacktraceFrame {
+ fn deserialize<D>(d: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'a>,
+ {
+ let frame: SerializedFrame = SerializedFrame::deserialize(d)?;
+ Ok(BacktraceFrame {
+ frame: Frame::Deserialized {
+ ip: frame.ip,
+ symbol_address: frame.symbol_address,
+ module_base_address: frame.module_base_address,
+ },
+ symbols: frame.symbols,
+ })
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_frame_conversion() {
+ let mut frames = vec![];
+ crate::trace(|frame| {
+ let converted = BacktraceFrame::from(frame.clone());
+ frames.push(converted);
+ true
+ });
+
+ let mut manual = Backtrace::from(frames);
+ manual.resolve();
+ let frames = manual.frames();
+
+ for frame in frames {
+ println!("{:?}", frame.ip());
+ println!("{:?}", frame.symbol_address());
+ println!("{:?}", frame.module_base_address());
+ println!("{:?}", frame.symbols());
+ }
+ }
+}
diff --git a/library/backtrace/src/dbghelp.rs b/library/backtrace/src/dbghelp.rs
new file mode 100644
index 000000000..e01002beb
--- /dev/null
+++ b/library/backtrace/src/dbghelp.rs
@@ -0,0 +1,351 @@
+//! A module to assist in managing dbghelp bindings on Windows
+//!
+//! Backtraces on Windows (at least for MSVC) are largely powered through
+//! `dbghelp.dll` and the various functions that it contains. These functions
+//! are currently loaded *dynamically* rather than linking to `dbghelp.dll`
+//! statically. This is currently done by the standard library (and is in theory
+//! required there), but is an effort to help reduce the static dll dependencies
+//! of a library since backtraces are typically pretty optional. That being
+//! said, `dbghelp.dll` almost always successfully loads on Windows.
+//!
+//! Note though that since we're loading all this support dynamically we can't
+//! actually use the raw definitions in `winapi`, but rather we need to define
+//! the function pointer types ourselves and use that. We don't really want to
+//! be in the business of duplicating winapi, so we have a Cargo feature
+//! `verify-winapi` which asserts that all bindings match those in winapi and
+//! this feature is enabled on CI.
+//!
+//! Finally, you'll note here that the dll for `dbghelp.dll` is never unloaded,
+//! and that's currently intentional. The thinking is that we can globally cache
+//! it and use it between calls to the API, avoiding expensive loads/unloads. If
+//! this is a problem for leak detectors or something like that we can cross the
+//! bridge when we get there.
+
+#![allow(non_snake_case)]
+
+use super::windows::*;
+use core::mem;
+use core::ptr;
+
+// Work around `SymGetOptions` and `SymSetOptions` not being present in winapi
+// itself. Otherwise this is only used when we're double-checking types against
+// winapi.
+#[cfg(feature = "verify-winapi")]
+mod dbghelp {
+ use crate::windows::*;
+ pub use winapi::um::dbghelp::{
+ StackWalk64, StackWalkEx, SymCleanup, SymFromAddrW, SymFunctionTableAccess64,
+ SymGetLineFromAddrW64, SymGetModuleBase64, SymGetOptions, SymInitializeW, SymSetOptions,
+ };
+
+ extern "system" {
+ // Not defined in winapi yet
+ pub fn SymFromInlineContextW(
+ hProcess: HANDLE,
+ Address: DWORD64,
+ InlineContext: ULONG,
+ Displacement: PDWORD64,
+ Symbol: PSYMBOL_INFOW,
+ ) -> BOOL;
+ pub fn SymGetLineFromInlineContextW(
+ hProcess: HANDLE,
+ dwAddr: DWORD64,
+ InlineContext: ULONG,
+ qwModuleBaseAddress: DWORD64,
+ pdwDisplacement: PDWORD,
+ Line: PIMAGEHLP_LINEW64,
+ ) -> BOOL;
+ }
+
+ pub fn assert_equal_types<T>(a: T, _b: T) -> T {
+ a
+ }
+}
+
+// This macro is used to define a `Dbghelp` structure which internally contains
+// all the function pointers that we might load.
+macro_rules! dbghelp {
+ (extern "system" {
+ $(fn $name:ident($($arg:ident: $argty:ty),*) -> $ret: ty;)*
+ }) => (
+ pub struct Dbghelp {
+ /// The loaded DLL for `dbghelp.dll`
+ dll: HMODULE,
+
+ // Each function pointer for each function we might use
+ $($name: usize,)*
+ }
+
+ static mut DBGHELP: Dbghelp = Dbghelp {
+ // Initially we haven't loaded the DLL
+ dll: 0 as *mut _,
+ // Initiall all functions are set to zero to say they need to be
+ // dynamically loaded.
+ $($name: 0,)*
+ };
+
+ // Convenience typedef for each function type.
+ $(pub type $name = unsafe extern "system" fn($($argty),*) -> $ret;)*
+
+ impl Dbghelp {
+ /// Attempts to open `dbghelp.dll`. Returns success if it works or
+ /// error if `LoadLibraryW` fails.
+ ///
+ /// Panics if library is already loaded.
+ fn ensure_open(&mut self) -> Result<(), ()> {
+ if !self.dll.is_null() {
+ return Ok(())
+ }
+ let lib = b"dbghelp.dll\0";
+ unsafe {
+ self.dll = LoadLibraryA(lib.as_ptr() as *const i8);
+ if self.dll.is_null() {
+ Err(())
+ } else {
+ Ok(())
+ }
+ }
+ }
+
+ // Function for each method we'd like to use. When called it will
+ // either read the cached function pointer or load it and return the
+ // loaded value. Loads are asserted to succeed.
+ $(pub fn $name(&mut self) -> Option<$name> {
+ unsafe {
+ if self.$name == 0 {
+ let name = concat!(stringify!($name), "\0");
+ self.$name = self.symbol(name.as_bytes())?;
+ }
+ let ret = mem::transmute::<usize, $name>(self.$name);
+ #[cfg(feature = "verify-winapi")]
+ dbghelp::assert_equal_types(ret, dbghelp::$name);
+ Some(ret)
+ }
+ })*
+
+ fn symbol(&self, symbol: &[u8]) -> Option<usize> {
+ unsafe {
+ match GetProcAddress(self.dll, symbol.as_ptr() as *const _) as usize {
+ 0 => None,
+ n => Some(n),
+ }
+ }
+ }
+ }
+
+ // Convenience proxy to use the cleanup locks to reference dbghelp
+ // functions.
+ #[allow(dead_code)]
+ impl Init {
+ $(pub fn $name(&self) -> $name {
+ unsafe {
+ DBGHELP.$name().unwrap()
+ }
+ })*
+
+ pub fn dbghelp(&self) -> *mut Dbghelp {
+ unsafe {
+ &mut DBGHELP
+ }
+ }
+ }
+ )
+
+}
+
+const SYMOPT_DEFERRED_LOADS: DWORD = 0x00000004;
+
+dbghelp! {
+ extern "system" {
+ fn SymGetOptions() -> DWORD;
+ fn SymSetOptions(options: DWORD) -> DWORD;
+ fn SymInitializeW(
+ handle: HANDLE,
+ path: PCWSTR,
+ invade: BOOL
+ ) -> BOOL;
+ fn SymCleanup(handle: HANDLE) -> BOOL;
+ fn StackWalk64(
+ MachineType: DWORD,
+ hProcess: HANDLE,
+ hThread: HANDLE,
+ StackFrame: LPSTACKFRAME64,
+ ContextRecord: PVOID,
+ ReadMemoryRoutine: PREAD_PROCESS_MEMORY_ROUTINE64,
+ FunctionTableAccessRoutine: PFUNCTION_TABLE_ACCESS_ROUTINE64,
+ GetModuleBaseRoutine: PGET_MODULE_BASE_ROUTINE64,
+ TranslateAddress: PTRANSLATE_ADDRESS_ROUTINE64
+ ) -> BOOL;
+ fn SymFunctionTableAccess64(
+ hProcess: HANDLE,
+ AddrBase: DWORD64
+ ) -> PVOID;
+ fn SymGetModuleBase64(
+ 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,
+ hThread: HANDLE,
+ StackFrame: LPSTACKFRAME_EX,
+ ContextRecord: PVOID,
+ ReadMemoryRoutine: PREAD_PROCESS_MEMORY_ROUTINE64,
+ FunctionTableAccessRoutine: PFUNCTION_TABLE_ACCESS_ROUTINE64,
+ GetModuleBaseRoutine: PGET_MODULE_BASE_ROUTINE64,
+ TranslateAddress: PTRANSLATE_ADDRESS_ROUTINE64,
+ Flags: DWORD
+ ) -> BOOL;
+ fn SymFromInlineContextW(
+ hProcess: HANDLE,
+ Address: DWORD64,
+ InlineContext: ULONG,
+ Displacement: PDWORD64,
+ Symbol: PSYMBOL_INFOW
+ ) -> BOOL;
+ fn SymGetLineFromInlineContextW(
+ hProcess: HANDLE,
+ dwAddr: DWORD64,
+ InlineContext: ULONG,
+ qwModuleBaseAddress: DWORD64,
+ pdwDisplacement: PDWORD,
+ Line: PIMAGEHLP_LINEW64
+ ) -> BOOL;
+ }
+}
+
+pub struct Init {
+ lock: HANDLE,
+}
+
+/// Initialize all support necessary to access `dbghelp` API functions from this
+/// crate.
+///
+/// Note that this function is **safe**, it internally has its own
+/// synchronization. Also note that it is safe to call this function multiple
+/// times recursively.
+pub fn init() -> Result<Init, ()> {
+ use core::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+
+ unsafe {
+ // First thing we need to do is to synchronize this function. This can
+ // be called concurrently from other threads or recursively within one
+ // thread. Note that it's trickier than that though because what we're
+ // using here, `dbghelp`, *also* needs to be synchronized with all other
+ // callers to `dbghelp` in this process.
+ //
+ // Typically there aren't really that many calls to `dbghelp` within the
+ // same process and we can probably safely assume that we're the only
+ // ones accessing it. There is, however, one primary other user we have
+ // to worry about which is ironically ourselves, but in the standard
+ // library. The Rust standard library depends on this crate for
+ // backtrace support, and this crate also exists on crates.io. This
+ // means that if the standard library is printing a panic backtrace it
+ // may race with this crate coming from crates.io, causing segfaults.
+ //
+ // To help solve this synchronization problem we employ a
+ // Windows-specific trick here (it is, after all, a Windows-specific
+ // restriction about synchronization). We create a *session-local* named
+ // mutex to protect this call. The intention here is that the standard
+ // library and this crate don't have to share Rust-level APIs to
+ // synchronize here but can instead work behind the scenes to make sure
+ // they're synchronizing with one another. That way when this function
+ // is called through the standard library or through crates.io we can be
+ // sure that the same mutex is being acquired.
+ //
+ // So all of that is to say that the first thing we do here is we
+ // atomically create a `HANDLE` which is a named mutex on Windows. We
+ // synchronize a bit with other threads sharing this function
+ // specifically and ensure that only one handle is created per instance
+ // of this function. Note that the handle is never closed once it's
+ // stored in the global.
+ //
+ // After we've actually go the lock we simply acquire it, and our `Init`
+ // handle we hand out will be responsible for dropping it eventually.
+ static LOCK: AtomicUsize = AtomicUsize::new(0);
+ let mut lock = LOCK.load(SeqCst);
+ if lock == 0 {
+ lock = CreateMutexA(
+ ptr::null_mut(),
+ 0,
+ "Local\\RustBacktraceMutex\0".as_ptr() as _,
+ ) as usize;
+ if lock == 0 {
+ return Err(());
+ }
+ if let Err(other) = LOCK.compare_exchange(0, lock, SeqCst, SeqCst) {
+ debug_assert!(other != 0);
+ CloseHandle(lock as HANDLE);
+ lock = other;
+ }
+ }
+ debug_assert!(lock != 0);
+ let lock = lock as HANDLE;
+ let r = WaitForSingleObjectEx(lock, INFINITE, FALSE);
+ debug_assert_eq!(r, 0);
+ let ret = Init { lock };
+
+ // Ok, phew! Now that we're all safely synchronized, let's actually
+ // start processing everything. First up we need to ensure that
+ // `dbghelp.dll` is actually loaded in this process. We do this
+ // dynamically to avoid a static dependency. This has historically been
+ // done to work around weird linking issues and is intended at making
+ // binaries a bit more portable since this is largely just a debugging
+ // utility.
+ //
+ // Once we've opened `dbghelp.dll` we need to call some initialization
+ // functions in it, and that's detailed more below. We only do this
+ // once, though, so we've got a global boolean indicating whether we're
+ // done yet or not.
+ DBGHELP.ensure_open()?;
+
+ static mut INITIALIZED: bool = false;
+ if INITIALIZED {
+ return Ok(ret);
+ }
+
+ let orig = DBGHELP.SymGetOptions().unwrap()();
+
+ // Ensure that the `SYMOPT_DEFERRED_LOADS` flag is set, because
+ // according to MSVC's own docs about this: "This is the fastest, most
+ // efficient way to use the symbol handler.", so let's do that!
+ DBGHELP.SymSetOptions().unwrap()(orig | SYMOPT_DEFERRED_LOADS);
+
+ // Actually initialize symbols with MSVC. Note that this can fail, but we
+ // ignore it. There's not a ton of prior art for this per se, but LLVM
+ // internally seems to ignore the return value here and one of the
+ // sanitizer libraries in LLVM prints a scary warning if this fails but
+ // basically ignores it in the long run.
+ //
+ // One case this comes up a lot for Rust is that the standard library and
+ // this crate on crates.io both want to compete for `SymInitializeW`. The
+ // standard library historically wanted to initialize then cleanup most of
+ // the time, but now that it's using this crate it means that someone will
+ // get to initialization first and the other will pick up that
+ // initialization.
+ DBGHELP.SymInitializeW().unwrap()(GetCurrentProcess(), ptr::null_mut(), TRUE);
+ INITIALIZED = true;
+ Ok(ret)
+ }
+}
+
+impl Drop for Init {
+ fn drop(&mut self) {
+ unsafe {
+ let r = ReleaseMutex(self.lock);
+ debug_assert!(r != 0);
+ }
+ }
+}
diff --git a/library/backtrace/src/lib.rs b/library/backtrace/src/lib.rs
new file mode 100644
index 000000000..e5dea3387
--- /dev/null
+++ b/library/backtrace/src/lib.rs
@@ -0,0 +1,193 @@
+//! A library for acquiring a backtrace at runtime
+//!
+//! This library is meant to supplement the `RUST_BACKTRACE=1` support of the
+//! standard library by allowing an acquisition of a backtrace at runtime
+//! programmatically. The backtraces generated by this library do not need to be
+//! parsed, for example, and expose the functionality of multiple backend
+//! implementations.
+//!
+//! # Usage
+//!
+//! First, add this to your Cargo.toml
+//!
+//! ```toml
+//! [dependencies]
+//! backtrace = "0.3"
+//! ```
+//!
+//! Next:
+//!
+//! ```
+//! fn main() {
+//! # // Unsafe here so test passes on no_std.
+//! # #[cfg(feature = "std")] {
+//! backtrace::trace(|frame| {
+//! let ip = frame.ip();
+//! let symbol_address = frame.symbol_address();
+//!
+//! // Resolve this instruction pointer to a symbol name
+//! backtrace::resolve_frame(frame, |symbol| {
+//! if let Some(name) = symbol.name() {
+//! // ...
+//! }
+//! if let Some(filename) = symbol.filename() {
+//! // ...
+//! }
+//! });
+//!
+//! true // keep going to the next frame
+//! });
+//! }
+//! # }
+//! ```
+//!
+//! # Backtrace accuracy
+//!
+//! This crate implements best-effort attempts to get the native backtrace. This
+//! is not always guaranteed to work, and some platforms don't return any
+//! backtrace at all. If your application requires accurate backtraces then it's
+//! recommended to closely evaluate this crate to see whether it's suitable
+//! for your use case on your target platforms.
+//!
+//! Even on supported platforms, there's a number of reasons that backtraces may
+//! be less-than-accurate, including but not limited to:
+//!
+//! * Unwind information may not be available. This crate primarily implements
+//! backtraces by unwinding the stack, but not all functions may have
+//! unwinding information (e.g. DWARF unwinding information).
+//!
+//! * Rust code may be compiled without unwinding information for some
+//! functions. This can also happen for Rust code compiled with
+//! `-Cpanic=abort`. You can remedy this, however, with
+//! `-Cforce-unwind-tables` as a compiler option.
+//!
+//! * Unwind information may be inaccurate or corrupt. In the worst case
+//! inaccurate unwind information can lead this library to segfault. In the
+//! best case inaccurate information will result in a truncated stack trace.
+//!
+//! * Backtraces may not report filenames/line numbers correctly due to missing
+//! or corrupt debug information. This won't lead to segfaults unlike corrupt
+//! unwinding information, but missing or malformed debug information will
+//! mean that filenames and line numbers will not be available. This may be
+//! because debug information wasn't generated by the compiler, or it's just
+//! missing on the filesystem.
+//!
+//! * Not all platforms are supported. For example there's no way to get a
+//! backtrace on WebAssembly at the moment.
+//!
+//! * Crate features may be disabled. Currently this crate supports using Gimli
+//! libbacktrace on non-Windows platforms for reading debuginfo for
+//! backtraces. If both crate features are disabled, however, then these
+//! platforms will generate a backtrace but be unable to generate symbols for
+//! it.
+//!
+//! In most standard workflows for most standard platforms you generally don't
+//! need to worry about these caveats. We'll try to fix ones where we can over
+//! time, but otherwise it's important to be aware of the limitations of
+//! unwinding-based backtraces!
+
+#![doc(html_root_url = "https://docs.rs/backtrace")]
+#![deny(missing_docs)]
+#![no_std]
+#![cfg_attr(
+ all(feature = "std", target_env = "sgx", target_vendor = "fortanix"),
+ feature(sgx_platform)
+)]
+#![warn(rust_2018_idioms)]
+// When we're building as part of libstd, silence all warnings since they're
+// irrelevant as this crate is developed out-of-tree.
+#![cfg_attr(backtrace_in_libstd, allow(warnings))]
+#![cfg_attr(not(feature = "std"), allow(dead_code))]
+// We know this is deprecated, it's only here for back-compat reasons.
+#![cfg_attr(feature = "rustc-serialize", allow(deprecated))]
+
+#[cfg(feature = "std")]
+#[macro_use]
+extern crate std;
+
+// This is only used for gimli right now, which is only used on some platforms, and miri
+// so don't worry if it's unused in other configurations.
+#[allow(unused_extern_crates)]
+extern crate alloc;
+
+pub use self::backtrace::{trace_unsynchronized, Frame};
+mod backtrace;
+
+pub use self::symbolize::resolve_frame_unsynchronized;
+pub use self::symbolize::{resolve_unsynchronized, Symbol, SymbolName};
+mod symbolize;
+
+pub use self::types::BytesOrWideString;
+mod types;
+
+#[cfg(feature = "std")]
+pub use self::symbolize::clear_symbol_cache;
+
+mod print;
+pub use print::{BacktraceFmt, BacktraceFrameFmt, PrintFmt};
+
+cfg_if::cfg_if! {
+ if #[cfg(feature = "std")] {
+ pub use self::backtrace::trace;
+ pub use self::symbolize::{resolve, resolve_frame};
+ pub use self::capture::{Backtrace, BacktraceFrame, BacktraceSymbol};
+ mod capture;
+ }
+}
+
+#[allow(dead_code)]
+struct Bomb {
+ enabled: bool,
+}
+
+#[allow(dead_code)]
+impl Drop for Bomb {
+ fn drop(&mut self) {
+ if self.enabled {
+ panic!("cannot panic during the backtrace function");
+ }
+ }
+}
+
+#[allow(dead_code)]
+#[cfg(feature = "std")]
+mod lock {
+ use std::boxed::Box;
+ use std::cell::Cell;
+ use std::sync::{Mutex, MutexGuard, Once};
+
+ pub struct LockGuard(Option<MutexGuard<'static, ()>>);
+
+ static mut LOCK: *mut Mutex<()> = 0 as *mut _;
+ static INIT: Once = Once::new();
+ thread_local!(static LOCK_HELD: Cell<bool> = Cell::new(false));
+
+ impl Drop for LockGuard {
+ fn drop(&mut self) {
+ if self.0.is_some() {
+ LOCK_HELD.with(|slot| {
+ assert!(slot.get());
+ slot.set(false);
+ });
+ }
+ }
+ }
+
+ pub fn lock() -> LockGuard {
+ if LOCK_HELD.with(|l| l.get()) {
+ return LockGuard(None);
+ }
+ LOCK_HELD.with(|s| s.set(true));
+ unsafe {
+ INIT.call_once(|| {
+ LOCK = Box::into_raw(Box::new(Mutex::new(())));
+ });
+ LockGuard(Some((*LOCK).lock().unwrap()))
+ }
+ }
+}
+
+#[cfg(all(windows, not(target_vendor = "uwp")))]
+mod dbghelp;
+#[cfg(windows)]
+mod windows;
diff --git a/library/backtrace/src/print.rs b/library/backtrace/src/print.rs
new file mode 100644
index 000000000..cc677122a
--- /dev/null
+++ b/library/backtrace/src/print.rs
@@ -0,0 +1,302 @@
+#[cfg(feature = "std")]
+use super::{BacktraceFrame, BacktraceSymbol};
+use super::{BytesOrWideString, Frame, SymbolName};
+use core::ffi::c_void;
+use core::fmt;
+
+const HEX_WIDTH: usize = 2 + 2 * core::mem::size_of::<usize>();
+
+#[cfg(target_os = "fuchsia")]
+mod fuchsia;
+
+/// A formatter for backtraces.
+///
+/// This type can be used to print a backtrace regardless of where the backtrace
+/// itself comes from. If you have a `Backtrace` type then its `Debug`
+/// implementation already uses this printing format.
+pub struct BacktraceFmt<'a, 'b> {
+ fmt: &'a mut fmt::Formatter<'b>,
+ frame_index: usize,
+ format: PrintFmt,
+ print_path:
+ &'a mut (dyn FnMut(&mut fmt::Formatter<'_>, BytesOrWideString<'_>) -> fmt::Result + 'b),
+}
+
+/// The styles of printing that we can print
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub enum PrintFmt {
+ /// Prints a terser backtrace which ideally only contains relevant information
+ Short,
+ /// Prints a backtrace that contains all possible information
+ Full,
+ #[doc(hidden)]
+ __Nonexhaustive,
+}
+
+impl<'a, 'b> BacktraceFmt<'a, 'b> {
+ /// Create a new `BacktraceFmt` which will write output to the provided
+ /// `fmt`.
+ ///
+ /// The `format` argument will control the style in which the backtrace is
+ /// printed, and the `print_path` argument will be used to print the
+ /// `BytesOrWideString` instances of filenames. This type itself doesn't do
+ /// any printing of filenames, but this callback is required to do so.
+ pub fn new(
+ fmt: &'a mut fmt::Formatter<'b>,
+ format: PrintFmt,
+ print_path: &'a mut (dyn FnMut(&mut fmt::Formatter<'_>, BytesOrWideString<'_>) -> fmt::Result
+ + 'b),
+ ) -> Self {
+ BacktraceFmt {
+ fmt,
+ frame_index: 0,
+ format,
+ print_path,
+ }
+ }
+
+ /// Prints a preamble for the backtrace about to be printed.
+ ///
+ /// This is required on some platforms for backtraces to be fully
+ /// symbolicated later, and otherwise this should just be the first method
+ /// you call after creating a `BacktraceFmt`.
+ pub fn add_context(&mut self) -> fmt::Result {
+ #[cfg(target_os = "fuchsia")]
+ fuchsia::print_dso_context(self.fmt)?;
+ Ok(())
+ }
+
+ /// Adds a frame to the backtrace output.
+ ///
+ /// This commit returns an RAII instance of a `BacktraceFrameFmt` which can be used
+ /// to actually print a frame, and on destruction it will increment the
+ /// frame counter.
+ pub fn frame(&mut self) -> BacktraceFrameFmt<'_, 'a, 'b> {
+ BacktraceFrameFmt {
+ fmt: self,
+ symbol_index: 0,
+ }
+ }
+
+ /// Completes the backtrace output.
+ ///
+ /// This is currently a no-op but is added for future compatibility with
+ /// backtrace formats.
+ pub fn finish(&mut self) -> fmt::Result {
+ // Currently a no-op-- including this hook to allow for future additions.
+ Ok(())
+ }
+}
+
+/// A formatter for just one frame of a backtrace.
+///
+/// This type is created by the `BacktraceFmt::frame` function.
+pub struct BacktraceFrameFmt<'fmt, 'a, 'b> {
+ fmt: &'fmt mut BacktraceFmt<'a, 'b>,
+ symbol_index: usize,
+}
+
+impl BacktraceFrameFmt<'_, '_, '_> {
+ /// Prints a `BacktraceFrame` with this frame formatter.
+ ///
+ /// This will recursively print all `BacktraceSymbol` instances within the
+ /// `BacktraceFrame`.
+ ///
+ /// # Required features
+ ///
+ /// This function requires the `std` feature of the `backtrace` crate to be
+ /// enabled, and the `std` feature is enabled by default.
+ #[cfg(feature = "std")]
+ pub fn backtrace_frame(&mut self, frame: &BacktraceFrame) -> fmt::Result {
+ let symbols = frame.symbols();
+ for symbol in symbols {
+ self.backtrace_symbol(frame, symbol)?;
+ }
+ if symbols.is_empty() {
+ self.print_raw(frame.ip(), None, None, None)?;
+ }
+ Ok(())
+ }
+
+ /// Prints a `BacktraceSymbol` within a `BacktraceFrame`.
+ ///
+ /// # Required features
+ ///
+ /// This function requires the `std` feature of the `backtrace` crate to be
+ /// enabled, and the `std` feature is enabled by default.
+ #[cfg(feature = "std")]
+ pub fn backtrace_symbol(
+ &mut self,
+ frame: &BacktraceFrame,
+ symbol: &BacktraceSymbol,
+ ) -> fmt::Result {
+ self.print_raw_with_column(
+ frame.ip(),
+ symbol.name(),
+ // TODO: this isn't great that we don't end up printing anything
+ // with non-utf8 filenames. Thankfully almost everything is utf8 so
+ // this shouldn't be too too bad.
+ symbol
+ .filename()
+ .and_then(|p| Some(BytesOrWideString::Bytes(p.to_str()?.as_bytes()))),
+ symbol.lineno(),
+ symbol.colno(),
+ )?;
+ Ok(())
+ }
+
+ /// Prints a raw traced `Frame` and `Symbol`, typically from within the raw
+ /// callbacks of this crate.
+ pub fn symbol(&mut self, frame: &Frame, symbol: &super::Symbol) -> fmt::Result {
+ self.print_raw_with_column(
+ frame.ip(),
+ symbol.name(),
+ symbol.filename_raw(),
+ symbol.lineno(),
+ symbol.colno(),
+ )?;
+ Ok(())
+ }
+
+ /// Adds a raw frame to the backtrace output.
+ ///
+ /// This method, unlike the previous, takes the raw arguments in case
+ /// they're being source from different locations. Note that this may be
+ /// called multiple times for one frame.
+ pub fn print_raw(
+ &mut self,
+ frame_ip: *mut c_void,
+ symbol_name: Option<SymbolName<'_>>,
+ filename: Option<BytesOrWideString<'_>>,
+ lineno: Option<u32>,
+ ) -> fmt::Result {
+ self.print_raw_with_column(frame_ip, symbol_name, filename, lineno, None)
+ }
+
+ /// Adds a raw frame to the backtrace output, including column information.
+ ///
+ /// This method, like the previous, takes the raw arguments in case
+ /// they're being source from different locations. Note that this may be
+ /// called multiple times for one frame.
+ pub fn print_raw_with_column(
+ &mut self,
+ frame_ip: *mut c_void,
+ symbol_name: Option<SymbolName<'_>>,
+ filename: Option<BytesOrWideString<'_>>,
+ lineno: Option<u32>,
+ colno: Option<u32>,
+ ) -> fmt::Result {
+ // Fuchsia is unable to symbolize within a process so it has a special
+ // format which can be used to symbolize later. Print that instead of
+ // printing addresses in our own format here.
+ if cfg!(target_os = "fuchsia") {
+ self.print_raw_fuchsia(frame_ip)?;
+ } else {
+ self.print_raw_generic(frame_ip, symbol_name, filename, lineno, colno)?;
+ }
+ self.symbol_index += 1;
+ Ok(())
+ }
+
+ #[allow(unused_mut)]
+ fn print_raw_generic(
+ &mut self,
+ mut frame_ip: *mut c_void,
+ symbol_name: Option<SymbolName<'_>>,
+ filename: Option<BytesOrWideString<'_>>,
+ lineno: Option<u32>,
+ colno: Option<u32>,
+ ) -> fmt::Result {
+ // No need to print "null" frames, it basically just means that the
+ // system backtrace was a bit eager to trace back super far.
+ if let PrintFmt::Short = self.fmt.format {
+ if frame_ip.is_null() {
+ return Ok(());
+ }
+ }
+
+ // 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)?;
+ }
+ } else {
+ write!(self.fmt.fmt, " ")?;
+ if let PrintFmt::Full = self.fmt.format {
+ write!(self.fmt.fmt, "{:1$}", "", HEX_WIDTH + 3)?;
+ }
+ }
+
+ // Next up write out the symbol name, using the alternate formatting for
+ // 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)?,
+ (None, _) | (_, PrintFmt::__Nonexhaustive) => write!(self.fmt.fmt, "<unknown>")?,
+ }
+ self.fmt.fmt.write_str("\n")?;
+
+ // And last up, print out the filename/line number if they're available.
+ if let (Some(file), Some(line)) = (filename, lineno) {
+ self.print_fileline(file, line, colno)?;
+ }
+
+ Ok(())
+ }
+
+ fn print_fileline(
+ &mut self,
+ file: BytesOrWideString<'_>,
+ line: u32,
+ colno: Option<u32>,
+ ) -> fmt::Result {
+ // Filename/line are printed on lines under the symbol name, so print
+ // some appropriate whitespace to sort of right-align ourselves.
+ if let PrintFmt::Full = self.fmt.format {
+ write!(self.fmt.fmt, "{:1$}", "", HEX_WIDTH)?;
+ }
+ write!(self.fmt.fmt, " at ")?;
+
+ // 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)?;
+
+ // Add column number, if available.
+ if let Some(colno) = colno {
+ write!(self.fmt.fmt, ":{}", colno)?;
+ }
+
+ write!(self.fmt.fmt, "\n")?;
+ Ok(())
+ }
+
+ fn print_raw_fuchsia(&mut self, frame_ip: *mut c_void) -> fmt::Result {
+ // We only care about the first symbol of a frame
+ if self.symbol_index == 0 {
+ self.fmt.fmt.write_str("{{{bt:")?;
+ write!(self.fmt.fmt, "{}:{:?}", self.fmt.frame_index, frame_ip)?;
+ self.fmt.fmt.write_str("}}}\n")?;
+ }
+ Ok(())
+ }
+}
+
+impl Drop for BacktraceFrameFmt<'_, '_, '_> {
+ fn drop(&mut self) {
+ self.fmt.frame_index += 1;
+ }
+}
diff --git a/library/backtrace/src/print/fuchsia.rs b/library/backtrace/src/print/fuchsia.rs
new file mode 100644
index 000000000..959253acb
--- /dev/null
+++ b/library/backtrace/src/print/fuchsia.rs
@@ -0,0 +1,436 @@
+use core::fmt::{self, Write};
+use core::mem::{size_of, transmute};
+use core::slice::from_raw_parts;
+use libc::c_char;
+
+extern "C" {
+ // dl_iterate_phdr takes a callback that will receive a dl_phdr_info pointer
+ // for every DSO that has been linked into the process. dl_iterate_phdr also
+ // ensures that the dynamic linker is locked from start to finish of the
+ // iteration. If the callback returns a non-zero value the iteration is
+ // terminated early. 'data' will be passed as the third argument to the
+ // callback on each call. 'size' gives the size of the dl_phdr_info.
+ #[allow(improper_ctypes)]
+ fn dl_iterate_phdr(
+ f: extern "C" fn(info: &dl_phdr_info, size: usize, data: &mut DsoPrinter<'_, '_>) -> i32,
+ data: &mut DsoPrinter<'_, '_>,
+ ) -> i32;
+}
+
+// We need to parse out the build ID and some basic program header data
+// which means that we need a bit of stuff from the ELF spec as well.
+
+const PT_LOAD: u32 = 1;
+const PT_NOTE: u32 = 4;
+
+// Now we have to replicate, bit for bit, the structure of the dl_phdr_info
+// type used by fuchsia's current dynamic linker. Chromium also has this ABI
+// boundary as well as crashpad. Eventully we'd like to move these cases to
+// use elf-search but we'd need to provide that in the SDK and that has not
+// yet been done. Thus we (and they) are stuck having to use this method
+// which incurs a tight coupling with the fuchsia libc.
+
+#[allow(non_camel_case_types)]
+#[repr(C)]
+struct dl_phdr_info {
+ addr: *const u8,
+ name: *const c_char,
+ phdr: *const Elf_Phdr,
+ phnum: u16,
+ adds: u64,
+ subs: u64,
+ tls_modid: usize,
+ tls_data: *const u8,
+}
+
+impl dl_phdr_info {
+ fn program_headers(&self) -> PhdrIter<'_> {
+ PhdrIter {
+ phdrs: self.phdr_slice(),
+ base: self.addr,
+ }
+ }
+ // We have no way of knowing of checking if e_phoff and e_phnum are valid.
+ // libc should ensure this for us however so it's safe to form a slice here.
+ fn phdr_slice(&self) -> &[Elf_Phdr] {
+ unsafe { from_raw_parts(self.phdr, self.phnum as usize) }
+ }
+}
+
+struct PhdrIter<'a> {
+ phdrs: &'a [Elf_Phdr],
+ base: *const u8,
+}
+
+impl<'a> Iterator for PhdrIter<'a> {
+ type Item = Phdr<'a>;
+ fn next(&mut self) -> Option<Self::Item> {
+ self.phdrs.split_first().map(|(phdr, new_phdrs)| {
+ self.phdrs = new_phdrs;
+ Phdr {
+ phdr,
+ base: self.base,
+ }
+ })
+ }
+}
+
+// Elf_Phdr represents a 64-bit ELF program header in the endianness of the target
+// architecture.
+#[allow(non_camel_case_types)]
+#[derive(Clone, Debug)]
+#[repr(C)]
+struct Elf_Phdr {
+ p_type: u32,
+ p_flags: u32,
+ p_offset: u64,
+ p_vaddr: u64,
+ p_paddr: u64,
+ p_filesz: u64,
+ p_memsz: u64,
+ p_align: u64,
+}
+
+// Phdr represents a valid ELF program header and its contents.
+struct Phdr<'a> {
+ phdr: &'a Elf_Phdr,
+ base: *const u8,
+}
+
+impl<'a> Phdr<'a> {
+ // We have no way of checking if p_addr or p_memsz are valid. Fuchsia's libc
+ // parses the notes first however so by virtue of being here these headers
+ // must be valid. NoteIter does not require the underlying data to be valid
+ // but it does require the bounds to be valid. We trust that libc has ensured
+ // that this is the case for us here.
+ fn notes(&self) -> NoteIter<'a> {
+ unsafe {
+ NoteIter::new(
+ self.base.add(self.phdr.p_offset as usize),
+ self.phdr.p_memsz as usize,
+ )
+ }
+ }
+}
+
+// The note type for build IDs.
+const NT_GNU_BUILD_ID: u32 = 3;
+
+// Elf_Nhdr represents an ELF note header in the endianness of the target.
+#[allow(non_camel_case_types)]
+#[repr(C)]
+struct Elf_Nhdr {
+ n_namesz: u32,
+ n_descsz: u32,
+ n_type: u32,
+}
+
+// Note represents an ELF note (header + contents). The name is left as a u8
+// slice because it is not always null terminated and rust makes it easy enough
+// to check that the bytes match eitherway.
+struct Note<'a> {
+ name: &'a [u8],
+ desc: &'a [u8],
+ tipe: u32,
+}
+
+// NoteIter lets you safely iterate over a note segment. It terminates as soon
+// as an error occurs or there are no more notes. If you iterate over invalid
+// data it will function as though no notes were found.
+struct NoteIter<'a> {
+ base: &'a [u8],
+ error: bool,
+}
+
+impl<'a> NoteIter<'a> {
+ // It is an invariant of function that the pointer and size given denote a
+ // valid range of bytes that can all be read. The contents of these bytes
+ // can be anything but the range must be valid for this to be safe.
+ unsafe fn new(base: *const u8, size: usize) -> Self {
+ NoteIter {
+ base: from_raw_parts(base, size),
+ error: false,
+ }
+ }
+}
+
+// align_to aligns 'x' to 'to'-byte alignment assuming 'to' is a power of 2.
+// This follows a standard pattern in C/C++ ELF parsing code where
+// (x + to - 1) & -to is used. Rust does not let you negate usize so I use
+// 2's-complement conversion to recreate that.
+fn align_to(x: usize, to: usize) -> usize {
+ (x + to - 1) & (!to + 1)
+}
+
+// take_bytes_align4 consumes num bytes from the slice (if present) and
+// additionally ensures that the final slice is properlly aligned. If an
+// either the number of bytes requested is too large or the slice can't be
+// realigned afterwards due to not enough remaining bytes existing, None is
+// returned and the slice is not modified.
+fn take_bytes_align4<'a>(num: usize, bytes: &mut &'a [u8]) -> Option<&'a [u8]> {
+ if bytes.len() < align_to(num, 4) {
+ return None;
+ }
+ let (out, bytes_new) = bytes.split_at(num);
+ *bytes = &bytes_new[align_to(num, 4) - num..];
+ Some(out)
+}
+
+// This function has no real invariants the caller must uphold other than
+// perhaps that 'bytes' should be aligned for performance (and on some
+// architectures correctness). The values in the Elf_Nhdr fields might
+// be nonsense but this function ensures no such thing.
+fn take_nhdr<'a>(bytes: &mut &'a [u8]) -> Option<&'a Elf_Nhdr> {
+ if size_of::<Elf_Nhdr>() > bytes.len() {
+ return None;
+ }
+ // This is safe as long as there is enough space and we just confirmed that
+ // in the if statement above so this should not be unsafe.
+ let out = unsafe { transmute::<*const u8, &'a Elf_Nhdr>(bytes.as_ptr()) };
+ // Note that sice_of::<Elf_Nhdr>() is always 4-byte aligned.
+ *bytes = &bytes[size_of::<Elf_Nhdr>()..];
+ Some(out)
+}
+
+impl<'a> Iterator for NoteIter<'a> {
+ type Item = Note<'a>;
+ fn next(&mut self) -> Option<Self::Item> {
+ // Check if we've reached the end.
+ if self.base.len() == 0 || self.error {
+ return None;
+ }
+ // We transmute out an nhdr but we carefully consider the resulting
+ // struct. We don't trust the namesz or descsz and we make no unsafe
+ // decisions based on the type. So even if we get out complete garbage
+ // we should still be safe.
+ let nhdr = take_nhdr(&mut self.base)?;
+ let name = take_bytes_align4(nhdr.n_namesz as usize, &mut self.base)?;
+ let desc = take_bytes_align4(nhdr.n_descsz as usize, &mut self.base)?;
+ Some(Note {
+ name: name,
+ desc: desc,
+ tipe: nhdr.n_type,
+ })
+ }
+}
+
+struct Perm(u32);
+
+/// Indicates that a segment is executable.
+const PERM_X: u32 = 0b00000001;
+/// Indicates that a segment is writable.
+const PERM_W: u32 = 0b00000010;
+/// Indicates that a segment is readable.
+const PERM_R: u32 = 0b00000100;
+
+impl core::fmt::Display for Perm {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let v = self.0;
+ if v & PERM_R != 0 {
+ f.write_char('r')?
+ }
+ if v & PERM_W != 0 {
+ f.write_char('w')?
+ }
+ if v & PERM_X != 0 {
+ f.write_char('x')?
+ }
+ Ok(())
+ }
+}
+
+/// Represents an ELF segment at runtime.
+struct Segment {
+ /// Gives the runtime virtual address of this segment's contents.
+ addr: usize,
+ /// Gives the memory size of this segment's contents.
+ size: usize,
+ /// Gives the module virtual address of this segment with the ELF file.
+ mod_rel_addr: usize,
+ /// Gives the permissions found in the ELF file. These permissions are not
+ /// necessarily the permissions present at runtime however.
+ flags: Perm,
+}
+
+/// Lets one iterate over Segments from a DSO.
+struct SegmentIter<'a> {
+ phdrs: &'a [Elf_Phdr],
+ base: usize,
+}
+
+impl Iterator for SegmentIter<'_> {
+ type Item = Segment;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.phdrs.split_first().and_then(|(phdr, new_phdrs)| {
+ self.phdrs = new_phdrs;
+ if phdr.p_type != PT_LOAD {
+ self.next()
+ } else {
+ Some(Segment {
+ addr: phdr.p_vaddr as usize + self.base,
+ size: phdr.p_memsz as usize,
+ mod_rel_addr: phdr.p_vaddr as usize,
+ flags: Perm(phdr.p_flags),
+ })
+ }
+ })
+ }
+}
+
+/// Represents an ELF DSO (Dynamic Shared Object). This type references
+/// the data stored in the actual DSO rather than making its own copy.
+struct Dso<'a> {
+ /// The dynamic linker always gives us a name, even if the name is empty.
+ /// In the case of the main executable this name will be empty. In the case
+ /// of a shared object it will be the soname (see DT_SONAME).
+ name: &'a str,
+ /// On Fuchsia virtually all binaries have build IDs but this is not a strict
+ /// requirement. There's no way to match up DSO information with a real ELF
+ /// file afterwards if there is no build_id so we require that every DSO
+ /// have one here. DSO's without a build_id are ignored.
+ build_id: &'a [u8],
+
+ base: usize,
+ phdrs: &'a [Elf_Phdr],
+}
+
+impl Dso<'_> {
+ /// Returns an iterator over Segments in this DSO.
+ fn segments(&self) -> SegmentIter<'_> {
+ SegmentIter {
+ phdrs: self.phdrs.as_ref(),
+ base: self.base,
+ }
+ }
+}
+
+struct HexSlice<'a> {
+ bytes: &'a [u8],
+}
+
+impl fmt::Display for HexSlice<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ for byte in self.bytes {
+ write!(f, "{:02x}", byte)?;
+ }
+ Ok(())
+ }
+}
+
+fn get_build_id<'a>(info: &'a dl_phdr_info) -> Option<&'a [u8]> {
+ for phdr in info.program_headers() {
+ if phdr.phdr.p_type == PT_NOTE {
+ for note in phdr.notes() {
+ if note.tipe == NT_GNU_BUILD_ID && (note.name == b"GNU\0" || note.name == b"GNU") {
+ return Some(note.desc);
+ }
+ }
+ }
+ }
+ None
+}
+
+/// These errors encode issues that arise while parsing information about
+/// each DSO.
+enum Error {
+ /// NameError means that an error occurred while converting a C style string
+ /// into a rust string.
+ NameError(core::str::Utf8Error),
+ /// 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.
+ BuildIDError,
+}
+
+/// Calls either 'dso' or 'error' for each DSO linked into the process by the
+/// dynamic linker.
+///
+/// # Arguments
+///
+/// * `visitor` - A DsoPrinter that will have one of eats methods called foreach DSO.
+fn for_each_dso(mut visitor: &mut DsoPrinter<'_, '_>) {
+ extern "C" fn callback(
+ info: &dl_phdr_info,
+ _size: usize,
+ visitor: &mut DsoPrinter<'_, '_>,
+ ) -> i32 {
+ // dl_iterate_phdr ensures that info.name will point to a valid
+ // 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) };
+ let name = match core::str::from_utf8(name_slice) {
+ Ok(name) => name,
+ Err(err) => {
+ return visitor.error(Error::NameError(err)) as i32;
+ }
+ };
+ let build_id = match get_build_id(info) {
+ Some(build_id) => build_id,
+ None => {
+ return visitor.error(Error::BuildIDError) as i32;
+ }
+ };
+ visitor.dso(Dso {
+ name: name,
+ build_id: build_id,
+ phdrs: info.phdr_slice(),
+ base: info.addr as usize,
+ }) as i32
+ }
+ unsafe { dl_iterate_phdr(callback, &mut visitor) };
+}
+
+struct DsoPrinter<'a, 'b> {
+ writer: &'a mut core::fmt::Formatter<'b>,
+ module_count: usize,
+ error: core::fmt::Result,
+}
+
+impl DsoPrinter<'_, '_> {
+ fn dso(&mut self, dso: Dso<'_>) -> bool {
+ let mut write = || {
+ write!(
+ self.writer,
+ "{{{{{{module:{:#x}:{}:elf:{}}}}}}}\n",
+ self.module_count,
+ dso.name,
+ HexSlice {
+ bytes: dso.build_id.as_ref()
+ }
+ )?;
+ for seg in dso.segments() {
+ write!(
+ self.writer,
+ "{{{{{{mmap:{:#x}:{:#x}:load:{:#x}:{}:{:#x}}}}}}}\n",
+ seg.addr, seg.size, self.module_count, seg.flags, seg.mod_rel_addr
+ )?;
+ }
+ self.module_count += 1;
+ Ok(())
+ };
+ match write() {
+ Ok(()) => false,
+ Err(err) => {
+ self.error = Err(err);
+ true
+ }
+ }
+ }
+ fn error(&mut self, _error: Error) -> bool {
+ false
+ }
+}
+
+/// This function prints the Fuchsia symbolizer markup for all information contained in a DSO.
+pub fn print_dso_context(out: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ out.write_str("{{{reset}}}\n")?;
+ let mut visitor = DsoPrinter {
+ writer: out,
+ module_count: 0,
+ error: Ok(()),
+ };
+ for_each_dso(&mut visitor);
+ visitor.error
+}
diff --git a/library/backtrace/src/symbolize/dbghelp.rs b/library/backtrace/src/symbolize/dbghelp.rs
new file mode 100644
index 000000000..181dba731
--- /dev/null
+++ b/library/backtrace/src/symbolize/dbghelp.rs
@@ -0,0 +1,218 @@
+//! Symbolication strategy using `dbghelp.dll` on Windows, only used for MSVC
+//!
+//! This symbolication strategy, like with backtraces, uses dynamically loaded
+//! information from `dbghelp.dll`. (see `src/dbghelp.rs` for info about why
+//! it's dynamically loaded).
+//!
+//! This API selects its resolution strategy based on the frame provided or the
+//! information we have at hand. If a frame from `StackWalkEx` is given to us
+//! then we use similar APIs to generate correct information about inlined
+//! functions. Otherwise if all we have is an address or an older stack frame
+//! from `StackWalk64` we use the older APIs for symbolication.
+//!
+//! There's a good deal of support in this module, but a good chunk of it is
+//! converting back and forth between Windows types and Rust types. For example
+//! symbols come to us as wide strings which we then convert to utf-8 strings if
+//! we can.
+
+#![allow(bad_style)]
+
+use super::super::{backtrace::StackFrame, dbghelp, windows::*};
+use super::{BytesOrWideString, ResolveWhat, SymbolName};
+use core::char;
+use core::ffi::c_void;
+use core::marker;
+use core::mem;
+use core::slice;
+
+// Store an OsString on std so we can provide the symbol name and filename.
+pub struct Symbol<'a> {
+ name: *const [u8],
+ addr: *mut c_void,
+ line: Option<u32>,
+ filename: Option<*const [u16]>,
+ #[cfg(feature = "std")]
+ _filename_cache: Option<::std::ffi::OsString>,
+ #[cfg(not(feature = "std"))]
+ _filename_cache: (),
+ _marker: marker::PhantomData<&'a i32>,
+}
+
+impl Symbol<'_> {
+ pub fn name(&self) -> Option<SymbolName<'_>> {
+ Some(SymbolName::new(unsafe { &*self.name }))
+ }
+
+ pub fn addr(&self) -> Option<*mut c_void> {
+ Some(self.addr as *mut _)
+ }
+
+ pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
+ self.filename
+ .map(|slice| unsafe { BytesOrWideString::Wide(&*slice) })
+ }
+
+ pub fn colno(&self) -> Option<u32> {
+ None
+ }
+
+ pub fn lineno(&self) -> Option<u32> {
+ self.line
+ }
+
+ #[cfg(feature = "std")]
+ pub fn filename(&self) -> Option<&::std::path::Path> {
+ use std::path::Path;
+
+ self._filename_cache.as_ref().map(Path::new)
+ }
+}
+
+#[repr(C, align(8))]
+struct Aligned8<T>(T);
+
+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_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),
+ },
+ }
+}
+
+unsafe fn resolve_with_inline(
+ dbghelp: &dbghelp::Init,
+ frame: &STACKFRAME_EX,
+ cb: &mut dyn FnMut(&super::Symbol),
+) {
+ 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,
+ )
+ },
+ cb,
+ )
+}
+
+unsafe fn resolve_without_inline(
+ dbghelp: &dbghelp::Init,
+ addr: *mut c_void,
+ 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,
+ )
+}
+
+unsafe fn do_resolve(
+ sym_from_addr: impl FnOnce(*mut SYMBOL_INFOW) -> BOOL,
+ get_line_from_addr: impl FnOnce(&mut IMAGEHLP_LINEW64) -> BOOL,
+ cb: &mut dyn FnMut(&super::Symbol),
+) {
+ const SIZE: usize = 2 * MAX_SYM_NAME + mem::size_of::<SYMBOL_INFOW>();
+ let mut data = Aligned8([0u8; SIZE]);
+ let data = &mut data.0;
+ let info = &mut *(data.as_mut_ptr() as *mut SYMBOL_INFOW);
+ info.MaxNameLen = MAX_SYM_NAME as ULONG;
+ // the struct size in C. the value is different to
+ // `size_of::<SYMBOL_INFOW>() - MAX_SYM_NAME + 1` (== 81)
+ // due to struct alignment.
+ info.SizeOfStruct = 88;
+
+ if sym_from_addr(info) != TRUE {
+ return;
+ }
+
+ // If the symbol name is greater than MaxNameLen, SymFromAddrW will
+ // 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 = slice::from_raw_parts(name_ptr, name_len);
+
+ // Reencode the utf-16 symbol to utf-8 so we can use `SymbolName::new` like
+ // all other platforms
+ let mut name_len = 0;
+ let mut name_buffer = [0; 256];
+ {
+ let mut remaining = &mut name_buffer[..];
+ for c in char::decode_utf16(name.iter().cloned()) {
+ let c = c.unwrap_or(char::REPLACEMENT_CHARACTER);
+ let len = c.len_utf8();
+ if len < remaining.len() {
+ c.encode_utf8(remaining);
+ let tmp = remaining;
+ remaining = &mut tmp[len..];
+ name_len += len;
+ } else {
+ break;
+ }
+ }
+ }
+ let name = &name_buffer[..name_len] as *const [u8];
+
+ let mut line = mem::zeroed::<IMAGEHLP_LINEW64>();
+ line.SizeOfStruct = mem::size_of::<IMAGEHLP_LINEW64>() as DWORD;
+
+ let mut filename = None;
+ let mut lineno = None;
+ if get_line_from_addr(&mut line) == TRUE {
+ lineno = Some(line.LineNumber as u32);
+
+ let base = line.FileName;
+ let mut len = 0;
+ while *base.offset(len) != 0 {
+ len += 1;
+ }
+
+ let len = len as usize;
+
+ filename = Some(slice::from_raw_parts(base, len) as *const [u16]);
+ }
+
+ cb(&super::Symbol {
+ inner: Symbol {
+ name,
+ addr: info.Address as *mut _,
+ line: lineno,
+ filename,
+ _filename_cache: cache(filename),
+ _marker: marker::PhantomData,
+ },
+ })
+}
+
+#[cfg(feature = "std")]
+unsafe fn cache(filename: Option<*const [u16]>) -> Option<::std::ffi::OsString> {
+ use std::os::windows::ffi::OsStringExt;
+ filename.map(|f| ::std::ffi::OsString::from_wide(&*f))
+}
+
+#[cfg(not(feature = "std"))]
+unsafe fn cache(_filename: Option<*const [u16]>) {}
+
+pub unsafe fn clear_symbol_cache() {}
diff --git a/library/backtrace/src/symbolize/gimli.rs b/library/backtrace/src/symbolize/gimli.rs
new file mode 100644
index 000000000..5f10122dd
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli.rs
@@ -0,0 +1,462 @@
+//! Support for symbolication using the `gimli` crate on crates.io
+//!
+//! This is the default symbolication implementation for Rust.
+
+use self::gimli::read::EndianSlice;
+use self::gimli::NativeEndian as Endian;
+use self::mmap::Mmap;
+use self::stash::Stash;
+use super::BytesOrWideString;
+use super::ResolveWhat;
+use super::SymbolName;
+use addr2line::gimli;
+use core::convert::TryInto;
+use core::mem;
+use core::u32;
+use libc::c_void;
+use mystd::ffi::OsString;
+use mystd::fs::File;
+use mystd::path::Path;
+use mystd::prelude::v1::*;
+
+#[cfg(backtrace_in_libstd)]
+mod mystd {
+ pub use crate::*;
+}
+#[cfg(not(backtrace_in_libstd))]
+extern crate std as mystd;
+
+cfg_if::cfg_if! {
+ if #[cfg(windows)] {
+ #[path = "gimli/mmap_windows.rs"]
+ mod mmap;
+ } else if #[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "fuchsia",
+ target_os = "haiku",
+ target_os = "ios",
+ target_os = "linux",
+ target_os = "macos",
+ target_os = "openbsd",
+ target_os = "solaris",
+ target_os = "illumos",
+ ))] {
+ #[path = "gimli/mmap_unix.rs"]
+ mod mmap;
+ } else {
+ #[path = "gimli/mmap_fake.rs"]
+ mod mmap;
+ }
+}
+
+mod stash;
+
+const MAPPINGS_CACHE_SIZE: usize = 4;
+
+struct Mapping {
+ // 'static lifetime is a lie to hack around lack of support for self-referential structs.
+ cx: Context<'static>,
+ _map: Mmap,
+ _stash: Stash,
+}
+
+enum Either<A, B> {
+ #[allow(dead_code)]
+ A(A),
+ B(B),
+}
+
+impl Mapping {
+ /// Creates a `Mapping` by ensuring that the `data` specified is used to
+ /// create a `Context` and it can only borrow from that or the `Stash` of
+ /// decompressed sections or auxiliary data.
+ fn mk<F>(data: Mmap, mk: F) -> Option<Mapping>
+ where
+ F: for<'a> FnOnce(&'a [u8], &'a Stash) -> Option<Context<'a>>,
+ {
+ Mapping::mk_or_other(data, move |data, stash| {
+ let cx = mk(data, stash)?;
+ Some(Either::B(cx))
+ })
+ }
+
+ /// Creates a `Mapping` from `data`, or if the closure decides to, returns a
+ /// different mapping.
+ fn mk_or_other<F>(data: Mmap, mk: F) -> Option<Mapping>
+ where
+ F: for<'a> FnOnce(&'a [u8], &'a Stash) -> Option<Either<Mapping, Context<'a>>>,
+ {
+ let stash = Stash::new();
+ let cx = match mk(&data, &stash)? {
+ Either::A(mapping) => return Some(mapping),
+ Either::B(cx) => cx,
+ };
+ Some(Mapping {
+ // Convert to 'static lifetimes since the symbols should
+ // only borrow `map` and `stash` and we're preserving them below.
+ cx: unsafe { core::mem::transmute::<Context<'_>, Context<'static>>(cx) },
+ _map: data,
+ _stash: stash,
+ })
+ }
+}
+
+struct Context<'a> {
+ dwarf: addr2line::Context<EndianSlice<'a, Endian>>,
+ object: Object<'a>,
+}
+
+impl<'data> Context<'data> {
+ fn new(
+ stash: &'data Stash,
+ object: Object<'data>,
+ sup: Option<Object<'data>>,
+ ) -> Option<Context<'data>> {
+ let mut sections = gimli::Dwarf::load(|id| -> Result<_, ()> {
+ let data = object.section(stash, id.name()).unwrap_or(&[]);
+ Ok(EndianSlice::new(data, Endian))
+ })
+ .ok()?;
+
+ if let Some(sup) = sup {
+ sections
+ .load_sup(|id| -> Result<_, ()> {
+ let data = sup.section(stash, id.name()).unwrap_or(&[]);
+ Ok(EndianSlice::new(data, Endian))
+ })
+ .ok()?;
+ }
+ let dwarf = addr2line::Context::from_dwarf(sections).ok()?;
+
+ Some(Context { dwarf, object })
+ }
+}
+
+fn mmap(path: &Path) -> Option<Mmap> {
+ let file = File::open(path).ok()?;
+ let len = file.metadata().ok()?.len().try_into().ok()?;
+ unsafe { Mmap::map(&file, len) }
+}
+
+cfg_if::cfg_if! {
+ if #[cfg(windows)] {
+ mod coff;
+ use self::coff::Object;
+ } else if #[cfg(any(
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "tvos",
+ target_os = "watchos",
+ ))] {
+ mod macho;
+ use self::macho::Object;
+ } else {
+ mod elf;
+ use self::elf::Object;
+ }
+}
+
+cfg_if::cfg_if! {
+ if #[cfg(windows)] {
+ mod libs_windows;
+ use libs_windows::native_libraries;
+ } else if #[cfg(any(
+ target_os = "macos",
+ target_os = "ios",
+ target_os = "tvos",
+ target_os = "watchos",
+ ))] {
+ mod libs_macos;
+ use libs_macos::native_libraries;
+ } else if #[cfg(target_os = "illumos")] {
+ mod libs_illumos;
+ use libs_illumos::native_libraries;
+ } else if #[cfg(all(
+ any(
+ target_os = "linux",
+ target_os = "fuchsia",
+ target_os = "freebsd",
+ target_os = "openbsd",
+ all(target_os = "android", feature = "dl_iterate_phdr"),
+ ),
+ not(target_env = "uclibc"),
+ ))] {
+ mod libs_dl_iterate_phdr;
+ use libs_dl_iterate_phdr::native_libraries;
+ } else if #[cfg(target_env = "libnx")] {
+ mod libs_libnx;
+ use libs_libnx::native_libraries;
+ } else if #[cfg(target_os = "haiku")] {
+ mod libs_haiku;
+ use libs_haiku::native_libraries;
+ } else {
+ // Everything else should doesn't know how to load native libraries.
+ fn native_libraries() -> Vec<Library> {
+ Vec::new()
+ }
+ }
+}
+
+#[derive(Default)]
+struct Cache {
+ /// All known shared libraries that have been loaded.
+ libraries: Vec<Library>,
+
+ /// Mappings cache where we retain parsed dwarf information.
+ ///
+ /// This list has a fixed capacity for its entire lifetime which never
+ /// increases. The `usize` element of each pair is an index into `libraries`
+ /// above where `usize::max_value()` represents the current executable. The
+ /// `Mapping` is corresponding parsed dwarf information.
+ ///
+ /// Note that this is basically an LRU cache and we'll be shifting things
+ /// around in here as we symbolize addresses.
+ mappings: Vec<(usize, Mapping)>,
+}
+
+struct Library {
+ name: OsString,
+ /// Segments of this library loaded into memory, and where they're loaded.
+ segments: Vec<LibrarySegment>,
+ /// The "bias" of this library, typically where it's loaded into memory.
+ /// This value is added to each segment's stated address to get the actual
+ /// virtual memory address that the segment is loaded into. Additionally
+ /// this bias is subtracted from real virtual memory addresses to index into
+ /// debuginfo and the symbol table.
+ bias: usize,
+}
+
+struct LibrarySegment {
+ /// The stated address of this segment in the object file. This is not
+ /// actually where the segment is loaded, but rather this address plus the
+ /// containing library's `bias` is where to find it.
+ stated_virtual_memory_address: usize,
+ /// The size of this segment in memory.
+ len: usize,
+}
+
+// unsafe because this is required to be externally synchronized
+pub unsafe fn clear_symbol_cache() {
+ Cache::with_global(|cache| cache.mappings.clear());
+}
+
+impl Cache {
+ fn new() -> Cache {
+ Cache {
+ mappings: Vec::with_capacity(MAPPINGS_CACHE_SIZE),
+ libraries: native_libraries(),
+ }
+ }
+
+ // unsafe because this is required to be externally synchronized
+ unsafe fn with_global(f: impl FnOnce(&mut Self)) {
+ // A very small, very simple LRU cache for debug info mappings.
+ //
+ // The hit rate should be very high, since the typical stack doesn't cross
+ // between many shared libraries.
+ //
+ // The `addr2line::Context` structures are pretty expensive to create. Its
+ // cost is expected to be amortized by subsequent `locate` queries, which
+ // leverage the structures built when constructing `addr2line::Context`s to
+ // get nice speedups. If we didn't have this cache, that amortization would
+ // never happen, and symbolicating backtraces would be ssssllllooooowwww.
+ static mut MAPPINGS_CACHE: Option<Cache> = None;
+
+ f(MAPPINGS_CACHE.get_or_insert_with(|| Cache::new()))
+ }
+
+ fn avma_to_svma(&self, addr: *const u8) -> Option<(usize, *const u8)> {
+ self.libraries
+ .iter()
+ .enumerate()
+ .filter_map(|(i, lib)| {
+ // First up, test if this `lib` has any segment containing the
+ // `addr` (handling relocation). If this check passes then we
+ // can continue below and actually translate the address.
+ //
+ // Note that we're using `wrapping_add` here to avoid overflow
+ // checks. It's been seen in the wild that the SVMA + bias
+ // computation overflows. It seems a bit odd that would happen
+ // but there's not a huge amount we can do about it other than
+ // probably just ignore those segments since they're likely
+ // pointing off into space. This originally came up in
+ // rust-lang/backtrace-rs#329.
+ if !lib.segments.iter().any(|s| {
+ let svma = s.stated_virtual_memory_address;
+ let start = svma.wrapping_add(lib.bias);
+ let end = start.wrapping_add(s.len);
+ let address = addr as usize;
+ start <= address && address < end
+ }) {
+ return None;
+ }
+
+ // Now that we know `lib` contains `addr`, we can offset with
+ // the bias to find the stated virtual memory address.
+ let svma = (addr as usize).wrapping_sub(lib.bias);
+ Some((i, svma as *const u8))
+ })
+ .next()
+ }
+
+ fn mapping_for_lib<'a>(&'a mut self, lib: usize) -> Option<&'a mut Context<'a>> {
+ let idx = self.mappings.iter().position(|(idx, _)| *idx == lib);
+
+ // Invariant: after this conditional completes without early returning
+ // from an error, the cache entry for this path is at index 0.
+
+ if let Some(idx) = idx {
+ // When the mapping is already in the cache, move it to the front.
+ if idx != 0 {
+ let entry = self.mappings.remove(idx);
+ self.mappings.insert(0, entry);
+ }
+ } else {
+ // 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())?;
+
+ if self.mappings.len() == MAPPINGS_CACHE_SIZE {
+ self.mappings.pop();
+ }
+
+ self.mappings.insert(0, (lib, mapping));
+ }
+
+ let cx: &'a mut Context<'static> = &mut self.mappings[0].1.cx;
+ // don't leak the `'static` lifetime, make sure it's scoped to just
+ // ourselves
+ Some(unsafe { mem::transmute::<&'a mut Context<'static>, &'a mut Context<'a>>(cx) })
+ }
+}
+
+pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) {
+ let addr = what.address_or_ip();
+ let mut call = |sym: Symbol<'_>| {
+ // Extend the lifetime of `sym` to `'static` since we are unfortunately
+ // required to here, but it's only ever going out as a reference so no
+ // reference to it should be persisted beyond this frame anyway.
+ let sym = mem::transmute::<Symbol<'_>, Symbol<'static>>(sym);
+ (cb)(&super::Symbol { inner: sym });
+ };
+
+ Cache::with_global(|cache| {
+ let (lib, addr) = match cache.avma_to_svma(addr as *const u8) {
+ Some(pair) => pair,
+ None => return,
+ };
+
+ // Finally, get a cached mapping or create a new mapping for this file, and
+ // evaluate the DWARF info to find the file/line/name for this address.
+ let cx = match cache.mapping_for_lib(lib) {
+ Some(cx) => cx,
+ None => return,
+ };
+ let mut any_frames = false;
+ if let Ok(mut frames) = cx.dwarf.find_frames(addr as u64) {
+ while let Ok(Some(frame)) = frames.next() {
+ any_frames = true;
+ let name = match frame.function {
+ Some(f) => Some(f.name.slice()),
+ None => cx.object.search_symtab(addr as u64),
+ };
+ call(Symbol::Frame {
+ addr: addr as *mut c_void,
+ location: frame.location,
+ name,
+ });
+ }
+ }
+ if !any_frames {
+ if let Some((object_cx, object_addr)) = cx.object.search_object_map(addr as u64) {
+ if let Ok(mut frames) = object_cx.dwarf.find_frames(object_addr) {
+ while let Ok(Some(frame)) = frames.next() {
+ any_frames = true;
+ call(Symbol::Frame {
+ addr: addr as *mut c_void,
+ location: frame.location,
+ name: frame.function.map(|f| f.name.slice()),
+ });
+ }
+ }
+ }
+ }
+ if !any_frames {
+ if let Some(name) = cx.object.search_symtab(addr as u64) {
+ call(Symbol::Symtab {
+ addr: addr as *mut c_void,
+ name,
+ });
+ }
+ }
+ });
+}
+
+pub enum Symbol<'a> {
+ /// We were able to locate frame information for this symbol, and
+ /// `addr2line`'s frame internally has all the nitty gritty details.
+ Frame {
+ addr: *mut c_void,
+ location: Option<addr2line::Location<'a>>,
+ name: Option<&'a [u8]>,
+ },
+ /// Couldn't find debug information, but we found it in the symbol table of
+ /// the elf executable.
+ Symtab { addr: *mut c_void, name: &'a [u8] },
+}
+
+impl Symbol<'_> {
+ pub fn name(&self) -> Option<SymbolName<'_>> {
+ match self {
+ Symbol::Frame { name, .. } => {
+ let name = name.as_ref()?;
+ Some(SymbolName::new(name))
+ }
+ Symbol::Symtab { name, .. } => Some(SymbolName::new(name)),
+ }
+ }
+
+ pub fn addr(&self) -> Option<*mut c_void> {
+ match self {
+ Symbol::Frame { addr, .. } => Some(*addr),
+ Symbol::Symtab { .. } => None,
+ }
+ }
+
+ pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
+ match self {
+ Symbol::Frame { location, .. } => {
+ let file = location.as_ref()?.file?;
+ Some(BytesOrWideString::Bytes(file.as_bytes()))
+ }
+ Symbol::Symtab { .. } => None,
+ }
+ }
+
+ pub fn filename(&self) -> Option<&Path> {
+ match self {
+ Symbol::Frame { location, .. } => {
+ let file = location.as_ref()?.file?;
+ Some(Path::new(file))
+ }
+ Symbol::Symtab { .. } => None,
+ }
+ }
+
+ pub fn lineno(&self) -> Option<u32> {
+ match self {
+ Symbol::Frame { location, .. } => location.as_ref()?.line,
+ Symbol::Symtab { .. } => None,
+ }
+ }
+
+ pub fn colno(&self) -> Option<u32> {
+ match self {
+ Symbol::Frame { location, .. } => location.as_ref()?.column,
+ Symbol::Symtab { .. } => None,
+ }
+ }
+}
diff --git a/library/backtrace/src/symbolize/gimli/coff.rs b/library/backtrace/src/symbolize/gimli/coff.rs
new file mode 100644
index 000000000..84d334207
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/coff.rs
@@ -0,0 +1,108 @@
+use super::{Context, Mapping, Path, Stash, Vec};
+use core::convert::TryFrom;
+use object::pe::{ImageDosHeader, ImageSymbol};
+use object::read::pe::{ImageNtHeaders, ImageOptionalHeader, SectionTable};
+use object::read::StringTable;
+use object::LittleEndian as LE;
+
+#[cfg(target_pointer_width = "32")]
+type Pe = object::pe::ImageNtHeaders32;
+#[cfg(target_pointer_width = "64")]
+type Pe = object::pe::ImageNtHeaders64;
+
+impl Mapping {
+ pub fn new(path: &Path) -> Option<Mapping> {
+ let map = super::mmap(path)?;
+ Mapping::mk(map, |data, stash| {
+ Context::new(stash, Object::parse(data)?, None)
+ })
+ }
+}
+
+pub struct Object<'a> {
+ data: &'a [u8],
+ sections: SectionTable<'a>,
+ symbols: Vec<(usize, &'a ImageSymbol)>,
+ strings: StringTable<'a>,
+}
+
+pub fn get_image_base(data: &[u8]) -> Option<usize> {
+ let dos_header = ImageDosHeader::parse(data).ok()?;
+ let mut offset = dos_header.nt_headers_offset().into();
+ let (nt_headers, _) = Pe::parse(data, &mut offset).ok()?;
+ usize::try_from(nt_headers.optional_header().image_base()).ok()
+}
+
+impl<'a> Object<'a> {
+ fn parse(data: &'a [u8]) -> Option<Object<'a>> {
+ let dos_header = ImageDosHeader::parse(data).ok()?;
+ let mut offset = dos_header.nt_headers_offset().into();
+ let (nt_headers, _) = Pe::parse(data, &mut offset).ok()?;
+ let sections = nt_headers.sections(data, offset).ok()?;
+ let symtab = nt_headers.symbols(data).ok()?;
+ let strings = symtab.strings();
+ let image_base = usize::try_from(nt_headers.optional_header().image_base()).ok()?;
+
+ // Collect all the symbols into a local vector which is sorted
+ // by address and contains enough data to learn about the symbol
+ // name. Note that we only look at function symbols and also
+ // note that the sections are 1-indexed because the zero section
+ // is special (apparently).
+ let mut symbols = Vec::new();
+ let mut i = 0;
+ let len = symtab.len();
+ while i < len {
+ let sym = symtab.symbol(i).ok()?;
+ i += 1 + sym.number_of_aux_symbols as usize;
+ let section_number = sym.section_number.get(LE);
+ if sym.derived_type() != object::pe::IMAGE_SYM_DTYPE_FUNCTION || section_number == 0 {
+ continue;
+ }
+ let addr = usize::try_from(sym.value.get(LE)).ok()?;
+ let section = sections
+ .section(usize::try_from(section_number).ok()?)
+ .ok()?;
+ let va = usize::try_from(section.virtual_address.get(LE)).ok()?;
+ symbols.push((addr + va + image_base, sym));
+ }
+ symbols.sort_unstable_by_key(|x| x.0);
+ Some(Object {
+ data,
+ sections,
+ strings,
+ symbols,
+ })
+ }
+
+ pub fn section(&self, _: &Stash, name: &str) -> Option<&'a [u8]> {
+ Some(
+ self.sections
+ .section_by_name(self.strings, name.as_bytes())?
+ .1
+ .pe_data(self.data)
+ .ok()?,
+ )
+ }
+
+ pub fn search_symtab<'b>(&'b self, addr: u64) -> Option<&'b [u8]> {
+ // Note that unlike other formats COFF doesn't embed the size of
+ // each symbol. As a last ditch effort search for the *closest*
+ // symbol to a particular address and return that one. This gets
+ // really wonky once symbols start getting removed because the
+ // symbols returned here can be totally incorrect, but we have
+ // no idea of knowing how to detect that.
+ let addr = usize::try_from(addr).ok()?;
+ let i = match self.symbols.binary_search_by_key(&addr, |p| p.0) {
+ Ok(i) => i,
+ // typically `addr` isn't in the array, but `i` is where
+ // we'd insert it, so the previous position must be the
+ // greatest less than `addr`
+ Err(i) => i.checked_sub(1)?,
+ };
+ self.symbols[i].1.name(self.strings).ok()
+ }
+
+ pub(super) fn search_object_map(&self, _addr: u64) -> Option<(&Context<'_>, u64)> {
+ None
+ }
+}
diff --git a/library/backtrace/src/symbolize/gimli/elf.rs b/library/backtrace/src/symbolize/gimli/elf.rs
new file mode 100644
index 000000000..bc71ee2c9
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/elf.rs
@@ -0,0 +1,423 @@
+use super::mystd::ffi::{OsStr, OsString};
+use super::mystd::fs;
+use super::mystd::os::unix::ffi::{OsStrExt, OsStringExt};
+use super::mystd::path::{Path, PathBuf};
+use super::Either;
+use super::{Context, Mapping, Stash, Vec};
+use core::convert::{TryFrom, TryInto};
+use core::str;
+use object::elf::{ELFCOMPRESS_ZLIB, ELF_NOTE_GNU, NT_GNU_BUILD_ID, SHF_COMPRESSED};
+use object::read::elf::{CompressionHeader, FileHeader, SectionHeader, SectionTable, Sym};
+use object::read::StringTable;
+use object::{BigEndian, Bytes, NativeEndian};
+
+#[cfg(target_pointer_width = "32")]
+type Elf = object::elf::FileHeader32<NativeEndian>;
+#[cfg(target_pointer_width = "64")]
+type Elf = object::elf::FileHeader64<NativeEndian>;
+
+impl Mapping {
+ pub fn new(path: &Path) -> Option<Mapping> {
+ let map = super::mmap(path)?;
+ Mapping::mk_or_other(map, |map, stash| {
+ let object = Object::parse(&map)?;
+
+ // Try to locate an external debug file using the build ID.
+ if let Some(path_debug) = object.build_id().and_then(locate_build_id) {
+ if let Some(mapping) = Mapping::new_debug(path_debug, None) {
+ return Some(Either::A(mapping));
+ }
+ }
+
+ // Try to locate an external debug file using the GNU debug link section.
+ if let Some((path_debug, crc)) = object.gnu_debuglink_path(path) {
+ if let Some(mapping) = Mapping::new_debug(path_debug, Some(crc)) {
+ return Some(Either::A(mapping));
+ }
+ }
+
+ Context::new(stash, object, None).map(Either::B)
+ })
+ }
+
+ /// Load debuginfo from an external debug file.
+ fn new_debug(path: PathBuf, crc: Option<u32>) -> Option<Mapping> {
+ let map = super::mmap(&path)?;
+ Mapping::mk(map, |map, stash| {
+ let object = Object::parse(&map)?;
+
+ if let Some(_crc) = crc {
+ // TODO: check crc
+ }
+
+ // Try to locate a supplementary object file.
+ if let Some((path_sup, build_id_sup)) = object.gnu_debugaltlink_path(&path) {
+ if let Some(map_sup) = super::mmap(&path_sup) {
+ let map_sup = stash.set_mmap_aux(map_sup);
+ if let Some(sup) = Object::parse(map_sup) {
+ if sup.build_id() == Some(build_id_sup) {
+ return Context::new(stash, object, Some(sup));
+ }
+ }
+ }
+ }
+
+ Context::new(stash, object, None)
+ })
+ }
+}
+
+struct ParsedSym {
+ address: u64,
+ size: u64,
+ name: u32,
+}
+
+pub struct Object<'a> {
+ /// Zero-sized type representing the native endianness.
+ ///
+ /// We could use a literal instead, but this helps ensure correctness.
+ endian: NativeEndian,
+ /// The entire file data.
+ data: &'a [u8],
+ sections: SectionTable<'a, Elf>,
+ strings: StringTable<'a>,
+ /// List of pre-parsed and sorted symbols by base address.
+ syms: Vec<ParsedSym>,
+}
+
+impl<'a> Object<'a> {
+ fn parse(data: &'a [u8]) -> Option<Object<'a>> {
+ let elf = Elf::parse(data).ok()?;
+ let endian = elf.endian().ok()?;
+ let sections = elf.sections(endian, data).ok()?;
+ let mut syms = sections
+ .symbols(endian, data, object::elf::SHT_SYMTAB)
+ .ok()?;
+ if syms.is_empty() {
+ syms = sections
+ .symbols(endian, data, object::elf::SHT_DYNSYM)
+ .ok()?;
+ }
+ let strings = syms.strings();
+
+ let mut syms = syms
+ .iter()
+ // Only look at function/object symbols. This mirrors what
+ // libbacktrace does and in general we're only symbolicating
+ // function addresses in theory. Object symbols correspond
+ // to data, and maybe someone's crazy enough to have a
+ // function go into static data?
+ .filter(|sym| {
+ let st_type = sym.st_type();
+ st_type == object::elf::STT_FUNC || st_type == object::elf::STT_OBJECT
+ })
+ // skip anything that's in an undefined section header,
+ // since it means it's an imported function and we're only
+ // symbolicating with locally defined functions.
+ .filter(|sym| sym.st_shndx(endian) != object::elf::SHN_UNDEF)
+ .map(|sym| {
+ let address = sym.st_value(endian).into();
+ let size = sym.st_size(endian).into();
+ let name = sym.st_name(endian);
+ ParsedSym {
+ address,
+ size,
+ name,
+ }
+ })
+ .collect::<Vec<_>>();
+ syms.sort_unstable_by_key(|s| s.address);
+ Some(Object {
+ endian,
+ data,
+ sections,
+ strings,
+ syms,
+ })
+ }
+
+ pub fn section(&self, stash: &'a Stash, name: &str) -> Option<&'a [u8]> {
+ if let Some(section) = self.section_header(name) {
+ let mut data = Bytes(section.data(self.endian, self.data).ok()?);
+
+ // Check for DWARF-standard (gABI) compression, i.e., as generated
+ // by ld's `--compress-debug-sections=zlib-gabi` flag.
+ let flags: u64 = section.sh_flags(self.endian).into();
+ if (flags & u64::from(SHF_COMPRESSED)) == 0 {
+ // Not compressed.
+ return Some(data.0);
+ }
+
+ let header = data.read::<<Elf as FileHeader>::CompressionHeader>().ok()?;
+ if header.ch_type(self.endian) != ELFCOMPRESS_ZLIB {
+ // Zlib compression is the only known type.
+ return None;
+ }
+ let size = usize::try_from(header.ch_size(self.endian)).ok()?;
+ let buf = stash.allocate(size);
+ decompress_zlib(data.0, buf)?;
+ return Some(buf);
+ }
+
+ // Check for the nonstandard GNU compression format, i.e., as generated
+ // by ld's `--compress-debug-sections=zlib-gnu` flag. This means that if
+ // we're actually asking for `.debug_info` then we need to look up a
+ // section named `.zdebug_info`.
+ if !name.starts_with(".debug_") {
+ return None;
+ }
+ let debug_name = name[7..].as_bytes();
+ let compressed_section = self
+ .sections
+ .iter()
+ .filter_map(|header| {
+ let name = self.sections.section_name(self.endian, header).ok()?;
+ if name.starts_with(b".zdebug_") && &name[8..] == debug_name {
+ Some(header)
+ } else {
+ None
+ }
+ })
+ .next()?;
+ let mut data = Bytes(compressed_section.data(self.endian, self.data).ok()?);
+ if data.read_bytes(8).ok()?.0 != b"ZLIB\0\0\0\0" {
+ return None;
+ }
+ let size = usize::try_from(data.read::<object::U32Bytes<_>>().ok()?.get(BigEndian)).ok()?;
+ let buf = stash.allocate(size);
+ decompress_zlib(data.0, buf)?;
+ Some(buf)
+ }
+
+ fn section_header(&self, name: &str) -> Option<&<Elf as FileHeader>::SectionHeader> {
+ self.sections
+ .section_by_name(self.endian, name.as_bytes())
+ .map(|(_index, section)| section)
+ }
+
+ pub fn search_symtab<'b>(&'b self, addr: u64) -> Option<&'b [u8]> {
+ // Same sort of binary search as Windows above
+ 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 <= addr && addr <= sym.address + sym.size {
+ self.strings.get(sym.name).ok()
+ } else {
+ None
+ }
+ }
+
+ pub(super) fn search_object_map(&self, _addr: u64) -> Option<(&Context<'_>, u64)> {
+ None
+ }
+
+ fn build_id(&self) -> Option<&'a [u8]> {
+ for section in self.sections.iter() {
+ if let Ok(Some(mut notes)) = section.notes(self.endian, self.data) {
+ while let Ok(Some(note)) = notes.next() {
+ if note.name() == ELF_NOTE_GNU && note.n_type(self.endian) == NT_GNU_BUILD_ID {
+ return Some(note.desc());
+ }
+ }
+ }
+ }
+ None
+ }
+
+ // The contents of the ".gnu_debuglink" section is documented at:
+ // https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
+ fn gnu_debuglink_path(&self, path: &Path) -> Option<(PathBuf, u32)> {
+ let section = self.section_header(".gnu_debuglink")?;
+ let data = section.data(self.endian, self.data).ok()?;
+ let len = data.iter().position(|x| *x == 0)?;
+ let filename = &data[..len];
+ let offset = (len + 1 + 3) & !3;
+ let crc_bytes = data
+ .get(offset..offset + 4)
+ .and_then(|bytes| bytes.try_into().ok())?;
+ let crc = u32::from_ne_bytes(crc_bytes);
+ let path_debug = locate_debuglink(path, filename)?;
+ Some((path_debug, crc))
+ }
+
+ // The format of the ".gnu_debugaltlink" section is based on gdb.
+ fn gnu_debugaltlink_path(&self, path: &Path) -> Option<(PathBuf, &'a [u8])> {
+ let section = self.section_header(".gnu_debugaltlink")?;
+ let data = section.data(self.endian, self.data).ok()?;
+ let len = data.iter().position(|x| *x == 0)?;
+ let filename = &data[..len];
+ let build_id = &data[len + 1..];
+ let path_sup = locate_debugaltlink(path, filename, build_id)?;
+ Some((path_sup, build_id))
+ }
+}
+
+fn decompress_zlib(input: &[u8], output: &mut [u8]) -> Option<()> {
+ use miniz_oxide::inflate::core::inflate_flags::{
+ TINFL_FLAG_PARSE_ZLIB_HEADER, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF,
+ };
+ use miniz_oxide::inflate::core::{decompress, DecompressorOxide};
+ use miniz_oxide::inflate::TINFLStatus;
+
+ let (status, in_read, out_read) = decompress(
+ &mut DecompressorOxide::new(),
+ input,
+ output,
+ 0,
+ TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | TINFL_FLAG_PARSE_ZLIB_HEADER,
+ );
+ if status == TINFLStatus::Done && in_read == input.len() && out_read == output.len() {
+ Some(())
+ } else {
+ None
+ }
+}
+
+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"))] {
+ use core::sync::atomic::{AtomicU8, Ordering};
+ static DEBUG_PATH_EXISTS: AtomicU8 = AtomicU8::new(0);
+
+ let mut exists = DEBUG_PATH_EXISTS.load(Ordering::Relaxed);
+ if exists == 0 {
+ exists = if Path::new(OsStr::from_bytes(DEBUG_PATH)).is_dir() {
+ 1
+ } else {
+ 2
+ };
+ DEBUG_PATH_EXISTS.store(exists, Ordering::Relaxed);
+ }
+ exists == 1
+ } else {
+ false
+ }
+ }
+}
+
+/// Locate a debug file based on its build ID.
+///
+/// The format of build id paths is documented at:
+/// https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
+fn locate_build_id(build_id: &[u8]) -> Option<PathBuf> {
+ const BUILD_ID_PATH: &[u8] = b"/usr/lib/debug/.build-id/";
+ const BUILD_ID_SUFFIX: &[u8] = b".debug";
+
+ if build_id.len() < 2 {
+ return None;
+ }
+
+ if !debug_path_exists() {
+ return None;
+ }
+
+ let mut path =
+ Vec::with_capacity(BUILD_ID_PATH.len() + BUILD_ID_SUFFIX.len() + build_id.len() * 2 + 1);
+ path.extend(BUILD_ID_PATH);
+ path.push(hex(build_id[0] >> 4));
+ path.push(hex(build_id[0] & 0xf));
+ path.push(b'/');
+ for byte in &build_id[1..] {
+ path.push(hex(byte >> 4));
+ path.push(hex(byte & 0xf));
+ }
+ path.extend(BUILD_ID_SUFFIX);
+ Some(PathBuf::from(OsString::from_vec(path)))
+}
+
+fn hex(byte: u8) -> u8 {
+ if byte < 10 {
+ b'0' + byte
+ } else {
+ b'a' + byte - 10
+ }
+}
+
+/// Locate a file specified in a `.gnu_debuglink` section.
+///
+/// `path` is the file containing the section.
+/// `filename` is from the contents of the section.
+///
+/// Search order is based on gdb, documented at:
+/// https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
+///
+/// gdb also allows the user to customize the debug search path, but we don't.
+///
+/// gdb also supports debuginfod, but we don't yet.
+fn locate_debuglink(path: &Path, filename: &[u8]) -> Option<PathBuf> {
+ let path = fs::canonicalize(path).ok()?;
+ let parent = path.parent()?;
+ let mut f = PathBuf::from(OsString::with_capacity(
+ DEBUG_PATH.len() + parent.as_os_str().len() + filename.len() + 2,
+ ));
+ let filename = Path::new(OsStr::from_bytes(filename));
+
+ // Try "/parent/filename" if it differs from "path"
+ f.push(parent);
+ f.push(filename);
+ if f != path && f.is_file() {
+ return Some(f);
+ }
+
+ // Try "/parent/.debug/filename"
+ let mut s = OsString::from(f);
+ s.clear();
+ f = PathBuf::from(s);
+ f.push(parent);
+ f.push(".debug");
+ f.push(filename);
+ if f.is_file() {
+ return Some(f);
+ }
+
+ if debug_path_exists() {
+ // Try "/usr/lib/debug/parent/filename"
+ let mut s = OsString::from(f);
+ s.clear();
+ f = PathBuf::from(s);
+ f.push(OsStr::from_bytes(DEBUG_PATH));
+ f.push(parent.strip_prefix("/").unwrap());
+ f.push(filename);
+ if f.is_file() {
+ return Some(f);
+ }
+ }
+
+ None
+}
+
+/// Locate a file specified in a `.gnu_debugaltlink` section.
+///
+/// `path` is the file containing the section.
+/// `filename` and `build_id` are the contents of the section.
+///
+/// Search order is based on gdb:
+/// - filename, which is either absolute or relative to `path`
+/// - the build ID path under `BUILD_ID_PATH`
+///
+/// gdb also allows the user to customize the debug search path, but we don't.
+///
+/// gdb also supports debuginfod, but we don't yet.
+fn locate_debugaltlink(path: &Path, filename: &[u8], build_id: &[u8]) -> Option<PathBuf> {
+ let filename = Path::new(OsStr::from_bytes(filename));
+ if filename.is_absolute() {
+ if filename.is_file() {
+ return Some(filename.into());
+ }
+ } else {
+ let path = fs::canonicalize(path).ok()?;
+ let parent = path.parent()?;
+ let mut f = PathBuf::from(parent);
+ f.push(filename);
+ if f.is_file() {
+ return Some(f);
+ }
+ }
+
+ locate_build_id(build_id)
+}
diff --git a/library/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs b/library/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs
new file mode 100644
index 000000000..a011e6080
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/libs_dl_iterate_phdr.rs
@@ -0,0 +1,53 @@
+// Other Unix (e.g. Linux) platforms use ELF as an object file format
+// and typically implement an API called `dl_iterate_phdr` to load
+// native libraries.
+
+use super::mystd::borrow::ToOwned;
+use super::mystd::env;
+use super::mystd::ffi::{CStr, OsStr};
+use super::mystd::os::unix::prelude::*;
+use super::{Library, LibrarySegment, OsString, Vec};
+use core::slice;
+
+pub(super) fn native_libraries() -> Vec<Library> {
+ let mut ret = Vec::new();
+ unsafe {
+ libc::dl_iterate_phdr(Some(callback), &mut ret as *mut Vec<_> as *mut _);
+ }
+ return ret;
+}
+
+// `info` should be a valid pointers.
+// `vec` should be a valid pointer to a `std::Vec`.
+unsafe extern "C" fn callback(
+ info: *mut libc::dl_phdr_info,
+ _size: libc::size_t,
+ vec: *mut libc::c_void,
+) -> libc::c_int {
+ let info = &*info;
+ let libs = &mut *(vec as *mut Vec<Library>);
+ let is_main_prog = info.dlpi_name.is_null() || *info.dlpi_name == 0;
+ let name = if is_main_prog {
+ if libs.is_empty() {
+ env::current_exe().map(|e| e.into()).unwrap_or_default()
+ } else {
+ OsString::new()
+ }
+ } else {
+ let bytes = CStr::from_ptr(info.dlpi_name).to_bytes();
+ OsStr::from_bytes(bytes).to_owned()
+ };
+ let headers = slice::from_raw_parts(info.dlpi_phdr, info.dlpi_phnum as usize);
+ libs.push(Library {
+ name,
+ segments: headers
+ .iter()
+ .map(|header| LibrarySegment {
+ len: (*header).p_memsz as usize,
+ stated_virtual_memory_address: (*header).p_vaddr as usize,
+ })
+ .collect(),
+ bias: info.dlpi_addr as usize,
+ });
+ 0
+}
diff --git a/library/backtrace/src/symbolize/gimli/libs_haiku.rs b/library/backtrace/src/symbolize/gimli/libs_haiku.rs
new file mode 100644
index 000000000..87e023e69
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/libs_haiku.rs
@@ -0,0 +1,48 @@
+// Haiku implements the image_info struct and the get_next_image_info()
+// functions to iterate through the loaded executable images. The
+// image_info struct contains a pointer to the start of the .text
+// section within the virtual address space, as well as the size of
+// that section. All the read-only segments of the ELF-binary are in
+// that part of the address space.
+
+use super::mystd::borrow::ToOwned;
+use super::mystd::ffi::{CStr, OsStr};
+use super::mystd::mem::MaybeUninit;
+use super::mystd::os::unix::prelude::*;
+use super::{Library, LibrarySegment, Vec};
+
+pub(super) fn native_libraries() -> Vec<Library> {
+ let mut libraries: Vec<Library> = Vec::new();
+
+ unsafe {
+ let mut info = MaybeUninit::<libc::image_info>::zeroed();
+ let mut cookie: i32 = 0;
+ // Load the first image to get a valid info struct
+ let mut status =
+ libc::get_next_image_info(libc::B_CURRENT_TEAM, &mut cookie, info.as_mut_ptr());
+ if status != libc::B_OK {
+ return libraries;
+ }
+ let mut info = info.assume_init();
+
+ while status == libc::B_OK {
+ let mut segments = Vec::new();
+ segments.push(LibrarySegment {
+ stated_virtual_memory_address: 0,
+ len: info.text_size as usize,
+ });
+
+ let bytes = CStr::from_ptr(info.name.as_ptr()).to_bytes();
+ let name = OsStr::from_bytes(bytes).to_owned();
+ libraries.push(Library {
+ name: name,
+ segments: segments,
+ bias: info.text as usize,
+ });
+
+ status = libc::get_next_image_info(libc::B_CURRENT_TEAM, &mut cookie, &mut info);
+ }
+ }
+
+ libraries
+}
diff --git a/library/backtrace/src/symbolize/gimli/libs_illumos.rs b/library/backtrace/src/symbolize/gimli/libs_illumos.rs
new file mode 100644
index 000000000..e64975e0c
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/libs_illumos.rs
@@ -0,0 +1,99 @@
+use super::mystd::borrow::ToOwned;
+use super::mystd::ffi::{CStr, OsStr};
+use super::mystd::os::unix::prelude::*;
+use super::{Library, LibrarySegment, Vec};
+use core::mem;
+use object::NativeEndian;
+
+#[cfg(target_pointer_width = "64")]
+use object::elf::{FileHeader64 as FileHeader, ProgramHeader64 as ProgramHeader};
+
+type EHdr = FileHeader<NativeEndian>;
+type PHdr = ProgramHeader<NativeEndian>;
+
+#[repr(C)]
+struct LinkMap {
+ l_addr: libc::c_ulong,
+ l_name: *const libc::c_char,
+ l_ld: *const libc::c_void,
+ l_next: *const LinkMap,
+ l_prev: *const LinkMap,
+ l_refname: *const libc::c_char,
+}
+
+const RTLD_SELF: *const libc::c_void = -3isize as *const libc::c_void;
+const RTLD_DI_LINKMAP: libc::c_int = 2;
+
+extern "C" {
+ fn dlinfo(
+ handle: *const libc::c_void,
+ request: libc::c_int,
+ p: *mut libc::c_void,
+ ) -> libc::c_int;
+}
+
+pub(super) fn native_libraries() -> Vec<Library> {
+ let mut libs = Vec::new();
+
+ // Request the current link map from the runtime linker:
+ let map = unsafe {
+ let mut map: *const LinkMap = mem::zeroed();
+ if dlinfo(
+ RTLD_SELF,
+ RTLD_DI_LINKMAP,
+ (&mut map) as *mut *const LinkMap as *mut libc::c_void,
+ ) != 0
+ {
+ return libs;
+ }
+ map
+ };
+
+ // Each entry in the link map represents a loaded object:
+ let mut l = map;
+ while !l.is_null() {
+ // Fetch the fully qualified path of the loaded object:
+ let bytes = unsafe { CStr::from_ptr((*l).l_name) }.to_bytes();
+ let name = OsStr::from_bytes(bytes).to_owned();
+
+ // The base address of the object loaded into memory:
+ let addr = unsafe { (*l).l_addr };
+
+ // Use the ELF header for this object to locate the program
+ // header:
+ let e: *const EHdr = unsafe { (*l).l_addr as *const EHdr };
+ let phoff = unsafe { (*e).e_phoff }.get(NativeEndian);
+ let phnum = unsafe { (*e).e_phnum }.get(NativeEndian);
+ let etype = unsafe { (*e).e_type }.get(NativeEndian);
+
+ let phdr: *const PHdr = (addr + phoff) as *const PHdr;
+ let phdr = unsafe { core::slice::from_raw_parts(phdr, phnum as usize) };
+
+ libs.push(Library {
+ name,
+ segments: phdr
+ .iter()
+ .map(|p| {
+ let memsz = p.p_memsz.get(NativeEndian);
+ let vaddr = p.p_vaddr.get(NativeEndian);
+ LibrarySegment {
+ len: memsz as usize,
+ stated_virtual_memory_address: vaddr as usize,
+ }
+ })
+ .collect(),
+ bias: if etype == object::elf::ET_EXEC {
+ // Program header addresses for the base executable are
+ // already absolute.
+ 0
+ } else {
+ // Other addresses are relative to the object base.
+ addr as usize
+ },
+ });
+
+ l = unsafe { (*l).l_next };
+ }
+
+ libs
+}
diff --git a/library/backtrace/src/symbolize/gimli/libs_libnx.rs b/library/backtrace/src/symbolize/gimli/libs_libnx.rs
new file mode 100644
index 000000000..93b5ba17e
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/libs_libnx.rs
@@ -0,0 +1,27 @@
+use super::{Library, LibrarySegment, Vec};
+
+// DevkitA64 doesn't natively support debug info, but the build system will
+// place debug info at the path `romfs:/debug_info.elf`.
+pub(super) fn native_libraries() -> Vec<Library> {
+ extern "C" {
+ static __start__: u8;
+ }
+
+ let bias = unsafe { &__start__ } as *const u8 as usize;
+
+ let mut ret = Vec::new();
+ let mut segments = Vec::new();
+ segments.push(LibrarySegment {
+ stated_virtual_memory_address: 0,
+ len: usize::max_value() - bias,
+ });
+
+ let path = "romfs:/debug_info.elf";
+ ret.push(Library {
+ name: path.into(),
+ segments,
+ bias,
+ });
+
+ ret
+}
diff --git a/library/backtrace/src/symbolize/gimli/libs_macos.rs b/library/backtrace/src/symbolize/gimli/libs_macos.rs
new file mode 100644
index 000000000..17703b88a
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/libs_macos.rs
@@ -0,0 +1,146 @@
+#![allow(deprecated)]
+
+use super::mystd::ffi::{CStr, OsStr};
+use super::mystd::os::unix::prelude::*;
+use super::mystd::prelude::v1::*;
+use super::{Library, LibrarySegment};
+use core::convert::TryInto;
+use core::mem;
+
+pub(super) fn native_libraries() -> Vec<Library> {
+ let mut ret = Vec::new();
+ let images = unsafe { libc::_dyld_image_count() };
+ for i in 0..images {
+ ret.extend(native_library(i));
+ }
+ return ret;
+}
+
+fn native_library(i: u32) -> Option<Library> {
+ use object::macho;
+ use object::read::macho::{MachHeader, Segment};
+ use object::NativeEndian;
+
+ // Fetch the name of this library which corresponds to the path of
+ // where to load it as well.
+ let name = unsafe {
+ let name = libc::_dyld_get_image_name(i);
+ if name.is_null() {
+ return None;
+ }
+ CStr::from_ptr(name)
+ };
+
+ // Load the image header of this library and delegate to `object` to
+ // parse all the load commands so we can figure out all the segments
+ // involved here.
+ let (mut load_commands, endian) = unsafe {
+ let header = libc::_dyld_get_image_header(i);
+ if header.is_null() {
+ return None;
+ }
+ match (*header).magic {
+ macho::MH_MAGIC => {
+ let endian = NativeEndian;
+ let header = &*(header as *const macho::MachHeader32<NativeEndian>);
+ let data = core::slice::from_raw_parts(
+ header as *const _ as *const u8,
+ 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<NativeEndian>);
+ let data = core::slice::from_raw_parts(
+ header as *const _ as *const u8,
+ mem::size_of_val(header) + header.sizeofcmds.get(endian) as usize,
+ );
+ (header.load_commands(endian, data, 0).ok()?, endian)
+ }
+ _ => return None,
+ }
+ };
+
+ // Iterate over the segments and register known regions for segments
+ // that we find. Additionally record information bout text segments
+ // for processing later, see comments below.
+ let mut segments = Vec::new();
+ let mut first_text = 0;
+ let mut text_fileoff_zero = false;
+ while let Some(cmd) = load_commands.next().ok()? {
+ if let Some((seg, _)) = cmd.segment_32().ok()? {
+ if seg.name() == b"__TEXT" {
+ first_text = segments.len();
+ if seg.fileoff(endian) == 0 && seg.filesize(endian) > 0 {
+ text_fileoff_zero = true;
+ }
+ }
+ segments.push(LibrarySegment {
+ len: seg.vmsize(endian).try_into().ok()?,
+ stated_virtual_memory_address: seg.vmaddr(endian).try_into().ok()?,
+ });
+ }
+ if let Some((seg, _)) = cmd.segment_64().ok()? {
+ if seg.name() == b"__TEXT" {
+ first_text = segments.len();
+ if seg.fileoff(endian) == 0 && seg.filesize(endian) > 0 {
+ text_fileoff_zero = true;
+ }
+ }
+ segments.push(LibrarySegment {
+ len: seg.vmsize(endian).try_into().ok()?,
+ stated_virtual_memory_address: seg.vmaddr(endian).try_into().ok()?,
+ });
+ }
+ }
+
+ // Determine the "slide" for this library which ends up being the
+ // bias we use to figure out where in memory objects are loaded.
+ // This is a bit of a weird computation though and is the result of
+ // trying a few things in the wild and seeing what sticks.
+ //
+ // The general idea is that the `bias` plus a segment's
+ // `stated_virtual_memory_address` is going to be where in the
+ // actual address space the segment resides. The other thing we rely
+ // on though is that a real address minus the `bias` is the index to
+ // look up in the symbol table and debuginfo.
+ //
+ // It turns out, though, that for system loaded libraries these
+ // calculations are incorrect. For native executables, however, it
+ // appears correct. Lifting some logic from LLDB's source it has
+ // some special-casing for the first `__TEXT` section loaded from
+ // file offset 0 with a nonzero size. For whatever reason when this
+ // is present it appears to mean that the symbol table is relative
+ // to just the vmaddr slide for the library. If it's *not* present
+ // then the symbol table is relative to the the vmaddr slide plus
+ // the segment's stated address.
+ //
+ // To handle this situation if we *don't* find a text section at
+ // file offset zero then we increase the bias by the first text
+ // sections's stated address and decrease all stated addresses by
+ // that amount as well. That way the symbol table is always appears
+ // relative to the library's bias amount. This appears to have the
+ // right results for symbolizing via the symbol table.
+ //
+ // Honestly I'm not entirely sure whether this is right or if
+ // there's something else that should indicate how to do this. For
+ // now though this seems to work well enough (?) and we should
+ // always be able to tweak this over time if necessary.
+ //
+ // For some more information see #318
+ let mut slide = unsafe { libc::_dyld_get_image_vmaddr_slide(i) as usize };
+ if !text_fileoff_zero {
+ let adjust = segments[first_text].stated_virtual_memory_address;
+ for segment in segments.iter_mut() {
+ segment.stated_virtual_memory_address -= adjust;
+ }
+ slide += adjust;
+ }
+
+ Some(Library {
+ name: OsStr::from_bytes(name.to_bytes()).to_owned(),
+ segments,
+ bias: slide,
+ })
+}
diff --git a/library/backtrace/src/symbolize/gimli/libs_windows.rs b/library/backtrace/src/symbolize/gimli/libs_windows.rs
new file mode 100644
index 000000000..b47ed4245
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/libs_windows.rs
@@ -0,0 +1,89 @@
+use super::super::super::windows::*;
+use super::mystd::os::windows::prelude::*;
+use super::{coff, mmap, Library, LibrarySegment, OsString};
+use alloc::vec;
+use alloc::vec::Vec;
+use core::mem;
+use core::mem::MaybeUninit;
+
+// For loading native libraries on Windows, see some discussion on
+// rust-lang/rust#71060 for the various strategies here.
+pub(super) fn native_libraries() -> Vec<Library> {
+ let mut ret = Vec::new();
+ unsafe {
+ add_loaded_images(&mut ret);
+ }
+ return ret;
+}
+
+unsafe fn add_loaded_images(ret: &mut Vec<Library>) {
+ let snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
+ if snap == INVALID_HANDLE_VALUE {
+ return;
+ }
+
+ let mut me = MaybeUninit::<MODULEENTRY32W>::zeroed().assume_init();
+ me.dwSize = mem::size_of_val(&me) as DWORD;
+ if Module32FirstW(snap, &mut me) == TRUE {
+ loop {
+ if let Some(lib) = load_library(&me) {
+ ret.push(lib);
+ }
+
+ if Module32NextW(snap, &mut me) != TRUE {
+ break;
+ }
+ }
+ }
+
+ CloseHandle(snap);
+}
+
+unsafe fn load_library(me: &MODULEENTRY32W) -> Option<Library> {
+ let pos = me
+ .szExePath
+ .iter()
+ .position(|i| *i == 0)
+ .unwrap_or(me.szExePath.len());
+ let name = OsString::from_wide(&me.szExePath[..pos]);
+
+ // MinGW libraries currently don't support ASLR
+ // (rust-lang/rust#16514), but DLLs can still be relocated around in
+ // the address space. It appears that addresses in debug info are
+ // all as-if this library was loaded at its "image base", which is a
+ // field in its COFF file headers. Since this is what debuginfo
+ // seems to list we parse the symbol table and store addresses as if
+ // the library was loaded at "image base" as well.
+ //
+ // The library may not be loaded at "image base", however.
+ // (presumably something else may be loaded there?) This is where
+ // the `bias` field comes into play, and we need to figure out the
+ // value of `bias` here. Unfortunately though it's not clear how to
+ // acquire this from a loaded module. What we do have, however, is
+ // the actual load address (`modBaseAddr`).
+ //
+ // As a bit of a cop-out for now we mmap the file, read the file
+ // header information, then drop the mmap. This is wasteful because
+ // we'll probably reopen the mmap later, but this should work well
+ // enough for now.
+ //
+ // Once we have the `image_base` (desired load location) and the
+ // `base_addr` (actual load location) we can fill in the `bias`
+ // (difference between the actual and desired) and then the stated
+ // address of each segment is the `image_base` since that's what the
+ // file says.
+ //
+ // For now it appears that unlike ELF/MachO we can make do with one
+ // segment per library, using `modBaseSize` as the whole size.
+ let mmap = mmap(name.as_ref())?;
+ let image_base = coff::get_image_base(&mmap)?;
+ let base_addr = me.modBaseAddr as usize;
+ Some(Library {
+ name,
+ bias: base_addr.wrapping_sub(image_base),
+ segments: vec![LibrarySegment {
+ stated_virtual_memory_address: image_base,
+ len: me.modBaseSize as usize,
+ }],
+ })
+}
diff --git a/library/backtrace/src/symbolize/gimli/macho.rs b/library/backtrace/src/symbolize/gimli/macho.rs
new file mode 100644
index 000000000..ec5673843
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/macho.rs
@@ -0,0 +1,324 @@
+use super::{Box, Context, Mapping, Path, Stash, Vec};
+use core::convert::TryInto;
+use object::macho;
+use object::read::macho::{MachHeader, Nlist, Section, Segment as _};
+use object::{Bytes, NativeEndian};
+
+#[cfg(target_pointer_width = "32")]
+type Mach = object::macho::MachHeader32<NativeEndian>;
+#[cfg(target_pointer_width = "64")]
+type Mach = object::macho::MachHeader64<NativeEndian>;
+type MachSegment = <Mach as MachHeader>::Segment;
+type MachSection = <Mach as MachHeader>::Section;
+type MachNlist = <Mach as MachHeader>::Nlist;
+
+impl Mapping {
+ // The loading path for OSX is is so different we just have a completely
+ // different implementation of the function here. On OSX we need to go
+ // probing the filesystem for a bunch of files.
+ pub fn new(path: &Path) -> Option<Mapping> {
+ // First up we need to load the unique UUID which is stored in the macho
+ // header of the file we're reading, specified at `path`.
+ let map = super::mmap(path)?;
+ let (macho, data) = find_header(&map)?;
+ let endian = macho.endian().ok()?;
+ let uuid = macho.uuid(endian, data, 0).ok()?;
+
+ // Next we need to look for a `*.dSYM` file. For now we just probe the
+ // containing directory and look around for something that matches
+ // `*.dSYM`. Once it's found we root through the dwarf resources that it
+ // contains and try to find a macho file which has a matching UUID as
+ // the one of our own file. If we find a match that's the dwarf file we
+ // want to return.
+ if let Some(uuid) = uuid {
+ if let Some(parent) = path.parent() {
+ if let Some(mapping) = Mapping::load_dsym(parent, uuid) {
+ return Some(mapping);
+ }
+ }
+ }
+
+ // Looks like nothing matched our UUID, so let's at least return our own
+ // file. This should have the symbol table for at least some
+ // symbolication purposes.
+ Mapping::mk(map, |data, stash| {
+ let (macho, data) = find_header(data)?;
+ let endian = macho.endian().ok()?;
+ let obj = Object::parse(macho, endian, data)?;
+ Context::new(stash, obj, None)
+ })
+ }
+
+ fn load_dsym(dir: &Path, uuid: [u8; 16]) -> Option<Mapping> {
+ for entry in dir.read_dir().ok()? {
+ let entry = entry.ok()?;
+ let filename = match entry.file_name().into_string() {
+ Ok(name) => name,
+ Err(_) => continue,
+ };
+ if !filename.ends_with(".dSYM") {
+ continue;
+ }
+ let candidates = entry.path().join("Contents/Resources/DWARF");
+ if let Some(mapping) = Mapping::try_dsym_candidate(&candidates, uuid) {
+ return Some(mapping);
+ }
+ }
+ None
+ }
+
+ fn try_dsym_candidate(dir: &Path, uuid: [u8; 16]) -> Option<Mapping> {
+ // Look for files in the `DWARF` directory which have a matching uuid to
+ // the original object file. If we find one then we found the debug
+ // information.
+ for entry in dir.read_dir().ok()? {
+ let entry = entry.ok()?;
+ let map = super::mmap(&entry.path())?;
+ let candidate = Mapping::mk(map, |data, stash| {
+ let (macho, data) = find_header(data)?;
+ let endian = macho.endian().ok()?;
+ let entry_uuid = macho.uuid(endian, data, 0).ok()??;
+ if entry_uuid != uuid {
+ return None;
+ }
+ let obj = Object::parse(macho, endian, data)?;
+ Context::new(stash, obj, None)
+ });
+ if let Some(candidate) = candidate {
+ return Some(candidate);
+ }
+ }
+
+ None
+ }
+}
+
+fn find_header(data: &'_ [u8]) -> Option<(&'_ Mach, &'_ [u8])> {
+ use object::endian::BigEndian;
+
+ let desired_cpu = || {
+ if cfg!(target_arch = "x86") {
+ Some(macho::CPU_TYPE_X86)
+ } else if cfg!(target_arch = "x86_64") {
+ Some(macho::CPU_TYPE_X86_64)
+ } else if cfg!(target_arch = "arm") {
+ Some(macho::CPU_TYPE_ARM)
+ } else if cfg!(target_arch = "aarch64") {
+ Some(macho::CPU_TYPE_ARM64)
+ } else {
+ None
+ }
+ };
+
+ let mut data = Bytes(data);
+ match data
+ .clone()
+ .read::<object::endian::U32<NativeEndian>>()
+ .ok()?
+ .get(NativeEndian)
+ {
+ macho::MH_MAGIC_64 | macho::MH_CIGAM_64 | macho::MH_MAGIC | macho::MH_CIGAM => {}
+
+ macho::FAT_MAGIC | macho::FAT_CIGAM => {
+ let mut header_data = data;
+ let endian = BigEndian;
+ let header = header_data.read::<macho::FatHeader>().ok()?;
+ let nfat = header.nfat_arch.get(endian);
+ let arch = (0..nfat)
+ .filter_map(|_| header_data.read::<macho::FatArch32>().ok())
+ .find(|arch| desired_cpu() == Some(arch.cputype.get(endian)))?;
+ let offset = arch.offset.get(endian);
+ let size = arch.size.get(endian);
+ data = data
+ .read_bytes_at(offset.try_into().ok()?, size.try_into().ok()?)
+ .ok()?;
+ }
+
+ macho::FAT_MAGIC_64 | macho::FAT_CIGAM_64 => {
+ let mut header_data = data;
+ let endian = BigEndian;
+ let header = header_data.read::<macho::FatHeader>().ok()?;
+ let nfat = header.nfat_arch.get(endian);
+ let arch = (0..nfat)
+ .filter_map(|_| header_data.read::<macho::FatArch64>().ok())
+ .find(|arch| desired_cpu() == Some(arch.cputype.get(endian)))?;
+ let offset = arch.offset.get(endian);
+ let size = arch.size.get(endian);
+ data = data
+ .read_bytes_at(offset.try_into().ok()?, size.try_into().ok()?)
+ .ok()?;
+ }
+
+ _ => return None,
+ }
+
+ Mach::parse(data.0, 0).ok().map(|h| (h, data.0))
+}
+
+// This is used both for executables/libraries and source object files.
+pub struct Object<'a> {
+ endian: NativeEndian,
+ data: &'a [u8],
+ dwarf: Option<&'a [MachSection]>,
+ syms: Vec<(&'a [u8], u64)>,
+ syms_sort_by_name: bool,
+ // Only set for executables/libraries, and not the source object files.
+ object_map: Option<object::ObjectMap<'a>>,
+ // The outer Option is for lazy loading, and the inner Option allows load errors to be cached.
+ object_mappings: Box<[Option<Option<Mapping>>]>,
+}
+
+impl<'a> Object<'a> {
+ fn parse(mach: &'a Mach, endian: NativeEndian, data: &'a [u8]) -> Option<Object<'a>> {
+ let is_object = mach.filetype(endian) == object::macho::MH_OBJECT;
+ let mut dwarf = None;
+ let mut syms = Vec::new();
+ let mut syms_sort_by_name = false;
+ let mut commands = mach.load_commands(endian, data, 0).ok()?;
+ let mut object_map = None;
+ let mut object_mappings = Vec::new();
+ while let Ok(Some(command)) = commands.next() {
+ if let Some((segment, section_data)) = MachSegment::from_command(command).ok()? {
+ // Object files should have all sections in a single unnamed segment load command.
+ if segment.name() == b"__DWARF" || (is_object && segment.name() == b"") {
+ dwarf = segment.sections(endian, section_data).ok();
+ }
+ } else if let Some(symtab) = command.symtab().ok()? {
+ let symbols = symtab.symbols::<Mach, _>(endian, data).ok()?;
+ syms = symbols
+ .iter()
+ .filter_map(|nlist: &MachNlist| {
+ let name = nlist.name(endian, symbols.strings()).ok()?;
+ if name.len() > 0 && nlist.is_definition() {
+ Some((name, u64::from(nlist.n_value(endian))))
+ } else {
+ None
+ }
+ })
+ .collect();
+ if is_object {
+ // We never search object file symbols by address.
+ // Instead, we already know the symbol name from the executable, and we
+ // need to search by name to find the matching symbol in the object file.
+ syms.sort_unstable_by_key(|(name, _)| *name);
+ syms_sort_by_name = true;
+ } else {
+ syms.sort_unstable_by_key(|(_, addr)| *addr);
+ let map = symbols.object_map(endian);
+ object_mappings.resize_with(map.objects().len(), || None);
+ object_map = Some(map);
+ }
+ }
+ }
+
+ Some(Object {
+ endian,
+ data,
+ dwarf,
+ syms,
+ syms_sort_by_name,
+ object_map,
+ object_mappings: object_mappings.into_boxed_slice(),
+ })
+ }
+
+ pub fn section(&self, _: &Stash, name: &str) -> Option<&'a [u8]> {
+ let name = name.as_bytes();
+ let dwarf = self.dwarf?;
+ let section = dwarf.into_iter().find(|section| {
+ let section_name = section.name();
+ section_name == name || {
+ section_name.starts_with(b"__")
+ && name.starts_with(b".")
+ && &section_name[2..] == &name[1..]
+ }
+ })?;
+ Some(section.data(self.endian, self.data).ok()?)
+ }
+
+ pub fn search_symtab<'b>(&'b self, addr: u64) -> Option<&'b [u8]> {
+ debug_assert!(!self.syms_sort_by_name);
+ let i = match self.syms.binary_search_by_key(&addr, |(_, addr)| *addr) {
+ Ok(i) => i,
+ Err(i) => i.checked_sub(1)?,
+ };
+ let (sym, _addr) = self.syms.get(i)?;
+ Some(sym)
+ }
+
+ /// Try to load a context for an object file.
+ ///
+ /// If dsymutil was not run, then the DWARF may be found in the source object files.
+ pub(super) fn search_object_map<'b>(&'b mut self, addr: u64) -> Option<(&Context<'b>, u64)> {
+ // `object_map` contains a map from addresses to symbols and object paths.
+ // Look up the address and get a mapping for the object.
+ let object_map = self.object_map.as_ref()?;
+ let symbol = object_map.get(addr)?;
+ let object_index = symbol.object_index();
+ let mapping = self.object_mappings.get_mut(object_index)?;
+ if mapping.is_none() {
+ // No cached mapping, so create it.
+ *mapping = Some(object_mapping(object_map.objects().get(object_index)?));
+ }
+ let cx: &'b Context<'static> = &mapping.as_ref()?.as_ref()?.cx;
+ // Don't leak the `'static` lifetime, make sure it's scoped to just ourselves.
+ let cx = unsafe { core::mem::transmute::<&'b Context<'static>, &'b Context<'b>>(cx) };
+
+ // We must translate the address in order to be able to look it up
+ // in the DWARF in the object file.
+ debug_assert!(cx.object.syms.is_empty() || cx.object.syms_sort_by_name);
+ let i = cx
+ .object
+ .syms
+ .binary_search_by_key(&symbol.name(), |(name, _)| *name)
+ .ok()?;
+ let object_symbol = cx.object.syms.get(i)?;
+ let object_addr = addr
+ .wrapping_sub(symbol.address())
+ .wrapping_add(object_symbol.1);
+ Some((cx, object_addr))
+ }
+}
+
+fn object_mapping(path: &[u8]) -> Option<Mapping> {
+ use super::mystd::ffi::OsStr;
+ use super::mystd::os::unix::prelude::*;
+
+ let map;
+
+ // `N_OSO` symbol names can be either `/path/to/object.o` or `/path/to/archive.a(object.o)`.
+ let member_name = if let Some((archive_path, member_name)) = split_archive_path(path) {
+ map = super::mmap(Path::new(OsStr::from_bytes(archive_path)))?;
+ Some(member_name)
+ } else {
+ map = super::mmap(Path::new(OsStr::from_bytes(path)))?;
+ None
+ };
+ Mapping::mk(map, |data, stash| {
+ let data = match member_name {
+ Some(member_name) => {
+ let archive = object::read::archive::ArchiveFile::parse(data).ok()?;
+ let member = archive
+ .members()
+ .filter_map(Result::ok)
+ .find(|m| m.name() == member_name)?;
+ member.data(data).ok()?
+ }
+ None => data,
+ };
+ let (macho, data) = find_header(data)?;
+ let endian = macho.endian().ok()?;
+ let obj = Object::parse(macho, endian, data)?;
+ Context::new(stash, obj, None)
+ })
+}
+
+fn split_archive_path(path: &[u8]) -> Option<(&[u8], &[u8])> {
+ let (last, path) = path.split_last()?;
+ if *last != b')' {
+ return None;
+ }
+ let index = path.iter().position(|&x| x == b'(')?;
+ let (archive, rest) = path.split_at(index);
+ Some((archive, &rest[1..]))
+}
diff --git a/library/backtrace/src/symbolize/gimli/mmap_fake.rs b/library/backtrace/src/symbolize/gimli/mmap_fake.rs
new file mode 100644
index 000000000..ce5096415
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/mmap_fake.rs
@@ -0,0 +1,25 @@
+use super::{mystd::io::Read, File};
+use alloc::vec::Vec;
+use core::ops::Deref;
+
+pub struct Mmap {
+ vec: Vec<u8>,
+}
+
+impl Mmap {
+ pub unsafe fn map(mut file: &File, len: usize) -> Option<Mmap> {
+ let mut mmap = Mmap {
+ vec: Vec::with_capacity(len),
+ };
+ file.read_to_end(&mut mmap.vec).ok()?;
+ Some(mmap)
+ }
+}
+
+impl Deref for Mmap {
+ type Target = [u8];
+
+ fn deref(&self) -> &[u8] {
+ &self.vec[..]
+ }
+}
diff --git a/library/backtrace/src/symbolize/gimli/mmap_unix.rs b/library/backtrace/src/symbolize/gimli/mmap_unix.rs
new file mode 100644
index 000000000..5806c9f7e
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/mmap_unix.rs
@@ -0,0 +1,44 @@
+use super::mystd::fs::File;
+use super::mystd::os::unix::prelude::*;
+use core::ops::Deref;
+use core::ptr;
+use core::slice;
+
+pub struct Mmap {
+ ptr: *mut libc::c_void,
+ len: usize,
+}
+
+impl Mmap {
+ pub unsafe fn map(file: &File, len: usize) -> Option<Mmap> {
+ let ptr = libc::mmap(
+ ptr::null_mut(),
+ len,
+ libc::PROT_READ,
+ libc::MAP_PRIVATE,
+ file.as_raw_fd(),
+ 0,
+ );
+ if ptr == libc::MAP_FAILED {
+ return None;
+ }
+ Some(Mmap { ptr, len })
+ }
+}
+
+impl Deref for Mmap {
+ type Target = [u8];
+
+ fn deref(&self) -> &[u8] {
+ unsafe { slice::from_raw_parts(self.ptr as *const u8, self.len) }
+ }
+}
+
+impl Drop for Mmap {
+ fn drop(&mut self) {
+ unsafe {
+ let r = libc::munmap(self.ptr, self.len);
+ debug_assert_eq!(r, 0);
+ }
+ }
+}
diff --git a/library/backtrace/src/symbolize/gimli/mmap_windows.rs b/library/backtrace/src/symbolize/gimli/mmap_windows.rs
new file mode 100644
index 000000000..22f53fe03
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/mmap_windows.rs
@@ -0,0 +1,57 @@
+use super::super::super::windows::*;
+use super::mystd::fs::File;
+use super::mystd::os::windows::prelude::*;
+use core::ops::Deref;
+use core::ptr;
+use core::slice;
+
+pub struct Mmap {
+ // keep the file alive to prevent it from ebeing deleted which would cause
+ // us to read bad data.
+ _file: File,
+ ptr: *mut c_void,
+ len: usize,
+}
+
+impl Mmap {
+ pub unsafe fn map(file: &File, len: usize) -> Option<Mmap> {
+ let file = file.try_clone().ok()?;
+ let mapping = CreateFileMappingA(
+ file.as_raw_handle() as *mut _,
+ ptr::null_mut(),
+ PAGE_READONLY,
+ 0,
+ 0,
+ ptr::null(),
+ );
+ if mapping.is_null() {
+ return None;
+ }
+ let ptr = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, len);
+ CloseHandle(mapping);
+ if ptr.is_null() {
+ return None;
+ }
+ Some(Mmap {
+ _file: file,
+ ptr,
+ len,
+ })
+ }
+}
+impl Deref for Mmap {
+ type Target = [u8];
+
+ fn deref(&self) -> &[u8] {
+ unsafe { slice::from_raw_parts(self.ptr as *const u8, self.len) }
+ }
+}
+
+impl Drop for Mmap {
+ fn drop(&mut self) {
+ unsafe {
+ let r = UnmapViewOfFile(self.ptr);
+ debug_assert!(r != 0);
+ }
+ }
+}
diff --git a/library/backtrace/src/symbolize/gimli/stash.rs b/library/backtrace/src/symbolize/gimli/stash.rs
new file mode 100644
index 000000000..3adfc598a
--- /dev/null
+++ b/library/backtrace/src/symbolize/gimli/stash.rs
@@ -0,0 +1,52 @@
+// only used on Linux right now, so allow dead code elsewhere
+#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
+
+use super::Mmap;
+use alloc::vec;
+use alloc::vec::Vec;
+use core::cell::UnsafeCell;
+
+/// A simple arena allocator for byte buffers.
+pub struct Stash {
+ buffers: UnsafeCell<Vec<Vec<u8>>>,
+ mmap_aux: UnsafeCell<Option<Mmap>>,
+}
+
+impl Stash {
+ pub fn new() -> Stash {
+ Stash {
+ buffers: UnsafeCell::new(Vec::new()),
+ mmap_aux: UnsafeCell::new(None),
+ }
+ }
+
+ /// Allocates a buffer of the specified size and returns a mutable reference
+ /// to it.
+ pub fn allocate(&self, size: usize) -> &mut [u8] {
+ // SAFETY: this is the only function that ever constructs a mutable
+ // reference to `self.buffers`.
+ let buffers = unsafe { &mut *self.buffers.get() };
+ let i = buffers.len();
+ buffers.push(vec![0; size]);
+ // SAFETY: we never remove elements from `self.buffers`, so a reference
+ // to the data inside any buffer will live as long as `self` does.
+ &mut buffers[i]
+ }
+
+ /// Stores a `Mmap` for the lifetime of this `Stash`, returning a pointer
+ /// which is scoped to just this lifetime.
+ pub fn set_mmap_aux(&self, map: Mmap) -> &[u8] {
+ // SAFETY: this is the only location for a mutable pointer to
+ // `mmap_aux`, and this structure isn't threadsafe to shared across
+ // threads either. This also is careful to store at most one `mmap_aux`
+ // since overwriting a previous one would invalidate the previous
+ // pointer. Given that though we can safely return a pointer to our
+ // interior-owned contents.
+ unsafe {
+ let mmap_aux = &mut *self.mmap_aux.get();
+ assert!(mmap_aux.is_none());
+ *mmap_aux = Some(map);
+ mmap_aux.as_ref().unwrap()
+ }
+ }
+}
diff --git a/library/backtrace/src/symbolize/miri.rs b/library/backtrace/src/symbolize/miri.rs
new file mode 100644
index 000000000..5b0dc3084
--- /dev/null
+++ b/library/backtrace/src/symbolize/miri.rs
@@ -0,0 +1,56 @@
+use core::ffi::c_void;
+use core::marker::PhantomData;
+
+use super::super::backtrace::miri::{resolve_addr, Frame};
+use super::BytesOrWideString;
+use super::{ResolveWhat, SymbolName};
+
+pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) {
+ let sym = match what {
+ ResolveWhat::Address(addr) => Symbol {
+ inner: resolve_addr(addr),
+ _unused: PhantomData,
+ },
+ ResolveWhat::Frame(frame) => Symbol {
+ inner: frame.inner.clone(),
+ _unused: PhantomData,
+ },
+ };
+ cb(&super::Symbol { inner: sym })
+}
+
+pub struct Symbol<'a> {
+ inner: Frame,
+ _unused: PhantomData<&'a ()>,
+}
+
+impl<'a> Symbol<'a> {
+ pub fn name(&self) -> Option<SymbolName<'_>> {
+ Some(SymbolName::new(&self.inner.inner.name))
+ }
+
+ pub fn addr(&self) -> Option<*mut c_void> {
+ Some(self.inner.addr)
+ }
+
+ pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
+ Some(BytesOrWideString::Bytes(&self.inner.inner.filename))
+ }
+
+ pub fn lineno(&self) -> Option<u32> {
+ Some(self.inner.inner.lineno)
+ }
+
+ pub fn colno(&self) -> Option<u32> {
+ Some(self.inner.inner.colno)
+ }
+
+ #[cfg(feature = "std")]
+ pub fn filename(&self) -> Option<&std::path::Path> {
+ Some(std::path::Path::new(
+ core::str::from_utf8(&self.inner.inner.filename).unwrap(),
+ ))
+ }
+}
+
+pub unsafe fn clear_symbol_cache() {}
diff --git a/library/backtrace/src/symbolize/mod.rs b/library/backtrace/src/symbolize/mod.rs
new file mode 100644
index 000000000..dbc346522
--- /dev/null
+++ b/library/backtrace/src/symbolize/mod.rs
@@ -0,0 +1,485 @@
+use core::{fmt, str};
+
+cfg_if::cfg_if! {
+ if #[cfg(feature = "std")] {
+ use std::path::Path;
+ use std::prelude::v1::*;
+ }
+}
+
+use super::backtrace::Frame;
+use super::types::BytesOrWideString;
+use core::ffi::c_void;
+use rustc_demangle::{try_demangle, Demangle};
+
+/// Resolve an address to a symbol, passing the symbol to the specified
+/// closure.
+///
+/// This function will look up the given address in areas such as the local
+/// symbol table, dynamic symbol table, or DWARF debug info (depending on the
+/// activated implementation) to find symbols to yield.
+///
+/// The closure may not be called if resolution could not be performed, and it
+/// also may be called more than once in the case of inlined functions.
+///
+/// Symbols yielded represent the execution at the specified `addr`, returning
+/// file/line pairs for that address (if available).
+///
+/// Note that if you have a `Frame` then it's recommended to use the
+/// `resolve_frame` function instead of this one.
+///
+/// # Required features
+///
+/// This function requires the `std` feature of the `backtrace` crate to be
+/// enabled, and the `std` feature is enabled by default.
+///
+/// # Panics
+///
+/// This function strives to never panic, but if the `cb` provided panics then
+/// some platforms will force a double panic to abort the process. Some
+/// platforms use a C library which internally uses callbacks which cannot be
+/// unwound through, so panicking from `cb` may trigger a process abort.
+///
+/// # Example
+///
+/// ```
+/// extern crate backtrace;
+///
+/// fn main() {
+/// backtrace::trace(|frame| {
+/// let ip = frame.ip();
+///
+/// backtrace::resolve(ip, |symbol| {
+/// // ...
+/// });
+///
+/// false // only look at the top frame
+/// });
+/// }
+/// ```
+#[cfg(feature = "std")]
+pub fn resolve<F: FnMut(&Symbol)>(addr: *mut c_void, cb: F) {
+ let _guard = crate::lock::lock();
+ unsafe { resolve_unsynchronized(addr, cb) }
+}
+
+/// Resolve a previously capture frame to a symbol, passing the symbol to the
+/// specified closure.
+///
+/// This function performs the same function as `resolve` except that it takes a
+/// `Frame` as an argument instead of an address. This can allow some platform
+/// implementations of backtracing to provide more accurate symbol information
+/// or information about inline frames for example. It's recommended to use this
+/// if you can.
+///
+/// # Required features
+///
+/// This function requires the `std` feature of the `backtrace` crate to be
+/// enabled, and the `std` feature is enabled by default.
+///
+/// # Panics
+///
+/// This function strives to never panic, but if the `cb` provided panics then
+/// some platforms will force a double panic to abort the process. Some
+/// platforms use a C library which internally uses callbacks which cannot be
+/// unwound through, so panicking from `cb` may trigger a process abort.
+///
+/// # Example
+///
+/// ```
+/// extern crate backtrace;
+///
+/// fn main() {
+/// backtrace::trace(|frame| {
+/// backtrace::resolve_frame(frame, |symbol| {
+/// // ...
+/// });
+///
+/// false // only look at the top frame
+/// });
+/// }
+/// ```
+#[cfg(feature = "std")]
+pub fn resolve_frame<F: FnMut(&Symbol)>(frame: &Frame, cb: F) {
+ let _guard = crate::lock::lock();
+ unsafe { resolve_frame_unsynchronized(frame, cb) }
+}
+
+pub enum ResolveWhat<'a> {
+ Address(*mut c_void),
+ Frame(&'a Frame),
+}
+
+impl<'a> ResolveWhat<'a> {
+ #[allow(dead_code)]
+ fn address_or_ip(&self) -> *mut c_void {
+ match self {
+ ResolveWhat::Address(a) => adjust_ip(*a),
+ ResolveWhat::Frame(f) => adjust_ip(f.ip()),
+ }
+ }
+}
+
+// IP values from stack frames are typically (always?) the instruction
+// *after* the call that's the actual stack trace. Symbolizing this on
+// causes the filename/line number to be one ahead and perhaps into
+// the void if it's near the end of the function.
+//
+// This appears to basically always be the case on all platforms, so we always
+// subtract one from a resolved ip to resolve it to the previous call
+// instruction instead of the instruction being returned to.
+//
+// Ideally we would not do this. Ideally we would require callers of the
+// `resolve` APIs here to manually do the -1 and account that they want location
+// information for the *previous* instruction, not the current. Ideally we'd
+// also expose on `Frame` if we are indeed the address of the next instruction
+// or the current.
+//
+// For now though this is a pretty niche concern so we just internally always
+// subtract one. Consumers should keep working and getting pretty good results,
+// so we should be good enough.
+fn adjust_ip(a: *mut c_void) -> *mut c_void {
+ if a.is_null() {
+ a
+ } else {
+ (a as usize - 1) as *mut c_void
+ }
+}
+
+/// Same as `resolve`, only unsafe as it's unsynchronized.
+///
+/// This function does not have synchronization guarantees but is available when
+/// the `std` feature of this crate isn't compiled in. See the `resolve`
+/// function for more documentation and examples.
+///
+/// # Panics
+///
+/// See information on `resolve` for caveats on `cb` panicking.
+pub unsafe fn resolve_unsynchronized<F>(addr: *mut c_void, mut cb: F)
+where
+ F: FnMut(&Symbol),
+{
+ imp::resolve(ResolveWhat::Address(addr), &mut cb)
+}
+
+/// Same as `resolve_frame`, only unsafe as it's unsynchronized.
+///
+/// This function does not have synchronization guarantees but is available
+/// when the `std` feature of this crate isn't compiled in. See the
+/// `resolve_frame` function for more documentation and examples.
+///
+/// # Panics
+///
+/// See information on `resolve_frame` for caveats on `cb` panicking.
+pub unsafe fn resolve_frame_unsynchronized<F>(frame: &Frame, mut cb: F)
+where
+ F: FnMut(&Symbol),
+{
+ imp::resolve(ResolveWhat::Frame(frame), &mut cb)
+}
+
+/// A trait representing the resolution of a symbol in a file.
+///
+/// This trait is yielded as a trait object to the closure given to the
+/// `backtrace::resolve` function, and it is virtually dispatched as it's
+/// unknown which implementation is behind it.
+///
+/// A symbol can give contextual information about a function, for example the
+/// name, filename, line number, precise address, etc. Not all information is
+/// always available in a symbol, however, so all methods return an `Option`.
+pub struct Symbol {
+ // TODO: this lifetime bound needs to be persisted eventually to `Symbol`,
+ // but that's currently a breaking change. For now this is safe since
+ // `Symbol` is only ever handed out by reference and can't be cloned.
+ inner: imp::Symbol<'static>,
+}
+
+impl Symbol {
+ /// Returns the name of this function.
+ ///
+ /// The returned structure can be used to query various properties about the
+ /// symbol name:
+ ///
+ /// * The `Display` implementation will print out the demangled symbol.
+ /// * The raw `str` value of the symbol can be accessed (if it's valid
+ /// utf-8).
+ /// * The raw bytes for the symbol name can be accessed.
+ pub fn name(&self) -> Option<SymbolName<'_>> {
+ self.inner.name()
+ }
+
+ /// Returns the starting address of this function.
+ pub fn addr(&self) -> Option<*mut c_void> {
+ self.inner.addr().map(|p| p as *mut _)
+ }
+
+ /// Returns the raw filename as a slice. This is mainly useful for `no_std`
+ /// environments.
+ pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
+ self.inner.filename_raw()
+ }
+
+ /// Returns the column number for where this symbol is currently executing.
+ ///
+ /// Only gimli currently provides a value here and even then only if `filename`
+ /// returns `Some`, and so it is then consequently subject to similar caveats.
+ pub fn colno(&self) -> Option<u32> {
+ self.inner.colno()
+ }
+
+ /// Returns the line number for where this symbol is currently executing.
+ ///
+ /// This return value is typically `Some` if `filename` returns `Some`, and
+ /// is consequently subject to similar caveats.
+ pub fn lineno(&self) -> Option<u32> {
+ self.inner.lineno()
+ }
+
+ /// Returns the file name where this function was defined.
+ ///
+ /// This is currently only available when libbacktrace or gimli is being
+ /// used (e.g. unix platforms other) and when a binary is compiled with
+ /// debuginfo. If neither of these conditions is met then this will likely
+ /// return `None`.
+ ///
+ /// # Required features
+ ///
+ /// This function requires the `std` feature of the `backtrace` crate to be
+ /// enabled, and the `std` feature is enabled by default.
+ #[cfg(feature = "std")]
+ #[allow(unreachable_code)]
+ pub fn filename(&self) -> Option<&Path> {
+ self.inner.filename()
+ }
+}
+
+impl fmt::Debug for Symbol {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut d = f.debug_struct("Symbol");
+ if let Some(name) = self.name() {
+ d.field("name", &name);
+ }
+ if let Some(addr) = self.addr() {
+ d.field("addr", &addr);
+ }
+
+ #[cfg(feature = "std")]
+ {
+ if let Some(filename) = self.filename() {
+ d.field("filename", &filename);
+ }
+ }
+
+ if let Some(lineno) = self.lineno() {
+ d.field("lineno", &lineno);
+ }
+ d.finish()
+ }
+}
+
+cfg_if::cfg_if! {
+ if #[cfg(feature = "cpp_demangle")] {
+ // Maybe a parsed C++ symbol, if parsing the mangled symbol as Rust
+ // failed.
+ struct OptionCppSymbol<'a>(Option<::cpp_demangle::BorrowedSymbol<'a>>);
+
+ impl<'a> OptionCppSymbol<'a> {
+ fn parse(input: &'a [u8]) -> OptionCppSymbol<'a> {
+ OptionCppSymbol(::cpp_demangle::BorrowedSymbol::new(input).ok())
+ }
+
+ fn none() -> OptionCppSymbol<'a> {
+ OptionCppSymbol(None)
+ }
+ }
+ } else {
+ use core::marker::PhantomData;
+
+ // Make sure to keep this zero-sized, so that the `cpp_demangle` feature
+ // has no cost when disabled.
+ struct OptionCppSymbol<'a>(PhantomData<&'a ()>);
+
+ impl<'a> OptionCppSymbol<'a> {
+ fn parse(_: &'a [u8]) -> OptionCppSymbol<'a> {
+ OptionCppSymbol(PhantomData)
+ }
+
+ fn none() -> OptionCppSymbol<'a> {
+ OptionCppSymbol(PhantomData)
+ }
+ }
+ }
+}
+
+/// A wrapper around a symbol name to provide ergonomic accessors to the
+/// demangled name, the raw bytes, the raw string, etc.
+// Allow dead code for when the `cpp_demangle` feature is not enabled.
+#[allow(dead_code)]
+pub struct SymbolName<'a> {
+ bytes: &'a [u8],
+ demangled: Option<Demangle<'a>>,
+ cpp_demangled: OptionCppSymbol<'a>,
+}
+
+impl<'a> SymbolName<'a> {
+ /// Creates a new symbol name from the raw underlying bytes.
+ pub fn new(bytes: &'a [u8]) -> SymbolName<'a> {
+ let str_bytes = str::from_utf8(bytes).ok();
+ let demangled = str_bytes.and_then(|s| try_demangle(s).ok());
+
+ let cpp = if demangled.is_none() {
+ OptionCppSymbol::parse(bytes)
+ } else {
+ OptionCppSymbol::none()
+ };
+
+ SymbolName {
+ bytes: bytes,
+ demangled: demangled,
+ cpp_demangled: cpp,
+ }
+ }
+
+ /// Returns the raw (mangled) symbol name as a `str` if the symbol is valid utf-8.
+ ///
+ /// Use the `Display` implementation if you want the demangled version.
+ pub fn as_str(&self) -> Option<&'a str> {
+ self.demangled
+ .as_ref()
+ .map(|s| s.as_str())
+ .or_else(|| str::from_utf8(self.bytes).ok())
+ }
+
+ /// Returns the raw symbol name as a list of bytes
+ pub fn as_bytes(&self) -> &'a [u8] {
+ self.bytes
+ }
+}
+
+fn format_symbol_name(
+ fmt: fn(&str, &mut fmt::Formatter<'_>) -> fmt::Result,
+ mut bytes: &[u8],
+ f: &mut fmt::Formatter<'_>,
+) -> fmt::Result {
+ while bytes.len() > 0 {
+ match str::from_utf8(bytes) {
+ Ok(name) => {
+ fmt(name, f)?;
+ break;
+ }
+ Err(err) => {
+ fmt("\u{FFFD}", f)?;
+
+ match err.error_len() {
+ Some(len) => bytes = &bytes[err.valid_up_to() + len..],
+ None => break,
+ }
+ }
+ }
+ }
+ Ok(())
+}
+
+cfg_if::cfg_if! {
+ if #[cfg(feature = "cpp_demangle")] {
+ impl<'a> fmt::Display for SymbolName<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if let Some(ref s) = self.demangled {
+ s.fmt(f)
+ } else if let Some(ref cpp) = self.cpp_demangled.0 {
+ cpp.fmt(f)
+ } else {
+ format_symbol_name(fmt::Display::fmt, self.bytes, f)
+ }
+ }
+ }
+ } else {
+ impl<'a> fmt::Display for SymbolName<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if let Some(ref s) = self.demangled {
+ s.fmt(f)
+ } else {
+ format_symbol_name(fmt::Display::fmt, self.bytes, f)
+ }
+ }
+ }
+ }
+}
+
+cfg_if::cfg_if! {
+ if #[cfg(all(feature = "std", feature = "cpp_demangle"))] {
+ impl<'a> fmt::Debug for SymbolName<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use std::fmt::Write;
+
+ if let Some(ref s) = self.demangled {
+ return s.fmt(f)
+ }
+
+ // This may to print if the demangled symbol isn't actually
+ // valid, so handle the error here gracefully by not propagating
+ // it outwards.
+ if let Some(ref cpp) = self.cpp_demangled.0 {
+ let mut s = String::new();
+ if write!(s, "{}", cpp).is_ok() {
+ return s.fmt(f)
+ }
+ }
+
+ format_symbol_name(fmt::Debug::fmt, self.bytes, f)
+ }
+ }
+ } else {
+ impl<'a> fmt::Debug for SymbolName<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if let Some(ref s) = self.demangled {
+ s.fmt(f)
+ } else {
+ format_symbol_name(fmt::Debug::fmt, self.bytes, f)
+ }
+ }
+ }
+ }
+}
+
+/// Attempt to reclaim that cached memory used to symbolicate addresses.
+///
+/// This method will attempt to release any global data structures that have
+/// otherwise been cached globally or in the thread which typically represent
+/// parsed DWARF information or similar.
+///
+/// # Caveats
+///
+/// While this function is always available it doesn't actually do anything on
+/// most implementations. Libraries like dbghelp or libbacktrace do not provide
+/// facilities to deallocate state and manage the allocated memory. For now the
+/// `gimli-symbolize` feature of this crate is the only feature where this
+/// function has any effect.
+#[cfg(feature = "std")]
+pub fn clear_symbol_cache() {
+ let _guard = crate::lock::lock();
+ unsafe {
+ imp::clear_symbol_cache();
+ }
+}
+
+cfg_if::cfg_if! {
+ if #[cfg(miri)] {
+ mod miri;
+ use miri as imp;
+ } else if #[cfg(all(windows, target_env = "msvc", not(target_vendor = "uwp")))] {
+ mod dbghelp;
+ use dbghelp as imp;
+ } else if #[cfg(all(
+ any(unix, windows),
+ not(target_vendor = "uwp"),
+ not(target_os = "emscripten"),
+ any(not(backtrace_in_libstd), feature = "backtrace"),
+ ))] {
+ mod gimli;
+ use gimli as imp;
+ } else {
+ mod noop;
+ use noop as imp;
+ }
+}
diff --git a/library/backtrace/src/symbolize/noop.rs b/library/backtrace/src/symbolize/noop.rs
new file mode 100644
index 000000000..c53336531
--- /dev/null
+++ b/library/backtrace/src/symbolize/noop.rs
@@ -0,0 +1,41 @@
+//! Empty symbolication strategy used to compile for platforms that have no
+//! support.
+
+use super::{BytesOrWideString, ResolveWhat, SymbolName};
+use core::ffi::c_void;
+use core::marker;
+
+pub unsafe fn resolve(_addr: ResolveWhat<'_>, _cb: &mut dyn FnMut(&super::Symbol)) {}
+
+pub struct Symbol<'a> {
+ _marker: marker::PhantomData<&'a i32>,
+}
+
+impl Symbol<'_> {
+ pub fn name(&self) -> Option<SymbolName<'_>> {
+ None
+ }
+
+ pub fn addr(&self) -> Option<*mut c_void> {
+ None
+ }
+
+ pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
+ None
+ }
+
+ #[cfg(feature = "std")]
+ pub fn filename(&self) -> Option<&::std::path::Path> {
+ None
+ }
+
+ pub fn lineno(&self) -> Option<u32> {
+ None
+ }
+
+ pub fn colno(&self) -> Option<u32> {
+ None
+ }
+}
+
+pub unsafe fn clear_symbol_cache() {}
diff --git a/library/backtrace/src/types.rs b/library/backtrace/src/types.rs
new file mode 100644
index 000000000..c7e551059
--- /dev/null
+++ b/library/backtrace/src/types.rs
@@ -0,0 +1,83 @@
+//! Platform dependent types.
+
+cfg_if::cfg_if! {
+ if #[cfg(feature = "std")] {
+ use std::borrow::Cow;
+ use std::fmt;
+ use std::path::PathBuf;
+ use std::prelude::v1::*;
+ use std::str;
+ }
+}
+
+/// A platform independent representation of a string. When working with `std`
+/// enabled it is recommended to the convenience methods for providing
+/// conversions to `std` types.
+#[derive(Debug)]
+pub enum BytesOrWideString<'a> {
+ /// A slice, typically provided on Unix platforms.
+ Bytes(&'a [u8]),
+ /// Wide strings typically from Windows.
+ Wide(&'a [u16]),
+}
+
+#[cfg(feature = "std")]
+impl<'a> BytesOrWideString<'a> {
+ /// Lossy converts to a `Cow<str>`, will allocate if `Bytes` is not valid
+ /// UTF-8 or if `BytesOrWideString` is `Wide`.
+ ///
+ /// # 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 to_str_lossy(&self) -> Cow<'a, str> {
+ use self::BytesOrWideString::*;
+
+ match self {
+ &Bytes(slice) => String::from_utf8_lossy(slice),
+ &Wide(wide) => Cow::Owned(String::from_utf16_lossy(wide)),
+ }
+ }
+
+ /// Provides a `Path` representation of `BytesOrWideString`.
+ ///
+ /// # 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 into_path_buf(self) -> PathBuf {
+ #[cfg(unix)]
+ {
+ use std::ffi::OsStr;
+ use std::os::unix::ffi::OsStrExt;
+
+ if let BytesOrWideString::Bytes(slice) = self {
+ return PathBuf::from(OsStr::from_bytes(slice));
+ }
+ }
+
+ #[cfg(windows)]
+ {
+ use std::ffi::OsString;
+ use std::os::windows::ffi::OsStringExt;
+
+ if let BytesOrWideString::Wide(slice) = self {
+ return PathBuf::from(OsString::from_wide(slice));
+ }
+ }
+
+ if let BytesOrWideString::Bytes(b) = self {
+ if let Ok(s) = str::from_utf8(b) {
+ return PathBuf::from(s);
+ }
+ }
+ unreachable!()
+ }
+}
+
+#[cfg(feature = "std")]
+impl<'a> fmt::Display for BytesOrWideString<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.to_str_lossy().fmt(f)
+ }
+}
diff --git a/library/backtrace/src/windows.rs b/library/backtrace/src/windows.rs
new file mode 100644
index 000000000..d091874f1
--- /dev/null
+++ b/library/backtrace/src/windows.rs
@@ -0,0 +1,691 @@
+//! A module to define the FFI definitions we use on Windows for `dbghelp.dll`
+//!
+//! This module uses a custom macro, `ffi!`, to wrap all definitions to
+//! automatically generate tests to assert that our definitions here are the
+//! same as `winapi`.
+//!
+//! This module largely exists to integrate into libstd itself where winapi is
+//! not currently available.
+
+#![allow(bad_style, dead_code)]
+
+cfg_if::cfg_if! {
+ if #[cfg(feature = "verify-winapi")] {
+ pub use self::winapi::c_void;
+ pub use self::winapi::HINSTANCE;
+ pub use self::winapi::FARPROC;
+ pub use self::winapi::LPSECURITY_ATTRIBUTES;
+ #[cfg(target_pointer_width = "64")]
+ pub use self::winapi::PUNWIND_HISTORY_TABLE;
+ #[cfg(target_pointer_width = "64")]
+ pub use self::winapi::PRUNTIME_FUNCTION;
+
+ mod winapi {
+ pub use winapi::ctypes::*;
+ pub use winapi::shared::basetsd::*;
+ pub use winapi::shared::minwindef::*;
+ pub use winapi::um::dbghelp::*;
+ pub use winapi::um::fileapi::*;
+ pub use winapi::um::handleapi::*;
+ pub use winapi::um::libloaderapi::*;
+ pub use winapi::um::memoryapi::*;
+ pub use winapi::um::minwinbase::*;
+ pub use winapi::um::processthreadsapi::*;
+ pub use winapi::um::synchapi::*;
+ pub use winapi::um::tlhelp32::*;
+ pub use winapi::um::winbase::*;
+ pub use winapi::um::winnt::*;
+ }
+ } else {
+ pub use core::ffi::c_void;
+ pub type HINSTANCE = *mut c_void;
+ pub type FARPROC = *mut c_void;
+ pub type LPSECURITY_ATTRIBUTES = *mut c_void;
+ #[cfg(target_pointer_width = "64")]
+ pub type PRUNTIME_FUNCTION = *mut c_void;
+ #[cfg(target_pointer_width = "64")]
+ pub type PUNWIND_HISTORY_TABLE = *mut c_void;
+ }
+}
+
+macro_rules! ffi {
+ () => ();
+
+ (#[repr($($r:tt)*)] pub struct $name:ident { $(pub $field:ident: $ty:ty,)* } $($rest:tt)*) => (
+ #[repr($($r)*)]
+ #[cfg(not(feature = "verify-winapi"))]
+ #[derive(Copy, Clone)]
+ pub struct $name {
+ $(pub $field: $ty,)*
+ }
+
+ #[cfg(feature = "verify-winapi")]
+ pub use self::winapi::$name;
+
+ #[test]
+ #[cfg(feature = "verify-winapi")]
+ fn $name() {
+ use core::mem;
+
+ #[repr($($r)*)]
+ pub struct $name {
+ $(pub $field: $ty,)*
+ }
+
+ assert_eq!(
+ mem::size_of::<$name>(),
+ mem::size_of::<winapi::$name>(),
+ concat!("size of ", stringify!($name), " is wrong"),
+ );
+ assert_eq!(
+ mem::align_of::<$name>(),
+ mem::align_of::<winapi::$name>(),
+ concat!("align of ", stringify!($name), " is wrong"),
+ );
+
+ type Winapi = winapi::$name;
+
+ fn assert_same<T>(_: T, _: T) {}
+
+ unsafe {
+ let a = &*(mem::align_of::<$name>() as *const $name);
+ let b = &*(mem::align_of::<Winapi>() as *const Winapi);
+
+ $(
+ ffi!(@test_fields a b $field $ty);
+ )*
+ }
+ }
+
+ ffi!($($rest)*);
+ );
+
+ // Handling verification against unions in winapi requires some special care
+ (@test_fields $a:ident $b:ident FltSave $ty:ty) => (
+ // Skip this field on x86_64 `CONTEXT` since it's a union and a bit funny
+ );
+ (@test_fields $a:ident $b:ident D $ty:ty) => ({
+ let a = &$a.D;
+ let b = $b.D();
+ assert_same(a, b);
+ assert_eq!(a as *const $ty, b as *const $ty, "misplaced field D");
+ });
+ (@test_fields $a:ident $b:ident s $ty:ty) => ({
+ let a = &$a.s;
+ let b = $b.s();
+ assert_same(a, b);
+ assert_eq!(a as *const $ty, b as *const $ty, "misplaced field s");
+ });
+
+ // Otherwise test all fields normally.
+ (@test_fields $a:ident $b:ident $field:ident $ty:ty) => ({
+ let a = &$a.$field;
+ let b = &$b.$field;
+ assert_same(a, b);
+ assert_eq!(a as *const $ty, b as *const $ty,
+ concat!("misplaced field ", stringify!($field)));
+ });
+
+ (pub type $name:ident = $ty:ty; $($rest:tt)*) => (
+ pub type $name = $ty;
+
+ #[cfg(feature = "verify-winapi")]
+ #[allow(dead_code)]
+ const $name: () = {
+ fn _foo() {
+ trait SameType {}
+ impl<T> SameType for (T, T) {}
+ fn assert_same<T: SameType>() {}
+
+ assert_same::<($name, winapi::$name)>();
+ }
+ };
+
+ ffi!($($rest)*);
+ );
+
+ (pub const $name:ident: $ty:ty = $val:expr; $($rest:tt)*) => (
+ pub const $name: $ty = $val;
+
+ #[cfg(feature = "verify-winapi")]
+ #[allow(unused_imports)]
+ mod $name {
+ use super::*;
+ #[test]
+ fn assert_valid() {
+ let x: $ty = winapi::$name;
+ assert_eq!(x, $val);
+ }
+ }
+
+
+ ffi!($($rest)*);
+ );
+
+ (extern "system" { $(pub fn $name:ident($($args:tt)*) -> $ret:ty;)* } $($rest:tt)*) => (
+ extern "system" {
+ $(pub fn $name($($args)*) -> $ret;)*
+ }
+
+ $(
+ #[cfg(feature = "verify-winapi")]
+ mod $name {
+ #[test]
+ fn assert_same() {
+ use super::*;
+
+ assert_eq!($name as usize, winapi::$name as usize);
+ let mut x: unsafe extern "system" fn($($args)*) -> $ret;
+ x = $name;
+ drop(x);
+ x = winapi::$name;
+ drop(x);
+ }
+ }
+ )*
+
+ ffi!($($rest)*);
+ );
+
+ (impl $name:ident { $($i:tt)* } $($rest:tt)*) => (
+ #[cfg(not(feature = "verify-winapi"))]
+ impl $name {
+ $($i)*
+ }
+
+ ffi!($($rest)*);
+ );
+}
+
+ffi! {
+ #[repr(C)]
+ pub struct STACKFRAME64 {
+ pub AddrPC: ADDRESS64,
+ pub AddrReturn: ADDRESS64,
+ pub AddrFrame: ADDRESS64,
+ pub AddrStack: ADDRESS64,
+ pub AddrBStore: ADDRESS64,
+ pub FuncTableEntry: PVOID,
+ pub Params: [DWORD64; 4],
+ pub Far: BOOL,
+ pub Virtual: BOOL,
+ pub Reserved: [DWORD64; 3],
+ pub KdHelp: KDHELP64,
+ }
+
+ pub type LPSTACKFRAME64 = *mut STACKFRAME64;
+
+ #[repr(C)]
+ pub struct STACKFRAME_EX {
+ pub AddrPC: ADDRESS64,
+ pub AddrReturn: ADDRESS64,
+ pub AddrFrame: ADDRESS64,
+ pub AddrStack: ADDRESS64,
+ pub AddrBStore: ADDRESS64,
+ pub FuncTableEntry: PVOID,
+ pub Params: [DWORD64; 4],
+ pub Far: BOOL,
+ pub Virtual: BOOL,
+ pub Reserved: [DWORD64; 3],
+ pub KdHelp: KDHELP64,
+ pub StackFrameSize: DWORD,
+ pub InlineFrameContext: DWORD,
+ }
+
+ pub type LPSTACKFRAME_EX = *mut STACKFRAME_EX;
+
+ #[repr(C)]
+ pub struct IMAGEHLP_LINEW64 {
+ pub SizeOfStruct: DWORD,
+ pub Key: PVOID,
+ pub LineNumber: DWORD,
+ pub FileName: PWSTR,
+ pub Address: DWORD64,
+ }
+
+ pub type PIMAGEHLP_LINEW64 = *mut IMAGEHLP_LINEW64;
+
+ #[repr(C)]
+ pub struct SYMBOL_INFOW {
+ pub SizeOfStruct: ULONG,
+ pub TypeIndex: ULONG,
+ pub Reserved: [ULONG64; 2],
+ pub Index: ULONG,
+ pub Size: ULONG,
+ pub ModBase: ULONG64,
+ pub Flags: ULONG,
+ pub Value: ULONG64,
+ pub Address: ULONG64,
+ pub Register: ULONG,
+ pub Scope: ULONG,
+ pub Tag: ULONG,
+ pub NameLen: ULONG,
+ pub MaxNameLen: ULONG,
+ pub Name: [WCHAR; 1],
+ }
+
+ pub type PSYMBOL_INFOW = *mut SYMBOL_INFOW;
+
+ pub type PTRANSLATE_ADDRESS_ROUTINE64 = Option<
+ unsafe extern "system" fn(hProcess: HANDLE, hThread: HANDLE, lpaddr: LPADDRESS64) -> DWORD64,
+ >;
+ pub type PGET_MODULE_BASE_ROUTINE64 =
+ Option<unsafe extern "system" fn(hProcess: HANDLE, Address: DWORD64) -> DWORD64>;
+ pub type PFUNCTION_TABLE_ACCESS_ROUTINE64 =
+ Option<unsafe extern "system" fn(ahProcess: HANDLE, AddrBase: DWORD64) -> PVOID>;
+ pub type PREAD_PROCESS_MEMORY_ROUTINE64 = Option<
+ unsafe extern "system" fn(
+ hProcess: HANDLE,
+ qwBaseAddress: DWORD64,
+ lpBuffer: PVOID,
+ nSize: DWORD,
+ lpNumberOfBytesRead: LPDWORD,
+ ) -> BOOL,
+ >;
+
+ #[repr(C)]
+ pub struct ADDRESS64 {
+ pub Offset: DWORD64,
+ pub Segment: WORD,
+ pub Mode: ADDRESS_MODE,
+ }
+
+ pub type LPADDRESS64 = *mut ADDRESS64;
+
+ pub type ADDRESS_MODE = u32;
+
+ #[repr(C)]
+ pub struct KDHELP64 {
+ pub Thread: DWORD64,
+ pub ThCallbackStack: DWORD,
+ pub ThCallbackBStore: DWORD,
+ pub NextCallback: DWORD,
+ pub FramePointer: DWORD,
+ pub KiCallUserMode: DWORD64,
+ pub KeUserCallbackDispatcher: DWORD64,
+ pub SystemRangeStart: DWORD64,
+ pub KiUserExceptionDispatcher: DWORD64,
+ pub StackBase: DWORD64,
+ pub StackLimit: DWORD64,
+ pub BuildVersion: DWORD,
+ pub Reserved0: DWORD,
+ pub Reserved1: [DWORD64; 4],
+ }
+
+ #[repr(C)]
+ pub struct MODULEENTRY32W {
+ pub dwSize: DWORD,
+ pub th32ModuleID: DWORD,
+ pub th32ProcessID: DWORD,
+ pub GlblcntUsage: DWORD,
+ pub ProccntUsage: DWORD,
+ pub modBaseAddr: *mut u8,
+ pub modBaseSize: DWORD,
+ pub hModule: HMODULE,
+ pub szModule: [WCHAR; MAX_MODULE_NAME32 + 1],
+ pub szExePath: [WCHAR; MAX_PATH],
+ }
+
+ pub const MAX_SYM_NAME: usize = 2000;
+ pub const AddrModeFlat: ADDRESS_MODE = 3;
+ pub const TRUE: BOOL = 1;
+ pub const FALSE: BOOL = 0;
+ pub const PROCESS_QUERY_INFORMATION: DWORD = 0x400;
+ pub const IMAGE_FILE_MACHINE_ARM64: u16 = 43620;
+ pub const IMAGE_FILE_MACHINE_AMD64: u16 = 34404;
+ pub const IMAGE_FILE_MACHINE_I386: u16 = 332;
+ pub const IMAGE_FILE_MACHINE_ARMNT: u16 = 452;
+ pub const FILE_SHARE_READ: DWORD = 0x1;
+ pub const FILE_SHARE_WRITE: DWORD = 0x2;
+ pub const OPEN_EXISTING: DWORD = 0x3;
+ pub const GENERIC_READ: DWORD = 0x80000000;
+ pub const INFINITE: DWORD = !0;
+ pub const PAGE_READONLY: DWORD = 2;
+ pub const FILE_MAP_READ: DWORD = 4;
+ pub const TH32CS_SNAPMODULE: DWORD = 0x00000008;
+ pub const INVALID_HANDLE_VALUE: HANDLE = -1isize as HANDLE;
+ pub const MAX_MODULE_NAME32: usize = 255;
+ pub const MAX_PATH: usize = 260;
+
+ pub type DWORD = u32;
+ pub type PDWORD = *mut u32;
+ pub type BOOL = i32;
+ pub type DWORD64 = u64;
+ pub type PDWORD64 = *mut u64;
+ pub type HANDLE = *mut c_void;
+ pub type PVOID = HANDLE;
+ pub type PCWSTR = *const u16;
+ pub type LPSTR = *mut i8;
+ pub type LPCSTR = *const i8;
+ pub type PWSTR = *mut u16;
+ pub type WORD = u16;
+ pub type ULONG = u32;
+ pub type ULONG64 = u64;
+ pub type WCHAR = u16;
+ pub type PCONTEXT = *mut CONTEXT;
+ pub type LPDWORD = *mut DWORD;
+ pub type DWORDLONG = u64;
+ pub type HMODULE = HINSTANCE;
+ pub type SIZE_T = usize;
+ pub type LPVOID = *mut c_void;
+ pub type LPCVOID = *const c_void;
+ pub type LPMODULEENTRY32W = *mut MODULEENTRY32W;
+
+ extern "system" {
+ pub fn GetCurrentProcess() -> HANDLE;
+ pub fn GetCurrentThread() -> HANDLE;
+ 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,
+ name: LPCSTR,
+ ) -> HANDLE;
+ pub fn ReleaseMutex(hMutex: HANDLE) -> BOOL;
+ pub fn WaitForSingleObjectEx(
+ hHandle: HANDLE,
+ dwMilliseconds: DWORD,
+ bAlertable: BOOL,
+ ) -> DWORD;
+ pub fn CreateFileMappingA(
+ hFile: HANDLE,
+ lpFileMappingAttributes: LPSECURITY_ATTRIBUTES,
+ flProtect: DWORD,
+ dwMaximumSizeHigh: DWORD,
+ dwMaximumSizeLow: DWORD,
+ lpName: LPCSTR,
+ ) -> HANDLE;
+ pub fn MapViewOfFile(
+ hFileMappingObject: HANDLE,
+ dwDesiredAccess: DWORD,
+ dwFileOffsetHigh: DWORD,
+ dwFileOffsetLow: DWORD,
+ dwNumberOfBytesToMap: SIZE_T,
+ ) -> LPVOID;
+ pub fn UnmapViewOfFile(lpBaseAddress: LPCVOID) -> BOOL;
+ pub fn CreateToolhelp32Snapshot(
+ dwFlags: DWORD,
+ th32ProcessID: DWORD,
+ ) -> HANDLE;
+ pub fn Module32FirstW(
+ hSnapshot: HANDLE,
+ lpme: LPMODULEENTRY32W,
+ ) -> BOOL;
+ pub fn Module32NextW(
+ hSnapshot: HANDLE,
+ lpme: LPMODULEENTRY32W,
+ ) -> BOOL;
+ }
+}
+
+#[cfg(target_pointer_width = "64")]
+ffi! {
+ extern "system" {
+ pub fn RtlLookupFunctionEntry(
+ ControlPc: DWORD64,
+ ImageBase: PDWORD64,
+ HistoryTable: PUNWIND_HISTORY_TABLE,
+ ) -> PRUNTIME_FUNCTION;
+ }
+}
+
+#[cfg(target_arch = "aarch64")]
+ffi! {
+ #[repr(C, align(16))]
+ pub struct CONTEXT {
+ pub ContextFlags: DWORD,
+ pub Cpsr: DWORD,
+ pub u: CONTEXT_u,
+ pub Sp: u64,
+ pub Pc: u64,
+ pub V: [ARM64_NT_NEON128; 32],
+ pub Fpcr: DWORD,
+ pub Fpsr: DWORD,
+ pub Bcr: [DWORD; ARM64_MAX_BREAKPOINTS],
+ pub Bvr: [DWORD64; ARM64_MAX_BREAKPOINTS],
+ pub Wcr: [DWORD; ARM64_MAX_WATCHPOINTS],
+ pub Wvr: [DWORD64; ARM64_MAX_WATCHPOINTS],
+ }
+
+ #[repr(C)]
+ pub struct CONTEXT_u {
+ pub s: CONTEXT_u_s,
+ }
+
+ impl CONTEXT_u {
+ pub unsafe fn s(&self) -> &CONTEXT_u_s {
+ &self.s
+ }
+ }
+
+ #[repr(C)]
+ pub struct CONTEXT_u_s {
+ pub X0: u64,
+ pub X1: u64,
+ pub X2: u64,
+ pub X3: u64,
+ pub X4: u64,
+ pub X5: u64,
+ pub X6: u64,
+ pub X7: u64,
+ pub X8: u64,
+ pub X9: u64,
+ pub X10: u64,
+ pub X11: u64,
+ pub X12: u64,
+ pub X13: u64,
+ pub X14: u64,
+ pub X15: u64,
+ pub X16: u64,
+ pub X17: u64,
+ pub X18: u64,
+ pub X19: u64,
+ pub X20: u64,
+ pub X21: u64,
+ pub X22: u64,
+ pub X23: u64,
+ pub X24: u64,
+ pub X25: u64,
+ pub X26: u64,
+ pub X27: u64,
+ pub X28: u64,
+ pub Fp: u64,
+ pub Lr: u64,
+ }
+
+ pub const ARM64_MAX_BREAKPOINTS: usize = 8;
+ pub const ARM64_MAX_WATCHPOINTS: usize = 2;
+
+ #[repr(C)]
+ pub struct ARM64_NT_NEON128 {
+ pub D: [f64; 2],
+ }
+}
+
+#[cfg(target_arch = "x86")]
+ffi! {
+ #[repr(C)]
+ pub struct CONTEXT {
+ pub ContextFlags: DWORD,
+ pub Dr0: DWORD,
+ pub Dr1: DWORD,
+ pub Dr2: DWORD,
+ pub Dr3: DWORD,
+ pub Dr6: DWORD,
+ pub Dr7: DWORD,
+ pub FloatSave: FLOATING_SAVE_AREA,
+ pub SegGs: DWORD,
+ pub SegFs: DWORD,
+ pub SegEs: DWORD,
+ pub SegDs: DWORD,
+ pub Edi: DWORD,
+ pub Esi: DWORD,
+ pub Ebx: DWORD,
+ pub Edx: DWORD,
+ pub Ecx: DWORD,
+ pub Eax: DWORD,
+ pub Ebp: DWORD,
+ pub Eip: DWORD,
+ pub SegCs: DWORD,
+ pub EFlags: DWORD,
+ pub Esp: DWORD,
+ pub SegSs: DWORD,
+ pub ExtendedRegisters: [u8; 512],
+ }
+
+ #[repr(C)]
+ pub struct FLOATING_SAVE_AREA {
+ pub ControlWord: DWORD,
+ pub StatusWord: DWORD,
+ pub TagWord: DWORD,
+ pub ErrorOffset: DWORD,
+ pub ErrorSelector: DWORD,
+ pub DataOffset: DWORD,
+ pub DataSelector: DWORD,
+ pub RegisterArea: [u8; 80],
+ pub Spare0: DWORD,
+ }
+}
+
+#[cfg(target_arch = "x86_64")]
+ffi! {
+ #[repr(C, align(8))]
+ pub struct CONTEXT {
+ pub P1Home: DWORDLONG,
+ pub P2Home: DWORDLONG,
+ pub P3Home: DWORDLONG,
+ pub P4Home: DWORDLONG,
+ pub P5Home: DWORDLONG,
+ pub P6Home: DWORDLONG,
+
+ pub ContextFlags: DWORD,
+ pub MxCsr: DWORD,
+
+ pub SegCs: WORD,
+ pub SegDs: WORD,
+ pub SegEs: WORD,
+ pub SegFs: WORD,
+ pub SegGs: WORD,
+ pub SegSs: WORD,
+ pub EFlags: DWORD,
+
+ pub Dr0: DWORDLONG,
+ pub Dr1: DWORDLONG,
+ pub Dr2: DWORDLONG,
+ pub Dr3: DWORDLONG,
+ pub Dr6: DWORDLONG,
+ pub Dr7: DWORDLONG,
+
+ pub Rax: DWORDLONG,
+ pub Rcx: DWORDLONG,
+ pub Rdx: DWORDLONG,
+ pub Rbx: DWORDLONG,
+ pub Rsp: DWORDLONG,
+ pub Rbp: DWORDLONG,
+ pub Rsi: DWORDLONG,
+ pub Rdi: DWORDLONG,
+ pub R8: DWORDLONG,
+ pub R9: DWORDLONG,
+ pub R10: DWORDLONG,
+ pub R11: DWORDLONG,
+ pub R12: DWORDLONG,
+ pub R13: DWORDLONG,
+ pub R14: DWORDLONG,
+ pub R15: DWORDLONG,
+
+ pub Rip: DWORDLONG,
+
+ pub FltSave: FLOATING_SAVE_AREA,
+
+ pub VectorRegister: [M128A; 26],
+ pub VectorControl: DWORDLONG,
+
+ pub DebugControl: DWORDLONG,
+ pub LastBranchToRip: DWORDLONG,
+ pub LastBranchFromRip: DWORDLONG,
+ pub LastExceptionToRip: DWORDLONG,
+ pub LastExceptionFromRip: DWORDLONG,
+ }
+
+ #[repr(C)]
+ pub struct M128A {
+ pub Low: u64,
+ pub High: i64,
+ }
+}
+
+#[repr(C)]
+#[cfg(target_arch = "x86_64")]
+#[derive(Copy, Clone)]
+pub struct FLOATING_SAVE_AREA {
+ _Dummy: [u8; 512],
+}
+
+#[cfg(target_arch = "arm")]
+ffi! {
+ // #[repr(C)]
+ // pub struct NEON128 {
+ // pub Low: ULONG64,
+ // pub High: LONG64,
+ // }
+
+ // pub type PNEON128 = *mut NEON128;
+
+ #[repr(C)]
+ pub struct CONTEXT_u {
+ // pub Q: [NEON128; 16],
+ pub D: [ULONG64; 32],
+ // pub S: [DWORD; 32],
+ }
+
+ pub const ARM_MAX_BREAKPOINTS: usize = 8;
+ pub const ARM_MAX_WATCHPOINTS: usize = 1;
+
+ #[repr(C)]
+ pub struct CONTEXT {
+ pub ContextFlags: DWORD,
+ pub R0: DWORD,
+ pub R1: DWORD,
+ pub R2: DWORD,
+ pub R3: DWORD,
+ pub R4: DWORD,
+ pub R5: DWORD,
+ pub R6: DWORD,
+ pub R7: DWORD,
+ pub R8: DWORD,
+ pub R9: DWORD,
+ pub R10: DWORD,
+ pub R11: DWORD,
+ pub R12: DWORD,
+ pub Sp: DWORD,
+ pub Lr: DWORD,
+ pub Pc: DWORD,
+ pub Cpsr: DWORD,
+ pub Fpsrc: DWORD,
+ pub Padding: DWORD,
+ pub u: CONTEXT_u,
+ pub Bvr: [DWORD; ARM_MAX_BREAKPOINTS],
+ pub Bcr: [DWORD; ARM_MAX_BREAKPOINTS],
+ pub Wvr: [DWORD; ARM_MAX_WATCHPOINTS],
+ pub Wcr: [DWORD; ARM_MAX_WATCHPOINTS],
+ pub Padding2: [DWORD; 2],
+ }
+} // IFDEF(arm)
diff --git a/library/backtrace/tests/accuracy/auxiliary.rs b/library/backtrace/tests/accuracy/auxiliary.rs
new file mode 100644
index 000000000..9c8015d9a
--- /dev/null
+++ b/library/backtrace/tests/accuracy/auxiliary.rs
@@ -0,0 +1,15 @@
+#[inline(never)]
+pub fn callback<F>(f: F)
+where
+ F: FnOnce((&'static str, u32)),
+{
+ f((file!(), line!()))
+}
+
+#[inline(always)]
+pub fn callback_inlined<F>(f: F)
+where
+ F: FnOnce((&'static str, u32)),
+{
+ f((file!(), line!()))
+}
diff --git a/library/backtrace/tests/accuracy/main.rs b/library/backtrace/tests/accuracy/main.rs
new file mode 100644
index 000000000..149203a1b
--- /dev/null
+++ b/library/backtrace/tests/accuracy/main.rs
@@ -0,0 +1,117 @@
+mod auxiliary;
+
+macro_rules! pos {
+ () => {
+ (file!(), line!())
+ };
+}
+
+macro_rules! check {
+ ($($pos:expr),*) => ({
+ verify(&[$($pos,)* pos!()]);
+ })
+}
+
+type Pos = (&'static str, u32);
+
+#[test]
+fn doit() {
+ if
+ // Skip musl which is by default statically linked and doesn't support
+ // dynamic libraries.
+ !cfg!(target_env = "musl")
+ // Skip Miri, since it doesn't support dynamic libraries.
+ && !cfg!(miri)
+ {
+ // TODO(#238) this shouldn't have to happen first in this function, but
+ // currently it does.
+ let mut dir = std::env::current_exe().unwrap();
+ dir.pop();
+ if cfg!(windows) {
+ dir.push("dylib_dep.dll");
+ } else if cfg!(target_os = "macos") {
+ dir.push("libdylib_dep.dylib");
+ } else {
+ dir.push("libdylib_dep.so");
+ }
+ unsafe {
+ let lib = libloading::Library::new(&dir).unwrap();
+ let api = lib.get::<extern "C" fn(Pos, fn(Pos, Pos))>(b"foo").unwrap();
+ api(pos!(), |a, b| {
+ check!(a, b);
+ });
+ }
+ }
+
+ outer(pos!());
+}
+
+#[inline(never)]
+fn outer(main_pos: Pos) {
+ inner(main_pos, pos!());
+ inner_inlined(main_pos, pos!());
+}
+
+#[inline(never)]
+#[rustfmt::skip]
+fn inner(main_pos: Pos, outer_pos: Pos) {
+ check!(main_pos, outer_pos);
+ check!(main_pos, outer_pos);
+ let inner_pos = pos!(); auxiliary::callback(|aux_pos| {
+ check!(main_pos, outer_pos, inner_pos, aux_pos);
+ });
+ let inner_pos = pos!(); auxiliary::callback_inlined(|aux_pos| {
+ check!(main_pos, outer_pos, inner_pos, aux_pos);
+ });
+}
+
+#[inline(always)]
+#[rustfmt::skip]
+fn inner_inlined(main_pos: Pos, outer_pos: Pos) {
+ check!(main_pos, outer_pos);
+ check!(main_pos, outer_pos);
+
+ #[inline(always)]
+ fn inner_further_inlined(main_pos: Pos, outer_pos: Pos, inner_pos: Pos) {
+ check!(main_pos, outer_pos, inner_pos);
+ }
+ inner_further_inlined(main_pos, outer_pos, pos!());
+
+ let inner_pos = pos!(); auxiliary::callback(|aux_pos| {
+ check!(main_pos, outer_pos, inner_pos, aux_pos);
+ });
+ let inner_pos = pos!(); auxiliary::callback_inlined(|aux_pos| {
+ check!(main_pos, outer_pos, inner_pos, aux_pos);
+ });
+
+ // this tests a distinction between two independent calls to the inlined function.
+ // (un)fortunately, LLVM somehow merges two consecutive such calls into one node.
+ inner_further_inlined(main_pos, outer_pos, pos!());
+}
+
+fn verify(filelines: &[Pos]) {
+ let trace = backtrace::Backtrace::new();
+ println!("-----------------------------------");
+ println!("looking for:");
+ for (file, line) in filelines.iter().rev() {
+ println!("\t{}:{}", file, line);
+ }
+ 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),
+ };
+ if let Some(filename) = sym.filename() {
+ if let Some(lineno) = sym.lineno() {
+ if filename.ends_with(file) && lineno == *line {
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/library/backtrace/tests/concurrent-panics.rs b/library/backtrace/tests/concurrent-panics.rs
new file mode 100644
index 000000000..470245cc9
--- /dev/null
+++ b/library/backtrace/tests/concurrent-panics.rs
@@ -0,0 +1,78 @@
+use std::env;
+use std::panic;
+use std::process::Command;
+use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
+use std::sync::Arc;
+use std::thread;
+
+const PANICS: usize = 100;
+const THREADS: usize = 8;
+const VAR: &str = "__THE_TEST_YOU_ARE_LUKE";
+
+fn main() {
+ // These run in docker containers on CI where they can't re-exec the test,
+ // so just skip these for CI. No other reason this can't run on those
+ // platforms though.
+ // Miri does not have support for re-execing a file
+ if cfg!(unix)
+ && (cfg!(target_arch = "arm")
+ || cfg!(target_arch = "aarch64")
+ || cfg!(target_arch = "s390x"))
+ || cfg!(miri)
+ {
+ println!("test result: ok");
+ return;
+ }
+
+ if env::var(VAR).is_err() {
+ parent();
+ } else {
+ child();
+ }
+}
+
+fn parent() {
+ let me = env::current_exe().unwrap();
+ let result = Command::new(&me)
+ .env("RUST_BACKTRACE", "1")
+ .env(VAR, "1")
+ .output()
+ .unwrap();
+ if result.status.success() {
+ println!("test result: ok");
+ return;
+ }
+ println!("stdout:\n{}", String::from_utf8_lossy(&result.stdout));
+ println!("stderr:\n{}", String::from_utf8_lossy(&result.stderr));
+ println!("code: {}", result.status);
+ panic!();
+}
+
+fn child() {
+ let done = Arc::new(AtomicBool::new(false));
+ let done2 = done.clone();
+ let a = thread::spawn(move || {
+ while !done2.load(SeqCst) {
+ format!("{:?}", backtrace::Backtrace::new());
+ }
+ });
+
+ let threads = (0..THREADS)
+ .map(|_| {
+ thread::spawn(|| {
+ for _ in 0..PANICS {
+ assert!(panic::catch_unwind(|| {
+ panic!();
+ })
+ .is_err());
+ }
+ })
+ })
+ .collect::<Vec<_>>();
+ for thread in threads {
+ thread.join().unwrap();
+ }
+
+ done.store(true, SeqCst);
+ a.join().unwrap();
+}
diff --git a/library/backtrace/tests/long_fn_name.rs b/library/backtrace/tests/long_fn_name.rs
new file mode 100644
index 000000000..fa4cfda15
--- /dev/null
+++ b/library/backtrace/tests/long_fn_name.rs
@@ -0,0 +1,48 @@
+use backtrace::Backtrace;
+
+// 50-character module name
+mod _234567890_234567890_234567890_234567890_234567890 {
+ // 50-character struct name
+ #[allow(non_camel_case_types)]
+ pub struct _234567890_234567890_234567890_234567890_234567890<T>(T);
+ impl<T> _234567890_234567890_234567890_234567890_234567890<T> {
+ #[allow(dead_code)]
+ pub fn new() -> crate::Backtrace {
+ crate::Backtrace::new()
+ }
+ }
+}
+
+// Long function names must be truncated to (MAX_SYM_NAME - 1) characters.
+// Only run this test for msvc, since gnu prints "<no info>" for all frames.
+#[test]
+#[cfg(all(windows, target_env = "msvc"))]
+fn test_long_fn_name() {
+ use _234567890_234567890_234567890_234567890_234567890::_234567890_234567890_234567890_234567890_234567890 as S;
+
+ // 10 repetitions of struct name, so fully qualified function name is
+ // atleast 10 * (50 + 50) * 2 = 2000 characters long.
+ // It's actually longer since it also includes `::`, `<>` and the
+ // name of the current module
+ let bt = S::<S<S<S<S<S<S<S<S<S<i32>>>>>>>>>>::new();
+ println!("{:?}", bt);
+
+ let mut found_long_name_frame = false;
+
+ for frame in bt.frames() {
+ let symbols = frame.symbols();
+ if symbols.is_empty() {
+ continue;
+ }
+
+ if let Some(function_name) = symbols[0].name() {
+ let function_name = function_name.as_str().unwrap();
+ if function_name.contains("::_234567890_234567890_234567890_234567890_234567890") {
+ found_long_name_frame = true;
+ assert!(function_name.len() > 200);
+ }
+ }
+ }
+
+ assert!(found_long_name_frame);
+}
diff --git a/library/backtrace/tests/skip_inner_frames.rs b/library/backtrace/tests/skip_inner_frames.rs
new file mode 100644
index 000000000..8b57bef52
--- /dev/null
+++ b/library/backtrace/tests/skip_inner_frames.rs
@@ -0,0 +1,44 @@
+use backtrace::Backtrace;
+
+// This test only works on platforms which have a working `symbol_address`
+// function for frames which reports the starting address of a symbol. As a
+// result it's only enabled on a few platforms.
+const ENABLED: bool = cfg!(all(
+ // Windows hasn't really been tested, and OSX doesn't support actually
+ // finding an enclosing frame, so disable this
+ target_os = "linux",
+ // On ARM finding the enclosing function is simply returning the ip itself.
+ not(target_arch = "arm"),
+));
+
+#[test]
+fn backtrace_new_unresolved_should_start_with_call_site_trace() {
+ if !ENABLED {
+ return;
+ }
+ let mut b = Backtrace::new_unresolved();
+ b.resolve();
+ println!("{:?}", b);
+
+ assert!(!b.frames().is_empty());
+
+ let this_ip = backtrace_new_unresolved_should_start_with_call_site_trace as usize;
+ println!("this_ip: {:?}", this_ip as *const usize);
+ let frame_ip = b.frames().first().unwrap().symbol_address() as usize;
+ assert_eq!(this_ip, frame_ip);
+}
+
+#[test]
+fn backtrace_new_should_start_with_call_site_trace() {
+ if !ENABLED {
+ return;
+ }
+ let b = Backtrace::new();
+ println!("{:?}", b);
+
+ assert!(!b.frames().is_empty());
+
+ let this_ip = backtrace_new_should_start_with_call_site_trace as usize;
+ let frame_ip = b.frames().first().unwrap().symbol_address() as usize;
+ assert_eq!(this_ip, frame_ip);
+}
diff --git a/library/backtrace/tests/smoke.rs b/library/backtrace/tests/smoke.rs
new file mode 100644
index 000000000..683a6f0db
--- /dev/null
+++ b/library/backtrace/tests/smoke.rs
@@ -0,0 +1,323 @@
+use backtrace::Frame;
+use std::thread;
+
+#[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)]
+#[rustfmt::skip] // we care about line numbers here
+fn smoke_test_frames() {
+ frame_1(line!());
+ #[inline(never)] fn frame_1(start_line: u32) { frame_2(start_line) }
+ #[inline(never)] fn frame_2(start_line: u32) { frame_3(start_line) }
+ #[inline(never)] fn frame_3(start_line: u32) { frame_4(start_line) }
+ #[inline(never)] fn frame_4(start_line: u32) {
+ let mut v = Vec::new();
+ backtrace::trace(|cx| {
+ v.push(cx.clone());
+ true
+ });
+
+ // 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 offset = v
+ .iter()
+ .map(|frame| frame.symbol_address() as usize)
+ .enumerate()
+ .filter_map(|(i, sym)| {
+ if sym >= target {
+ Some((sym, i))
+ } else {
+ None
+ }
+ })
+ .min()
+ .unwrap()
+ .1;
+ let mut frames = v[offset..].iter();
+
+ assert_frame(
+ frames.next().unwrap(),
+ frame_4 as usize,
+ "frame_4",
+ "tests/smoke.rs",
+ start_line + 6,
+ 9,
+ );
+ assert_frame(
+ frames.next().unwrap(),
+ frame_3 as usize,
+ "frame_3",
+ "tests/smoke.rs",
+ start_line + 3,
+ 52,
+ );
+ assert_frame(
+ frames.next().unwrap(),
+ frame_2 as usize,
+ "frame_2",
+ "tests/smoke.rs",
+ start_line + 2,
+ 52,
+ );
+ assert_frame(
+ frames.next().unwrap(),
+ frame_1 as usize,
+ "frame_1",
+ "tests/smoke.rs",
+ start_line + 1,
+ 52,
+ );
+ assert_frame(
+ frames.next().unwrap(),
+ smoke_test_frames as usize,
+ "smoke_test_frames",
+ "",
+ 0,
+ 0,
+ );
+ }
+
+ fn assert_frame(
+ frame: &Frame,
+ actual_fn_pointer: usize,
+ expected_name: &str,
+ expected_file: &str,
+ expected_line: u32,
+ expected_col: u32,
+ ) {
+ backtrace::resolve_frame(frame, |sym| {
+ print!("symbol ip:{:?} address:{:?} ", frame.ip(), frame.symbol_address());
+ if let Some(name) = sym.name() {
+ print!("name:{} ", name);
+ }
+ if let Some(file) = sym.filename() {
+ print!("file:{} ", file.display());
+ }
+ if let Some(lineno) = sym.lineno() {
+ print!("lineno:{} ", lineno);
+ }
+ if let Some(colno) = sym.colno() {
+ print!("colno:{} ", colno);
+ }
+ println!();
+ });
+
+ let ip = frame.ip() as usize;
+ let sym = frame.symbol_address() as usize;
+ assert!(ip >= sym);
+ assert!(
+ sym >= actual_fn_pointer,
+ "{:?} < {:?} ({} {}:{}:{})",
+ sym as *const usize,
+ actual_fn_pointer as *const usize,
+ expected_name,
+ expected_file,
+ expected_line,
+ expected_col,
+ );
+
+ // windows dbghelp is *quite* liberal (and wrong) in many of its reports
+ // right now...
+ //
+ // This assertion can also fail for release builds, so skip it there
+ if cfg!(debug_assertions) {
+ assert!(sym - actual_fn_pointer < 1024);
+ }
+
+ let mut resolved = 0;
+
+ let mut name = None;
+ let mut addr = None;
+ let mut col = None;
+ let mut line = None;
+ let mut file = None;
+ backtrace::resolve_frame(frame, |sym| {
+ resolved += 1;
+ name = sym.name().map(|v| v.to_string());
+ addr = sym.addr();
+ col = sym.colno();
+ line = sym.lineno();
+ file = sym.filename().map(|v| v.to_path_buf());
+ });
+ assert!(resolved > 0);
+
+ let name = name.expect("didn't find a name");
+
+ // in release mode names get weird as functions can get merged
+ // together with `mergefunc`, so only assert this in debug mode
+ if cfg!(debug_assertions) {
+ assert!(
+ name.contains(expected_name),
+ "didn't find `{}` in `{}`",
+ expected_name,
+ name
+ );
+ }
+
+ addr.expect("didn't find a symbol");
+
+ if cfg!(debug_assertions) {
+ let line = line.expect("didn't find a line number");
+ let file = file.expect("didn't find a line number");
+ if !expected_file.is_empty() {
+ assert!(
+ file.ends_with(expected_file),
+ "{:?} didn't end with {:?}",
+ file,
+ expected_file
+ );
+ }
+ if expected_line != 0 {
+ assert!(
+ line == expected_line,
+ "bad line number on frame for `{}`: {} != {}",
+ expected_name,
+ line,
+ expected_line
+ );
+ }
+
+ // dbghelp on MSVC doesn't support column numbers
+ if !cfg!(target_env = "msvc") {
+ let col = col.expect("didn't find a column number");
+ if expected_col != 0 {
+ assert!(
+ col == expected_col,
+ "bad column number on frame for `{}`: {} != {}",
+ expected_name,
+ col,
+ expected_col
+ );
+ }
+ }
+ }
+ }
+}
+
+#[test]
+fn many_threads() {
+ let threads = (0..16)
+ .map(|_| {
+ thread::spawn(|| {
+ for _ in 0..16 {
+ backtrace::trace(|frame| {
+ backtrace::resolve(frame.ip(), |symbol| {
+ let _s = symbol.name().map(|s| s.to_string());
+ });
+ true
+ });
+ }
+ })
+ })
+ .collect::<Vec<_>>();
+
+ for t in threads {
+ t.join().unwrap()
+ }
+}
+
+#[test]
+#[cfg(feature = "rustc-serialize")]
+fn is_rustc_serialize() {
+ extern crate rustc_serialize;
+
+ fn is_encode<T: rustc_serialize::Encodable>() {}
+ fn is_decode<T: rustc_serialize::Decodable>() {}
+
+ is_encode::<backtrace::Backtrace>();
+ is_decode::<backtrace::Backtrace>();
+}
+
+#[test]
+#[cfg(feature = "serde")]
+fn is_serde() {
+ extern crate serde;
+
+ fn is_serialize<T: serde::ser::Serialize>() {}
+ fn is_deserialize<T: serde::de::DeserializeOwned>() {}
+
+ is_serialize::<backtrace::Backtrace>();
+ is_deserialize::<backtrace::Backtrace>();
+}
+
+#[test]
+fn sp_smoke_test() {
+ let mut refs = vec![];
+ recursive_stack_references(&mut refs);
+ return;
+
+ #[inline(never)]
+ fn recursive_stack_references(refs: &mut Vec<usize>) {
+ assert!(refs.len() < 5);
+
+ let x = refs.len();
+ refs.push(&x as *const _ as usize);
+
+ if refs.len() < 5 {
+ recursive_stack_references(refs);
+ eprintln!("exiting: {}", x);
+ return;
+ }
+
+ backtrace::trace(make_trace_closure(refs));
+ eprintln!("exiting: {}", x);
+ }
+
+ // NB: the following `make_*` functions are pulled out of line, rather than
+ // defining their results as inline closures at their call sites, so that
+ // the resulting closures don't have "recursive_stack_references" in their
+ // mangled names.
+
+ fn make_trace_closure<'a>(
+ refs: &'a mut Vec<usize>,
+ ) -> impl FnMut(&backtrace::Frame) -> bool + 'a {
+ let mut child_sp = None;
+ let mut child_ref = None;
+ move |frame| {
+ eprintln!("\n=== frame ===================================");
+
+ let mut is_recursive_stack_references = false;
+ backtrace::resolve(frame.ip(), |sym| {
+ is_recursive_stack_references |=
+ sym.name()
+ .and_then(|name| name.as_str())
+ .map_or(false, |name| {
+ eprintln!("name = {}", name);
+ name.contains("recursive_stack_references")
+ })
+ });
+
+ let sp = frame.sp() as usize;
+ eprintln!("sp = {:p}", sp as *const u8);
+ if sp == 0 {
+ // If the SP is null, then we don't have an implementation for
+ // getting the SP on this target. Just keep walking the stack,
+ // but don't make our assertions about the on-stack pointers and
+ // SP values.
+ return true;
+ }
+
+ // The stack grows down.
+ if let Some(child_sp) = child_sp {
+ assert!(child_sp <= sp);
+ }
+
+ if is_recursive_stack_references {
+ let r = refs.pop().unwrap();
+ eprintln!("ref = {:p}", r as *const u8);
+ if sp != 0 {
+ assert!(r > sp);
+ if let Some(child_ref) = child_ref {
+ assert!(sp >= child_ref);
+ }
+ }
+ child_ref = Some(r);
+ }
+
+ child_sp = Some(sp);
+ true
+ }
+ }
+}