8000 Enable support for FreeBSD by G36maid · Pull Request #183 · rust-minidump/minidump-writer · GitHub
[go: up one dir, main page]

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@ linker = "aarch64-linux-android30-clang"
# By default the linker _doesn't_ generate a build-id, however we want one for our tests.
rustflags = ["-C", "link-args=-Wl,--build-id=sha1"]
runner = [".cargo/android-runner.sh"]

# The FreeBSD linker doesn't generate a build-id by default; the tests rely
# on one being present.
[target.x86_64-unknown-freebsd]
rustflags = ["-C", "link-args=-Wl,--build-id=sha1"]
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,35 @@ jobs:
cargo test --target x86_64-linux-android
cargo clippy --all-features --all-targets --target x86_64-linux-android -- -D warnings

test-freebsd:
name: Test (FreeBSD ${{ matrix.release }} x86_64)
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
include:
- release: "14.4"
- release: "15.0"
steps:
- uses: actions/checkout@v4
- name: Test on FreeBSD
uses: vmactions/freebsd-vm@v1
with:
release: ${{ matrix.release }}
usesh: true
prepare: |
fetch -qo /tmp/rustup-init https://static.rust-lang.org/rustup/dist/x86_64-unknown-freebsd/rustup-init
chmod +x /tmp/rustup-init
/tmp/rustup-init -y --profile minimal -c clippy -c rustfmt --default-toolchain 1.90.0
run: |
. "$HOME/.cargo/env"
sysctl security.bsd.unprivileged_proc_debug=1
cargo fmt --all -- --check
cargo test --lib
cargo test --test freebsd_minidump_writer
cargo test --test freebsd_module_reader
cargo clippy --all-features --all-targets -- -D warnings

deny-check:
runs-on: ubuntu-24.04
steps:
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

<!-- next-header -->
## [Unreleased] - ReleaseDate
### Added
- [PR#183](https://github.com/rust-minidump/minidump-writer/pull/183) added FreeBSD support (x86_64): process inspection via `ptrace`(`PT_IO`, `PT_GETREGS`, `PT_SUSPEND`), maps via `kinfo_getvmmap`, ELF module/build-id/soname reading, and the full minidump writer stream set, resolving [#162](https://github.com/rust-minidump/minidump-writer/issues/162).

## [0.13.0] - 2026-07-20
### Changed
- [PR#174](https://github.com/rust-minidump/minidump-writer/pull/174) and [PR#175](https://github.com/rust-minidump/minidump-writer/pull/175) refactored `src/linux/thread_info`.
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ fn write_minidump(crash_context: crash_context::CrashContext) {
- ⭕️ Unimplemented, but could be implemented in the future
- ❌ Unimplemented, and unlikely to ever be implemented

| Arch | unknown-linux-gnu | unknown-linux-musl | linux-android | pc-windows-msvc | apple-darwin | apple-ios |
----------- | ----------------- | ------------------ | ------------- | --------------- | ------------ | --------- |
`x86_64` | ✅ | ✅ | ⚠️ | ✅ | ✅ | ⭕️ |
`i686` | ✅ | ✅ | ❌ | ⭕️ | ❌ | ❌ |
`arm` | ⚠️ | ⚠️ | ⚠️ | ⭕️ | ❌ | ❌ |
`aarch64` | ⚠️ | ⚠️ | ⚠️ | ⭕️ | ✅ | ⭕️ |
| Arch | unknown-linux-gnu | unknown-linux-musl | linux-android | pc-windows-msvc | apple-darwin | apple-ios | unknown-freebsd |
----------- | ----------------- | ------------------ | ------------- | --------------- | ------------ | --------- | ---------------- |
`x86_64` | ✅ | ✅ | ⚠️ | ✅ | ✅ | ⭕️ | ✅ |
`i686` | ✅ | ✅ | ❌ | ⭕️ | ❌ | ❌ | ⭕️ |
`arm` | ⚠️ | ⚠️ | ⚠️ | ⭕️ | ❌ | ❌ | ❌ |
`aarch64` | ⚠️ | ⚠️ | ⚠️ | ⭕️ | ✅ | ⭕️ | ⭕️ |
4 changes: 4 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ fn main() {
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") {
println!("cargo:rustc-link-lib=dylib=dbghelp");
}
// kinfo_getvmmap() is provided by libutil
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("freebsd") {
println!("cargo:rustc-link-lib=dylib=util");
}
}
1 change: 1 addition & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ targets = [
"aarch64-apple-darwin",
"aarch64-linux-android",
"x86_64-linux-android",
"x86_64-unknown-freebsd",
]

[advisories]
Expand Down
171 changes: 171 additions & 0 deletions src/bin/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,174 @@ mod linux {
}
}

#[cfg(target_os = "freebsd")]
mod freebsd {
use super::*;

/// Signals readiness on stdout and parks forever.
fn wait_forever() -> ! {
println!("1");
loop {
std::thread::park();
}
}

fn spawn_and_wait(num: usize) -> Result<()> {
// One less than the requested amount, as the main thread counts as well
for _ in 1..num {
std::thread::spawn(|| {
println!("1");
loop {
std::thread::park();
}
});
}
println!("1");
loop {
std::thread::park();
}
}

fn spawn_name_wait(num: usize) -> Result<()> {
for id in 1..num {
std::thread::Builder::new()
.name(format!("thread_{id}"))
.spawn(move || {
let cname = std::ffi::CString::new(format!("thread_{id}"))
.expect("thread name contained null");
// SAFETY: pthread_set_name_np takes a valid pthread_t from
// pthread_self() and a valid null-terminated C string from
// CString which lives for the duration of the call.
unsafe {
libc::pthread_set_name_np(libc::pthread_self(), cname.as_ptr());
}
wait_forever();
})?;
}
println!("1");
loop {
std::thread::park();
}
}

fn spawn_mmap_wait() -> Result<()> {
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };
let mapped_mem = unsafe {
libc::mmap(
std::ptr::null_mut(),
page_size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANON,
-1,
0,
)
};
if mapped_mem == libc::MAP_FAILED {
return Err("mmap failed".into());
}

println!("{} {}", mapped_mem as usize, page_size);
wait_forever()
}

/// Spawns a grandchild running `spawn_and_wait` and prints its pid.
fn spawn_spawn_wait() -> Result<()> {
let exe = std::env::current_exe()?;
let mut grandchild = std::process::Command::new(exe)
.arg("spawn_and_wait")
.arg("1")
.stdout(std::process::Stdio::piped())
.spawn()?;
let pid = grandchild.id();
println!("{pid}");
grandchild.wait()?;
Ok(())
}

fn spawn_alloc_wait() -> Result<()> {
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };

let mut values = Vec::<u8>::with_capacity(page_size);
for idx in 0..page_size {
values.push((idx % 255) as u8);
}

println!("{:p} {}", values.as_ptr(), page_size);
wait_forever()
}

