├── .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 | [](https://github.com/rust-minidump/minidump-writer/actions/workflows/ci.yml)
8 | [](https://crates.io/crates/minidump-writer)
9 | [](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