├── .cargo ├── android-runner.sh └── config.toml ├── .github ├── CODEOWNERS └── workflows │ ├── audit.yml │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── deny.toml ├── examples └── synthetic.rs ├── release.toml ├── src ├── bin │ └── test.rs ├── dir_section.rs ├── lib.rs ├── linux.rs ├── linux │ ├── android.rs │ ├── app_memory.rs │ ├── auxv │ │ ├── mod.rs │ │ └── reader.rs │ ├── crash_context.rs │ ├── crash_context │ │ ├── aarch64.rs │ │ ├── arm.rs │ │ ├── x86.rs │ │ └── x86_64.rs │ ├── dso_debug.rs │ ├── dumper_cpu_info.rs │ ├── dumper_cpu_info │ │ ├── arm.rs │ │ └── x86_mips.rs │ ├── errors.rs │ ├── maps_reader.rs │ ├── mem_reader.rs │ ├── minidump_writer.rs │ ├── module_reader.rs │ ├── ptrace_dumper.rs │ ├── sections.rs │ ├── sections │ │ ├── app_memory.rs │ │ ├── exception_stream.rs │ │ ├── handle_data_stream.rs │ │ ├── mappings.rs │ │ ├── memory_info_list_stream.rs │ │ ├── memory_list_stream.rs │ │ ├── systeminfo_stream.rs │ │ ├── thread_list_stream.rs │ │ └── thread_names_stream.rs │ ├── serializers.rs │ ├── thread_info.rs │ └── thread_info │ │ ├── aarch64.rs │ │ ├── arm.rs │ │ ├── mips.rs │ │ └── x86.rs ├── mac.rs ├── mac │ ├── errors.rs │ ├── mach.rs │ ├── minidump_writer.rs │ ├── streams.rs │ ├── streams │ │ ├── breakpad_info.rs │ │ ├── exception.rs │ │ ├── memory_list.rs │ │ ├── misc_info.rs │ │ ├── module_list.rs │ │ ├── system_info.rs │ │ ├── thread_list.rs │ │ └── thread_names.rs │ └── task_dumper.rs ├── mem_writer.rs ├── minidump_cpu.rs ├── minidump_format.rs ├── serializers.rs ├── windows.rs └── windows │ ├── errors.rs │ ├── ffi.rs │ └── minidump_writer.rs └── tests ├── common └── mod.rs ├── linux_minidump_writer.rs ├── linux_minidump_writer_soft_error.rs ├── mac_minidump_writer.rs ├── ptrace_dumper.rs ├── task_dumper.rs └── windows_minidump_writer.rs /.cargo/android-runner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BINARY=$1 4 | shift 5 | 6 | # Make sure to run the following to copy the test helper binary over. 7 | # cargo run --target x86_64-linux-android --bin test 8 | adb push "$BINARY" "/data/local/$BINARY" 9 | adb shell "chmod 777 /data/local/$BINARY && env TEST_HELPER=/data/local/target/x86_64-linux-android/debug/test /data/local/$BINARY" "$@" 10 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # If you have a global config to use LLD on your machine, you might need to enable 2 | # this config to produce binaries that pass tests 3 | # [target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'] 4 | # rustflags = [ 5 | # "-C", 6 | # "link-arg=-fuse-ld=lld", 7 | # # LLD by default uses xxhash for build ids now, which breaks tests that assume 8 | # # GUIDS or longer 9 | # "-C", 10 | # "link-arg=-Wl,--build-id=sha1", 11 | # ] 12 | [target.x86_64-linux-android] 13 | linker = "x86_64-linux-android30-clang" 14 | # By default the linker _doesn't_ generate a build-id, however we want one for our tests. 15 | rustflags = ["-C", "link-args=-Wl,--build-id=sha1"] 16 | runner = [".cargo/android-runner.sh"] 17 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @Jake-Shadle @gabrielesvelto 2 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | 3 | on: 4 | schedule: 5 | # Runs at 00:00 UTC everyday 6 | - cron: "0 0 * * *" 7 | push: 8 | paths: 9 | - "**/Cargo.toml" 10 | - "**/Cargo.lock" 11 | - "**/audit.toml" 12 | 13 | jobs: 14 | audit: 15 | runs-on: ubuntu-22.04 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | - name: deny audit 20 | uses: EmbarkStudios/cargo-deny-action@v2 21 | with: 22 | command: check advisories 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - github-actions 8 | pull_request: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | lint: 16 | name: Lint 17 | runs-on: ubuntu-22.04 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Install Rust 21 | uses: dtolnay/rust-toolchain@stable 22 | with: 23 | components: rustfmt,clippy 24 | - name: rustfmt 25 | run: cargo fmt --all -- --check 26 | - name: clippy 27 | run: cargo clippy --all-features --all-targets -- -D warnings 28 | 29 | test: 30 | name: Test 31 | runs-on: ${{ matrix.job.os }} 32 | strategy: 33 | matrix: 34 | job: 35 | - { os: ubuntu-22.04, target: x86_64-unknown-linux-gnu, release: true } 36 | - { os: ubuntu-22.04, target: x86_64-unknown-linux-musl } 37 | - { os: windows-2022, target: x86_64-pc-windows-msvc } 38 | - { os: macos-13, target: x86_64-apple-darwin } 39 | - { os: macos-14, target: aarch64-apple-darwin } 40 | steps: 41 | - uses: actions/checkout@v4 42 | - name: Install Rust 43 | uses: dtolnay/rust-toolchain@stable 44 | with: 45 | target: ${{ matrix.job.target }} 46 | - name: Fetch 47 | run: cargo fetch --target ${{ matrix.job.target }} 48 | - name: Build 49 | run: cargo test --target ${{ matrix.job.target }} --no-run 50 | - name: Test 51 | run: cargo test --target ${{ matrix.job.target }} 52 | - name: Release test 53 | if: ${{ matrix.job.release }} 54 | run: | 55 | cargo test --target ${{ matrix.job.target }} --release --no-run 56 | cargo test --target ${{ matrix.job.target }} --release 57 | 58 | install-cross: 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: XAMPPRocky/get-github-release@v1 62 | id: cross 63 | with: 64 | owner: rust-embedded 65 | repo: cross 66 | matches: linux-musl 67 | token: ${{ secrets.GITHUB_TOKEN }} 68 | - uses: actions/upload-artifact@v4 69 | with: 70 | name: cross-linux-musl 71 | path: ${{ steps.cross.outputs.install_path }} 72 | 73 | # This job builds and tests non-tier1 targets 74 | build_lower_tier: 75 | name: Build sources 76 | runs-on: ubuntu-22.04 77 | needs: install-cross 78 | strategy: 79 | matrix: 80 | job: 81 | - target: i686-unknown-linux-gnu 82 | - target: aarch64-unknown-linux-gnu 83 | - target: aarch64-unknown-linux-musl 84 | - target: arm-unknown-linux-gnueabi 85 | - target: arm-unknown-linux-musleabi 86 | - target: arm-linux-androideabi 87 | - target: arm-unknown-linux-gnueabihf 88 | steps: 89 | - uses: actions/checkout@v4 90 | - name: Download Cross 91 | uses: actions/download-artifact@v4 92 | with: 93 | name: cross-linux-musl 94 | path: /tmp/ 95 | - run: chmod +x /tmp/cross 96 | - name: Install Rust 97 | uses: dtolnay/rust-toolchain@stable 98 | with: 99 | target: ${{ matrix.job.target }} 100 | - name: Build and Test 101 | run: | 102 | /tmp/cross build --target ${{ matrix.job.target }} --all-targets 103 | # /tmp/cross test --target ${{ matrix.job.target }} 104 | # /tmp/cross test --target ${{ matrix.job.target }} -- ignored 105 | 106 | # The cargo x86-64-linux-android target configuration in .cargo/config.toml 107 | # interacts with this job. 108 | test-android: 109 | name: Test android 110 | runs-on: ubuntu-latest 111 | env: 112 | ANDROID_HOME: /usr/local/lib/android/sdk 113 | steps: 114 | - uses: actions/checkout@v4 115 | - name: Install Rust 116 | uses: dtolnay/rust-toolchain@stable 117 | with: 118 | target: x86_64-linux-android 119 | - name: Enable KVM 120 | run: | 121 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 122 | sudo udevadm control --reload-rules 123 | sudo udevadm trigger --name-match=kvm 124 | # Add the (eventual) NDK toolchain bin directory to PATH so the linker is 125 | # available to cargo 126 | - run: echo "$ANDROID_HOME/ndk/26.2.11394342/toolchains/llvm/prebuilt/linux-x86_64/bin" >> $GITHUB_PATH 127 | - name: Build/run tests in android emulator 128 | uses: reactivecircus/android-emulator-runner@v2 129 | with: 130 | arch: x86_64 131 | api-level: 30 132 | ndk: 26.2.11394342 133 | script: | 134 | # run adb as root so we can create remote directories 135 | adb root 136 | # Copy test helper binary over as a side-effect of running it. 137 | cargo run --target x86_64-linux-android --bin test -- nop 138 | # Build and run tests 139 | cargo test --target x86_64-linux-android 140 | 141 | deny-check: 142 | runs-on: ubuntu-22.04 143 | steps: 144 | - uses: actions/checkout@v4 145 | - name: deny check 146 | uses: EmbarkStudios/cargo-deny-action@v2 147 | with: 148 | # Note that advisories are checked separately on a schedule in audit.yml 149 | command: check bans licenses sources 150 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .test-symbols 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minidump-writer" 3 | version = "0.10.2" 4 | authors = ["Martin Sirringhaus"] 5 | description = "Rust rewrite of Breakpad's minidump_writer" 6 | repository = "https://github.com/rust-minidump/minidump-writer" 7 | homepage = "https://github.com/rust-minidump/minidump-writer" 8 | edition = "2021" 9 | license = "MIT" 10 | 11 | [dependencies] 12 | bitflags = "2.8" 13 | byteorder = "1.4" 14 | cfg-if = "1.0" 15 | crash-context = "0.6" 16 | error-graph = { version = "0.1.1", features = ["serde"] } 17 | failspot = "0.2.0" 18 | log = "0.4" 19 | memoffset = "0.9" 20 | minidump-common = "0.25" 21 | scroll = "0.12" 22 | serde = { version = "1.0.208", features = ["derive"] } 23 | serde_json = "1.0.116" 24 | tempfile = "3.16" 25 | thiserror = "2.0" 26 | 27 | [target.'cfg(unix)'.dependencies] 28 | libc = "0.2" 29 | goblin = "0.9.2" 30 | memmap2 = "0.9" 31 | 32 | [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] 33 | nix = { version = "0.29", default-features = false, features = [ 34 | "mman", 35 | "process", 36 | "ptrace", 37 | "signal", 38 | "uio", 39 | "user", 40 | ] } 41 | # Used for parsing procfs info. 42 | # default-features is disabled since it pulls in chrono 43 | procfs-core = { version = "0.17", default-features = false, features = ["serde1"] } 44 | 45 | [target.'cfg(target_os = "windows")'.dependencies] 46 | bitflags = "2.4" 47 | 48 | [target.'cfg(target_os = "macos")'.dependencies] 49 | # Binds some additional mac specifics not in libc 50 | mach2 = "0.4" 51 | 52 | [dev-dependencies] 53 | # We auto-detect what the test binary that is spawned for most tests should be 54 | # compiled for 55 | current_platform = "0.2" 56 | failspot = { version = "0.2.0", features = ["enabled"] } 57 | # Minidump-processor is async so we need an executor 58 | futures = { version = "0.3", features = ["executor"] } 59 | minidump = "0.25" 60 | memmap2 = "0.9" 61 | 62 | [target.'cfg(target_os = "macos")'.dev-dependencies] 63 | # We dump symbols for the `test` executable so that we can validate that minidumps 64 | # created by this crate can be processed by minidump-processor 65 | dump_syms = { version = "2.2", default-features = false } 66 | #minidump-processor = { version = "0.25", default-features = false } 67 | minidump-unwind = { version = "0.25", features = ["debuginfo"] } 68 | similar-asserts = "1.6" 69 | uuid = "1.12" 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # `minidump-writer` 4 | 5 | **Rust rewrite of Breakpad's minidump_writer (client)** 6 | 7 | [![Rust CI](https://github.com/rust-minidump/minidump-writer/actions/workflows/ci.yml/badge.svg)](https://github.com/rust-minidump/minidump-writer/actions/workflows/ci.yml) 8 | [![crates.io](https://img.shields.io/crates/v/minidump-writer.svg)](https://crates.io/crates/minidump-writer) 9 | [![docs.rs](https://docs.rs/minidump-writer/badge.svg)](https://docs.rs/minidump-writer) 10 | 11 |
12 | 13 | This project is currently being very actively brought up from nothing, and is really ultimately many separate client implementations for different platforms. 14 | 15 | ## Usage / Examples 16 | 17 | The primary use case of this crate is for creating a minidump for an **external** process (ie a process other than the one that writes the minidump) as writing minidumps from within a crashing process is inherently unreliable. That being said, there are scenarios where creating a minidump can be useful outside of a crash scenario thus each supported platforms has a way to generate a minidump for a local process as well. 18 | 19 | For more information on how to dump an external process you can check out the documentation or code for the [minidumper](https://docs.rs/minidumper/latest/minidumper/) crate. 20 | 21 | ### Linux 22 | 23 | #### Local process 24 | 25 | The Linux implementation uses ptrace to gather information about the process when writing a minidump for it, which cannot be done from the process itself. It's possible to clone the process and dump the current process from that clone, but that's out of scope for an example. 26 | 27 | #### External process 28 | 29 | ```rust 30 | fn write_minidump(crash_context: crash_context::CrashContext) { 31 | // At a minimum, the crashdump writer needs to know the process and thread that the crash occurred in 32 | let mut writer = minidump_writer::minidump_writer::MinidumpWriter::new(crash_context.pid, crash_context.tid); 33 | 34 | // If provided with a full [crash_context::CrashContext](https://docs.rs/crash-context/latest/crash_context/struct.CrashContext.html), 35 | // the crash will contain more info on the crash cause, such as the signal 36 | writer.set_crash_context(minidump_writer::crash_context::CrashContext { inner: crash_context }); 37 | 38 | // Here we could add more context or modify how the minidump is written, eg 39 | // Add application specific memory blocks to the minidump 40 | //writer.set_app_memory() 41 | // Sanitize stack memory before it is written to the minidump by replacing 42 | // non-pointer values with a sentinel value 43 | //writer.sanitize_stack(); 44 | 45 | let mut minidump_file = std::fs::File::create("example_dump.mdmp").expect("failed to create file"); 46 | writer.dump(&mut minidump_file).expect("failed to write minidump"); 47 | } 48 | ``` 49 | 50 | ### Windows 51 | 52 | #### Local process 53 | 54 | ```rust 55 | fn write_minidump() { 56 | let mut minidump_file = std::fs::File::create("example_dump.mdmp").expect("failed to create file"); 57 | 58 | // Attempts to the write the minidump 59 | minidump_writer::minidump_writer::MinidumpWriter::dump_local_context( 60 | // The exception code, presumably one of STATUS_*. Defaults to STATUS_NONCONTINUABLE_EXCEPTION if not specified 61 | None, 62 | // If not specified, uses the current thread as the "crashing" thread, 63 | // so this is equivalent to passing `None`, but it could be any thread 64 | // in the process 65 | Some(unsafe { windows_sys::Win32::System::Threading::GetCurrentThreadId() }), 66 | &mut minidump_file, 67 | ).expect("failed to write minidump");; 68 | } 69 | ``` 70 | 71 | #### External process 72 | 73 | ```rust 74 | fn write_minidump(crash_context: crash_context::CrashContext) { 75 | use std::io::{Read, Seek}; 76 | 77 | // Create the file to write the minidump to. Unlike MacOS and Linux, the 78 | // system call used to write the minidump only supports outputting to a file 79 | let mut minidump_file = std::fs::File::create("example_dump.mdmp").expect("failed to create file"); 80 | // Attempts to the write the minidump for the crash context 81 | minidump_writer::minidump_writer::MinidumpWriter::dump_crash_context(crash_context, &mut minidump_file).expect("failed to write minidump");; 82 | 83 | let mut minidump_contents = Vec::with_capacity(minidump_file.stream_position().expect("failed to get stream length") as usize); 84 | minidump_file.rewind().expect("failed to rewind minidump file"); 85 | 86 | minidump_file.read_to_end(&mut minidump_contents).expect("failed to read minidump"); 87 | } 88 | ``` 89 | 90 | ### MacOS 91 | 92 | #### Local process 93 | 94 | ```rust 95 | fn write_minidump() { 96 | // Defaults to dumping the current process and thread. 97 | let mut writer = minidump_writer::minidump_writer::MinidumpWriter::new(None, None)?; 98 | 99 | let mut minidump_file = std::fs::File::create("example_dump.mdmp").expect("failed to create file"); 100 | writer.dump(&mut minidump_file).expect("failed to write minidump"); 101 | } 102 | ``` 103 | 104 | #### External process 105 | 106 | ```rust 107 | fn write_minidump(crash_context: crash_context::CrashContext) { 108 | let mut writer = minidump_writer::minidump_writer::MinidumpWriter::with_crash_context(crash_context)?; 109 | 110 | let mut minidump_file = std::fs::File::create("example_dump.mdmp").expect("failed to create file"); 111 | writer.dump(&mut minidump_file).expect("failed to write minidump"); 112 | } 113 | ``` 114 | 115 | ## Client Statuses 116 | 117 | - ✅ Usable, but care should be taken in production environments 118 | - ⚠️ Implemented (ie compiles), but untested and needs more work to be usable 119 | - ⭕️ Unimplemented, but could be implemented in the future 120 | - ❌ Unimplemented, and unlikely to ever be implemented 121 | 122 | | Arch | unknown-linux-gnu | unknown-linux-musl | linux-android | pc-windows-msvc | apple-darwin | apple-ios | 123 | ----------- | ----------------- | ------------------ | ------------- | --------------- | ------------ | --------- | 124 | `x86_64` | ✅ | ✅ | ⚠️ | ✅ | ✅ | ⭕️ | 125 | `i686` | ✅ | ✅ | ❌ | ⭕️ | ❌ | ❌ | 126 | `arm` | ⚠️ | ⚠️ | ⚠️ | ⭕️ | ❌ | ❌ | 127 | `aarch64` | ⚠️ | ⚠️ | ⚠️ | ⭕️ | ✅ | ⭕️ | 128 | `mips` | ⭕️ | ⭕️ | ❌ | ❌ | ❌ | ❌ | 129 | `mips64` | ⭕️ | ⭕️ | ❌ | ❌ | ❌ | ❌ | 130 | `powerpc` | ⭕️ | ⭕️ | ❌ | ❌ | ❌ | ❌ | 131 | `powerpc64` | ⭕️ | ⭕️ | ❌ | ❌ | ❌ | ❌ | 132 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") { 3 | println!("cargo:rustc-link-lib=dylib=dbghelp"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [graph] 2 | targets = [ 3 | { triple = "x86_64-unknown-linux-gnu" }, 4 | { triple = "x86_64-unknown-linux-musl" }, 5 | { triple = "x86_64-pc-windows-msvc" }, 6 | { triple = "x86_64-apple-darwin" }, 7 | { triple = "aarch64-apple-darwin" }, 8 | { triple = "aarch64-linux-android" }, 9 | { triple = "x86_64-linux-android" }, 10 | ] 11 | 12 | [advisories] 13 | ignore = [] 14 | 15 | [bans] 16 | multiple-versions = "deny" 17 | deny = [ 18 | { crate = "chrono", use-instead = "time", reason = "unneccessary dependency" }, 19 | ] 20 | skip-tree = [] 21 | 22 | [licenses] 23 | allow = ["MIT", "Apache-2.0", "Unicode-3.0"] 24 | -------------------------------------------------------------------------------- /examples/synthetic.rs: -------------------------------------------------------------------------------- 1 | //! Emits default minidump with no streams to specified path 2 | 3 | use std::fs::File; 4 | 5 | use minidump_writer::{ 6 | dir_section::DirSection, 7 | mem_writer::{Buffer, MemoryWriter}, 8 | minidump_format::{MDRawHeader, MD_HEADER_SIGNATURE, MD_HEADER_VERSION}, 9 | }; 10 | 11 | // usage: `cargo run --example synthetic /tmp/micro-minidump.dmp` 12 | fn main() { 13 | let output_path = std::env::args() 14 | .nth(1) 15 | .expect("missing argument: output file path"); 16 | 17 | let num_writers = 0u32; 18 | let buffer_capacity = 32; 19 | 20 | let mut destination = File::create(output_path).expect("failed to create file"); 21 | let mut buffer = Buffer::with_capacity(buffer_capacity); 22 | let mut header_section = MemoryWriter::::alloc(&mut buffer).unwrap(); 23 | let mut dir_section = DirSection::new(&mut buffer, num_writers, &mut destination).unwrap(); 24 | 25 | let header = MDRawHeader { 26 | signature: MD_HEADER_SIGNATURE, 27 | version: MD_HEADER_VERSION, 28 | stream_count: num_writers, 29 | stream_directory_rva: dir_section.position(), 30 | checksum: 0, 31 | time_date_stamp: 0u32, 32 | flags: 0, 33 | }; 34 | header_section.set_value(&mut buffer, header).unwrap(); 35 | dir_section.write_to_file(&mut buffer, None).unwrap(); 36 | } 37 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-commit-message = "Release {{version}}" 2 | tag-message = "Release {{version}}" 3 | tag-name = "{{version}}" 4 | pre-release-replacements = [ 5 | { file = "CHANGELOG.md", search = "Unreleased", replace = "{{version}}" }, 6 | { file = "CHANGELOG.md", search = "\\.\\.\\.HEAD", replace = "...{{tag_name}}" }, 7 | { file = "CHANGELOG.md", search = "ReleaseDate", replace = "{{date}}" }, 8 | { file = "CHANGELOG.md", search = "", replace = "\n## [Unreleased] - ReleaseDate" }, 9 | { file = "CHANGELOG.md", search = "", replace = "\n[Unreleased]: https://github.com/rust-minidump/minidump-writer/compare/{{tag_name}}...HEAD" }, 10 | ] 11 | -------------------------------------------------------------------------------- /src/dir_section.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | mem_writer::{Buffer, MemoryArrayWriter, MemoryWriterError}, 4 | minidump_format::MDRawDirectory, 5 | serializers::*, 6 | }, 7 | std::io::{Error, Seek, Write}, 8 | }; 9 | 10 | pub type DumpBuf = Buffer; 11 | 12 | #[derive(Debug, thiserror::Error, serde::Serialize)] 13 | pub enum FileWriterError { 14 | #[error("IO error")] 15 | IOError( 16 | #[from] 17 | #[serde(serialize_with = "serialize_io_error")] 18 | Error, 19 | ), 20 | #[error("Failed to write to memory")] 21 | MemoryWriterError(#[from] MemoryWriterError), 22 | } 23 | 24 | /// Utility that wraps writing minidump directory entries to an I/O stream, generally 25 | /// a [`std::fs::File`]. 26 | #[derive(Debug)] 27 | pub struct DirSection<'a, W> 28 | where 29 | W: Write + Seek, 30 | { 31 | curr_idx: usize, 32 | section: MemoryArrayWriter, 33 | /// If we have to append to some file, we have to know where we currently are 34 | destination_start_offset: u64, 35 | destination: &'a mut W, 36 | last_position_written_to_file: u64, 37 | } 38 | 39 | impl<'a, W> DirSection<'a, W> 40 | where 41 | W: Write + Seek, 42 | { 43 | pub fn new( 44 | buffer: &mut DumpBuf, 45 | index_length: u32, 46 | destination: &'a mut W, 47 | ) -> std::result::Result { 48 | let dir_section = 49 | MemoryArrayWriter::::alloc_array(buffer, index_length as usize)?; 50 | 51 | Ok(Self { 52 | curr_idx: 0, 53 | section: dir_section, 54 | destination_start_offset: destination.stream_position()?, 55 | destination, 56 | last_position_written_to_file: 0, 57 | }) 58 | } 59 | 60 | #[inline] 61 | pub fn position(&self) -> u32 { 62 | self.section.position 63 | } 64 | 65 | pub fn dump_dir_entry( 66 | &mut self, 67 | buffer: &mut DumpBuf, 68 | dirent: MDRawDirectory, 69 | ) -> std::result::Result<(), FileWriterError> { 70 | self.section.set_value_at(buffer, dirent, self.curr_idx)?; 71 | 72 | // Now write it to file 73 | 74 | // First get all the positions 75 | let curr_file_pos = self.destination.stream_position()?; 76 | let idx_pos = self.section.location_of_index(self.curr_idx); 77 | self.curr_idx += 1; 78 | 79 | self.destination.seek(std::io::SeekFrom::Start( 80 | self.destination_start_offset + idx_pos.rva as u64, 81 | ))?; 82 | let start = idx_pos.rva as usize; 83 | let end = (idx_pos.rva + idx_pos.data_size) as usize; 84 | self.destination.write_all(&buffer[start..end])?; 85 | 86 | // Reset file-position 87 | self.destination 88 | .seek(std::io::SeekFrom::Start(curr_file_pos))?; 89 | 90 | Ok(()) 91 | } 92 | 93 | /// Writes 2 things to file: 94 | /// 1. The given dirent into the dir section in the header (if any is given) 95 | /// 2. Everything in the in-memory buffer that was added since the last call to this function 96 | pub fn write_to_file( 97 | &mut self, 98 | buffer: &mut DumpBuf, 99 | dirent: Option, 100 | ) -> std::result::Result<(), FileWriterError> { 101 | if let Some(dirent) = dirent { 102 | self.dump_dir_entry(buffer, dirent)?; 103 | } 104 | 105 | let start_pos = self.last_position_written_to_file as usize; 106 | self.destination.write_all(&buffer[start_pos..])?; 107 | self.last_position_written_to_file = buffer.position(); 108 | Ok(()) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | cfg_if::cfg_if! { 2 | if #[cfg(any(target_os = "linux", target_os = "android"))] { 3 | mod linux; 4 | 5 | pub use linux::*; 6 | } else if #[cfg(target_os = "windows")] { 7 | mod windows; 8 | 9 | pub use windows::*; 10 | } else if #[cfg(target_os = "macos")] { 11 | mod mac; 12 | 13 | pub use mac::*; 14 | } 15 | } 16 | 17 | pub mod dir_section; 18 | pub mod mem_writer; 19 | pub mod minidump_cpu; 20 | pub mod minidump_format; 21 | 22 | mod serializers; 23 | 24 | failspot::failspot_name! { 25 | pub enum FailSpotName { 26 | StopProcess, 27 | FillMissingAuxvInfo, 28 | ThreadName, 29 | SuspendThreads, 30 | CpuInfoFileOpen, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/linux.rs: -------------------------------------------------------------------------------- 1 | // `WriterError` is large and clippy doesn't like that, but not a huge deal atm 2 | #![allow(clippy::result_large_err)] 3 | 4 | #[cfg(target_os = "android")] 5 | mod android; 6 | pub mod app_memory; 7 | pub(crate) mod auxv; 8 | pub mod crash_context; 9 | mod dso_debug; 10 | mod dumper_cpu_info; 11 | pub mod errors; 12 | pub mod maps_reader; 13 | pub mod mem_reader; 14 | pub mod minidump_writer; 15 | pub mod module_reader; 16 | pub mod ptrace_dumper; 17 | pub(crate) mod sections; 18 | mod serializers; 19 | pub mod thread_info; 20 | 21 | pub use maps_reader::LINUX_GATE_LIBRARY_NAME; 22 | pub type Pid = i32; 23 | -------------------------------------------------------------------------------- /src/linux/android.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::AndroidError; 2 | use crate::maps_reader::MappingInfo; 3 | use crate::ptrace_dumper::PtraceDumper; 4 | use crate::Pid; 5 | use goblin::elf; 6 | 7 | cfg_if::cfg_if! { 8 | if #[cfg(target_pointer_width = "32")] { 9 | use elf::dynamic::dyn32::{Dyn, SIZEOF_DYN}; 10 | use elf::header::header32 as elf_header; 11 | use elf::program_header::program_header32::ProgramHeader; 12 | 13 | const DT_ANDROID_REL: u32 = (elf::dynamic::DT_LOOS + 2) as u32; 14 | const DT_ANDROID_RELA: u32 = (elf::dynamic::DT_LOOS + 4) as u32; 15 | } else if #[cfg(target_pointer_width = "64")] { 16 | use elf::dynamic::dyn64::{Dyn, SIZEOF_DYN}; 17 | use elf::header::header64 as elf_header; 18 | use elf::program_header::program_header64::ProgramHeader; 19 | 20 | const DT_ANDROID_REL: u64 = elf::dynamic::DT_LOOS + 2; 21 | const DT_ANDROID_RELA: u64 = elf::dynamic::DT_LOOS + 4; 22 | } else { 23 | compile_error!("invalid pointer width"); 24 | } 25 | } 26 | 27 | type Result = std::result::Result; 28 | 29 | struct DynVaddresses { 30 | min_vaddr: usize, 31 | dyn_vaddr: usize, 32 | dyn_count: usize, 33 | } 34 | 35 | fn has_android_packed_relocations(pid: Pid, load_bias: usize, vaddrs: DynVaddresses) -> Result<()> { 36 | let dyn_addr = load_bias + vaddrs.dyn_vaddr; 37 | for idx in 0..vaddrs.dyn_count { 38 | let addr = dyn_addr + SIZEOF_DYN * idx; 39 | let dyn_data = PtraceDumper::copy_from_process(pid, addr, SIZEOF_DYN)?; 40 | // TODO: Couldn't find a nice way to use goblin for that, to avoid the unsafe-block 41 | let dyn_obj: Dyn; 42 | unsafe { 43 | dyn_obj = std::mem::transmute::<[u8; SIZEOF_DYN], Dyn>(dyn_data.as_slice().try_into()?); 44 | } 45 | 46 | if dyn_obj.d_tag == DT_ANDROID_REL || dyn_obj.d_tag == DT_ANDROID_RELA { 47 | return Ok(()); 48 | } 49 | } 50 | Err(AndroidError::NoRelFound) 51 | } 52 | 53 | fn get_effective_load_bias(pid: Pid, ehdr: &elf_header::Header, address: usize) -> usize { 54 | let ph = parse_loaded_elf_program_headers(pid, ehdr, address); 55 | // If |min_vaddr| is non-zero and we find Android packed relocation tags, 56 | // return the effective load bias. 57 | 58 | if ph.min_vaddr != 0 { 59 | let load_bias = address - ph.min_vaddr; 60 | if has_android_packed_relocations(pid, load_bias, ph).is_ok() { 61 | return load_bias; 62 | } 63 | } 64 | // Either |min_vaddr| is zero, or it is non-zero but we did not find the 65 | // expected Android packed relocations tags. 66 | address 67 | } 68 | 69 | fn parse_loaded_elf_program_headers( 70 | pid: Pid, 71 | ehdr: &elf_header::Header, 72 | address: usize, 73 | ) -> DynVaddresses { 74 | let phdr_addr = address + ehdr.e_phoff as usize; 75 | let mut min_vaddr = usize::MAX; 76 | let mut dyn_vaddr = 0; 77 | let mut dyn_count = 0; 78 | 79 | let phdr_opt = PtraceDumper::copy_from_process( 80 | pid, 81 | phdr_addr, 82 | elf_header::SIZEOF_EHDR * ehdr.e_phnum as usize, 83 | ); 84 | if let Ok(ph_data) = phdr_opt { 85 | // TODO: The original C code doesn't have error-handling here at all. 86 | // We silently ignore "not parsable" for now, but might bubble it up. 87 | // TODO2: `from_bytes` might panic, `parse()` would return a Result<>, so maybe better 88 | // to switch to that at some point. 89 | for phdr in ProgramHeader::from_bytes(&ph_data, ehdr.e_phnum as usize) { 90 | let p_vaddr = phdr.p_vaddr as usize; 91 | if phdr.p_type == elf::program_header::PT_LOAD && p_vaddr < min_vaddr { 92 | min_vaddr = p_vaddr; 93 | } 94 | 95 | if phdr.p_type == elf::program_header::PT_DYNAMIC { 96 | dyn_vaddr = p_vaddr; 97 | dyn_count = phdr.p_memsz as usize / SIZEOF_DYN; 98 | } 99 | } 100 | } 101 | 102 | DynVaddresses { 103 | min_vaddr, 104 | dyn_vaddr, 105 | dyn_count, 106 | } 107 | } 108 | 109 | pub fn late_process_mappings(pid: Pid, mappings: &mut [MappingInfo]) -> Result<()> { 110 | // Only consider exec mappings that indicate a file path was mapped, and 111 | // where the ELF header indicates a mapped shared library. 112 | for map in mappings 113 | .iter_mut() 114 | .filter(|m| m.is_executable() && m.name_is_path()) 115 | { 116 | let ehdr_opt = 117 | PtraceDumper::copy_from_process(pid, map.start_address, elf_header::SIZEOF_EHDR) 118 | .ok() 119 | .and_then(|x| elf_header::Header::parse(&x).ok()); 120 | 121 | if let Some(ehdr) = ehdr_opt { 122 | if ehdr.e_type == elf_header::ET_DYN { 123 | // Compute the effective load bias for this mapped library, and update 124 | // the mapping to hold that rather than |start_addr|, at the same time 125 | // adjusting |size| to account for the change in |start_addr|. Where 126 | // the library does not contain Android packed relocations, 127 | // GetEffectiveLoadBias() returns |start_addr| and the mapping entry 128 | // is not changed. 129 | let load_bias = get_effective_load_bias(pid, &ehdr, map.start_address); 130 | map.size += map.start_address - load_bias; 131 | map.start_address = load_bias; 132 | } 133 | } 134 | } 135 | Ok(()) 136 | } 137 | -------------------------------------------------------------------------------- /src/linux/app_memory.rs: -------------------------------------------------------------------------------- 1 | // These entries store a list of memory regions that the client wants included 2 | // in the minidump. 3 | #[derive(Debug, Default, PartialEq, Eq)] 4 | pub struct AppMemory { 5 | pub ptr: usize, 6 | pub length: usize, 7 | } 8 | 9 | pub type AppMemoryList = Vec; 10 | -------------------------------------------------------------------------------- /src/linux/auxv/mod.rs: -------------------------------------------------------------------------------- 1 | use { 2 | self::reader::ProcfsAuxvIter, 3 | crate::{serializers::*, Pid}, 4 | error_graph::WriteErrorList, 5 | failspot::failspot, 6 | std::{fs::File, io::BufReader}, 7 | thiserror::Error, 8 | }; 9 | 10 | mod reader; 11 | 12 | /// The type used in auxv keys and values. 13 | #[cfg(target_pointer_width = "32")] 14 | pub type AuxvType = u32; 15 | /// The type used in auxv keys and values. 16 | #[cfg(target_pointer_width = "64")] 17 | pub type AuxvType = u64; 18 | 19 | #[cfg(target_os = "android")] 20 | mod consts { 21 | use super::AuxvType; 22 | pub const AT_PHDR: AuxvType = 3; 23 | pub const AT_PHNUM: AuxvType = 5; 24 | pub const AT_ENTRY: AuxvType = 9; 25 | pub const AT_SYSINFO_EHDR: AuxvType = 33; 26 | } 27 | #[cfg(not(target_os = "android"))] 28 | mod consts { 29 | use super::AuxvType; 30 | pub const AT_PHDR: AuxvType = libc::AT_PHDR; 31 | pub const AT_PHNUM: AuxvType = libc::AT_PHNUM; 32 | pub const AT_ENTRY: AuxvType = libc::AT_ENTRY; 33 | pub const AT_SYSINFO_EHDR: AuxvType = libc::AT_SYSINFO_EHDR; 34 | } 35 | 36 | /// An auxv key-value pair. 37 | #[derive(Debug, PartialEq, Eq)] 38 | pub struct AuxvPair { 39 | pub key: AuxvType, 40 | pub value: AuxvType, 41 | } 42 | 43 | /// Auxv info that can be passed from crashing process 44 | /// 45 | /// Since `/proc/{pid}/auxv` can sometimes be inaccessible, the calling process should prefer to transfer this 46 | /// information directly using the Linux `getauxval()` call (if possible). 47 | /// 48 | /// Any field that is set to `0` will be considered unset. In that case, minidump-writer might try other techniques 49 | /// to obtain it (like reading `/proc/{pid}/auxv`). 50 | #[repr(C)] 51 | #[derive(Clone, Debug, Default)] 52 | pub struct DirectAuxvDumpInfo { 53 | /// The value of `getauxval(AT_PHNUM)` 54 | pub program_header_count: AuxvType, 55 | /// The value of `getauxval(AT_PHDR)` 56 | pub program_header_address: AuxvType, 57 | /// The value of `getauxval(AT_SYSINFO_EHDR)` 58 | pub linux_gate_address: AuxvType, 59 | /// The value of `getauxval(AT_ENTRY)` 60 | pub entry_address: AuxvType, 61 | } 62 | 63 | impl From for AuxvDumpInfo { 64 | fn from(f: DirectAuxvDumpInfo) -> AuxvDumpInfo { 65 | AuxvDumpInfo { 66 | program_header_count: (f.program_header_count > 0).then_some(f.program_header_count), 67 | program_header_address: (f.program_header_address > 0) 68 | .then_some(f.program_header_address), 69 | linux_gate_address: (f.linux_gate_address > 0).then_some(f.linux_gate_address), 70 | entry_address: (f.entry_address > 0).then_some(f.entry_address), 71 | } 72 | } 73 | } 74 | 75 | #[derive(Debug, Default)] 76 | pub struct AuxvDumpInfo { 77 | program_header_count: Option, 78 | program_header_address: Option, 79 | linux_gate_address: Option, 80 | entry_address: Option, 81 | } 82 | 83 | impl AuxvDumpInfo { 84 | pub fn try_filling_missing_info( 85 | &mut self, 86 | pid: Pid, 87 | mut soft_errors: impl WriteErrorList, 88 | ) -> Result<(), AuxvError> { 89 | if self.is_complete() { 90 | return Ok(()); 91 | } 92 | 93 | let auxv_path = format!("/proc/{pid}/auxv"); 94 | let auxv_file = File::open(&auxv_path).map_err(|e| AuxvError::OpenError(auxv_path, e))?; 95 | 96 | for pair_result in ProcfsAuxvIter::new(BufReader::new(auxv_file)) { 97 | let AuxvPair { key, value } = match pair_result { 98 | Ok(pair) => pair, 99 | Err(e) => { 100 | soft_errors.push(e); 101 | continue; 102 | } 103 | }; 104 | let dest_field = match key { 105 | consts::AT_PHNUM => &mut self.program_header_count, 106 | consts::AT_PHDR => &mut self.program_header_address, 107 | consts::AT_SYSINFO_EHDR => &mut self.linux_gate_address, 108 | consts::AT_ENTRY => &mut self.entry_address, 109 | _ => continue, 110 | }; 111 | if dest_field.is_none() { 112 | *dest_field = Some(value); 113 | } 114 | } 115 | 116 | failspot!(FillMissingAuxvInfo soft_errors.push(AuxvError::InvalidFormat)); 117 | 118 | Ok(()) 119 | } 120 | pub fn get_program_header_count(&self) -> Option { 121 | self.program_header_count 122 | } 123 | pub fn get_program_header_address(&self) -> Option { 124 | self.program_header_address 125 | } 126 | pub fn get_linux_gate_address(&self) -> Option { 127 | self.linux_gate_address 128 | } 129 | pub fn get_entry_address(&self) -> Option { 130 | self.entry_address 131 | } 132 | pub fn is_complete(&self) -> bool { 133 | self.program_header_count.is_some() 134 | && self.program_header_address.is_some() 135 | && self.linux_gate_address.is_some() 136 | && self.entry_address.is_some() 137 | } 138 | } 139 | 140 | #[derive(Debug, Error, serde::Serialize)] 141 | pub enum AuxvError { 142 | #[error("Failed to open file {0}")] 143 | OpenError( 144 | String, 145 | #[source] 146 | #[serde(serialize_with = "serialize_io_error")] 147 | std::io::Error, 148 | ), 149 | #[error("No auxv entry found for PID {0}")] 150 | NoAuxvEntryFound(Pid), 151 | #[error("Invalid auxv format (should not hit EOF before AT_NULL)")] 152 | InvalidFormat, 153 | #[error("IO Error")] 154 | IOError( 155 | #[from] 156 | #[serde(serialize_with = "serialize_io_error")] 157 | std::io::Error, 158 | ), 159 | } 160 | -------------------------------------------------------------------------------- /src/linux/auxv/reader.rs: -------------------------------------------------------------------------------- 1 | // This file is heavily based on https://bitbucket.org/marshallpierce/rust-auxv 2 | // Thus I'm keeping the original MIT-license copyright here: 3 | // Copyright 2017 Marshall Pierce 4 | // 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: 5 | // The above copyright notice and this permission notice shall be in…substantial portions of the Software. 6 | // 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. 7 | 8 | use { 9 | super::{AuxvError, AuxvPair, AuxvType}, 10 | byteorder::{NativeEndian, ReadBytesExt}, 11 | std::{ 12 | fs::File, 13 | io::{BufReader, Read}, 14 | }, 15 | }; 16 | 17 | /// An iterator across auxv pairs from procfs. 18 | pub struct ProcfsAuxvIter { 19 | pair_size: usize, 20 | buf: Vec, 21 | input: BufReader, 22 | keep_going: bool, 23 | } 24 | 25 | impl ProcfsAuxvIter { 26 | pub fn new(input: BufReader) -> Self { 27 | let pair_size = 2 * std::mem::size_of::(); 28 | let buf: Vec = Vec::with_capacity(pair_size); 29 | 30 | Self { 31 | pair_size, 32 | buf, 33 | input, 34 | keep_going: true, 35 | } 36 | } 37 | } 38 | 39 | impl Iterator for ProcfsAuxvIter { 40 | type Item = Result; 41 | fn next(&mut self) -> Option { 42 | if !self.keep_going { 43 | return None; 44 | } 45 | // assume something will fail 46 | self.keep_going = false; 47 | 48 | self.buf = vec![0; self.pair_size]; 49 | 50 | let mut read_bytes: usize = 0; 51 | while read_bytes < self.pair_size { 52 | // read exactly buf's len of bytes. 53 | match self.input.read(&mut self.buf[read_bytes..]) { 54 | Ok(n) => { 55 | if n == 0 { 56 | // should not hit EOF before AT_NULL 57 | return Some(Err(AuxvError::InvalidFormat)); 58 | } 59 | 60 | read_bytes += n; 61 | } 62 | Err(x) => return Some(Err(x.into())), 63 | } 64 | } 65 | 66 | let mut reader = &self.buf[..]; 67 | let aux_key = match read_long(&mut reader) { 68 | Ok(x) => x, 69 | Err(x) => return Some(Err(x.into())), 70 | }; 71 | let aux_val = match read_long(&mut reader) { 72 | Ok(x) => x, 73 | Err(x) => return Some(Err(x.into())), 74 | }; 75 | 76 | let at_null; 77 | #[cfg(any(target_arch = "arm", all(target_os = "android", target_arch = "x86")))] 78 | { 79 | at_null = 0; 80 | } 81 | #[cfg(not(any(target_arch = "arm", all(target_os = "android", target_arch = "x86"))))] 82 | { 83 | at_null = libc::AT_NULL; 84 | } 85 | 86 | if aux_key == at_null { 87 | return None; 88 | } 89 | 90 | self.keep_going = true; 91 | Some(Ok(AuxvPair { 92 | key: aux_key, 93 | value: aux_val, 94 | })) 95 | } 96 | } 97 | 98 | fn read_long(reader: &mut dyn Read) -> std::io::Result { 99 | match std::mem::size_of::() { 100 | 4 => reader.read_u32::().map(|u| u as AuxvType), 101 | 8 => reader.read_u64::().map(|u| u as AuxvType), 102 | x => panic!("Unexpected type width: {}", x), 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/linux/crash_context.rs: -------------------------------------------------------------------------------- 1 | //! Minidump defines register structures which are different from the raw 2 | //! structures which we get from the kernel. These are platform specific 3 | //! functions to juggle the `ucontext_t` and user structures into minidump format. 4 | 5 | pub struct CrashContext { 6 | pub inner: crash_context::CrashContext, 7 | } 8 | 9 | cfg_if::cfg_if! { 10 | if #[cfg(target_arch = "x86_64")] { 11 | mod x86_64; 12 | } else if #[cfg(target_arch = "x86")] { 13 | mod x86; 14 | } else if #[cfg(target_arch = "aarch64")] { 15 | mod aarch64; 16 | } else if #[cfg(target_arch = "arm")] { 17 | mod arm; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/linux/crash_context/aarch64.rs: -------------------------------------------------------------------------------- 1 | use super::CrashContext; 2 | use crate::{ 3 | minidump_cpu::{RawContextCPU, FP_REG_COUNT, GP_REG_COUNT}, 4 | minidump_format::format, 5 | }; 6 | 7 | impl CrashContext { 8 | pub fn get_instruction_pointer(&self) -> usize { 9 | self.inner.context.uc_mcontext.pc as usize 10 | } 11 | 12 | pub fn get_stack_pointer(&self) -> usize { 13 | self.inner.context.uc_mcontext.sp as usize 14 | } 15 | 16 | pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { 17 | out.context_flags = format::ContextFlagsArm64Old::CONTEXT_ARM64_OLD_FULL.bits() as u64; 18 | 19 | { 20 | let gregs = &self.inner.context.uc_mcontext; 21 | out.cpsr = gregs.pstate as u32; 22 | out.iregs[..GP_REG_COUNT].copy_from_slice(&gregs.regs[..GP_REG_COUNT]); 23 | out.sp = gregs.sp; 24 | out.pc = gregs.pc; 25 | } 26 | 27 | { 28 | let fs = &self.inner.float_state; 29 | out.fpsr = fs.fpsr; 30 | out.fpcr = fs.fpcr; 31 | out.float_regs[..FP_REG_COUNT].copy_from_slice(&fs.vregs[..FP_REG_COUNT]); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/linux/crash_context/arm.rs: -------------------------------------------------------------------------------- 1 | use super::CrashContext; 2 | use crate::minidump_cpu::RawContextCPU; 3 | 4 | impl CrashContext { 5 | pub fn get_instruction_pointer(&self) -> usize { 6 | self.inner.context.uc_mcontext.arm_pc as usize 7 | } 8 | 9 | pub fn get_stack_pointer(&self) -> usize { 10 | self.inner.context.uc_mcontext.arm_sp as usize 11 | } 12 | 13 | pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { 14 | out.context_flags = 15 | crate::minidump_format::format::ContextFlagsArm::CONTEXT_ARM_FULL.bits(); 16 | 17 | { 18 | let iregs = &mut out.iregs; 19 | let gregs = &self.inner.context.uc_mcontext; 20 | iregs[0] = gregs.arm_r0; 21 | iregs[1] = gregs.arm_r1; 22 | iregs[2] = gregs.arm_r2; 23 | iregs[3] = gregs.arm_r3; 24 | iregs[4] = gregs.arm_r4; 25 | iregs[5] = gregs.arm_r5; 26 | iregs[6] = gregs.arm_r6; 27 | iregs[7] = gregs.arm_r7; 28 | iregs[8] = gregs.arm_r8; 29 | iregs[9] = gregs.arm_r9; 30 | iregs[10] = gregs.arm_r10; 31 | 32 | iregs[11] = gregs.arm_fp; 33 | iregs[12] = gregs.arm_ip; 34 | iregs[13] = gregs.arm_sp; 35 | iregs[14] = gregs.arm_lr; 36 | iregs[15] = gregs.arm_pc; 37 | 38 | out.cpsr = gregs.arm_cpsr; 39 | } 40 | 41 | // TODO: this todo has been in breakpad for years.... 42 | // TODO: fix this after fixing ExceptionHandler 43 | //out.float_save.fpscr = 0; 44 | //out.float_save.regs = [0; MD_FLOATINGSAVEAREA_ARM_FPR_COUNT]; 45 | //out.float_save.extra = [0; MD_FLOATINGSAVEAREA_ARM_FPEXTRA_COUNT]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/linux/crash_context/x86.rs: -------------------------------------------------------------------------------- 1 | use super::CrashContext; 2 | use crate::{minidump_cpu::RawContextCPU, minidump_format::format::ContextFlagsX86}; 3 | use libc::{ 4 | REG_CS, REG_DS, REG_EAX, REG_EBP, REG_EBX, REG_ECX, REG_EDI, REG_EDX, REG_EFL, REG_EIP, REG_ES, 5 | REG_ESI, REG_ESP, REG_FS, REG_GS, REG_SS, REG_UESP, 6 | }; 7 | impl CrashContext { 8 | pub fn get_instruction_pointer(&self) -> usize { 9 | self.inner.context.uc_mcontext.gregs[REG_EIP as usize] as usize 10 | } 11 | 12 | pub fn get_stack_pointer(&self) -> usize { 13 | self.inner.context.uc_mcontext.gregs[REG_ESP as usize] as usize 14 | } 15 | 16 | pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { 17 | out.context_flags = ContextFlagsX86::CONTEXT_X86_FULL.bits() 18 | | ContextFlagsX86::CONTEXT_X86_FLOATING_POINT.bits(); 19 | 20 | { 21 | let gregs = &self.inner.context.uc_mcontext.gregs; 22 | out.gs = gregs[REG_GS as usize] as u32; 23 | out.fs = gregs[REG_FS as usize] as u32; 24 | out.es = gregs[REG_ES as usize] as u32; 25 | out.ds = gregs[REG_DS as usize] as u32; 26 | 27 | out.edi = gregs[REG_EDI as usize] as u32; 28 | out.esi = gregs[REG_ESI as usize] as u32; 29 | out.ebx = gregs[REG_EBX as usize] as u32; 30 | out.edx = gregs[REG_EDX as usize] as u32; 31 | out.ecx = gregs[REG_ECX as usize] as u32; 32 | out.eax = gregs[REG_EAX as usize] as u32; 33 | 34 | out.ebp = gregs[REG_EBP as usize] as u32; 35 | out.eip = gregs[REG_EIP as usize] as u32; 36 | out.cs = gregs[REG_CS as usize] as u32; 37 | out.eflags = gregs[REG_EFL as usize] as u32; 38 | out.esp = gregs[REG_UESP as usize] as u32; 39 | out.ss = gregs[REG_SS as usize] as u32; 40 | } 41 | 42 | { 43 | let fs = &self.inner.float_state; 44 | let out = &mut out.float_save; 45 | out.control_word = fs.cw; 46 | out.status_word = fs.sw; 47 | out.tag_word = fs.tag; 48 | out.error_offset = fs.ipoff; 49 | out.error_selector = fs.cssel; 50 | out.data_offset = fs.dataoff; 51 | out.data_selector = fs.datasel; 52 | 53 | debug_assert_eq!(fs._st.len() * std::mem::size_of::(), 80); 54 | out.register_area.copy_from_slice(unsafe { 55 | std::slice::from_raw_parts( 56 | fs._st.as_ptr().cast(), 57 | fs._st.len() * std::mem::size_of::(), 58 | ) 59 | }); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/linux/crash_context/x86_64.rs: -------------------------------------------------------------------------------- 1 | use super::CrashContext; 2 | use crate::{ 3 | minidump_cpu::RawContextCPU, minidump_format::format, thread_info::copy_u32_registers, 4 | }; 5 | use libc::{ 6 | REG_CSGSFS, REG_EFL, REG_R10, REG_R11, REG_R12, REG_R13, REG_R14, REG_R15, REG_R8, REG_R9, 7 | REG_RAX, REG_RBP, REG_RBX, REG_RCX, REG_RDI, REG_RDX, REG_RIP, REG_RSI, REG_RSP, 8 | }; 9 | use scroll::Pwrite; 10 | 11 | impl CrashContext { 12 | pub fn get_instruction_pointer(&self) -> usize { 13 | self.inner.context.uc_mcontext.gregs[REG_RIP as usize] as usize 14 | } 15 | 16 | pub fn get_stack_pointer(&self) -> usize { 17 | self.inner.context.uc_mcontext.gregs[REG_RSP as usize] as usize 18 | } 19 | 20 | pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { 21 | out.context_flags = format::ContextFlagsAmd64::CONTEXT_AMD64_FULL.bits(); 22 | 23 | { 24 | let gregs = &self.inner.context.uc_mcontext.gregs; 25 | out.cs = (gregs[REG_CSGSFS as usize] & 0xffff) as u16; 26 | 27 | out.fs = ((gregs[REG_CSGSFS as usize] >> 32) & 0xffff) as u16; 28 | out.gs = ((gregs[REG_CSGSFS as usize] >> 16) & 0xffff) as u16; 29 | 30 | out.eflags = gregs[REG_EFL as usize] as u32; 31 | 32 | out.rax = gregs[REG_RAX as usize] as u64; 33 | out.rcx = gregs[REG_RCX as usize] as u64; 34 | out.rdx = gregs[REG_RDX as usize] as u64; 35 | out.rbx = gregs[REG_RBX as usize] as u64; 36 | 37 | out.rsp = gregs[REG_RSP as usize] as u64; 38 | out.rbp = gregs[REG_RBP as usize] as u64; 39 | out.rsi = gregs[REG_RSI as usize] as u64; 40 | out.rdi = gregs[REG_RDI as usize] as u64; 41 | out.r8 = gregs[REG_R8 as usize] as u64; 42 | out.r9 = gregs[REG_R9 as usize] as u64; 43 | out.r10 = gregs[REG_R10 as usize] as u64; 44 | out.r11 = gregs[REG_R11 as usize] as u64; 45 | out.r12 = gregs[REG_R12 as usize] as u64; 46 | out.r13 = gregs[REG_R13 as usize] as u64; 47 | out.r14 = gregs[REG_R14 as usize] as u64; 48 | out.r15 = gregs[REG_R15 as usize] as u64; 49 | 50 | out.rip = gregs[REG_RIP as usize] as u64; 51 | } 52 | 53 | { 54 | let fs = &self.inner.float_state; 55 | 56 | let mut float_save = format::XMM_SAVE_AREA32 { 57 | control_word: fs.cwd, 58 | status_word: fs.swd, 59 | tag_word: fs.ftw as u8, 60 | error_opcode: fs.fop, 61 | error_offset: fs.rip as u32, 62 | data_offset: fs.rdp as u32, 63 | error_selector: 0, // We don't have this. 64 | data_selector: 0, // We don't have this. 65 | mx_csr: fs.mxcsr, 66 | mx_csr_mask: fs.mxcr_mask, 67 | ..Default::default() 68 | }; 69 | 70 | copy_u32_registers(&mut float_save.float_registers, &fs.st_space); 71 | copy_u32_registers(&mut float_save.xmm_registers, &fs.xmm_space); 72 | 73 | out.float_save 74 | .pwrite_with(float_save, 0, scroll::Endian::Little) 75 | .expect("this is impossible"); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/linux/dso_debug.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | linux::{auxv::AuxvDumpInfo, errors::SectionDsoDebugError, ptrace_dumper::PtraceDumper}, 3 | mem_writer::{write_string_to_location, Buffer, MemoryArrayWriter, MemoryWriter}, 4 | minidump_format::*, 5 | }; 6 | 7 | type Result = std::result::Result; 8 | 9 | cfg_if::cfg_if! { 10 | if #[cfg(target_pointer_width = "32")] { 11 | use goblin::elf::program_header::program_header32::SIZEOF_PHDR; 12 | } else if #[cfg(target_pointer_width = "64")] { 13 | use goblin::elf::program_header::program_header64::SIZEOF_PHDR; 14 | } 15 | } 16 | 17 | cfg_if::cfg_if! { 18 | if #[cfg(all(target_pointer_width = "64", target_arch = "arm"))] { 19 | type ElfAddr = u64; 20 | } else if #[cfg(all(target_pointer_width = "64", not(target_arch = "arm")))] { 21 | type ElfAddr = libc::Elf64_Addr; 22 | } else if #[cfg(all(target_pointer_width = "32", target_arch = "arm"))] { 23 | type ElfAddr = u32; 24 | } else if #[cfg(all(target_pointer_width = "32", not(target_arch = "arm")))] { 25 | type ElfAddr = libc::Elf32_Addr; 26 | } 27 | } 28 | 29 | // COPY from 30 | #[derive(Debug, Clone, Default)] 31 | #[repr(C)] 32 | pub struct LinkMap { 33 | /* These first few members are part of the protocol with the debugger. 34 | This is the same format used in SVR4. */ 35 | l_addr: ElfAddr, /* Difference between the address in the ELF 36 | file and the addresses in memory. */ 37 | l_name: usize, /* Absolute file name object was found in. WAS: `char*` */ 38 | l_ld: usize, /* Dynamic section of the shared object. WAS: `ElfW(Dyn) *` */ 39 | l_next: usize, /* Chain of loaded objects. WAS: `struct link_map *` */ 40 | l_prev: usize, /* Chain of loaded objects. WAS: `struct link_map *` */ 41 | } 42 | 43 | // COPY from 44 | /// This state value describes the mapping change taking place when 45 | /// the `r_brk' address is called. 46 | #[derive(Debug, Clone, Default)] 47 | #[allow(non_camel_case_types, unused)] 48 | #[repr(C)] 49 | enum RState { 50 | /// Mapping change is complete. 51 | #[default] 52 | RT_CONSISTENT, 53 | /// Beginning to add a new object. 54 | RT_ADD, 55 | /// Beginning to remove an object mapping. 56 | RT_DELETE, 57 | } 58 | 59 | // COPY from 60 | #[derive(Debug, Clone, Default)] 61 | #[repr(C)] 62 | pub struct RDebug { 63 | r_version: libc::c_int, /* Version number for this protocol. */ 64 | r_map: usize, /* Head of the chain of loaded objects. WAS: `struct link_map *` */ 65 | 66 | /* This is the address of a function internal to the run-time linker, 67 | that will always be called when the linker begins to map in a 68 | library or unmap it, and again when the mapping change is complete. 69 | The debugger can set a breakpoint at this address if it wants to 70 | notice shared object mapping changes. */ 71 | r_brk: ElfAddr, 72 | r_state: RState, 73 | r_ldbase: ElfAddr, /* Base address the linker is loaded at. */ 74 | } 75 | 76 | pub fn write_dso_debug_stream( 77 | buffer: &mut Buffer, 78 | blamed_thread: i32, 79 | auxv: &AuxvDumpInfo, 80 | ) -> Result { 81 | let phnum_max = 82 | auxv.get_program_header_count() 83 | .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHNUM in auxv"))? as usize; 84 | let phdr = auxv 85 | .get_program_header_address() 86 | .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHDR in auxv"))? as usize; 87 | 88 | let ph = PtraceDumper::copy_from_process(blamed_thread, phdr, SIZEOF_PHDR * phnum_max)?; 89 | let program_headers; 90 | #[cfg(target_pointer_width = "64")] 91 | { 92 | program_headers = goblin::elf::program_header::program_header64::ProgramHeader::from_bytes( 93 | &ph, phnum_max, 94 | ); 95 | } 96 | #[cfg(target_pointer_width = "32")] 97 | { 98 | program_headers = goblin::elf::program_header::program_header32::ProgramHeader::from_bytes( 99 | &ph, phnum_max, 100 | ); 101 | }; 102 | 103 | // Assume the program base is at the beginning of the same page as the PHDR 104 | let mut base = phdr & !0xfff; 105 | let mut dyn_addr = 0; 106 | // Search for the program PT_DYNAMIC segment 107 | for ph in program_headers { 108 | // Adjust base address with the virtual address of the PT_LOAD segment 109 | // corresponding to offset 0 110 | if ph.p_type == goblin::elf::program_header::PT_LOAD && ph.p_offset == 0 { 111 | base -= ph.p_vaddr as usize; 112 | } 113 | if ph.p_type == goblin::elf::program_header::PT_DYNAMIC { 114 | dyn_addr = ph.p_vaddr; 115 | } 116 | } 117 | 118 | if dyn_addr == 0 { 119 | return Err(SectionDsoDebugError::CouldNotFind( 120 | "dyn_addr in program headers", 121 | )); 122 | } 123 | 124 | dyn_addr += base as ElfAddr; 125 | 126 | let dyn_size = std::mem::size_of::(); 127 | let mut r_debug = 0usize; 128 | let mut dynamic_length = 0usize; 129 | 130 | // The dynamic linker makes information available that helps gdb find all 131 | // DSOs loaded into the program. If this information is indeed available, 132 | // dump it to a MD_LINUX_DSO_DEBUG stream. 133 | loop { 134 | let dyn_data = PtraceDumper::copy_from_process( 135 | blamed_thread, 136 | dyn_addr as usize + dynamic_length, 137 | dyn_size, 138 | )?; 139 | dynamic_length += dyn_size; 140 | 141 | // goblin::elf::Dyn doesn't have padding bytes 142 | let (head, body, _tail) = unsafe { dyn_data.align_to::() }; 143 | assert!(head.is_empty(), "Data was not aligned"); 144 | let dyn_struct = &body[0]; 145 | 146 | let debug_tag = goblin::elf::dynamic::DT_DEBUG; 147 | if dyn_struct.d_tag == debug_tag { 148 | r_debug = dyn_struct.d_val as usize; 149 | } else if dyn_struct.d_tag == goblin::elf::dynamic::DT_NULL { 150 | break; 151 | } 152 | } 153 | 154 | // The "r_map" field of that r_debug struct contains a linked list of all 155 | // loaded DSOs. 156 | // Our list of DSOs potentially is different from the ones in the crashing 157 | // process. So, we have to be careful to never dereference pointers 158 | // directly. Instead, we use CopyFromProcess() everywhere. 159 | // See for a more detailed discussion of the how the dynamic 160 | // loader communicates with debuggers. 161 | 162 | let debug_entry_data = 163 | PtraceDumper::copy_from_process(blamed_thread, r_debug, std::mem::size_of::())?; 164 | 165 | // goblin::elf::Dyn doesn't have padding bytes 166 | let (head, body, _tail) = unsafe { debug_entry_data.align_to::() }; 167 | assert!(head.is_empty(), "Data was not aligned"); 168 | let debug_entry = &body[0]; 169 | 170 | // Count the number of loaded DSOs 171 | let mut dso_vec = Vec::new(); 172 | let mut curr_map = debug_entry.r_map; 173 | while curr_map != 0 { 174 | let link_map_data = PtraceDumper::copy_from_process( 175 | blamed_thread, 176 | curr_map, 177 | std::mem::size_of::(), 178 | )?; 179 | 180 | // LinkMap is repr(C) and doesn't have padding bytes, so this should be safe 181 | let (head, body, _tail) = unsafe { link_map_data.align_to::() }; 182 | assert!(head.is_empty(), "Data was not aligned"); 183 | let map = &body[0]; 184 | 185 | curr_map = map.l_next; 186 | dso_vec.push(map.clone()); 187 | } 188 | 189 | let mut linkmap_rva = u32::MAX; 190 | if !dso_vec.is_empty() { 191 | // If we have at least one DSO, create an array of MDRawLinkMap 192 | // entries in the minidump file. 193 | let mut linkmap = MemoryArrayWriter::::alloc_array(buffer, dso_vec.len())?; 194 | linkmap_rva = linkmap.location().rva; 195 | 196 | // Iterate over DSOs and write their information to mini dump 197 | for (idx, map) in dso_vec.iter().enumerate() { 198 | let mut filename = String::new(); 199 | if map.l_name > 0 { 200 | let filename_data = 201 | PtraceDumper::copy_from_process(blamed_thread, map.l_name, 256)?; 202 | 203 | // C - string is NULL-terminated 204 | if let Some(name) = filename_data.splitn(2, |x| *x == b'\0').next() { 205 | filename = String::from_utf8(name.to_vec())?; 206 | } 207 | } 208 | let location = write_string_to_location(buffer, &filename)?; 209 | let entry = MDRawLinkMap { 210 | addr: map.l_addr, 211 | name: location.rva, 212 | ld: map.l_ld as ElfAddr, 213 | }; 214 | 215 | linkmap.set_value_at(buffer, entry, idx)?; 216 | } 217 | } 218 | 219 | // Write MD_LINUX_DSO_DEBUG record 220 | let debug = MDRawDebug { 221 | version: debug_entry.r_version as u32, 222 | map: linkmap_rva, 223 | dso_count: dso_vec.len() as u32, 224 | brk: debug_entry.r_brk, 225 | ldbase: debug_entry.r_ldbase, 226 | dynamic: dyn_addr, 227 | }; 228 | let debug_loc = MemoryWriter::::alloc_with_val(buffer, debug)?; 229 | 230 | let mut dirent = MDRawDirectory { 231 | stream_type: MDStreamType::LinuxDsoDebug as u32, 232 | location: debug_loc.location(), 233 | }; 234 | 235 | dirent.location.data_size += dynamic_length as u32; 236 | let dso_debug_data = 237 | PtraceDumper::copy_from_process(blamed_thread, dyn_addr as usize, dynamic_length)?; 238 | MemoryArrayWriter::write_bytes(buffer, &dso_debug_data); 239 | 240 | Ok(dirent) 241 | } 242 | -------------------------------------------------------------------------------- /src/linux/dumper_cpu_info.rs: -------------------------------------------------------------------------------- 1 | cfg_if::cfg_if! { 2 | if #[cfg(any( 3 | target_arch = "x86_64", 4 | target_arch = "x86", 5 | target_arch = "mips", 6 | target_arch = "mips64" 7 | ))] 8 | { 9 | pub mod x86_mips; 10 | pub use x86_mips as imp; 11 | } else if #[cfg(any( 12 | target_arch = "arm", 13 | target_arch = "aarch64", 14 | ))] 15 | { 16 | pub mod arm; 17 | pub use arm as imp; 18 | } 19 | } 20 | 21 | pub use imp::write_cpu_information; 22 | 23 | use crate::minidump_format::PlatformId; 24 | use nix::sys::utsname::uname; 25 | 26 | /// Retrieves the [`MDOSPlatform`] and synthesized version information 27 | pub fn os_information() -> (PlatformId, String) { 28 | let platform_id = if cfg!(target_os = "android") { 29 | PlatformId::Android 30 | } else { 31 | PlatformId::Linux 32 | }; 33 | 34 | // This is quite unfortunate, but the primary reason that uname could fail 35 | // would be if it failed to fill out the nodename (hostname) field, even 36 | // though we don't care about that particular field at all 37 | let info = uname().map_or_else( 38 | |_e| { 39 | let os = if platform_id == PlatformId::Linux { 40 | "Linux" 41 | } else { 42 | "Android" 43 | }; 44 | 45 | let machine = if cfg!(target_arch = "x86_64") { 46 | "x86_64" 47 | } else if cfg!(target_arch = "x86") { 48 | "x86" 49 | } else if cfg!(target_arch = "aarch64") { 50 | "aarch64" 51 | } else if cfg!(target_arch = "arm") { 52 | "arm" 53 | } else { 54 | "" 55 | }; 56 | 57 | // TODO: Fallback to other sources of information, eg /etc/os-release 58 | format!("{os} {machine}") 59 | }, 60 | |info| { 61 | format!( 62 | "{} {} {} {}", 63 | info.sysname().to_str().unwrap_or(""), 64 | info.release().to_str().unwrap_or(""), 65 | info.version().to_str().unwrap_or(""), 66 | info.machine().to_str().unwrap_or(""), 67 | ) 68 | }, 69 | ); 70 | 71 | (platform_id, info) 72 | } 73 | -------------------------------------------------------------------------------- /src/linux/dumper_cpu_info/x86_mips.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{errors::CpuInfoError, minidump_format::*}, 3 | failspot::failspot, 4 | std::{ 5 | io::{BufRead, BufReader}, 6 | path, 7 | }, 8 | }; 9 | 10 | type Result = std::result::Result; 11 | 12 | struct CpuInfoEntry { 13 | info_name: &'static str, 14 | value: i32, 15 | found: bool, 16 | } 17 | 18 | impl CpuInfoEntry { 19 | fn new(info_name: &'static str, value: i32, found: bool) -> Self { 20 | CpuInfoEntry { 21 | info_name, 22 | value, 23 | found, 24 | } 25 | } 26 | } 27 | 28 | pub fn write_cpu_information(sys_info: &mut MDRawSystemInfo) -> Result<()> { 29 | let vendor_id_name = "vendor_id"; 30 | let mut cpu_info_table = [ 31 | CpuInfoEntry::new("processor", -1, false), 32 | #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] 33 | CpuInfoEntry::new("model", 0, false), 34 | #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] 35 | CpuInfoEntry::new("stepping", 0, false), 36 | #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] 37 | CpuInfoEntry::new("cpu family", 0, false), 38 | ]; 39 | 40 | // processor_architecture should always be set, do this first 41 | sys_info.processor_architecture = if cfg!(target_arch = "mips") { 42 | MDCPUArchitecture::PROCESSOR_ARCHITECTURE_MIPS 43 | } else if cfg!(target_arch = "mips64") { 44 | MDCPUArchitecture::PROCESSOR_ARCHITECTURE_MIPS64 45 | } else if cfg!(target_arch = "x86") { 46 | MDCPUArchitecture::PROCESSOR_ARCHITECTURE_INTEL 47 | } else { 48 | MDCPUArchitecture::PROCESSOR_ARCHITECTURE_AMD64 49 | } as u16; 50 | 51 | failspot!( 52 | CpuInfoFileOpen 53 | bail(std::io::Error::other("test requested cpuinfo file failure")) 54 | ); 55 | 56 | let cpuinfo_file = std::fs::File::open(path::PathBuf::from("/proc/cpuinfo"))?; 57 | 58 | let mut vendor_id = String::new(); 59 | for line in BufReader::new(cpuinfo_file).lines() { 60 | let line = line?; 61 | // Expected format: + ':' 62 | // Note that: 63 | // - empty lines happen. 64 | // - can contain spaces. 65 | // - some fields have an empty 66 | if line.trim().is_empty() { 67 | continue; 68 | } 69 | 70 | let mut liter = line.split(':').map(|x| x.trim()); 71 | let field = liter.next().unwrap(); // guaranteed to have at least one item 72 | let value = if let Some(val) = liter.next() { 73 | val 74 | } else { 75 | continue; 76 | }; 77 | 78 | let mut is_first_entry = true; 79 | for entry in cpu_info_table.iter_mut() { 80 | if !is_first_entry && entry.found { 81 | // except for the 'processor' field, ignore repeated values. 82 | continue; 83 | } 84 | is_first_entry = false; 85 | if field == entry.info_name { 86 | if let Ok(v) = value.parse() { 87 | entry.value = v; 88 | entry.found = true; 89 | } else { 90 | continue; 91 | } 92 | } 93 | 94 | // special case for vendor_id 95 | if field == vendor_id_name && !value.is_empty() { 96 | vendor_id = value.into(); 97 | } 98 | } 99 | } 100 | // make sure we got everything we wanted 101 | if !cpu_info_table.iter().all(|x| x.found) { 102 | return Err(CpuInfoError::NotAllProcEntriesFound); 103 | } 104 | // cpu_info_table[0] holds the last cpu id listed in /proc/cpuinfo, 105 | // assuming this is the highest id, change it to the number of CPUs 106 | // by adding one. 107 | cpu_info_table[0].value += 1; 108 | 109 | sys_info.number_of_processors = cpu_info_table[0].value as u8; // TODO: might not work on special machines with LOTS of CPUs 110 | #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] 111 | { 112 | sys_info.processor_level = cpu_info_table[3].value as u16; 113 | sys_info.processor_revision = 114 | ((cpu_info_table[1].value << 8) | cpu_info_table[2].value) as u16; 115 | } 116 | if !vendor_id.is_empty() { 117 | let vendor_id = vendor_id.as_bytes(); 118 | // The vendor_id is the first 12 (3 * size_of::()) bytes 119 | let vendor_len = std::cmp::min(3 * std::mem::size_of::(), vendor_id.len()); 120 | sys_info.cpu.data[..vendor_len].copy_from_slice(&vendor_id[..vendor_len]); 121 | } 122 | 123 | Ok(()) 124 | } 125 | -------------------------------------------------------------------------------- /src/linux/mem_reader.rs: -------------------------------------------------------------------------------- 1 | //! Functionality for reading a remote process's memory 2 | 3 | use crate::{errors::CopyFromProcessError, ptrace_dumper::PtraceDumper, Pid}; 4 | 5 | enum Style { 6 | /// Uses [`process_vm_readv`](https://linux.die.net/man/2/process_vm_readv) 7 | /// to read the memory. 8 | /// 9 | /// This is not available on old <3.2 (really, ancient) kernels, and requires 10 | /// the same permissions as ptrace 11 | VirtualMem, 12 | /// Reads the memory from `/proc//mem` 13 | /// 14 | /// Available on basically all versions of Linux, but could fail if the process 15 | /// has insufficient privileges, ie ptrace 16 | File(std::fs::File), 17 | /// Reads the memory with [ptrace (`PTRACE_PEEKDATA`)](https://man7.org/linux/man-pages/man2/ptrace.2.html) 18 | /// 19 | /// Reads data one word at a time, so slow, but fairly reliable, as long as 20 | /// the process can be ptraced 21 | Ptrace, 22 | /// No methods succeeded, generally there isn't a case where failing a syscall 23 | /// will work if called again 24 | Unavailable { 25 | vmem: nix::Error, 26 | file: nix::Error, 27 | ptrace: nix::Error, 28 | }, 29 | } 30 | 31 | pub struct MemReader { 32 | /// The pid of the child to read 33 | pid: nix::unistd::Pid, 34 | style: Option