fn create_files_wait(num: usize) -> Result<()> {
use std::{fmt::Write, io::Read};

let mut file_array = Vec::<std::fs::File>::with_capacity(num);

let mut rand = std::fs::File::open("/dev/urandom").expect("failed to open /dev/urandom");
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

let mut root = std::env::temp_dir();
root.push("minidump-writer");

if !root.exists() {
std::fs::create_dir_all(&root).expect("failed to create $TMP/minidump-writer dir");
}

let mut rand_indices = [0u8; 6];

for id in 0..num {
let mut name = String::new();
name.push_str("test_file");

rand.read_exact(&mut rand_indices)
.expect("failed to read /dev/urandom");

for index in rand_indices {
name.push(CHARS[index as usize % CHARS.len()] as char);
}

write!(&mut name, "{id}").unwrap();

let path = root.join(name);
file_array.push(std::fs::File::create(&path).expect("failed to create path"));
println!("1");
}
// Keep the files open (and referenced) until the dump is taken.
println!("1");
loop {
std::thread::park();
println!("{}", file_array.len());
}
}

pub(super) fn real_main(args: Vec<String>) -> Result<()> {
match args.len() {
1 => match args[0].as_ref() {
"nop" => Ok(()),
"setup" => Ok(()),
"spawn_mmap_wait" => spawn_mmap_wait(),
"spawn_alloc_wait" => spawn_alloc_wait(),
"spawn_spawn_wait" => spawn_spawn_wait(),
_ => Err("Len 1: Unknown test option".into()),
},
2 => match args[0].as_ref() {
"spawn_and_wait" => {
let num_of_threads: usize = args[1].parse().unwrap();
spawn_and_wait(num_of_threads)
}
"spawn_name_wait" => {
let num_of_threads: usize = args[1].parse().unwrap();
spawn_name_wait(num_of_threads)
}
"create_files_wait" => {
let num_of_files: usize = args[1].parse().unwrap();
create_files_wait(num_of_files)
}
_ => Err(format!("Len 2: Unknown test option: {}", args[0]).into()),
},
_ => Err("Unknown test option".into()),
}
}
}

#[cfg(target_os = "windows")]
mod windows {
use super::*;
Expand Down Expand Up @@ -668,11 +836,14 @@ fn main() -> Result<()> {
cfg_if::cfg_if! {
if #[cfg(any(target_os = "linux", target_os = "android"))] {
linux::real_main(args)
} else if #[cfg(target_os = "freebsd")] {
freebsd::real_main(args)
} else if #[cfg(target_os = "windows")] {
windows::real_main(args)
} else if #[cfg(target_os = "macos")] {
mac::real_main(args)
} else {
let _ = args;
unimplemented!();
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/freebsd/app_memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// These entries store a list of memory regions that the client wants included
// in the minidump.
#[derive(Debug, Default, PartialEq, Eq)]
pub struct AppMemory {
pub ptr: usize,
pub length: usize,
}

pub type AppMemoryList = Vec<AppMemory>;
Loading
Loading
0