diff --git a/.cargo/config.in b/.cargo/config.in index 5e96889077fd..6dc398487c72 100644 --- a/.cargo/config.in +++ b/.cargo/config.in @@ -17,6 +17,11 @@ git = "https://github.com/shravanrn/nix/" replace-with = "vendored-sources" rev = "4af6c367603869a30fddb5ffb0aba2b9477ba92e" +[source."https://github.com/msirringhaus/minidump_writer_linux.git"] +git = "https://github.com/msirringhaus/minidump_writer_linux.git" +replace-with = "vendored-sources" +rev = "4ea371049a9cca212cc13c19b7952c1c014085c6" + [source."https://github.com/mozilla/neqo"] git = "https://github.com/mozilla/neqo" replace-with = "vendored-sources" diff --git a/Cargo.lock b/Cargo.lock index 865bd76d83cc..02dfa95f031f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2062,6 +2062,7 @@ dependencies = [ "rlbox_lucet_sandbox", "rsdparsa_capi", "rusqlite", + "rust_minidump_writer_linux", "rustc_version", "shift_or_euc_c", "static_prefs", @@ -3134,6 +3135,20 @@ dependencies = [ "unicase", ] +[[package]] +name = "minidump_writer_linux" +version = "0.1.0" +source = "git+https://github.com/msirringhaus/minidump_writer_linux.git?rev=4ea371049a9cca212cc13c19b7952c1c014085c6#4ea371049a9cca212cc13c19b7952c1c014085c6" +dependencies = [ + "byteorder", + "goblin", + "libc", + "memmap", + "memoffset 0.5.1", + "nix", + "tempfile", +] + [[package]] name = "miniz_oxide" version = "0.4.3" @@ -4482,6 +4497,14 @@ dependencies = [ "serde", ] +[[package]] +name = "rust_minidump_writer_linux" +version = "0.1.0" +dependencies = [ + "libc", + "minidump_writer_linux", +] + [[package]] name = "rustc-demangle" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index e309fe93afb4..0f58a71cdf10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "security/manager/ssl/osclientcerts", "testing/geckodriver", "toolkit/crashreporter/rust", + "toolkit/crashreporter/rust_minidump_writer_linux", "toolkit/library/gtest/rust", "toolkit/library/rust/", "toolkit/mozapps/defaultagent/rust", @@ -80,6 +81,7 @@ spirv_cross = { git = "https://github.com/kvark/spirv_cross", branch = "wgpu4" } # failure's backtrace feature might break our builds, see bug 1608157. failure = { git = "https://github.com/badboy/failure", rev = "64af847bc5fdcb6d2438bec8a6030812a80519a5" } failure_derive = { git = "https://github.com/badboy/failure", rev = "64af847bc5fdcb6d2438bec8a6030812a80519a5" } +minidump_writer_linux = { git = "https://github.com/msirringhaus/minidump_writer_linux.git", rev = "4ea371049a9cca212cc13c19b7952c1c014085c6" } [patch.crates-io.cranelift-codegen] git = "https://github.com/mozilla-spidermonkey/wasmtime" diff --git a/third_party/rust/minidump_writer_linux/.cargo-checksum.json b/third_party/rust/minidump_writer_linux/.cargo-checksum.json new file mode 100644 index 000000000000..7d5bbf58d0db --- /dev/null +++ b/third_party/rust/minidump_writer_linux/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"d83f06b41499b1f763f31498c5412b946a09b93454555140f37b0edd66959e4b","LICENSE":"1ecdd8e8977af83c07c5f97bec87b47d27059b7ea323ca3160fbfa2314f5d99c","src/app_memory.rs":"909676c916c0ffaa3a813632c162f5b1207f8502408b6b3bab48a5f842948c71","src/auxv_reader.rs":"6aff2c1ef3ad0942b93445f5098b07789afbce512f6b4055e9c195df307aea27","src/bin/test.rs":"2278080fd4a13ec5423cb99d7e97a677ea6d73e29c7a99038175ee7a9dfe9c4f","src/cpu_set.rs":"7094f972a7217bf20eeb5b504802177b7aa5dd6cf220235e858fc42259c52f59","src/crash_context/crash_context_aarch64.rs":"97da8a6a66a53ebee4e329c4b01c0a68e74d707adbfb6f2e3ed6fd4318289d0f","src/crash_context/crash_context_arm.rs":"ddf1be0f67e4f43dfae8373d1b902065c671435bbeb7cf4428b2bcb218a1c25a","src/crash_context/crash_context_mips.rs":"3e263d547a3f13ee81d1557a13db6bbd446133821986c2ded94d361634ec1f3a","src/crash_context/crash_context_x86.rs":"8c8566be3c103ec65c83e8f9b2ad4f7a6f19f17656be4b4deed50d7723cc57d8","src/crash_context/crash_context_x86_64.rs":"c658a81a31d66a38c52ffbe816e3945301173f9e6e98c795a822a06be40968bc","src/crash_context/mod.rs":"1578792d46480a79982943ee3b820958ea335a847bbb6b74f44bead6edf17078","src/dso_debug.rs":"c11c6c4d64974042051f000818ac4c3a8895ed2757076612f9209e885d2d5d33","src/dumper_cpu_info/cpu_info_arm.rs":"137953f8424ffb38f89f8fc436a462a64b42583ab36d00d897a81e297736d2d9","src/dumper_cpu_info/cpu_info_x86_mips.rs":"c42db73377cee066f9d4ddca13a77a0692eef513fcefde1f2b91dacce6778f89","src/dumper_cpu_info/mod.rs":"320b629c23ffa30eaead762a74e500f0bc029793234461ef2959681b7650b1e9","src/lib.rs":"87375f3a4cfadb91fd366cb75e1959a08e654e5225955ed56e15d6fc9d208aa0","src/linux_ptrace_dumper.rs":"7095c6adc0d94283a90bdd97a9458df29a7bf2685624e568bc1be8fb1a223baf","src/maps_reader.rs":"7333d74e635b1b7653df25411e23f133c2025e835339f38bf44d7ae5c29e001a","src/minidump_cpu/minidump_cpu_aarch64.rs":"18524f8a3a3d3eda7d0fafb1c168ff06dee1886da0f4278f3c2a54147ef79bc9","src/minidump_cpu/minidump_cpu_amd64.rs":"c4b3cef86ea53864c026d7c598d8a7f59ca6b1ad7e881f94ce34f39dd6b72e33","src/minidump_cpu/minidump_cpu_arm.rs":"2b95ecef302de39a0c105011392dc1419dd3f62762482038318f564d5ed3797a","src/minidump_cpu/minidump_cpu_mips.rs":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","src/minidump_cpu/minidump_cpu_ppc.rs":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","src/minidump_cpu/minidump_cpu_ppc64.rs":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","src/minidump_cpu/minidump_cpu_sparc.rs":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","src/minidump_cpu/minidump_cpu_x86.rs":"3dcb2aa4264d76f0727b9750ffce0032738bea97be95fde92089a9d11926c63c","src/minidump_cpu/mod.rs":"50e8df5e294e9d162c97dacb9448bba0ffc1e4bf992f8a2b9cc718f12b92a370","src/minidump_format.rs":"56f737bd95fbbd5b4b3d7773dc491e267887d78b12916645bccbcef16acf2cfc","src/minidump_writer.rs":"5e5cf95b9efa7f8a8ad48c43c03e8a4101efee4206a4a14f92a5cdb676d5e6b6","src/sections/app_memory.rs":"13074b16ffbd144e0cc209ad2cb64571dbb74ba987f65026338645d440e3cd89","src/sections/exception_stream.rs":"08562fcd73e48ce5d251080504526f4f7e65f536325da816f69ff0d8354f4bc4","src/sections/mappings.rs":"15d489a3d053627c793a6794c85bc9d38d72a7299e496142cc38afa2c2a8d54c","src/sections/memory_list_stream.rs":"c15ec66f844122f98d553be501e881837275709d0d8e5ec4ecf4d076fcaf79d7","src/sections/mod.rs":"71911a6265365b75e74b87a83754ae60af7f17bf1faf539450e3cd1dc9a9f0c0","src/sections/systeminfo_stream.rs":"70f8d62a86a874536edfeb9ad0a47916fe90934ae0b29b61ed871335ac2f776f","src/sections/thread_list_stream.rs":"f69de2e50c033d5a1c6a597b942025a6442e71158f376c6afcfe6f3ac929dad7","src/thread_info/mod.rs":"d86206025f4a9612caa90391231403c9bc6512a85309eb4f53b95826ad837215","src/thread_info/thread_info_aarch64.rs":"941bcf68a03d647b946bda2e1542d8965b827e392eb3e4fb4e0193f7e300e12c","src/thread_info/thread_info_arm.rs":"60a9008a4e4d156777b7a3b3d0ef28ec4f97fda217759cdf211e604f9dc9bbd6","src/thread_info/thread_info_mips.rs":"203d1219e81da1f33ac06f36d94eb1452ab9a57cb3ca5258a1a60a0f2c5706d0","src/thread_info/thread_info_x86.rs":"ee9669fd29b295dbded39b1010be5bf9d4100462bb611e2065eb744919197874","tests/common/mod.rs":"19479260f662649f9cdf001c4076614c1e943ae72bce702ef1eed1f48b3beefe","tests/minidump_writer.rs":"dbccfe2a720cee6db6cc16457982c82c1e1ffd0d0d025eaab183befe9925294e","tests/ptrace_dumper.rs":"5d3438f82a9966cd316ec204b977cc674b9dec2f482ecf9f75f92184d39ed58c"},"package":null} \ No newline at end of file diff --git a/third_party/rust/minidump_writer_linux/Cargo.toml b/third_party/rust/minidump_writer_linux/Cargo.toml new file mode 100644 index 000000000000..ff1a219580ca --- /dev/null +++ b/third_party/rust/minidump_writer_linux/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "minidump_writer_linux" +version = "0.1.0" +authors = ["Martin Sirringhaus"] +edition = "2018" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tempfile = "3.1.0" +nix = "0.13.1" +libc = "0.2.74" +memoffset = "0.5.1" +byteorder = "1.3.2" +memmap = "0.7.0" +goblin = "0.1.2" + +[dev-dependencies] +minidump = {git = "https://github.com/luser/rust-minidump" } +minidump-common = {git = "https://github.com/luser/rust-minidump" } diff --git a/third_party/rust/minidump_writer_linux/LICENSE b/third_party/rust/minidump_writer_linux/LICENSE new file mode 100644 index 000000000000..6f9ec1352ea4 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/rust/minidump_writer_linux/src/app_memory.rs b/third_party/rust/minidump_writer_linux/src/app_memory.rs new file mode 100644 index 000000000000..dfffdfa343e3 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/app_memory.rs @@ -0,0 +1,9 @@ +// These entries store a list of memory regions that the client wants included +// in the minidump. +#[derive(Debug, Default, PartialEq)] +pub struct AppMemory { + pub ptr: usize, + pub length: usize, +} + +pub type AppMemoryList = Vec; diff --git a/third_party/rust/minidump_writer_linux/src/auxv_reader.rs b/third_party/rust/minidump_writer_linux/src/auxv_reader.rs new file mode 100644 index 000000000000..59df624882ab --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/auxv_reader.rs @@ -0,0 +1,107 @@ +// This file is heavily based on https://bitbucket.org/marshallpierce/rust-auxv +// Thus I'm keeping the original MIT-license copyright here: +// Copyright 2017 Marshall Pierce +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be in…substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +use crate::Result; +use byteorder::{NativeEndian, ReadBytesExt}; +use std::fs::File; +use std::io::{BufReader, Read}; + +/// The type used in auxv keys and values. +#[cfg(target_pointer_width = "32")] +pub type AuxvType = u32; +/// The type used in auxv keys and values. +#[cfg(target_pointer_width = "64")] +pub type AuxvType = u64; + +/// An auxv key-value pair. +#[derive(Debug, PartialEq)] +pub struct AuxvPair { + pub key: AuxvType, + pub value: AuxvType, +} + +/// An iterator across auxv pairs froom procfs. +pub struct ProcfsAuxvIter { + pair_size: usize, + buf: Vec, + input: BufReader, + keep_going: bool, +} + +impl ProcfsAuxvIter { + pub fn new(input: BufReader) -> Self { + let pair_size = 2 * std::mem::size_of::(); + let buf: Vec = Vec::with_capacity(pair_size); + + ProcfsAuxvIter { + pair_size, + buf, + input, + keep_going: true, + } + } +} + +impl Iterator for ProcfsAuxvIter { + type Item = Result; + fn next(&mut self) -> Option { + if !self.keep_going { + return None; + } + // assume something will fail + self.keep_going = false; + + self.buf.clear(); + // fill vec so we can slice into it + for _ in 0..self.pair_size { + self.buf.push(0); + } + + let mut read_bytes: usize = 0; + while read_bytes < self.pair_size { + // read exactly buf's len of bytes. + match self.input.read(&mut self.buf[read_bytes..]) { + Ok(n) => { + if n == 0 { + // should not hit EOF before AT_NULL + return Some(Err("ProcfsAuxvError::InvalidFormat".into())); + } + + read_bytes += n; + } + Err(x) => return Some(Err(Box::new(x))), + } + } + + let mut reader = &self.buf[..]; + let aux_key = match read_long(&mut reader) { + Ok(x) => x, + Err(x) => return Some(Err(Box::new(x))), + }; + let aux_val = match read_long(&mut reader) { + Ok(x) => x, + Err(x) => return Some(Err(Box::new(x))), + }; + + if aux_key == libc::AT_NULL { + return None; + } + + self.keep_going = true; + Some(Ok(AuxvPair { + key: aux_key, + value: aux_val, + })) + } +} + +fn read_long(reader: &mut dyn Read) -> std::io::Result { + match std::mem::size_of::() { + 4 => reader.read_u32::().map(|u| u as AuxvType), + 8 => reader.read_u64::().map(|u| u as AuxvType), + x => panic!("Unexpected type width: {}", x), + } +} diff --git a/third_party/rust/minidump_writer_linux/src/bin/test.rs b/third_party/rust/minidump_writer_linux/src/bin/test.rs new file mode 100644 index 000000000000..8989a2826a68 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/bin/test.rs @@ -0,0 +1,264 @@ +// This binary shouldn't be under /src, but under /tests, but that is +// currently not possible (https://github.com/rust-lang/cargo/issues/4356) +use minidump_writer_linux::linux_ptrace_dumper::{LinuxPtraceDumper, AT_SYSINFO_EHDR}; +use minidump_writer_linux::{linux_ptrace_dumper, Result, LINUX_GATE_LIBRARY_NAME}; +use nix::sys::mman::{mmap, MapFlags, ProtFlags}; +use nix::unistd::getppid; +use std::convert::TryInto; +use std::env; + +macro_rules! test { + ($x:expr, $errmsg:expr) => { + if $x { + Ok(()) + } else { + Err($errmsg) + } + }; +} + +fn test_setup() -> Result<()> { + let ppid = getppid(); + linux_ptrace_dumper::LinuxPtraceDumper::new(ppid.as_raw())?; + Ok(()) +} + +fn test_thread_list() -> Result<()> { + let ppid = getppid(); + let dumper = linux_ptrace_dumper::LinuxPtraceDumper::new(ppid.as_raw())?; + test!(dumper.threads.len() >= 1, "No threads")?; + test!( + dumper + .threads + .iter() + .filter(|&x| x == &ppid.as_raw()) + .count() + == 1, + "Thread found multiple times" + )?; + Ok(()) +} + +fn test_copy_from_process(stack_var: usize, heap_var: usize) -> Result<()> { + let ppid = getppid().as_raw(); + let mut dumper = linux_ptrace_dumper::LinuxPtraceDumper::new(ppid)?; + dumper.suspend_threads()?; + let stack_res = LinuxPtraceDumper::copy_from_process(ppid, stack_var as *mut libc::c_void, 1)?; + + let expected_stack: libc::c_long = 0x11223344; + test!( + stack_res == expected_stack.to_ne_bytes(), + "stack var not correct" + )?; + + let heap_res = LinuxPtraceDumper::copy_from_process(ppid, heap_var as *mut libc::c_void, 1)?; + let expected_heap: libc::c_long = 0x55667788; + test!( + heap_res == expected_heap.to_ne_bytes(), + "heap var not correct" + )?; + dumper.resume_threads()?; + Ok(()) +} + +fn test_find_mappings(addr1: usize, addr2: usize) -> Result<()> { + let ppid = getppid(); + let dumper = linux_ptrace_dumper::LinuxPtraceDumper::new(ppid.as_raw())?; + dumper + .find_mapping(addr1) + .ok_or("No mapping for addr1 found")?; + + dumper + .find_mapping(addr2) + .ok_or("No mapping for addr2 found")?; + + test!(dumper.find_mapping(0).is_none(), "NULL found")?; + Ok(()) +} + +fn test_file_id() -> Result<()> { + let ppid = getppid().as_raw(); + let exe_link = format!("/proc/{}/exe", ppid); + let exe_name = std::fs::read_link(&exe_link)?.into_os_string(); + let mut dumper = linux_ptrace_dumper::LinuxPtraceDumper::new(getppid().as_raw())?; + let mut found_exe = None; + for (idx, mapping) in dumper.mappings.iter().enumerate() { + if mapping.name.as_ref().map(|x| x.into()).as_ref() == Some(&exe_name) { + found_exe = Some(idx); + break; + } + } + let idx = found_exe.unwrap(); + let id = dumper.elf_identifier_for_mapping_index(idx)?; + assert!(!id.is_empty()); + assert!(id.iter().any(|&x| x > 0)); + Ok(()) +} + +fn test_merged_mappings(path: String, mapped_mem: usize, mem_size: usize) -> Result<()> { + // Now check that LinuxPtraceDumper interpreted the mappings properly. + let dumper = linux_ptrace_dumper::LinuxPtraceDumper::new(getppid().as_raw())?; + let mut mapping_count = 0; + for map in &dumper.mappings { + if map.name == Some(path.clone()) { + mapping_count += 1; + // This mapping should encompass the entire original mapped + // range. + assert_eq!(map.start_address, mapped_mem); + assert_eq!(map.size, mem_size); + assert_eq!(0, map.offset); + } + } + assert_eq!(1, mapping_count); + Ok(()) +} + +fn test_linux_gate_mapping_id() -> Result<()> { + let ppid = getppid().as_raw(); + let mut dumper = linux_ptrace_dumper::LinuxPtraceDumper::new(ppid)?; + let mut found_linux_gate = false; + for mut mapping in dumper.mappings.clone() { + if mapping.name.as_deref() == Some(LINUX_GATE_LIBRARY_NAME) { + found_linux_gate = true; + dumper.suspend_threads()?; + let id = LinuxPtraceDumper::elf_identifier_for_mapping(&mut mapping, ppid)?; + test!(!id.is_empty(), "id-vec is empty")?; + test!(id.iter().any(|&x| x > 0), "all id elements are 0")?; + dumper.resume_threads()?; + break; + } + } + test!(found_linux_gate == true, "found no linux_gate")?; + Ok(()) +} + +fn test_mappings_include_linux_gate() -> Result<()> { + let ppid = getppid().as_raw(); + let dumper = linux_ptrace_dumper::LinuxPtraceDumper::new(ppid)?; + let linux_gate_loc = dumper.auxv[&AT_SYSINFO_EHDR]; + test!(linux_gate_loc != 0, "linux_gate_loc == 0")?; + let mut found_linux_gate = false; + for mapping in &dumper.mappings { + if mapping.name.as_deref() == Some(LINUX_GATE_LIBRARY_NAME) { + found_linux_gate = true; + test!( + linux_gate_loc == mapping.start_address.try_into()?, + "linux_gate_loc != start_address" + )?; + + // This doesn't work here, as we do not test via "fork()", so the addresses are different + // let ll = mapping.start_address as *const u8; + // for idx in 0..header::SELFMAG { + // let mag = unsafe { std::ptr::read(ll.offset(idx as isize)) == header::ELFMAG[idx] }; + // test!( + // mag, + // format!("ll: {} != ELFMAG: {} at {}", mag, header::ELFMAG[idx], idx) + // )?; + // } + break; + } + } + test!(found_linux_gate == true, "found no linux_gate")?; + Ok(()) +} + +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_mmap_wait() -> Result<()> { + let page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE).unwrap(); + let memory_size = page_size.unwrap() as usize; + // Get some memory to be mapped by the child-process + let mapped_mem = unsafe { + mmap( + std::ptr::null_mut(), + memory_size, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_PRIVATE | MapFlags::MAP_ANON, + -1, + 0, + ) + .unwrap() + }; + + println!("{} {}", mapped_mem as usize, memory_size); + loop { + std::thread::park(); + } +} + +fn spawn_alloc_wait() -> Result<()> { + let page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE).unwrap(); + let memory_size = page_size.unwrap() as usize; + + let mut values = Vec::::with_capacity(memory_size); + for idx in 0..memory_size { + values.push((idx % 255) as u8); + } + + println!("{:p} {}", values.as_ptr(), memory_size); + loop { + std::thread::park(); + } +} + +fn main() -> Result<()> { + let args: Vec<_> = env::args().skip(1).collect(); + match args.len() { + 1 => match args[0].as_ref() { + "file_id" => test_file_id(), + "setup" => test_setup(), + "thread_list" => test_thread_list(), + "mappings_include_linux_gate" => test_mappings_include_linux_gate(), + "linux_gate_mapping_id" => test_linux_gate_mapping_id(), + "spawn_mmap_wait" => spawn_mmap_wait(), + "spawn_alloc_wait" => spawn_alloc_wait(), + _ => Err("Len 1: Unknown test option".into()), + }, + 2 => { + if args[0] == "spawn_and_wait" { + let num_of_threads: usize = args[1].parse().unwrap(); + spawn_and_wait(num_of_threads) + } else { + Err(format!("Len 2: Unknown test option: {}", args[0]).into()) + } + } + 3 => { + if args[0] == "find_mappings" { + let addr1: usize = args[1].parse().unwrap(); + let addr2: usize = args[2].parse().unwrap(); + test_find_mappings(addr1, addr2) + } else if args[0] == "copy_from_process" { + let stack_var: usize = args[1].parse().unwrap(); + let heap_var: usize = args[2].parse().unwrap(); + test_copy_from_process(stack_var, heap_var) + } else { + Err(format!("Len 3: Unknown test option: {}", args[0]).into()) + } + } + 4 => { + if args[0] == "merged_mappings" { + let path = &args[1]; + let mapped_mem: usize = args[2].parse().unwrap(); + let mem_size: usize = args[3].parse().unwrap(); + test_merged_mappings(path.to_string(), mapped_mem, mem_size) + } else { + Err(format!("Len 4: Unknown test option: {}", args[0]).into()) + } + } + _ => Err("Unknown test option".into()), + } +} diff --git a/third_party/rust/minidump_writer_linux/src/cpu_set.rs b/third_party/rust/minidump_writer_linux/src/cpu_set.rs new file mode 100644 index 000000000000..a15dee24d264 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/cpu_set.rs @@ -0,0 +1,250 @@ +use crate::Result; +use core::mem::size_of; +use std::fs::File; +use std::io::Read; + +type MaskWordType = u32; +const MAX_CPUS: usize = 1024; +const MASK_WORD_BITS: usize = 8 * size_of::(); +const MASK_WORD_COUNT: usize = (MAX_CPUS + MASK_WORD_BITS - 1) / MASK_WORD_BITS; + +pub struct CpuSet { + // The maximum number of supported CPUs. + mask: [MaskWordType; MASK_WORD_COUNT], +} + +impl CpuSet { + pub fn new() -> Self { + CpuSet { + mask: [0; MASK_WORD_COUNT], + } + } + + pub fn get_count(&self) -> usize { + let mut result = 0 as usize; + for idx in 0..MASK_WORD_COUNT { + result += self.mask[idx].count_ones() as usize; + } + return result; + } + + pub fn set_bit(&mut self, idx: usize) { + println!("Setting2 {}", idx); + if idx < MAX_CPUS { + self.mask[idx / MASK_WORD_BITS] |= 1 << (idx % MASK_WORD_BITS); + } + } + + pub fn parse_sys_file(&mut self, file: &mut File) -> Result<()> { + let mut content = String::new(); + file.read_to_string(&mut content)?; + // Expected format: comma-separated list of items, where each + // item can be a decimal integer, or two decimal integers separated + // by a dash. + // E.g.: + // 0 + // 0,1,2,3 + // 0-3 + // 1,10-23 + for items in content.split(',') { + let items = items.trim(); + if items.is_empty() { + continue; + } + // TODO: ranges + println!("Setting {}", items); + let cores: std::result::Result, _> = + items.split("-").map(|x| x.parse::()).collect(); + let cores = cores?; + match cores.as_slice() { + [x] => self.set_bit(*x), + [x, y] => { + for core in *x..=*y { + self.set_bit(core) + } + } + _ => { + return Err(format!("Unparsable cores: {:?}", cores).into()); + } + } + } + Ok(()) + } + + pub fn intersect_with(&mut self, other: &Self) { + for idx in 0..MASK_WORD_COUNT { + self.mask[idx] &= other.mask[idx]; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + // In tests we can have access to std + extern crate std; + use std::io::Write; + + fn new_file(content: &str) -> File { + let mut file = tempfile::Builder::new() + .prefix("cpu_sets") + .tempfile() + .unwrap(); + write!(file, "{}", content).unwrap(); + std::fs::File::open(file).unwrap() + } + + #[test] + fn test_empty_count() { + let set = CpuSet::new(); + assert_eq!(set.get_count(), 0); + } + + #[test] + fn test_one_cpu() { + let mut file = new_file("10"); + let mut set = CpuSet::new(); + let res = set.parse_sys_file(&mut file); + assert!(res.is_ok()); + assert_eq!(set.get_count(), 1); + } + + #[test] + fn test_one_cpu_newline() { + let mut file = new_file("10\n"); + let mut set = CpuSet::new(); + let res = set.parse_sys_file(&mut file); + assert!(res.is_ok()); + assert_eq!(set.get_count(), 1); + } + + #[test] + fn test_two_cpus() { + let mut file = new_file("1,10\n"); + let mut set = CpuSet::new(); + let res = set.parse_sys_file(&mut file); + assert!(res.is_ok()); + assert_eq!(set.get_count(), 2); + // TODO: Actually check the cpus?! + } + + #[test] + fn test_two_cpus_with_range() { + let mut file = new_file("1-2\n"); + let mut set = CpuSet::new(); + let res = set.parse_sys_file(&mut file); + assert!(res.is_ok()); + assert_eq!(set.get_count(), 2); + // TODO: Actually check the cpus?! + } + + #[test] + fn test_ten_cpus_with_range() { + let mut file = new_file("9-18\n"); + let mut set = CpuSet::new(); + let res = set.parse_sys_file(&mut file); + assert!(res.is_ok()); + assert_eq!(set.get_count(), 10); + // TODO: Actually check the cpus?! + } + + #[test] + fn test_multiple_items() { + let mut file = new_file("0, 2-4, 128\n"); + let mut set = CpuSet::new(); + let res = set.parse_sys_file(&mut file); + assert!(res.is_ok()); + assert_eq!(set.get_count(), 5); + // TODO: Actually check the cpus?! + } + + #[test] + fn test_intersects_with() { + let mut file1 = new_file("9-19\n"); + let mut set1 = CpuSet::new(); + let res = set1.parse_sys_file(&mut file1); + assert!(res.is_ok()); + assert_eq!(set1.get_count(), 11); + // TODO: Actually check the cpus?! + + let mut file2 = new_file("16-24\n"); + let mut set2 = CpuSet::new(); + let res = set2.parse_sys_file(&mut file2); + assert!(res.is_ok()); + assert_eq!(set2.get_count(), 9); + set1.intersect_with(&set2); + assert_eq!(set1.get_count(), 4); + assert_eq!(set2.get_count(), 9); + } + + #[test] + fn test_bad_input() { + let mut file = new_file("abc\n"); + let mut set = CpuSet::new(); + let res = set.parse_sys_file(&mut file); + assert!(res.is_err()); + assert_eq!(set.get_count(), 0); + // TODO: Actually check the cpus?! + } + + #[test] + fn test_bad_input_range() { + let mut file = new_file("1-abc\n"); + let mut set = CpuSet::new(); + let res = set.parse_sys_file(&mut file); + assert!(res.is_err()); + assert_eq!(set.get_count(), 0); + // TODO: Actually check the cpus?! + } +} + +/* + +TEST(CpuSetTest, IntersectWith) { + ScopedTestFile file1("9-19"); + ASSERT_TRUE(file1.IsOk()); + CpuSet set1; + ASSERT_TRUE(set1.ParseSysFile(file1.GetFd())); + ASSERT_EQ(11, set1.GetCount()); + + ScopedTestFile file2("16-24"); + ASSERT_TRUE(file2.IsOk()); + CpuSet set2; + ASSERT_TRUE(set2.ParseSysFile(file2.GetFd())); + ASSERT_EQ(9, set2.GetCount()); + + set1.IntersectWith(set2); + ASSERT_EQ(4, set1.GetCount()); + ASSERT_EQ(9, set2.GetCount()); +} + +TEST(CpuSetTest, SelfIntersection) { + ScopedTestFile file1("9-19"); + ASSERT_TRUE(file1.IsOk()); + CpuSet set1; + ASSERT_TRUE(set1.ParseSysFile(file1.GetFd())); + ASSERT_EQ(11, set1.GetCount()); + + set1.IntersectWith(set1); + ASSERT_EQ(11, set1.GetCount()); +} + +TEST(CpuSetTest, EmptyIntersection) { + ScopedTestFile file1("0-19"); + ASSERT_TRUE(file1.IsOk()); + CpuSet set1; + ASSERT_TRUE(set1.ParseSysFile(file1.GetFd())); + ASSERT_EQ(20, set1.GetCount()); + + ScopedTestFile file2("20-39"); + ASSERT_TRUE(file2.IsOk()); + CpuSet set2; + ASSERT_TRUE(set2.ParseSysFile(file2.GetFd())); + ASSERT_EQ(20, set2.GetCount()); + + set1.IntersectWith(set2); + ASSERT_EQ(0, set1.GetCount()); + + ASSERT_EQ(20, set2.GetCount()); +} +*/ diff --git a/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_aarch64.rs b/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_aarch64.rs new file mode 100644 index 000000000000..d54a356b404e --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_aarch64.rs @@ -0,0 +1,12 @@ +use super::CrashContext; +use libc::greg_t; + +impl CrashContext { + pub fn get_instruction_pointer(&self) -> greg_t { + self.context.uc_mcontext.sp + } + + pub fn get_stack_pointer(&self) -> greg_t { + self.context.uc_mcontext.pc + } +} diff --git a/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_arm.rs b/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_arm.rs new file mode 100644 index 000000000000..98db45016a0f --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_arm.rs @@ -0,0 +1,12 @@ +use super::CrashContext; +use libc::greg_t; + +impl CrashContext { + pub fn get_instruction_pointer(&self) -> greg_t { + self.context.uc_mcontext.arm_pc + } + + pub fn get_stack_pointer(&self) -> greg_t { + self.context.uc_mcontext.arm_sp + } +} diff --git a/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_mips.rs b/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_mips.rs new file mode 100644 index 000000000000..9ff61b5de9c3 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_mips.rs @@ -0,0 +1,12 @@ +use super::CrashContext; +use libc::{greg_t, MD_CONTEXT_MIPS_REG_SP}; + +impl CrashContext { + pub fn get_instruction_pointer(&self) -> greg_t { + self.context.uc_mcontext.pc + } + + pub fn get_stack_pointer(&self) -> greg_t { + self.context.uc_mcontext.gregs[MD_CONTEXT_MIPS_REG_SP as usize] + } +} diff --git a/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_x86.rs b/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_x86.rs new file mode 100644 index 000000000000..8170c2ed4ad8 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_x86.rs @@ -0,0 +1,50 @@ +use super::CrashContext; +use crate::minidump_cpu::imp::*; +use crate::minidump_cpu::RawContextCPU; +use libc::{greg_t, REG_EIP, REG_ESP, REG_GS, REG_FS, REG_ES, REG_DS, + REG_EDI, REG_ESI, REG_EBX, REG_EDX, REG_ECX, REG_EAX, REG_EBP, REG_CS, + REG_EFL, REG_UESP, REG_SS}; +impl CrashContext { + pub fn get_instruction_pointer(&self) -> greg_t { + self.context.uc_mcontext.gregs[REG_EIP as usize] + } + + pub fn get_stack_pointer(&self) -> greg_t { + self.context.uc_mcontext.gregs[REG_ESP as usize] + } + + pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { + out.context_flags = MD_CONTEXT_X86_FULL | + MD_CONTEXT_X86_FLOATING_POINT; + + out.gs = self.context.uc_mcontext.gregs[REG_GS as usize] as u32; + out.fs = self.context.uc_mcontext.gregs[REG_FS as usize] as u32; + out.es = self.context.uc_mcontext.gregs[REG_ES as usize] as u32; + out.ds = self.context.uc_mcontext.gregs[REG_DS as usize] as u32; + + out.edi = self.context.uc_mcontext.gregs[REG_EDI as usize] as u32; + out.esi = self.context.uc_mcontext.gregs[REG_ESI as usize] as u32; + out.ebx = self.context.uc_mcontext.gregs[REG_EBX as usize] as u32; + out.edx = self.context.uc_mcontext.gregs[REG_EDX as usize] as u32; + out.ecx = self.context.uc_mcontext.gregs[REG_ECX as usize] as u32; + out.eax = self.context.uc_mcontext.gregs[REG_EAX as usize] as u32; + + out.ebp = self.context.uc_mcontext.gregs[REG_EBP as usize] as u32; + out.eip = self.context.uc_mcontext.gregs[REG_EIP as usize] as u32; + out.cs = self.context.uc_mcontext.gregs[REG_CS as usize] as u32; + out.eflags = self.context.uc_mcontext.gregs[REG_EFL as usize] as u32; + out.esp = self.context.uc_mcontext.gregs[REG_UESP as usize] as u32; + out.ss = self.context.uc_mcontext.gregs[REG_SS as usize] as u32; + + out.float_save.control_word = self.float_state.cw; + out.float_save.status_word = self.float_state.sw; + out.float_save.tag_word = self.float_state.tag; + out.float_save.error_offset = self.float_state.ipoff; + out.float_save.error_selector = self.float_state.cssel; + out.float_save.data_offset = self.float_state.dataoff; + out.float_save.data_selector = self.float_state.datasel; + + // 8 registers * 10 bytes per register. + // my_memcpy(out->float_save.register_area, fp->_st, 10 * 8); + } +} diff --git a/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_x86_64.rs b/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_x86_64.rs new file mode 100644 index 000000000000..94cb1efbd0f0 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/crash_context/crash_context_x86_64.rs @@ -0,0 +1,69 @@ +use super::CrashContext; +use crate::minidump_cpu::imp::*; +use crate::minidump_cpu::RawContextCPU; +use crate::thread_info::to_u128; +use libc::{ + greg_t, REG_CSGSFS, REG_EFL, REG_R10, REG_R11, REG_R12, REG_R13, REG_R14, REG_R15, REG_R8, + REG_R9, REG_RAX, REG_RBP, REG_RBX, REG_RCX, REG_RDI, REG_RDX, REG_RIP, REG_RSI, REG_RSP, +}; + +impl CrashContext { + pub fn get_instruction_pointer(&self) -> greg_t { + self.context.uc_mcontext.gregs[REG_RIP as usize] + } + + pub fn get_stack_pointer(&self) -> greg_t { + self.context.uc_mcontext.gregs[REG_RSP as usize] + } + + pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { + out.context_flags = MD_CONTEXT_AMD64_FULL; + out.cs = (self.context.uc_mcontext.gregs[REG_CSGSFS as usize] & 0xffff) as u16; + + out.fs = ((self.context.uc_mcontext.gregs[REG_CSGSFS as usize] >> 32) & 0xffff) as u16; + out.gs = ((self.context.uc_mcontext.gregs[REG_CSGSFS as usize] >> 16) & 0xffff) as u16; + + out.eflags = self.context.uc_mcontext.gregs[REG_EFL as usize] as u32; + + out.rax = self.context.uc_mcontext.gregs[REG_RAX as usize] as u64; + out.rcx = self.context.uc_mcontext.gregs[REG_RCX as usize] as u64; + out.rdx = self.context.uc_mcontext.gregs[REG_RDX as usize] as u64; + out.rbx = self.context.uc_mcontext.gregs[REG_RBX as usize] as u64; + + out.rsp = self.context.uc_mcontext.gregs[REG_RSP as usize] as u64; + out.rbp = self.context.uc_mcontext.gregs[REG_RBP as usize] as u64; + out.rsi = self.context.uc_mcontext.gregs[REG_RSI as usize] as u64; + out.rdi = self.context.uc_mcontext.gregs[REG_RDI as usize] as u64; + out.r8 = self.context.uc_mcontext.gregs[REG_R8 as usize] as u64; + out.r9 = self.context.uc_mcontext.gregs[REG_R9 as usize] as u64; + out.r10 = self.context.uc_mcontext.gregs[REG_R10 as usize] as u64; + out.r11 = self.context.uc_mcontext.gregs[REG_R11 as usize] as u64; + out.r12 = self.context.uc_mcontext.gregs[REG_R12 as usize] as u64; + out.r13 = self.context.uc_mcontext.gregs[REG_R13 as usize] as u64; + out.r14 = self.context.uc_mcontext.gregs[REG_R14 as usize] as u64; + out.r15 = self.context.uc_mcontext.gregs[REG_R15 as usize] as u64; + + out.rip = self.context.uc_mcontext.gregs[REG_RIP as usize] as u64; + + out.flt_save.control_word = self.float_state.cwd; + out.flt_save.status_word = self.float_state.swd; + out.flt_save.tag_word = self.float_state.ftw as u8; + out.flt_save.error_opcode = self.float_state.fop; + out.flt_save.error_offset = self.float_state.rip as u32; + out.flt_save.data_offset = self.float_state.rdp as u32; + out.flt_save.error_selector = 0; // We don't have this. + out.flt_save.data_selector = 0; // We don't have this. + out.flt_save.mx_csr = self.float_state.mxcsr; + out.flt_save.mx_csr_mask = self.float_state.mxcr_mask; + + let data = to_u128(&self.float_state.st_space); + for idx in 0..data.len() { + out.flt_save.float_registers[idx] = data[idx]; + } + + let data = to_u128(&self.float_state.xmm_space); + for idx in 0..data.len() { + out.flt_save.xmm_registers[idx] = data[idx]; + } + } +} diff --git a/third_party/rust/minidump_writer_linux/src/crash_context/mod.rs b/third_party/rust/minidump_writer_linux/src/crash_context/mod.rs new file mode 100644 index 000000000000..0549707b87d6 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/crash_context/mod.rs @@ -0,0 +1,49 @@ +use libc; + +// Minidump defines register structures which are different from the raw +// structures which we get from the kernel. These are platform specific +// functions to juggle the ucontext_t and user structures into minidump format. + +#[cfg(target_arch = "x86_64")] +#[path = "crash_context_x86_64.rs"] +pub mod imp; +#[cfg(target_arch = "x86")] +#[path = "crash_context_x86.rs"] +pub mod imp; +#[cfg(target_arch = "arm")] +#[path = "crash_context_arm.rs"] +pub mod imp; +#[cfg(target_arch = "aarch64")] +#[path = "crash_context_aarch64.rs"] +pub mod imp; +#[cfg(target_arch = "mips")] +#[path = "crash_context_mips.rs"] +pub mod imp; + +#[cfg(target_arch = "aarch64")] +pub type fpstate_t = libc::fpsimd_context; // Currently not part of libc! This will produce an error. +#[cfg(not(any( + target_arch = "aarch64", + target_arch = "mips", + target_arch = "arm-eabi" +)))] + +#[cfg(target_arch = "x86")] +#[allow(non_camel_case_types)] +pub type fpstate_t = libc::_libc_fpstate; +#[cfg(target_arch = "x86_64")] +#[allow(non_camel_case_types)] +pub type fpstate_t = libc::user_fpregs_struct; + +#[repr(C)] +#[derive(Clone)] +pub struct CrashContext { + pub siginfo: libc::siginfo_t, + pub tid: libc::pid_t, // the crashing thread. + pub context: libc::ucontext_t, + // #ifdef this out because FP state is not part of user ABI for Linux ARM. + // In case of MIPS Linux FP state is already part of ucontext_t so + // 'float_state' is not required. + #[cfg(not(any(target_arch = "mips", target_arch = "arm-eabi")))] + pub float_state: fpstate_t, +} diff --git a/third_party/rust/minidump_writer_linux/src/dso_debug.rs b/third_party/rust/minidump_writer_linux/src/dso_debug.rs new file mode 100644 index 000000000000..effd7cc4d5b1 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/dso_debug.rs @@ -0,0 +1,245 @@ +use crate::auxv_reader::AuxvType; +use crate::linux_ptrace_dumper::LinuxPtraceDumper; +use crate::minidump_format::*; +use crate::sections::{write_string_to_location, MemoryArrayWriter, MemoryWriter}; +use crate::Result; +use libc; +use std::collections::HashMap; +use std::io::Cursor; + +#[cfg(target_pointer_width = "64")] +type ElfPhdr = libc::Elf64_Phdr; +#[cfg(target_pointer_width = "32")] +type ElfPhdr = libc::Elf32_Phdr; + +#[cfg(target_pointer_width = "64")] +type ElfAddr = libc::Elf64_Addr; +#[cfg(target_pointer_width = "32")] +type ElfAddr = libc::Elf32_Addr; + +// COPY from +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct LinkMap { + /* These first few members are part of the protocol with the debugger. + This is the same format used in SVR4. */ + l_addr: ElfAddr, /* Difference between the address in the ELF + file and the addresses in memory. */ + l_name: usize, /* Absolute file name object was found in. WAS: `char*` */ + l_ld: usize, /* Dynamic section of the shared object. WAS: `ElfW(Dyn) *` */ + l_next: usize, /* Chain of loaded objects. WAS: `struct link_map *` */ + l_prev: usize, /* Chain of loaded objects. WAS: `struct link_map *` */ +} + +// COPY from +#[derive(Debug, Clone)] +#[allow(non_camel_case_types, unused)] +#[repr(C)] +enum RState { + /* This state value describes the mapping change taking place when + the `r_brk' address is called. */ + RT_CONSISTENT, /* Mapping change is complete. */ + RT_ADD, /* Beginning to add a new object. */ + RT_DELETE, /* Beginning to remove an object mapping. */ +} +impl Default for RState { + fn default() -> Self { + RState::RT_CONSISTENT // RStates are not used anyway + } +} +// COPY from +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct RDebug { + r_version: libc::c_int, /* Version number for this protocol. */ + r_map: usize, /* Head of the chain of loaded objects. WAS: `struct link_map *` */ + + /* This is the address of a function internal to the run-time linker, + that will always be called when the linker begins to map in a + library or unmap it, and again when the mapping change is complete. + The debugger can set a breakpoint at this address if it wants to + notice shared object mapping changes. */ + r_brk: ElfAddr, + r_state: RState, + r_ldbase: ElfAddr, /* Base address the linker is loaded at. */ +} + +pub fn write_dso_debug_stream( + buffer: &mut Cursor>, + blamed_thread: i32, + auxv: &HashMap, +) -> Result { + let phnum_max = *auxv.get(&libc::AT_PHNUM).ok_or("Could not find AT_PHNUM")? as usize; + let phdr = *auxv.get(&libc::AT_PHDR).ok_or("Could not find AT_PHDR")? as usize; + + let phdr_size = std::mem::size_of::(); + let ph = LinuxPtraceDumper::copy_from_process( + blamed_thread, + phdr as *mut libc::c_void, + (phdr_size * phnum_max) as isize, + )?; + let program_headers; + #[cfg(target_pointer_width = "64")] + { + program_headers = goblin::elf::program_header::program_header64::ProgramHeader::from_bytes( + &ph, phnum_max, + ); + } + #[cfg(target_pointer_width = "32")] + { + program_headers = goblin::elf::program_header::program_header32::ProgramHeader::from_bytes( + &ph, phnum_max, + ); + }; + + // Assume the program base is at the beginning of the same page as the PHDR + let mut base = phdr & !0xfff; + let mut dyn_addr = 0 as ElfAddr; + // Search for the program PT_DYNAMIC segment + for ph in program_headers { + // Adjust base address with the virtual address of the PT_LOAD segment + // corresponding to offset 0 + if ph.p_type == goblin::elf::program_header::PT_LOAD && ph.p_offset == 0 { + base -= ph.p_vaddr as usize; + } + if ph.p_type == goblin::elf::program_header::PT_DYNAMIC { + dyn_addr = ph.p_vaddr; + } + } + + if dyn_addr == 0 { + return Err("Could not find dyn_addr".into()); + } + + dyn_addr += base as ElfAddr; + + let dyn_size = std::mem::size_of::(); + let mut r_debug = 0usize; + let mut dynamic_length = 0usize; + + // The dynamic linker makes information available that helps gdb find all + // DSOs loaded into the program. If this information is indeed available, + // dump it to a MD_LINUX_DSO_DEBUG stream. + loop { + let dyn_data = LinuxPtraceDumper::copy_from_process( + blamed_thread, + (dyn_addr as usize + dynamic_length) as *mut libc::c_void, + dyn_size as isize, + )?; + dynamic_length += dyn_size; + + // goblin::elf::Dyn doesn't have padding bytes + let (head, body, _tail) = unsafe { dyn_data.align_to::() }; + assert!(head.is_empty(), "Data was not aligned"); + let dyn_struct = &body[0]; + + // #ifdef __mips__ + // const int32_t debug_tag = DT_MIPS_RLD_MAP; + // #else + // const int32_t debug_tag = DT_DEBUG; + // #endif + let debug_tag = goblin::elf::dynamic::DT_DEBUG; + if dyn_struct.d_tag == debug_tag { + r_debug = dyn_struct.d_val as usize; + } else if dyn_struct.d_tag == goblin::elf::dynamic::DT_NULL { + break; + } + } + + // The "r_map" field of that r_debug struct contains a linked list of all + // loaded DSOs. + // Our list of DSOs potentially is different from the ones in the crashing + // process. So, we have to be careful to never dereference pointers + // directly. Instead, we use CopyFromProcess() everywhere. + // See for a more detailed discussion of the how the dynamic + // loader communicates with debuggers. + + let debug_entry_data = LinuxPtraceDumper::copy_from_process( + blamed_thread, + r_debug as *mut libc::c_void, + std::mem::size_of::() as isize, + )?; + + // goblin::elf::Dyn doesn't have padding bytes + let (head, body, _tail) = unsafe { debug_entry_data.align_to::() }; + assert!(head.is_empty(), "Data was not aligned"); + let debug_entry = &body[0]; + + // Count the number of loaded DSOs + let mut dso_vec = Vec::new(); + let mut curr_map = debug_entry.r_map; + while curr_map != 0 { + let link_map_data = LinuxPtraceDumper::copy_from_process( + blamed_thread, + curr_map as *mut libc::c_void, + std::mem::size_of::() as isize, + )?; + + // LinkMap is repr(C) and doesn't have padding bytes, so this should be safe + let (head, body, _tail) = unsafe { link_map_data.align_to::() }; + assert!(head.is_empty(), "Data was not aligned"); + let map = &body[0]; + + curr_map = map.l_next; + dso_vec.push(map.clone()); + } + + let mut linkmap_rva = u32::MAX; + if dso_vec.len() > 0 { + // If we have at least one DSO, create an array of MDRawLinkMap + // entries in the minidump file. + let mut linkmap = MemoryArrayWriter::::alloc_array(buffer, dso_vec.len())?; + linkmap_rva = linkmap.location().rva; + + // Iterate over DSOs and write their information to mini dump + for (idx, map) in dso_vec.iter().enumerate() { + let mut filename = String::new(); + if map.l_name > 0 { + let filename_data = LinuxPtraceDumper::copy_from_process( + blamed_thread, + map.l_name as *mut libc::c_void, + 256, + )?; + + // C - string is NULL-terminated + if let Some(name) = filename_data.splitn(2, |x| *x == b'\0').next() { + filename = String::from_utf8(name.to_vec())?; + } + } + let location = write_string_to_location(buffer, &filename)?; + let entry = MDRawLinkMap { + addr: map.l_addr, + name: location.rva, + ld: map.l_ld as ElfAddr, + }; + + linkmap.set_value_at(buffer, entry, idx)?; + } + } + + // Write MD_LINUX_DSO_DEBUG record + let debug = MDRawDebug { + version: debug_entry.r_version as u32, + map: linkmap_rva, + dso_count: dso_vec.len() as u32, + brk: debug_entry.r_brk, + ldbase: debug_entry.r_ldbase, + dynamic: dyn_addr, + }; + let debug_loc = MemoryWriter::::alloc_with_val(buffer, debug)?; + + let mut dirent = MDRawDirectory { + stream_type: MDStreamType::LinuxDsoDebug as u32, + location: debug_loc.location(), + }; + + dirent.location.data_size += dynamic_length as u32; + let dso_debug_data = LinuxPtraceDumper::copy_from_process( + blamed_thread, + dyn_addr as *mut libc::c_void, + dynamic_length as isize, + )?; + MemoryArrayWriter::::alloc_from_array(buffer, &dso_debug_data)?; + + Ok(dirent) +} diff --git a/third_party/rust/minidump_writer_linux/src/dumper_cpu_info/cpu_info_arm.rs b/third_party/rust/minidump_writer_linux/src/dumper_cpu_info/cpu_info_arm.rs new file mode 100644 index 000000000000..8442f171f5b4 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/dumper_cpu_info/cpu_info_arm.rs @@ -0,0 +1,207 @@ +// bool WriteCPUInformation(MDRawSystemInfo* sys_info) { +// // The CPUID value is broken up in several entries in /proc/cpuinfo. +// // This table is used to rebuild it from the entries. +// const struct CpuIdEntry { +// const char* field; +// char format; +// char bit_lshift; +// char bit_length; +// } cpu_id_entries[] = { +// { "CPU implementer", 'x', 24, 8 }, +// { "CPU variant", 'x', 20, 4 }, +// { "CPU part", 'x', 4, 12 }, +// { "CPU revision", 'd', 0, 4 }, +// }; + +// // The ELF hwcaps are listed in the "Features" entry as textual tags. +// // This table is used to rebuild them. +// const struct CpuFeaturesEntry { +// const char* tag; +// uint32_t hwcaps; +// } cpu_features_entries[] = { +// #if defined(__arm__) +// { "swp", MD_CPU_ARM_ELF_HWCAP_SWP }, +// { "half", MD_CPU_ARM_ELF_HWCAP_HALF }, +// { "thumb", MD_CPU_ARM_ELF_HWCAP_THUMB }, +// { "26bit", MD_CPU_ARM_ELF_HWCAP_26BIT }, +// { "fastmult", MD_CPU_ARM_ELF_HWCAP_FAST_MULT }, +// { "fpa", MD_CPU_ARM_ELF_HWCAP_FPA }, +// { "vfp", MD_CPU_ARM_ELF_HWCAP_VFP }, +// { "edsp", MD_CPU_ARM_ELF_HWCAP_EDSP }, +// { "java", MD_CPU_ARM_ELF_HWCAP_JAVA }, +// { "iwmmxt", MD_CPU_ARM_ELF_HWCAP_IWMMXT }, +// { "crunch", MD_CPU_ARM_ELF_HWCAP_CRUNCH }, +// { "thumbee", MD_CPU_ARM_ELF_HWCAP_THUMBEE }, +// { "neon", MD_CPU_ARM_ELF_HWCAP_NEON }, +// { "vfpv3", MD_CPU_ARM_ELF_HWCAP_VFPv3 }, +// { "vfpv3d16", MD_CPU_ARM_ELF_HWCAP_VFPv3D16 }, +// { "tls", MD_CPU_ARM_ELF_HWCAP_TLS }, +// { "vfpv4", MD_CPU_ARM_ELF_HWCAP_VFPv4 }, +// { "idiva", MD_CPU_ARM_ELF_HWCAP_IDIVA }, +// { "idivt", MD_CPU_ARM_ELF_HWCAP_IDIVT }, +// { "idiv", MD_CPU_ARM_ELF_HWCAP_IDIVA | MD_CPU_ARM_ELF_HWCAP_IDIVT }, +// #elif defined(__aarch64__) +// // No hwcaps on aarch64. +// #endif +// }; + +// // processor_architecture should always be set, do this first +// sys_info->processor_architecture = +// #if defined(__aarch64__) +// MD_CPU_ARCHITECTURE_ARM64_OLD; +// #else +// MD_CPU_ARCHITECTURE_ARM; +// #endif + +// // /proc/cpuinfo is not readable under various sandboxed environments +// // (e.g. Android services with the android:isolatedProcess attribute) +// // prepare for this by setting default values now, which will be +// // returned when this happens. +// // +// // Note: Bogus values are used to distinguish between failures (to +// // read /sys and /proc files) and really badly configured kernels. +// sys_info->number_of_processors = 0; +// sys_info->processor_level = 1U; // There is no ARMv1 +// sys_info->processor_revision = 42; +// sys_info->cpu.arm_cpu_info.cpuid = 0; +// sys_info->cpu.arm_cpu_info.elf_hwcaps = 0; + +// // Counting the number of CPUs involves parsing two sysfs files, +// // because the content of /proc/cpuinfo will only mirror the number +// // of 'online' cores, and thus will vary with time. +// // See http://www.kernel.org/doc/Documentation/cputopology.txt +// { +// CpuSet cpus_present; +// CpuSet cpus_possible; + +// int fd = sys_open("/sys/devices/system/cpu/present", O_RDONLY, 0); +// if (fd >= 0) { +// cpus_present.ParseSysFile(fd); +// sys_close(fd); + +// fd = sys_open("/sys/devices/system/cpu/possible", O_RDONLY, 0); +// if (fd >= 0) { +// cpus_possible.ParseSysFile(fd); +// sys_close(fd); + +// cpus_present.IntersectWith(cpus_possible); +// int cpu_count = cpus_present.GetCount(); +// if (cpu_count > 255) +// cpu_count = 255; +// sys_info->number_of_processors = static_cast(cpu_count); +// } +// } +// } + +// // Parse /proc/cpuinfo to reconstruct the CPUID value, as well +// // as the ELF hwcaps field. For the latter, it would be easier to +// // read /proc/self/auxv but unfortunately, this file is not always +// // readable from regular Android applications on later versions +// // (>= 4.1) of the Android platform. +// const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0); +// if (fd < 0) { +// // Do not return false here to allow the minidump generation +// // to happen properly. +// return true; +// } + +// { +// PageAllocator allocator; +// ProcCpuInfoReader* const reader = +// new(allocator) ProcCpuInfoReader(fd); +// const char* field; +// while (reader->GetNextField(&field)) { +// for (const CpuIdEntry& entry : cpu_id_entries) { +// if (my_strcmp(entry.field, field) != 0) +// continue; +// uintptr_t result = 0; +// const char* value = reader->GetValue(); +// const char* p = value; +// if (value[0] == '0' && value[1] == 'x') { +// p = my_read_hex_ptr(&result, value+2); +// } else if (entry.format == 'x') { +// p = my_read_hex_ptr(&result, value); +// } else { +// p = my_read_decimal_ptr(&result, value); +// } +// if (p == value) +// continue; + +// result &= (1U << entry.bit_length)-1; +// result <<= entry.bit_lshift; +// sys_info->cpu.arm_cpu_info.cpuid |= +// static_cast(result); +// } +// #if defined(__arm__) +// // Get the architecture version from the "Processor" field. +// // Note that it is also available in the "CPU architecture" field, +// // however, some existing kernels are misconfigured and will report +// // invalid values here (e.g. 6, while the CPU is ARMv7-A based). +// // The "Processor" field doesn't have this issue. +// if (!my_strcmp(field, "Processor")) { +// size_t value_len; +// const char* value = reader->GetValueAndLen(&value_len); +// // Expected format: (v) +// // Where is some text like "ARMv7 Processor rev 2" +// // and is a decimal corresponding to the ARM +// // architecture number. is either 'l' or 'b' +// // and corresponds to the endianess, it is ignored here. +// while (value_len > 0 && my_isspace(value[value_len-1])) +// value_len--; + +// size_t nn = value_len; +// while (nn > 0 && value[nn-1] != '(') +// nn--; +// if (nn > 0 && value[nn] == 'v') { +// uintptr_t arch_level = 5; +// my_read_decimal_ptr(&arch_level, value + nn + 1); +// sys_info->processor_level = static_cast(arch_level); +// } +// } +// #elif defined(__aarch64__) +// // The aarch64 architecture does not provide the architecture level +// // in the Processor field, so we instead check the "CPU architecture" +// // field. +// if (!my_strcmp(field, "CPU architecture")) { +// uintptr_t arch_level = 0; +// const char* value = reader->GetValue(); +// const char* p = value; +// p = my_read_decimal_ptr(&arch_level, value); +// if (p == value) +// continue; +// sys_info->processor_level = static_cast(arch_level); +// } +// #endif +// // Rebuild the ELF hwcaps from the 'Features' field. +// if (!my_strcmp(field, "Features")) { +// size_t value_len; +// const char* value = reader->GetValueAndLen(&value_len); + +// // Parse each space-separated tag. +// while (value_len > 0) { +// const char* tag = value; +// size_t tag_len = value_len; +// const char* p = my_strchr(tag, ' '); +// if (p) { +// tag_len = static_cast(p - tag); +// value += tag_len + 1; +// value_len -= tag_len + 1; +// } else { +// tag_len = strlen(tag); +// value_len = 0; +// } +// for (const CpuFeaturesEntry& entry : cpu_features_entries) { +// if (tag_len == strlen(entry.tag) && +// !memcmp(tag, entry.tag, tag_len)) { +// sys_info->cpu.arm_cpu_info.elf_hwcaps |= entry.hwcaps; +// break; +// } +// } +// } +// } +// } +// sys_close(fd); +// } + +// return true; +// } diff --git a/third_party/rust/minidump_writer_linux/src/dumper_cpu_info/cpu_info_x86_mips.rs b/third_party/rust/minidump_writer_linux/src/dumper_cpu_info/cpu_info_x86_mips.rs new file mode 100644 index 000000000000..8c0c84ccfdd4 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/dumper_cpu_info/cpu_info_x86_mips.rs @@ -0,0 +1,121 @@ +use crate::minidump_format::*; +use crate::Result; +use std::convert::TryInto; +use std::io::{BufRead, BufReader}; +use std::path; + +struct CpuInfoEntry { + info_name: &'static str, + value: i32, + found: bool, +} + +impl CpuInfoEntry { + fn new(info_name: &'static str, value: i32, found: bool) -> Self { + CpuInfoEntry { + info_name, + value, + found, + } + } +} + +pub fn write_cpu_information(sys_info: &mut MDRawSystemInfo) -> Result<()> { + let vendor_id_name = "vendor_id"; + let mut cpu_info_table = [ + CpuInfoEntry::new("processor", -1, false), + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] + CpuInfoEntry::new("model", 0, false), + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] + CpuInfoEntry::new("stepping", 0, false), + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] + CpuInfoEntry::new("cpu family", 0, false), + ]; + + // processor_architecture should always be set, do this first + if cfg!(target_arch = "mips") { + sys_info.processor_architecture = MDCPUArchitecture::Mips as u16; + } else if cfg!(target_arch = "mips64") { + sys_info.processor_architecture = MDCPUArchitecture::Mips64 as u16; + } else if cfg!(target_arch = "x86") { + sys_info.processor_architecture = MDCPUArchitecture::X86 as u16; + } else { + sys_info.processor_architecture = MDCPUArchitecture::Amd64 as u16; + } + + let cpuinfo_file = std::fs::File::open(path::PathBuf::from("/proc/cpuinfo"))?; + + let mut vendor_id = String::new(); + for line in BufReader::new(cpuinfo_file).lines() { + let line = line?; + // Expected format: + ':' + // Note that: + // - empty lines happen. + // - can contain spaces. + // - some fields have an empty + if line.trim().is_empty() { + continue; + } + + let split: Vec<_> = line.split(":").map(|x| x.trim()).collect(); + let field = split[0]; + let value = split.get(1); // Option, might be missing + + let mut is_first_entry = true; + for mut entry in cpu_info_table.iter_mut() { + if !is_first_entry && entry.found { + // except for the 'processor' field, ignore repeated values. + continue; + } + is_first_entry = false; + if field == entry.info_name { + if let Some(val) = value { + if let Ok(v) = val.parse() { + entry.value = v; + entry.found = true; + } else { + continue; + } + } else { + continue; + } + } + + // special case for vendor_id + if field == vendor_id_name && value.is_some() && !value.unwrap().is_empty() { + vendor_id = value.unwrap().to_string(); + } + } + } + // make sure we got everything we wanted + if !cpu_info_table.iter().all(|x| x.found == true) { + return Err("Not all entries in /proc/cpuinfo found".into()); + } + // cpu_info_table[0] holds the last cpu id listed in /proc/cpuinfo, + // assuming this is the highest id, change it to the number of CPUs + // by adding one. + cpu_info_table[0].value += 1; + + sys_info.number_of_processors = cpu_info_table[0].value as u8; // TODO: might not work on special machines with LOTS of CPUs + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] + { + sys_info.processor_level = cpu_info_table[3].value as u16; + sys_info.processor_revision = + (cpu_info_table[1].value << 8 | cpu_info_table[2].value) as u16; + } + if !vendor_id.is_empty() { + let mut slice = vendor_id.as_bytes(); + for id_part in sys_info.cpu.vendor_id.iter_mut() { + let (int_bytes, rest) = slice.split_at(std::mem::size_of::()); + slice = rest; + *id_part = match int_bytes.try_into() { + Ok(x) => u32::from_ne_bytes(x), + Err(_) => { + continue; + } + }; + } + } + + Ok(()) +} diff --git a/third_party/rust/minidump_writer_linux/src/dumper_cpu_info/mod.rs b/third_party/rust/minidump_writer_linux/src/dumper_cpu_info/mod.rs new file mode 100644 index 000000000000..7da02d02f4cd --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/dumper_cpu_info/mod.rs @@ -0,0 +1,43 @@ +#[cfg(any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "mips", + target_arch = "mips64" +))] +#[path = "cpu_info_x86_mips.rs"] +pub mod imp; +#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] +#[path = "cpu_info_arm.rs"] +pub mod imp; + +pub use imp::write_cpu_information; + +use crate::minidump_format::{MDOSPlatform, MDRawSystemInfo}; +use crate::sections::write_string_to_location; +use crate::Result; +use nix::sys::utsname::uname; +use std::io::Cursor; + +pub fn write_os_information( + buffer: &mut Cursor>, + sys_info: &mut MDRawSystemInfo, +) -> Result<()> { + let info = uname(); + if cfg!(target_os = "android") { + sys_info.platform_id = MDOSPlatform::Android as u32; + } else { + sys_info.platform_id = MDOSPlatform::Linux as u32; + } + let merged = vec![ + info.sysname(), + info.release(), + info.version(), + info.machine(), + ] + .join(" "); + + let location = write_string_to_location(buffer, &merged)?; + sys_info.csd_version_rva = location.rva; + + Ok(()) +} diff --git a/third_party/rust/minidump_writer_linux/src/lib.rs b/third_party/rust/minidump_writer_linux/src/lib.rs new file mode 100644 index 000000000000..2a08482ce021 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/lib.rs @@ -0,0 +1,21 @@ +use std::error; +use std::result; + +type Error = Box; +pub type Result = result::Result; + +pub mod app_memory; +mod auxv_reader; +pub mod cpu_set; +pub mod crash_context; +mod dso_debug; +mod dumper_cpu_info; +pub mod linux_ptrace_dumper; +pub mod maps_reader; +pub mod minidump_cpu; +pub mod minidump_format; +pub mod minidump_writer; +mod sections; +pub mod thread_info; + +pub use maps_reader::LINUX_GATE_LIBRARY_NAME; diff --git a/third_party/rust/minidump_writer_linux/src/linux_ptrace_dumper.rs b/third_party/rust/minidump_writer_linux/src/linux_ptrace_dumper.rs new file mode 100644 index 000000000000..2c0347370213 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/linux_ptrace_dumper.rs @@ -0,0 +1,538 @@ +// use libc::c_void; +use crate::auxv_reader::{AuxvType, ProcfsAuxvIter}; +use crate::maps_reader::{MappingInfo, MappingInfoParsingResult, DELETED_SUFFIX}; +use crate::minidump_format::MDGUID; +use crate::thread_info::{Pid, ThreadInfo}; +use crate::Result; +use crate::LINUX_GATE_LIBRARY_NAME; +use goblin::elf; +use nix::errno::Errno; +use nix::sys::{ptrace, wait}; +use std::collections::HashMap; +use std::convert::TryInto; +use std::ffi::c_void; +use std::io::{BufRead, BufReader}; +use std::path; + +#[derive(Debug)] +pub struct LinuxPtraceDumper { + pub pid: Pid, + threads_suspended: bool, + pub threads: Vec, + pub auxv: HashMap, + pub mappings: Vec, +} + +#[cfg(target_arch = "x86")] +pub const AT_SYSINFO_EHDR: u32 = 33; +#[cfg(target_arch = "x86_64")] +pub const AT_SYSINFO_EHDR: u64 = 33; + +impl Drop for LinuxPtraceDumper { + fn drop(&mut self) { + // Always try to resume all threads (e.g. in case of error) + let _ = self.resume_threads(); + } +} + +impl LinuxPtraceDumper { + /// Constructs a dumper for extracting information of a given process + /// with a process ID of |pid|. + pub fn new(pid: Pid) -> Result { + let mut dumper = LinuxPtraceDumper { + pid, + threads_suspended: false, + threads: Vec::new(), + auxv: HashMap::new(), + mappings: Vec::new(), + }; + dumper.init()?; + Ok(dumper) + } + + // TODO: late_init for chromeos and android + pub fn init(&mut self) -> Result<()> { + self.read_auxv()?; + self.enumerate_threads()?; + self.enumerate_mappings()?; + Ok(()) + } + /// Copies content of |length| bytes from a given process |child|, + /// starting from |src|, into |dest|. This method uses ptrace to extract + /// the content from the target process. Always returns true. + pub fn copy_from_process(child: Pid, src: *mut c_void, num_of_bytes: isize) -> Result> { + let pid = nix::unistd::Pid::from_raw(child); + let mut res = Vec::new(); + let mut idx = 0isize; + while idx < num_of_bytes { + match ptrace::read(pid, unsafe { src.offset(idx) }) { + Ok(word) => res.append(&mut word.to_ne_bytes().to_vec()), + Err(e) => { + return Err(format!("Failed in ptrace::read: {:?}", e).into()); + } + } + + idx += std::mem::size_of::() as isize; + } + Ok(res) + } + + /// Suspends a thread by attaching to it. + pub fn suspend_thread(child: Pid) -> Result<()> { + let pid = nix::unistd::Pid::from_raw(child); + // This may fail if the thread has just died or debugged. + ptrace::attach(pid)?; + loop { + match wait::waitpid(pid, Some(wait::WaitPidFlag::__WALL)) { + Ok(_) => break, + Err(nix::Error::Sys(Errno::EINTR)) => { + ptrace::detach(pid)?; + return Err(format!("Failed to attach to: {:?}. Got EINTR.", pid).into()); + } + Err(_) => continue, + } + } + if cfg!(any(target_arch = "x86", target_arch = "x86_64")) { + // On x86, the stack pointer is NULL or -1, when executing trusted code in + // the seccomp sandbox. Not only does this cause difficulties down the line + // when trying to dump the thread's stack, it also results in the minidumps + // containing information about the trusted threads. This information is + // generally completely meaningless and just pollutes the minidumps. + // We thus test the stack pointer and exclude any threads that are part of + // the seccomp sandbox's trusted code. + let skip_thread; + let regs = ptrace::getregs(pid); + if let Ok(regs) = regs { + #[cfg(target_arch = "x86_64")] + { + skip_thread = regs.rsp == 0; + } + #[cfg(target_arch = "x86")] + { + skip_thread = regs.esp == 0; + } + } else { + skip_thread = true; + } + if skip_thread { + ptrace::detach(pid)?; + return Err(format!("Skipped thread {:?} due to it being part of the seccomp sandbox's trusted code", child).into()); + } + } + Ok(()) + } + + /// Resumes a thread by detaching from it. + pub fn resume_thread(child: Pid) -> Result<()> { + let pid = nix::unistd::Pid::from_raw(child); + ptrace::detach(pid)?; + Ok(()) + } + + pub fn suspend_threads(&mut self) -> Result<()> { + // Iterate over all threads and try to suspend them. + // If the thread either disappeared before we could attach to it, or if + // it was part of the seccomp sandbox's trusted code, it is OK to + // silently drop it from the minidump. + self.threads.retain(|&x| Self::suspend_thread(x).is_ok()); + + if self.threads.is_empty() { + Err("No threads left".into()) + } else { + self.threads_suspended = true; + Ok(()) + } + } + + pub fn resume_threads(&mut self) -> Result<()> { + let mut result = Ok(()); + if self.threads_suspended { + for thread in &self.threads { + match Self::resume_thread(*thread) { + Ok(_) => {} + x => { + result = x; + } + } + } + } + self.threads_suspended = false; + return result; + } + + /// Parse /proc/$pid/task to list all the threads of the process identified by + /// pid. + fn enumerate_threads(&mut self) -> Result<()> { + let task_path = path::PathBuf::from(format!("/proc/{}/task", self.pid)); + if task_path.is_dir() { + for entry in std::fs::read_dir(task_path)? { + let name = entry? + .file_name() + .to_str() + .ok_or("Unparsable filename")? + .parse::(); + if let Ok(tid) = name { + self.threads.push(tid); + } + } + } + Ok(()) + } + + fn read_auxv(&mut self) -> Result<()> { + let auxv_path = path::PathBuf::from(format!("/proc/{}/auxv", self.pid)); + let auxv_file = std::fs::File::open(auxv_path)?; + let input = BufReader::new(auxv_file); + let reader = ProcfsAuxvIter::new(input); + for item in reader { + let item = item?; + self.auxv.insert(item.key, item.value); + } + Ok(()) + } + + fn enumerate_mappings(&mut self) -> Result<()> { + // linux_gate_loc is the beginning of the kernel's mapping of + // linux-gate.so in the process. It doesn't actually show up in the + // maps list as a filename, but it can be found using the AT_SYSINFO_EHDR + // aux vector entry, which gives the information necessary to special + // case its entry when creating the list of mappings. + // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more + // information. + let linux_gate_loc = *self.auxv.get(&AT_SYSINFO_EHDR).unwrap_or(&0); + // Although the initial executable is usually the first mapping, it's not + // guaranteed (see http://crosbug.com/25355); therefore, try to use the + // actual entry point to find the mapping. + let entry_point_loc = *self.auxv.get(&libc::AT_ENTRY).unwrap_or(&0); + + let auxv_path = path::PathBuf::from(format!("/proc/{}/maps", self.pid)); + let auxv_file = std::fs::File::open(auxv_path)?; + + for line in BufReader::new(auxv_file).lines() { + // /proc//maps looks like this + // 7fe34a863000-7fe34a864000 rw-p 00009000 00:31 4746408 /usr/lib64/libogg.so.0.8.4 + let line = line?; + match MappingInfo::parse_from_line(&line, linux_gate_loc, self.mappings.last_mut()) { + Ok(MappingInfoParsingResult::Success(map)) => self.mappings.push(map), + Ok(MappingInfoParsingResult::SkipLine) => continue, + Err(_) => continue, + } + } + + if entry_point_loc != 0 { + let mut swap_idx = None; + for (idx, module) in self.mappings.iter().enumerate() { + // If this module contains the entry-point, and it's not already the first + // one, then we need to make it be first. This is because the minidump + // format assumes the first module is the one that corresponds to the main + // executable (as codified in + // processor/minidump.cc:MinidumpModuleList::GetMainModule()). + if entry_point_loc >= module.start_address.try_into().unwrap() + && entry_point_loc < (module.start_address + module.size).try_into().unwrap() + { + swap_idx = Some(idx); + break; + } + } + if let Some(idx) = swap_idx { + self.mappings.swap(0, idx); + } + } + Ok(()) + } + + /// Read thread info from /proc/$pid/status. + /// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable, + /// these members are set to -1. Returns true if all three members are + /// available. + pub fn get_thread_info_by_index(&self, index: usize) -> Result { + if index > self.threads.len() { + return Err(format!( + "Index out of bounds! Got {}, only have {}\n", + index, + self.threads.len() + ) + .into()); + } + + let tid = self.threads[index]; + ThreadInfo::create(self.pid, tid) + } + + // Get information about the stack, given the stack pointer. We don't try to + // walk the stack since we might not have all the information needed to do + // unwind. So we just grab, up to, 32k of stack. + pub fn get_stack_info(&self, int_stack_pointer: usize) -> Result<(usize, usize)> { + // Move the stack pointer to the bottom of the page that it's in. + // NOTE: original code uses getpagesize(), which a) isn't there in Rust and + // b) shouldn't be used, as its not portable (see man getpagesize) + let page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE)? + .expect("page size apparently unlimited: doesn't make sense."); + let stack_pointer = int_stack_pointer & !(page_size as usize - 1); + + // The number of bytes of stack which we try to capture. + let stack_to_capture = 32 * 1024; + + let mapping = self + .find_mapping(stack_pointer) + .ok_or("No mapping for stack pointer found")?; + let offset = stack_pointer - mapping.start_address; + let distance_to_end = mapping.size - offset; + let stack_len = std::cmp::min(distance_to_end, stack_to_capture); + + Ok((stack_pointer, stack_len)) + } + + pub fn sanitize_stack_copy( + &self, + stack_copy: &mut [u8], + stack_pointer: usize, + sp_offset: usize, + ) -> Result<()> { + // We optimize the search for containing mappings in three ways: + // 1) We expect that pointers into the stack mapping will be common, so + // we cache that address range. + // 2) The last referenced mapping is a reasonable predictor for the next + // referenced mapping, so we test that first. + // 3) We precompute a bitfield based upon bits 32:32-n of the start and + // stop addresses, and use that to short circuit any values that can + // not be pointers. (n=11) + let defaced; + #[cfg(target_pointer_width = "64")] + { + defaced = 0x0defaced0defacedusize.to_ne_bytes() + } + #[cfg(target_pointer_width = "32")] + { + defaced = 0x0defacedusize.to_ne_bytes() + }; + // the bitfield length is 2^test_bits long. + let test_bits = 11; + // byte length of the corresponding array. + let array_size = 1 << (test_bits - 3); + let array_mask = array_size - 1; + // The amount to right shift pointers by. This captures the top bits + // on 32 bit architectures. On 64 bit architectures this would be + // uninformative so we take the same range of bits. + let shift = 32 - 11; + // let MappingInfo* last_hit_mapping = nullptr; + // let MappingInfo* hit_mapping = nullptr; + let stack_mapping = self.find_mapping_no_bias(stack_pointer); + let mut last_hit_mapping: Option<&MappingInfo> = None; + // The magnitude below which integers are considered to be to be + // 'small', and not constitute a PII risk. These are included to + // avoid eliding useful register values. + let small_int_magnitude: isize = 4096; + + let mut could_hit_mapping = vec![0; array_size]; + // Initialize the bitfield such that if the (pointer >> shift)'th + // bit, modulo the bitfield size, is not set then there does not + // exist a mapping in mappings that would contain that pointer. + for mapping in &self.mappings { + if !mapping.executable { + continue; + } + // For each mapping, work out the (unmodulo'ed) range of bits to + // set. + let mut start = mapping.start_address; + let mut end = start + mapping.size; + start >>= shift; + end >>= shift; + for bit in start..=end { + // Set each bit in the range, applying the modulus. + could_hit_mapping[(bit >> 3) & array_mask] |= 1 << (bit & 7); + } + } + + // Zero memory that is below the current stack pointer. + let offset = + (sp_offset + std::mem::size_of::() - 1) & !(std::mem::size_of::() - 1); + for x in &mut stack_copy[0..offset] { + *x = 0; + } + let mut chunks = stack_copy[offset..].chunks_exact_mut(std::mem::size_of::()); + + // Apply sanitization to each complete pointer-aligned word in the + // stack. + for sp in &mut chunks { + let addr = usize::from_ne_bytes(sp.to_vec().as_slice().try_into()?); + let addr_signed = isize::from_ne_bytes(sp.to_vec().as_slice().try_into()?); + + if addr <= small_int_magnitude as usize && addr_signed >= -small_int_magnitude { + continue; + } + + if let Some(stack_map) = stack_mapping { + if stack_map.contains_address(addr) { + continue; + } + } + if let Some(last_hit) = last_hit_mapping { + if last_hit.contains_address(addr) { + continue; + } + } + + let test = addr >> shift; + if could_hit_mapping[(test >> 3) & array_mask] & (1 << (test & 7)) != 0 { + if let Some(hit_mapping) = self.find_mapping_no_bias(addr) { + if hit_mapping.executable { + last_hit_mapping = Some(hit_mapping); + continue; + } + } + } + sp.copy_from_slice(&defaced); + } + // Zero any partial word at the top of the stack, if alignment is + // such that that is required. + for sp in chunks.into_remainder() { + *sp = 0; + } + Ok(()) + } + + // Find the mapping which the given memory address falls in. + pub fn find_mapping<'a>(&'a self, address: usize) -> Option<&'a MappingInfo> { + for map in &self.mappings { + if address >= map.start_address && address - map.start_address < map.size { + return Some(&map); + } + } + None + } + + // Find the mapping which the given memory address falls in. Uses the + // unadjusted mapping address range from the kernel, rather than the + // biased range. + pub fn find_mapping_no_bias<'a>(&'a self, address: usize) -> Option<&'a MappingInfo> { + for map in &self.mappings { + if address >= map.system_mapping_info.start_address + && address < map.system_mapping_info.end_address + { + return Some(&map); + } + } + None + } + + fn parse_build_id<'data>( + elf_obj: &elf::Elf<'data>, + mem_slice: &'data [u8], + ) -> Option<&'data [u8]> { + if let Some(mut notes) = elf_obj.iter_note_headers(mem_slice) { + while let Some(Ok(note)) = notes.next() { + if note.n_type == elf::note::NT_GNU_BUILD_ID { + return Some(note.desc); + } + } + } + if let Some(mut notes) = elf_obj.iter_note_sections(mem_slice, Some(".note.gnu.build-id")) { + while let Some(Ok(note)) = notes.next() { + if note.n_type == elf::note::NT_GNU_BUILD_ID { + return Some(note.desc); + } + } + } + None + } + + pub fn elf_file_identifier_from_mapped_file(mem_slice: &[u8]) -> Result> { + let elf_obj = elf::Elf::parse(mem_slice)?; + match Self::parse_build_id(&elf_obj, mem_slice) { + // Look for a build id note first. + Some(build_id) => { + return Ok(build_id.to_vec()); + } + // Fall back on hashing the first page of the text section. + None => { + // Attempt to locate the .text section of an ELF binary and generate + // a simple hash by XORing the first page worth of bytes into |result|. + for section in elf_obj.section_headers { + if section.sh_type != elf::section_header::SHT_PROGBITS { + continue; + } + if section.sh_flags & u64::from(elf::section_header::SHF_ALLOC) != 0 { + if section.sh_flags & u64::from(elf::section_header::SHF_EXECINSTR) != 0 { + unsafe { + let ptr = mem_slice.as_ptr().offset(section.sh_offset.try_into()?); + let text_section = std::slice::from_raw_parts( + ptr as *const u8, + section.sh_size.try_into()?, + ); + + // Only provide mem::size_of(MDGUID) bytes to keep identifiers produced by this + // function backwards-compatible. + let max_len = std::cmp::min(text_section.len(), 4096); + let mut result = vec![0u8; std::mem::size_of::()]; + let mut offset = 0; + while offset < max_len { + for idx in 0..std::mem::size_of::() { + if offset + idx >= text_section.len() { + break; + } + result[idx] ^= text_section[offset + idx]; + } + offset += std::mem::size_of::(); + } + return Ok(result); + } + } + } + } + Err("No build-id found".into()) + } + } + } + + pub fn elf_identifier_for_mapping_index(&mut self, idx: usize) -> Result> { + assert!(idx < self.mappings.len()); + + return Self::elf_identifier_for_mapping(&mut self.mappings[idx], self.pid); + } + + pub fn elf_identifier_for_mapping(mapping: &mut MappingInfo, pid: Pid) -> Result> { + if !MappingInfo::is_mapped_file_safe_to_open(&mapping.name) { + return Err("Not safe to open mapping".into()); + } + + // Special-case linux-gate because it's not a real file. + if mapping.name.as_deref() == Some(LINUX_GATE_LIBRARY_NAME) { + if pid == std::process::id().try_into()? { + let mem_slice = unsafe { + std::slice::from_raw_parts(mapping.start_address as *const u8, mapping.size) + }; + return Self::elf_file_identifier_from_mapped_file(mem_slice); + } else { + let mem_slice = Self::copy_from_process( + pid, + mapping.start_address as *mut libc::c_void, + mapping.size.try_into()?, + )?; + return Self::elf_file_identifier_from_mapped_file(&mem_slice); + } + } + let new_name = MappingInfo::handle_deleted_file_in_mapping( + &mapping.name.as_ref().unwrap_or(&String::new()), + pid, + )?; + + let mem_slice = MappingInfo::get_mmap(&Some(new_name.clone()), mapping.offset)?; + let build_id = Self::elf_file_identifier_from_mapped_file(&mem_slice)?; + + // This means we switched from "/my/binary" to "/proc/1234/exe", because /my/binary + // was deleted and thus has a "/my/binary (deleted)" entry. We found the mapping anyway + // so we remove the "(deleted)". + if let Some(old_name) = &mapping.name { + if &new_name != old_name { + mapping.name = Some( + old_name + .trim_end_matches(DELETED_SUFFIX) + .trim_end() + .to_string(), + ); + } + } + return Ok(build_id); + } +} diff --git a/third_party/rust/minidump_writer_linux/src/maps_reader.rs b/third_party/rust/minidump_writer_linux/src/maps_reader.rs new file mode 100644 index 000000000000..69c1fbe6b950 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/maps_reader.rs @@ -0,0 +1,651 @@ +use crate::auxv_reader::AuxvType; +use crate::thread_info::Pid; +use crate::Result; +use byteorder::{NativeEndian, ReadBytesExt}; +use goblin::elf; +use memmap::{Mmap, MmapOptions}; +use std::convert::TryInto; +use std::fs::File; +use std::mem::size_of; +use std::path::PathBuf; + +pub const LINUX_GATE_LIBRARY_NAME: &'static str = "linux-gate.so"; +pub const DELETED_SUFFIX: &'static str = " (deleted)"; +pub const MOZILLA_IPC_PREFIX: &'static str = "org.mozilla.ipc."; +pub const RESERVED_FLAGS: &'static str = " ---p"; + +#[derive(Debug, PartialEq, Clone)] +pub struct SystemMappingInfo { + pub start_address: usize, + pub end_address: usize, +} + +// One of these is produced for each mapping in the process (i.e. line in +// /proc/$x/maps). +#[derive(Debug, PartialEq, Clone)] +pub struct MappingInfo { + // On Android, relocation packing can mean that the reported start + // address of the mapping must be adjusted by a bias in order to + // compensate for the compression of the relocation section. The + // following two members hold (after LateInit) the adjusted mapping + // range. See crbug.com/606972 for more information. + pub start_address: usize, + pub size: usize, + // When Android relocation packing causes |start_addr| and |size| to + // be modified with a load bias, we need to remember the unbiased + // address range. The following structure holds the original mapping + // address range as reported by the operating system. + pub system_mapping_info: SystemMappingInfo, + pub offset: usize, // offset into the backed file. + pub executable: bool, // true if the mapping has the execute bit set. + pub name: Option, + // pub elf_obj: Option, +} + +#[derive(Debug)] +pub struct MappingEntry { + pub mapping: MappingInfo, + pub identifier: Vec, +} + +// A list of +pub type MappingList = Vec; + +#[derive(Debug)] +pub enum MappingInfoParsingResult { + SkipLine, + Success(MappingInfo), +} + +fn is_ipc_shared_memory_segment(pathname: Option<&str>) -> bool { + if let Some(name) = pathname { + name.contains(MOZILLA_IPC_PREFIX) && name.contains(DELETED_SUFFIX) + } else { + false + } +} + +fn is_mapping_a_path(pathname: Option<&str>) -> bool { + match pathname { + Some(x) => x.contains("/"), + None => false, + } +} + +impl MappingInfo { + pub fn parse_from_line( + line: &str, + linux_gate_loc: AuxvType, + last_mapping: Option<&mut MappingInfo>, + ) -> Result { + let mut last_whitespace = false; + + // There is no `line.splitn_whitespace(6)`, so we have to do it somewhat manually + // Split at the first whitespace, trim of the rest. + let mut splits = line + .trim() + .splitn(6, |c: char| { + if c.is_whitespace() { + if last_whitespace { + return false; + } + last_whitespace = true; + true + } else { + last_whitespace = false; + false + } + }) + .map(str::trim); + + let address = splits.next().ok_or("maps malformed: No address found")?; + let perms = splits.next().ok_or("maps malformed: No perms found")?; + let mut offset = + usize::from_str_radix(splits.next().ok_or("maps malformed: No offset found")?, 16)?; + let _dev = splits.next().ok_or("maps malformed: No dev found")?; + let _inode = splits.next().ok_or("maps malformed: No inode found")?; + let mut pathname = splits.next(); // Optional + + // Due to our ugly `splitn_whitespace()` hack from above, we might have + // only trailing whitespaces as the name, so we it might still be "Some()" + if let Some(x) = pathname { + if x.is_empty() { + pathname = None; + } + } + + let mut addresses = address.split('-'); + let start_address = usize::from_str_radix(addresses.next().unwrap(), 16)?; + let end_address = usize::from_str_radix(addresses.next().unwrap(), 16)?; + + let executable = perms.contains("x"); + + // Only copy name if the name is a valid path name, or if + // it's the VDSO image. + let is_path = is_mapping_a_path(pathname); + + if !is_path && linux_gate_loc != 0 && start_address == linux_gate_loc.try_into()? { + pathname = Some(LINUX_GATE_LIBRARY_NAME); + offset = 0; + } + + if is_ipc_shared_memory_segment(pathname) { + // Skip shared memory segments used for IPC + return Ok(MappingInfoParsingResult::SkipLine); + } + + match (pathname, last_mapping) { + (Some(_name), Some(module)) => { + // Merge adjacent mappings into one module, assuming they're a single + // library mapped by the dynamic linker. + if (start_address == module.start_address + module.size) + && (pathname == module.name.as_deref()) + { + module.system_mapping_info.end_address = end_address; + module.size = end_address - module.start_address; + module.executable |= executable; + return Ok(MappingInfoParsingResult::SkipLine); + } + } + (None, Some(module)) => { + // Also merge mappings that result from address ranges that the + // linker reserved but which a loaded library did not use. These + // appear as an anonymous private mapping with no access flags set + // and which directly follow an executable mapping. + let module_end_address = module.start_address + module.size; + if (start_address == module_end_address) + && module.executable + && is_mapping_a_path(module.name.as_deref()) + && offset == 0 + || offset == module_end_address && perms == RESERVED_FLAGS + { + module.size = end_address - module.start_address; + return Ok(MappingInfoParsingResult::SkipLine); + } + } + _ => (), + } + + let name = pathname.map(ToOwned::to_owned); + + let info = MappingInfo { + start_address, + size: end_address - start_address, + system_mapping_info: SystemMappingInfo { + start_address, + end_address, + }, + offset, + executable, + name, + // elf_obj, + }; + + Ok(MappingInfoParsingResult::Success(info)) + } + + pub fn get_mmap(name: &Option, offset: usize) -> Result { + if !MappingInfo::is_mapped_file_safe_to_open(&name) { + return Err("Not safe to open mapping".into()); + } + + // Not doing this as root_prefix is always "" at the moment + // if (!dumper.GetMappingAbsolutePath(mapping, filename)) + let filename = name.clone().unwrap_or(String::new()); + let mapped_file = unsafe { + MmapOptions::new() + .offset(offset.try_into()?) + .map(&File::open(filename)?)? + }; + + if mapped_file.is_empty() || mapped_file.len() < elf::header::SELFMAG { + return Err("mmap failed".into()); + } + + Ok(mapped_file) + } + + pub fn handle_deleted_file_in_mapping(path: &str, pid: Pid) -> Result { + // Check for ' (deleted)' in |path|. + // |path| has to be at least as long as "/x (deleted)". + if !path.ends_with(DELETED_SUFFIX) { + return Ok(path.to_string()); + } + + // Check |path| against the /proc/pid/exe 'symlink'. + let exe_link = format!("/proc/{}/exe", pid); + let link_path = std::fs::read_link(&exe_link)?; + + // This is a no-op for now (until we want to support root_prefix for chroot-envs) + // if (!GetMappingAbsolutePath(new_mapping, new_path)) + // return false; + + if link_path != PathBuf::from(path) { + return Err("symlink does not match".into()); + } + + // Check to see if someone actually named their executable 'foo (deleted)'. + + // This makes currently no sense, as exe_link == new_path + // if let (Some(exe_stat), Some(new_path_stat)) = (nix::stat::stat(exe_link), nix::stat::stat(new_path)) { + // if exe_stat.st_dev == new_path_stat.st_dev && exe_stat.st_ino == new_path_stat.st_ino { + // return Err("".into()); + // } + // } + return Ok(exe_link); + } + + pub fn stack_has_pointer_to_mapping(&self, stack_copy: &[u8], sp_offset: usize) -> bool { + // Loop over all stack words that would have been on the stack in + // the target process (i.e. are word aligned, and at addresses >= + // the stack pointer). Regardless of the alignment of |stack_copy|, + // the memory starting at |stack_copy| + |offset| represents an + // aligned word in the target process. + let low_addr = self.system_mapping_info.start_address; + let high_addr = self.system_mapping_info.end_address; + let mut offset = (sp_offset + size_of::() - 1) & !(size_of::() - 1); + while offset <= stack_copy.len() - size_of::() { + let addr = match std::mem::size_of::() { + 4 => stack_copy[offset..] + .as_ref() + .read_u32::() + .map(|u| u as usize), + 8 => stack_copy[offset..] + .as_ref() + .read_u64::() + .map(|u| u as usize), + x => panic!("Unexpected type width: {}", x), + }; + if let Ok(addr) = addr { + if low_addr <= addr && addr <= high_addr { + return true; + } + offset += size_of::(); + } else { + break; + } + } + false + } + + pub fn is_mapped_file_safe_to_open(name: &Option) -> bool { + // It is unsafe to attempt to open a mapped file that lives under /dev, + // because the semantics of the open may be driver-specific so we'd risk + // hanging the crash dumper. And a file in /dev/ almost certainly has no + // ELF file identifier anyways. + if let Some(name) = name { + if name.starts_with("/dev/") { + return false; + } + } + true + } + + fn elf_file_so_name(&self) -> Result { + // Find the shared object name (SONAME) by examining the ELF information + // for |mapping|. If the SONAME is found copy it into the passed buffer + // |soname| and return true. The size of the buffer is |soname_size|. + let mapped_file = MappingInfo::get_mmap(&self.name, self.offset)?; + + let elf_obj = elf::Elf::parse(&mapped_file)?; + + let soname = elf_obj.soname.ok_or("No soname found")?; + Ok(soname.to_string()) + } + + pub fn get_mapping_effective_name_and_path(&self) -> Result<(String, String)> { + let mut file_path = self.name.clone().unwrap_or(String::new()); + let file_name; + + // Tools such as minidump_stackwalk use the name of the module to look up + // symbols produced by dump_syms. dump_syms will prefer to use a module's + // DT_SONAME as the module name, if one exists, and will fall back to the + // filesystem name of the module. + + // Just use the filesystem name if no SONAME is present. + let file_name = match self.elf_file_so_name() { + Ok(name) => name, + Err(_) => { + // file_path := /path/to/libname.so + // file_name := libname.so + let split: Vec<_> = file_path.rsplitn(2, "/").collect(); + file_name = split.first().unwrap().to_string(); + return Ok((file_path, file_name)); + } + }; + + if self.executable && self.offset != 0 { + // If an executable is mapped from a non-zero offset, this is likely because + // the executable was loaded directly from inside an archive file (e.g., an + // apk on Android). + // In this case, we append the file_name to the mapped archive path: + // file_name := libname.so + // file_path := /path/to/ARCHIVE.APK/libname.so + file_path = format!("{}/{}", file_path, file_name); + } else { + // Otherwise, replace the basename with the SONAME. + let split: Vec<_> = file_path.rsplitn(2, '/').collect(); + if split.len() == 2 { + // NOTE: rsplitn reverses the order, so the remainder is the last item + file_path = format!("{}/{}", split[1], file_name); + } else { + file_path = file_name.clone(); + } + } + + Ok((file_path, file_name)) + } + + pub fn is_contained_in(&self, user_mapping_list: &MappingList) -> bool { + for user in user_mapping_list { + // Ignore any mappings that are wholly contained within + // mappings in the mapping_info_ list. + if self.start_address >= user.mapping.start_address + && (self.start_address + self.size) + <= (user.mapping.start_address + user.mapping.size) + { + return true; + } + } + false + } + + pub fn is_interesting(&self) -> bool { + // only want modules with filenames. + self.name.is_some() && + // Only want to include one mapping per shared lib. + // Avoid filtering executable mappings. + (self.offset == 0 || self.executable) && + // big enough to get a signature for. + self.size >= 4096 + } + + pub fn contains_address(&self, address: usize) -> bool { + self.system_mapping_info.start_address <= address + && address < self.system_mapping_info.end_address + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn get_lines_and_loc() -> (Vec<&'static str>, u64) { + (vec![ +"5597483fc000-5597483fe000 r--p 00000000 00:31 4750073 /usr/bin/cat", +"5597483fe000-559748402000 r-xp 00002000 00:31 4750073 /usr/bin/cat", +"559748402000-559748404000 r--p 00006000 00:31 4750073 /usr/bin/cat", +"559748404000-559748405000 r--p 00007000 00:31 4750073 /usr/bin/cat", +"559748405000-559748406000 rw-p 00008000 00:31 4750073 /usr/bin/cat", +"559749b0e000-559749b2f000 rw-p 00000000 00:00 0 [heap]", +"7efd968d3000-7efd968f5000 rw-p 00000000 00:00 0", +"7efd968f5000-7efd9694a000 r--p 00000000 00:31 5004638 /usr/lib/locale/en_US.utf8/LC_CTYPE", +"7efd9694a000-7efd96bc2000 r--p 00000000 00:31 5004373 /usr/lib/locale/en_US.utf8/LC_COLLATE", +"7efd96bc2000-7efd96bc4000 rw-p 00000000 00:00 0", +"7efd96bc4000-7efd96bea000 r--p 00000000 00:31 4996104 /lib64/libc-2.32.so", +"7efd96bea000-7efd96d39000 r-xp 00026000 00:31 4996104 /lib64/libc-2.32.so", +"7efd96d39000-7efd96d85000 r--p 00175000 00:31 4996104 /lib64/libc-2.32.so", +"7efd96d85000-7efd96d86000 ---p 001c1000 00:31 4996104 /lib64/libc-2.32.so", +"7efd96d86000-7efd96d89000 r--p 001c1000 00:31 4996104 /lib64/libc-2.32.so", +"7efd96d89000-7efd96d8c000 rw-p 001c4000 00:31 4996104 /lib64/libc-2.32.so", +"7efd96d8c000-7efd96d92000 rw-p 00000000 00:00 0", +"7efd96da0000-7efd96da1000 r--p 00000000 00:31 5004379 /usr/lib/locale/en_US.utf8/LC_NUMERIC", +"7efd96da1000-7efd96da2000 r--p 00000000 00:31 5004382 /usr/lib/locale/en_US.utf8/LC_TIME", +"7efd96da2000-7efd96da3000 r--p 00000000 00:31 5004377 /usr/lib/locale/en_US.utf8/LC_MONETARY", +"7efd96da3000-7efd96da4000 r--p 00000000 00:31 5004376 /usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES", +"7efd96da4000-7efd96da5000 r--p 00000000 00:31 5004380 /usr/lib/locale/en_US.utf8/LC_PAPER", +"7efd96da5000-7efd96da6000 r--p 00000000 00:31 5004378 /usr/lib/locale/en_US.utf8/LC_NAME", +"7efd96da6000-7efd96da7000 r--p 00000000 00:31 5004372 /usr/lib/locale/en_US.utf8/LC_ADDRESS", +"7efd96da7000-7efd96da8000 r--p 00000000 00:31 5004381 /usr/lib/locale/en_US.utf8/LC_TELEPHONE", +"7efd96da8000-7efd96da9000 r--p 00000000 00:31 5004375 /usr/lib/locale/en_US.utf8/LC_MEASUREMENT", +"7efd96da9000-7efd96db0000 r--s 00000000 00:31 5004639 /usr/lib64/gconv/gconv-modules.cache", +"7efd96db0000-7efd96db1000 r--p 00000000 00:31 5004374 /usr/lib/locale/en_US.utf8/LC_IDENTIFICATION", +"7efd96db1000-7efd96db2000 r--p 00000000 00:31 4996100 /lib64/ld-2.32.so", +"7efd96db2000-7efd96dd3000 r-xp 00001000 00:31 4996100 /lib64/ld-2.32.so", +"7efd96dd3000-7efd96ddc000 r--p 00022000 00:31 4996100 /lib64/ld-2.32.so", +"7efd96ddc000-7efd96ddd000 r--p 0002a000 00:31 4996100 /lib64/ld-2.32.so", +"7efd96ddd000-7efd96ddf000 rw-p 0002b000 00:31 4996100 /lib64/ld-2.32.so", +"7ffc6dfda000-7ffc6dffb000 rw-p 00000000 00:00 0 [stack]", +"7ffc6e0f3000-7ffc6e0f7000 r--p 00000000 00:00 0 [vvar]", +"7ffc6e0f7000-7ffc6e0f9000 r-xp 00000000 00:00 0 [vdso]", +"ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]" + ], 0x7ffc6e0f7000) + } + + fn get_all_mappings() -> Vec { + let mut mappings: Vec = Vec::new(); + let (lines, linux_gate_loc) = get_lines_and_loc(); + // Only /usr/bin/cat and [heap] + for line in lines { + match MappingInfo::parse_from_line(&line, linux_gate_loc, mappings.last_mut()) { + Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map), + Ok(MappingInfoParsingResult::SkipLine) => continue, + Err(_) => assert!(false), + } + } + assert_eq!(mappings.len(), 23); + mappings + } + + #[test] + fn test_merged() { + let mut mappings: Vec = Vec::new(); + let (lines, linux_gate_loc) = get_lines_and_loc(); + // Only /usr/bin/cat and [heap] + for line in lines[0..=6].iter() { + match MappingInfo::parse_from_line(&line, linux_gate_loc, mappings.last_mut()) { + Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map), + Ok(MappingInfoParsingResult::SkipLine) => continue, + Err(_) => assert!(false), + } + } + + assert_eq!(mappings.len(), 3); + let cat_map = MappingInfo { + start_address: 0x5597483fc000, + size: 40960, + system_mapping_info: SystemMappingInfo { + start_address: 0x5597483fc000, + end_address: 0x559748406000, + }, + offset: 0, + executable: true, + name: Some("/usr/bin/cat".to_string()), + }; + + assert_eq!(mappings[0], cat_map); + + let heap_map = MappingInfo { + start_address: 0x559749b0e000, + size: 135168, + system_mapping_info: SystemMappingInfo { + start_address: 0x559749b0e000, + end_address: 0x559749b2f000, + }, + offset: 0, + executable: false, + name: Some("[heap]".to_string()), + }; + + assert_eq!(mappings[1], heap_map); + + let empty_map = MappingInfo { + start_address: 0x7efd968d3000, + size: 139264, + system_mapping_info: SystemMappingInfo { + start_address: 0x7efd968d3000, + end_address: 0x7efd968f5000, + }, + offset: 0, + executable: false, + name: None, + }; + + assert_eq!(mappings[2], empty_map); + } + + #[test] + fn test_linux_gate_parsing() { + let mappings = get_all_mappings(); + + let gate_map = MappingInfo { + start_address: 0x7ffc6e0f7000, + size: 8192, + system_mapping_info: SystemMappingInfo { + start_address: 0x7ffc6e0f7000, + end_address: 0x7ffc6e0f9000, + }, + offset: 0, + executable: true, + name: Some("linux-gate.so".to_string()), + }; + + assert_eq!(mappings[21], gate_map); + } + + #[test] + fn test_reading_all() { + let mappings = get_all_mappings(); + + let found_items = vec![ + Some("/usr/bin/cat".to_string()), + Some("[heap]".to_string()), + None, + Some("/usr/lib/locale/en_US.utf8/LC_CTYPE".to_string()), + Some("/usr/lib/locale/en_US.utf8/LC_COLLATE".to_string()), + None, + Some("/lib64/libc-2.32.so".to_string()), + // The original shows a None here, but this is an address ranges that the + // linker reserved but which a loaded library did not use. These + // appear as an anonymous private mapping with no access flags set + // and which directly follow an executable mapping. + Some("/usr/lib/locale/en_US.utf8/LC_NUMERIC".to_string()), + Some("/usr/lib/locale/en_US.utf8/LC_TIME".to_string()), + Some("/usr/lib/locale/en_US.utf8/LC_MONETARY".to_string()), + Some("/usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES".to_string()), + Some("/usr/lib/locale/en_US.utf8/LC_PAPER".to_string()), + Some("/usr/lib/locale/en_US.utf8/LC_NAME".to_string()), + Some("/usr/lib/locale/en_US.utf8/LC_ADDRESS".to_string()), + Some("/usr/lib/locale/en_US.utf8/LC_TELEPHONE".to_string()), + Some("/usr/lib/locale/en_US.utf8/LC_MEASUREMENT".to_string()), + Some("/usr/lib64/gconv/gconv-modules.cache".to_string()), + Some("/usr/lib/locale/en_US.utf8/LC_IDENTIFICATION".to_string()), + Some("/lib64/ld-2.32.so".to_string()), + Some("[stack]".to_string()), + Some("[vvar]".to_string()), + // This is rewritten from [vdso] to linux-gate.so + Some("linux-gate.so".to_string()), + Some("[vsyscall]".to_string()), + ]; + + assert_eq!( + mappings.iter().map(|x| x.name.clone()).collect::>(), + found_items + ); + } + + #[test] + fn test_merged_reserved_mappings() { + let mappings = get_all_mappings(); + + let gate_map = MappingInfo { + start_address: 0x7efd96bc4000, + size: 1892352, // Merged the anonymous area after in this mapping, so its bigger.. + system_mapping_info: SystemMappingInfo { + start_address: 0x7efd96bc4000, + end_address: 0x7efd96d8c000, // ..but this is not visible here + }, + offset: 0, + executable: true, + name: Some("/lib64/libc-2.32.so".to_string()), + }; + + assert_eq!(mappings[6], gate_map); + } + + #[test] + fn test_get_mapping_effective_name() { + let lines = vec![ +"7f0b97b6f000-7f0b97b70000 r--p 00000000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so", +"7f0b97b70000-7f0b97b71000 r-xp 00000000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so", +"7f0b97b71000-7f0b97b73000 r--p 00000000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so", +"7f0b97b73000-7f0b97b74000 rw-p 00001000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so", + ]; + let linux_gate_loc = 0x7ffe091bf000; + let mut mappings: Vec = Vec::new(); + for line in lines { + match MappingInfo::parse_from_line(&line, linux_gate_loc, mappings.last_mut()) { + Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map), + Ok(MappingInfoParsingResult::SkipLine) => continue, + Err(_) => assert!(false), + } + } + assert_eq!(mappings.len(), 1); + + let (file_path, file_name) = mappings[0] + .get_mapping_effective_name_and_path() + .expect("Couldn't get effective name for mapping"); + assert_eq!(file_name, "libmozgtk.so"); + assert_eq!(file_path, "/home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so"); + } + + #[test] + fn test_whitespaces_in_maps() { + let lines = vec![ +" 7f0b97b6f000-7f0b97b70000 r--p 00000000 00:3e 27136458 libmozgtk.so", +"7f0b97b70000-7f0b97b71000 r-xp 00000000 00:3e 27136458 libmozgtk.so ", +"7f0b97b71000-7f0b97b73000 r--p 00000000 00:3e 27136458\t\t\tlibmozgtk.so", + ]; + let linux_gate_loc = 0x7ffe091bf000; + let mut mappings: Vec = Vec::new(); + for line in lines { + match MappingInfo::parse_from_line(&line, linux_gate_loc, mappings.last_mut()) { + Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map), + Ok(MappingInfoParsingResult::SkipLine) => continue, + Err(x) => panic!(format!("{:?}", x)), + } + } + assert_eq!(mappings.len(), 1); + + let expected_map = MappingInfo { + start_address: 0x7f0b97b6f000, + size: 16384, + system_mapping_info: SystemMappingInfo { + start_address: 0x7f0b97b6f000, + end_address: 0x7f0b97b73000, + }, + offset: 0, + executable: true, + name: Some("libmozgtk.so".to_string()), + }; + + assert_eq!(expected_map, mappings[0]); + } + + #[test] + fn test_whitespaces_in_name() { + let lines = vec![ +"10000000-20000000 r--p 00000000 00:3e 27136458 libmoz gtk.so", +"20000000-30000000 r--p 00000000 00:3e 27136458 libmozgtk.so (deleted)", +"30000000-40000000 r--p 00000000 00:3e 27136458 \"libmoz gtk.so (deleted)\"", +"30000000-40000000 r--p 00000000 00:3e 27136458 ", + ]; + let linux_gate_loc = 0x7ffe091bf000; + let mut mappings: Vec = Vec::new(); + for line in lines { + match MappingInfo::parse_from_line(&line, linux_gate_loc, mappings.last_mut()) { + Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map), + Ok(MappingInfoParsingResult::SkipLine) => continue, + Err(_) => assert!(false), + } + } + assert_eq!(mappings.len(), 4); + assert_eq!(mappings[0].name, Some("libmoz gtk.so".to_string())); + assert_eq!(mappings[1].name, Some("libmozgtk.so (deleted)".to_string())); + assert_eq!( + mappings[2].name, + Some("\"libmoz gtk.so (deleted)\"".to_string()) + ); + assert_eq!(mappings[3].name, None); + } +} diff --git a/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_aarch64.rs b/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_aarch64.rs new file mode 100644 index 000000000000..c35e2e3c66a1 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_aarch64.rs @@ -0,0 +1,12 @@ +pub const MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT: usize = 32; +pub const MD_CONTEXT_ARM64_GPR_COUNT: usize = 33; + +/* Indices into iregs for registers with a dedicated or conventional + * purpose. + */ +pub enum MDARM64RegisterNumbers { + MD_CONTEXT_ARM64_REG_FP = 29, + MD_CONTEXT_ARM64_REG_LR = 30, + MD_CONTEXT_ARM64_REG_SP = 31, + MD_CONTEXT_ARM64_REG_PC = 32, +} diff --git a/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_amd64.rs b/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_amd64.rs new file mode 100644 index 000000000000..3924d792fc6b --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_amd64.rs @@ -0,0 +1,179 @@ +#[repr(C)] +pub struct MDXmmSaveArea32AMD64 { + pub control_word: u16, + pub status_word: u16, + pub tag_word: u8, + pub reserved1: u8, + pub error_opcode: u16, + pub error_offset: u32, + pub error_selector: u16, + pub reserved2: u16, + pub data_offset: u32, + pub data_selector: u16, + pub reserved3: u16, + pub mx_csr: u32, + pub mx_csr_mask: u32, + pub float_registers: [u128; 8], + pub xmm_registers: [u128; 16], + pub reserved4: [u8; 96], +} + +// The std library doesn't provide "Default" for all +// array-lengths. Only up to 32. So we have to implement +// our own default, because of `reserved4: [u8; 96]` +impl Default for MDXmmSaveArea32AMD64 { + #[inline] + fn default() -> Self { + MDXmmSaveArea32AMD64 { + control_word: 0, + status_word: 0, + tag_word: 0, + reserved1: 0, + error_opcode: 0, + error_offset: 0, + error_selector: 0, + reserved2: 0, + data_offset: 0, + data_selector: 0, + reserved3: 0, + mx_csr: 0, + mx_csr_mask: 0, + float_registers: [0; 8], + xmm_registers: [0; 16], + reserved4: [0; 96], + } + } +} + +const MD_CONTEXT_AMD64_VR_COUNT: usize = 26; + +#[repr(C)] +#[derive(Default)] +pub struct MDRawContextAMD64 { + /* + * Register parameter home addresses. + */ + pub p1_home: u64, + pub p2_home: u64, + pub p3_home: u64, + pub p4_home: u64, + pub p5_home: u64, + pub p6_home: u64, + + /* The next field determines the layout of the structure, and which parts + * of it are populated */ + pub context_flags: u32, + pub mx_csr: u32, + + /* The next register is included with MD_CONTEXT_AMD64_CONTROL */ + pub cs: u16, + + /* The next 4 registers are included with MD_CONTEXT_AMD64_SEGMENTS */ + pub ds: u16, + pub es: u16, + pub fs: u16, + pub gs: u16, + + /* The next 2 registers are included with MD_CONTEXT_AMD64_CONTROL */ + pub ss: u16, + pub eflags: u32, + + /* The next 6 registers are included with MD_CONTEXT_AMD64_DEBUG_REGISTERS */ + pub dr0: u64, + pub dr1: u64, + pub dr2: u64, + pub dr3: u64, + pub dr6: u64, + pub dr7: u64, + + /* The next 4 registers are included with MD_CONTEXT_AMD64_INTEGER */ + pub rax: u64, + pub rcx: u64, + pub rdx: u64, + pub rbx: u64, + + /* The next register is included with MD_CONTEXT_AMD64_CONTROL */ + pub rsp: u64, + + /* The next 11 registers are included with MD_CONTEXT_AMD64_INTEGER */ + pub rbp: u64, + pub rsi: u64, + pub rdi: u64, + pub r8: u64, + pub r9: u64, + pub r10: u64, + pub r11: u64, + pub r12: u64, + pub r13: u64, + pub r14: u64, + pub r15: u64, + + /* The next register is included with MD_CONTEXT_AMD64_CONTROL */ + pub rip: u64, + + /* The next set of registers are included with + * MD_CONTEXT_AMD64_FLOATING_POINT + */ + pub flt_save: MDXmmSaveArea32AMD64, + // union { + // MDXmmSaveArea32AMD64 flt_save; + // struct { + // uint128_struct header[2]; + // uint128_struct legacy[8]; + // uint128_struct xmm0; + // uint128_struct xmm1; + // uint128_struct xmm2; + // uint128_struct xmm3; + // uint128_struct xmm4; + // uint128_struct xmm5; + // uint128_struct xmm6; + // uint128_struct xmm7; + // uint128_struct xmm8; + // uint128_struct xmm9; + // uint128_struct xmm10; + // uint128_struct xmm11; + // uint128_struct xmm12; + // uint128_struct xmm13; + // uint128_struct xmm14; + // uint128_struct xmm15; + // } sse_registers; + // }; + pub vector_register: [u128; MD_CONTEXT_AMD64_VR_COUNT], + pub vector_control: u64, + + /* The next 5 registers are included with MD_CONTEXT_AMD64_DEBUG_REGISTERS */ + pub debug_control: u64, + pub last_branch_to_rip: u64, + pub last_branch_from_rip: u64, + pub last_exception_to_rip: u64, + pub last_exception_from_rip: u64, +} + +/* For (MDRawContextAMD64).context_flags. These values indicate the type of +* context stored in the structure. The high 24 bits identify the CPU, the +* low 8 bits identify the type of context saved. */ +pub const MD_CONTEXT_AMD64: u32 = 0x00100000; /* CONTEXT_AMD64 */ +pub const MD_CONTEXT_AMD64_CONTROL: u32 = MD_CONTEXT_AMD64 | 0x00000001; +/* CONTEXT_CONTROL */ +pub const MD_CONTEXT_AMD64_INTEGER: u32 = MD_CONTEXT_AMD64 | 0x00000002; +/* CONTEXT_INTEGER */ +pub const MD_CONTEXT_AMD64_SEGMENTS: u32 = MD_CONTEXT_AMD64 | 0x00000004; +/* CONTEXT_SEGMENTS */ +pub const MD_CONTEXT_AMD64_FLOATING_POINT: u32 = MD_CONTEXT_AMD64 | 0x00000008; +/* CONTEXT_FLOATING_POINT */ +pub const MD_CONTEXT_AMD64_DEBUG_REGISTERS: u32 = MD_CONTEXT_AMD64 | 0x00000010; +/* CONTEXT_DEBUG_REGISTERS */ +pub const MD_CONTEXT_AMD64_XSTATE: u32 = MD_CONTEXT_AMD64 | 0x00000040; +/* CONTEXT_XSTATE */ + +/* WinNT.h refers to CONTEXT_MMX_REGISTERS but doesn't appear to define it +* I think it really means CONTEXT_FLOATING_POINT. +*/ + +pub const MD_CONTEXT_AMD64_FULL: u32 = + MD_CONTEXT_AMD64_CONTROL | MD_CONTEXT_AMD64_INTEGER | MD_CONTEXT_AMD64_FLOATING_POINT; +/* CONTEXT_FULL */ + +pub const MD_CONTEXT_AMD64_ALL: u32 = + MD_CONTEXT_AMD64_FULL | MD_CONTEXT_AMD64_SEGMENTS | MD_CONTEXT_AMD64_DEBUG_REGISTERS; +/* CONTEXT_ALL */ diff --git a/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_arm.rs b/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_arm.rs new file mode 100644 index 000000000000..a874d40e9a9e --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_arm.rs @@ -0,0 +1 @@ +pub const MD_CONTEXT_ARM_GPR_COUNT: usize = 16; diff --git a/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_mips.rs b/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_mips.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_ppc.rs b/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_ppc.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_ppc64.rs b/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_ppc64.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_sparc.rs b/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_sparc.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_x86.rs b/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_x86.rs new file mode 100644 index 000000000000..b0b073ff8e0f --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/minidump_cpu/minidump_cpu_x86.rs @@ -0,0 +1,151 @@ +pub const MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE: usize = 80; + +#[repr(C)] +#[derive(Debug, PartialEq)] +pub struct MDFloatingSaveAreaX86 { + pub control_word: u32, + pub status_word: u32, + pub tag_word: u32, + pub error_offset: u32, + pub error_selector: u32, + pub data_offset: u32, + pub data_selector: u32, + + /* register_area contains eight 80-bit (x87 "long double") quantities for + * floating-point registers %st0 (%mm0) through %st7 (%mm7). */ + pub register_area: [u8; MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE], + pub cr0_npx_state: u32, +} + +// The std library doesn't provide "Default" for all +// array-lengths. Only up to 32. So we have to implement +// our own default, because of `reserved4: [u8; 96]` +impl Default for MDFloatingSaveAreaX86 { + #[inline] + fn default() -> Self { + MDFloatingSaveAreaX86 { + control_word: 0, + status_word: 0, + tag_word: 0, + error_offset: 0, + error_selector: 0, + data_offset: 0, + data_selector: 0, + register_area: [0; MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE], + cr0_npx_state: 0, + } + } +} + +const MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE: usize = 512; +/* MAXIMUM_SUPPORTED_EXTENSION */ + +#[repr(C)] +#[derive(Debug, PartialEq)] +pub struct MDRawContextX86 { + /* The next field determines the layout of the structure, and which parts + * of it are populated */ + pub context_flags: u32, + + /* The next 6 registers are included with MD_CONTEXT_X86_DEBUG_REGISTERS */ + pub dr0: u32, + pub dr1: u32, + pub dr2: u32, + pub dr3: u32, + pub dr6: u32, + pub dr7: u32, + + /* The next field is included with MD_CONTEXT_X86_FLOATING_POINT */ + pub float_save: MDFloatingSaveAreaX86, + + /* The next 4 registers are included with MD_CONTEXT_X86_SEGMENTS */ + pub gs: u32, + pub fs: u32, + pub es: u32, + pub ds: u32, + /* The next 6 registers are included with MD_CONTEXT_X86_INTEGER */ + pub edi: u32, + pub esi: u32, + pub ebx: u32, + pub edx: u32, + pub ecx: u32, + pub eax: u32, + + /* The next 6 registers are included with MD_CONTEXT_X86_CONTROL */ + pub ebp: u32, + pub eip: u32, + pub cs: u32, /* WinNT.h says "must be sanitized" */ + pub eflags: u32, /* WinNT.h says "must be sanitized" */ + pub esp: u32, + pub ss: u32, + + /* The next field is included with MD_CONTEXT_X86_EXTENDED_REGISTERS. + * It contains vector (MMX/SSE) registers. It it laid out in the + * format used by the fxsave and fsrstor instructions, so it includes + * a copy of the x87 floating-point registers as well. See FXSAVE in + * "Intel Architecture Software Developer's Manual, Volume 2." */ + pub extended_registers: [u8; MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE], +} + +impl Default for MDRawContextX86 { + #[inline] + fn default() -> Self { + MDRawContextX86 { + context_flags: 0, + dr0: 0, + dr1: 0, + dr2: 0, + dr3: 0, + dr6: 0, + dr7: 0, + float_save: Default::default(), + gs: 0, + fs: 0, + es: 0, + ds: 0, + edi: 0, + esi: 0, + ebx: 0, + edx: 0, + ecx: 0, + eax: 0, + ebp: 0, + eip: 0, + cs: 0, + eflags: 0, + esp: 0, + ss: 0, + extended_registers: [0; MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE], + } + } +} + +/* For (MDRawContextX86).context_flags. These values indicate the type of + * context stored in the structure. The high 24 bits identify the CPU, the + * low 8 bits identify the type of context saved. */ +pub const MD_CONTEXT_X86: u32 = 0x00010000; +/* CONTEXT_i386, CONTEXT_i486: identifies CPU */ +pub const MD_CONTEXT_X86_CONTROL: u32 = MD_CONTEXT_X86 | 0x00000001; +/* CONTEXT_CONTROL */ +pub const MD_CONTEXT_X86_INTEGER: u32 = MD_CONTEXT_X86 | 0x00000002; +/* CONTEXT_INTEGER */ +pub const MD_CONTEXT_X86_SEGMENTS: u32 = MD_CONTEXT_X86 | 0x00000004; +/* CONTEXT_SEGMENTS */ +pub const MD_CONTEXT_X86_FLOATING_POINT: u32 = MD_CONTEXT_X86 | 0x00000008; +/* CONTEXT_FLOATING_POINT */ +pub const MD_CONTEXT_X86_DEBUG_REGISTERS: u32 = MD_CONTEXT_X86 | 0x00000010; +/* CONTEXT_DEBUG_REGISTERS */ +pub const MD_CONTEXT_X86_EXTENDED_REGISTERS: u32 = MD_CONTEXT_X86 | 0x00000020; +/* CONTEXT_EXTENDED_REGISTERS */ +pub const MD_CONTEXT_X86_XSTATE: u32 = MD_CONTEXT_X86 | 0x00000040; +/* CONTEXT_XSTATE */ + +pub const MD_CONTEXT_X86_FULL: u32 = + MD_CONTEXT_X86_CONTROL | MD_CONTEXT_X86_INTEGER | MD_CONTEXT_X86_SEGMENTS; +/* CONTEXT_FULL */ + +pub const MD_CONTEXT_X86_ALL: u32 = MD_CONTEXT_X86_FULL + | MD_CONTEXT_X86_FLOATING_POINT + | MD_CONTEXT_X86_DEBUG_REGISTERS + | MD_CONTEXT_X86_EXTENDED_REGISTERS; +/* CONTEXT_ALL */ diff --git a/third_party/rust/minidump_writer_linux/src/minidump_cpu/mod.rs b/third_party/rust/minidump_writer_linux/src/minidump_cpu/mod.rs new file mode 100644 index 000000000000..ebce0ad53d83 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/minidump_cpu/mod.rs @@ -0,0 +1,26 @@ +#[cfg(target_arch = "x86_64")] +#[path = "minidump_cpu_amd64.rs"] +pub mod imp; +#[cfg(target_arch = "x86")] +#[path = "minidump_cpu_x86.rs"] +pub mod imp; +#[cfg(target_arch = "arm")] +#[path = "minidump_cpu_arm.rs"] +pub mod imp; +#[cfg(target_arch = "aarch64")] +#[path = "minidump_cpu_aarch64.rs"] +pub mod imp; +#[cfg(target_arch = "mips")] +#[path = "minidump_cpu_mips.rs"] +pub mod imp; + +#[cfg(target_arch = "x86_64")] +pub type RawContextCPU = imp::MDRawContextAMD64; +#[cfg(target_arch = "x86")] +pub type RawContextCPU = imp::MDRawContextX86; +#[cfg(target_arch = "arm")] +pub type RawContextCPU = imp::MDRawContextARM; +#[cfg(target_arch = "aarch64")] +pub type RawContextCPU = imp::MDRawContextX86; +#[cfg(target_arch = "mips")] +pub type RawContextCPU = i32; diff --git a/third_party/rust/minidump_writer_linux/src/minidump_format.rs b/third_party/rust/minidump_writer_linux/src/minidump_format.rs new file mode 100644 index 000000000000..0bb675594a3b --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/minidump_format.rs @@ -0,0 +1,423 @@ +#[repr(C)] +#[derive(Debug, Default, PartialEq)] +pub struct MDGUID { + data1: u32, + data2: u16, + data3: u16, + data4: [u8; 8], +} + +#[repr(C)] +#[derive(Debug, Default, PartialEq, Clone, Copy)] +pub struct MDVSFixedFileInfo { + pub signature: u32, + pub struct_version: u32, + pub file_version_hi: u32, + pub file_version_lo: u32, + pub product_version_hi: u32, + pub product_version_lo: u32, + pub file_flags_mask: u32, /* Identifies valid bits in fileFlags */ + pub file_flags: u32, + pub file_os: u32, + pub file_type: u32, + pub file_subtype: u32, + pub file_date_hi: u32, + pub file_date_lo: u32, +} + +/* An MDRVA is an offset into the minidump file. The beginning of the + * MDRawHeader is at offset 0. */ +pub type MDRVA = u32; + +#[repr(C)] +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct MDLocationDescriptor { + pub data_size: u32, + pub rva: MDRVA, +} + +#[repr(C)] +#[derive(Debug, Default, Clone, PartialEq)] +pub struct MDMemoryDescriptor { + /* The base address of the memory range on the host that produced the + * minidump. */ + pub start_of_memory_range: u64, + pub memory: MDLocationDescriptor, +} + +#[repr(C)] +#[derive(Debug, Default, PartialEq)] +pub struct MDRawHeader { + pub signature: u32, + pub version: u32, + pub stream_count: u32, + pub stream_directory_rva: MDRVA, /* A |stream_count|-sized array of + * MDRawDirectory structures. */ + pub checksum: u32, /* Can be 0. In fact, that's all that's + * been found in minidump files. */ + pub time_date_stamp: u32, /* time_t */ + pub flags: u64, +} + +/* For (MDRawHeader).signature and (MDRawHeader).version. Note that only the + * low 16 bits of (MDRawHeader).version are MD_HEADER_VERSION. Per the + * documentation, the high 16 bits are implementation-specific. */ +pub const MD_HEADER_SIGNATURE: u32 = 0x504d444d; /* 'PMDM' */ +/* MINIDUMP_SIGNATURE */ +pub const MD_HEADER_VERSION: u32 = 0x0000a793; /* 42899 */ +/* MINIDUMP_VERSION */ + +#[repr(C)] +#[derive(Debug, Default, PartialEq)] +pub struct MDRawThread { + pub thread_id: u32, + pub suspend_count: u32, + pub priority_class: u32, + pub priority: u32, + pub teb: u64, /* Thread environment block */ + pub stack: MDMemoryDescriptor, + pub thread_context: MDLocationDescriptor, /* MDRawContext[CPU] */ +} + +pub type MDRawThreadList = Vec; + +/* The inclusion of a 64-bit type in MINIDUMP_MODULE forces the struct to + * be tail-padded out to a multiple of 64 bits under some ABIs (such as PPC). + * This doesn't occur on systems that don't tail-pad in this manner. Define + * this macro to be the usable size of the MDRawModule struct, and use it in + * place of sizeof(MDRawModule). */ +// pub const MD_MODULE_SIZE: usize = 108; +// NOTE: We use "packed" here instead, to size_of::() == 108 +// "packed" should be safe here, as we don't address reserved{0,1} at all +// and padding should happen only at the tail +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct MDRawModule { + pub base_of_image: u64, + pub size_of_image: u32, + pub checksum: u32, /* 0 if unknown */ + pub time_date_stamp: u32, /* time_t */ + pub module_name_rva: MDRVA, /* MDString, pathname or filename */ + pub version_info: MDVSFixedFileInfo, + + /* The next field stores a CodeView record and is populated when a module's + * debug information resides in a PDB file. It identifies the PDB file. */ + pub cv_record: MDLocationDescriptor, + + /* The next field is populated when a module's debug information resides + * in a DBG file. It identifies the DBG file. This field is effectively + * obsolete with modules built by recent toolchains. */ + pub misc_record: MDLocationDescriptor, + + /* Alignment problem: reserved0 and reserved1 are defined by the platform + * SDK as 64-bit quantities. However, that results in a structure whose + * alignment is unpredictable on different CPUs and ABIs. If the ABI + * specifies full alignment of 64-bit quantities in structures (as ppc + * does), there will be padding between miscRecord and reserved0. If + * 64-bit quantities can be aligned on 32-bit boundaries (as on x86), + * this padding will not exist. (Note that the structure up to this point + * contains 1 64-bit member followed by 21 32-bit members.) + * As a workaround, reserved0 and reserved1 are instead defined here as + * four 32-bit quantities. This should be harmless, as there are + * currently no known uses for these fields. */ + pub reserved0: [u32; 2], + pub reserved1: [u32; 2], +} + +#[repr(C)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct MDRawDirectory { + pub stream_type: u32, + pub location: MDLocationDescriptor, +} + +#[repr(C)] +#[derive(Debug, Default, PartialEq)] +pub struct MDException { + pub exception_code: u32, /* Windows: MDExceptionCodeWin, + * Mac OS X: MDExceptionMac, + * Linux: MDExceptionCodeLinux. */ + pub exception_flags: u32, /* Windows: 1 if noncontinuable, + Mac OS X: MDExceptionCodeMac. */ + pub exception_record: u64, /* Address (in the minidump-producing host's + * memory) of another MDException, for + * nested exceptions. */ + pub exception_address: u64, /* The address that caused the exception. + * Mac OS X: exception subcode (which is + * typically the address). */ + pub number_parameters: u32, /* Number of valid elements in + * exception_information. */ + pub __align: u32, + pub exception_information: [u64; 15], +} + +#[repr(C)] +#[derive(Debug, Default, PartialEq)] +pub struct MDRawExceptionStream { + pub thread_id: u32, /* Thread in which the exception + * occurred. Corresponds to + * (MDRawThread).thread_id. */ + pub __align: u32, + pub exception_record: MDException, + pub thread_context: MDLocationDescriptor, /* MDRawContext[CPU] */ +} + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[repr(C)] +#[derive(Debug, Default, PartialEq)] +pub struct MDCPUInformation { + pub vendor_id: [u32; 3], /* cpuid 0: ebx, edx, ecx */ + pub version_information: u32, /* cpuid 1: eax */ + pub feature_information: u32, /* cpuid 1: edx */ + pub amd_extended_cpu_features: u32, /* cpuid 0x80000001, ebx */ +} + +#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] +#[repr(C)] +#[derive(Debug, Default, PartialEq)] +pub struct MDCPUInformation { + pub cpuid: u32, + pub elf_hwcaps: u32, /* linux specific, 0 otherwise */ +} + +#[cfg(target_arch = "mips")] +#[repr(C)] +#[derive(Debug, Default, PartialEq)] +pub struct MDCPUInformation { + pub cpuid: [u64; 2], +} + +/* For (MDCPUInformation).arm_cpu_info.elf_hwcaps. + * This matches the Linux kernel definitions from */ +#[repr(u32)] +pub enum MDCPUInformationARMElfHwCaps { + Swp = 1 << 0, + Half = 1 << 1, + Thumb = 1 << 2, + Bit26 = 1 << 3, + FastMult = 1 << 4, + Fpa = 1 << 5, + Vfp = 1 << 6, + Edsp = 1 << 7, + Java = 1 << 8, + Iwmmxt = 1 << 9, + Crunch = 1 << 10, + Thumbee = 1 << 11, + Neon = 1 << 12, + Vfpv3 = 1 << 13, + Vfpv3d16 = 1 << 14, + Tls = 1 << 15, + Vfpv4 = 1 << 16, + Idiva = 1 << 17, + Idivt = 1 << 18, +} + +#[repr(C)] +#[derive(Debug, Default, PartialEq)] +pub struct MDRawSystemInfo { + /* The next 3 fields and numberOfProcessors are from the SYSTEM_INFO + * structure as returned by GetSystemInfo */ + pub processor_architecture: u16, + pub processor_level: u16, /* x86: 5 = 586, 6 = 686, ... */ + /* ARM: 6 = ARMv6, 7 = ARMv7 ... */ + pub processor_revision: u16, /* x86: 0xMMSS, where MM=model, + * SS=stepping */ + /* ARM: 0 */ + pub number_of_processors: u8, + pub product_type: u8, /* Windows: VER_NT_* from WinNT.h */ + + /* The next 5 fields are from the OSVERSIONINFO structure as returned + * by GetVersionEx */ + pub major_version: u32, + pub minor_version: u32, + pub build_number: u32, + pub platform_id: u32, + pub csd_version_rva: MDRVA, /* MDString further identifying the + * host OS. + * Windows: name of the installed OS + * service pack. + * Mac OS X: the Apple OS build number + * (sw_vers -buildVersion). + * Linux: uname -srvmo */ + + pub suite_mask: u16, /* Windows: VER_SUITE_* from WinNT.h */ + pub reserved2: u16, + + pub cpu: MDCPUInformation, +} + +#[cfg(target_pointer_width = "64")] +#[derive(Debug, Default, PartialEq)] +pub struct MDRawLinkMap { + pub addr: u64, + pub name: MDRVA, + pub ld: u64, +} + +#[cfg(target_pointer_width = "64")] +#[derive(Debug, Default, PartialEq)] +pub struct MDRawDebug { + pub version: u32, + pub map: MDRVA, /* array of MDRawLinkMap64 */ + pub dso_count: u32, + pub brk: u64, + pub ldbase: u64, + pub dynamic: u64, +} + +#[cfg(target_pointer_width = "32")] +#[derive(Debug, Default, PartialEq)] +pub struct MDRawLinkMap { + pub addr: u32, + pub name: MDRVA, + pub ld: u32, +} + +#[cfg(target_pointer_width = "32")] +#[derive(Debug, Default, PartialEq)] +pub struct MDRawDebug { + pub version: u32, + pub map: MDRVA, /* array of MDRawLinkMap32 */ + pub dso_count: u32, + pub brk: u32, + pub ldbase: u32, + pub dynamic: u32, +} + +/* For (MDRawSystemInfo).processor_architecture: */ +#[repr(u16)] +pub enum MDCPUArchitecture { + X86 = 0, /* PROCESSOR_ARCHITECTURE_INTEL */ + Mips = 1, /* PROCESSOR_ARCHITECTURE_MIPS */ + Alpha = 2, /* PROCESSOR_ARCHITECTURE_ALPHA */ + Ppc = 3, /* PROCESSOR_ARCHITECTURE_PPC */ + Shx = 4, /* PROCESSOR_ARCHITECTURE_SHX + * (Super-H) */ + Arm = 5, /* PROCESSOR_ARCHITECTURE_ARM */ + Ia64 = 6, /* PROCESSOR_ARCHITECTURE_IA64 */ + Alpha64 = 7, /* PROCESSOR_ARCHITECTURE_ALPHA64 */ + Msil = 8, /* PROCESSOR_ARCHITECTURE_MSIL + * (Microsoft Intermediate Language) */ + Amd64 = 9, /* PROCESSOR_ARCHITECTURE_AMD64 */ + X86Win64 = 10, + /* PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 (WoW64) */ + Arm64 = 12, /* PROCESSOR_ARCHITECTURE_ARM64 */ + Sparc = 0x8001, /* Breakpad-defined value for SPARC */ + Ppc64 = 0x8002, /* Breakpad-defined value for PPC64 */ + Arm64Old = 0x8003, /* Breakpad-defined value for ARM64 */ + Mips64 = 0x8004, /* Breakpad-defined value for MIPS64 */ + Unknown = 0xffff, /* PROCESSOR_ARCHITECTURE_UNKNOWN */ +} + +/* For (MDRawSystemInfo).platform_id: */ +#[repr(u32)] +pub enum MDOSPlatform { + Win32s = 0, /* VER_PLATFORM_WIN32s (Windows 3.1) */ + Win32Windows = 1, /* VER_PLATFORM_WIN32_WINDOWS (Windows 95-98-Me) */ + Win32Nt = 2, /* VER_PLATFORM_WIN32_NT (Windows NT, 2000+) */ + Win32Ce = 3, /* VER_PLATFORM_WIN32_CE, VER_PLATFORM_WIN32_HH + * (Windows CE, Windows Mobile, "Handheld") */ + /* The following values are Breakpad-defined. */ + Unix = 0x8000, /* Generic Unix-ish */ + MacOsX = 0x8101, /* Mac OS X/Darwin */ + Ios = 0x8102, /* iOS */ + Linux = 0x8201, /* Linux */ + Solaris = 0x8202, /* Solaris */ + Android = 0x8203, /* Android */ + Ps3 = 0x8204, /* PS3 */ + Nacl = 0x8205, /* Native Client (NaCl) */ + Fuchsia = 0x8206, /* Fuchsia */ +} + +/* + * Modern ELF toolchains insert a "build id" into the ELF headers that + * usually contains a hash of some ELF headers + sections to uniquely + * identify a binary. + * + * https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Developer_Guide/compiling-build-id.html + * https://sourceware.org/binutils/docs-2.26/ld/Options.html#index-g_t_002d_002dbuild_002did-292 + */ +pub const MD_CVINFOELF_SIGNATURE: u32 = 0x4270454c; /* cvSignature = 'BpEL' */ +/* Signature is followed by the bytes of the + * build id from GNU_BUILD_ID ELF note. + * This is variable-length, but usually 20 bytes + * as the binutils ld default is a SHA-1 hash. */ + +/* For (MDRawHeader).flags: */ +pub enum MDType { + /* MD_NORMAL is the standard type of minidump. It includes full + * streams for the thread list, module list, exception, system info, + * and miscellaneous info. A memory list stream is also present, + * pointing to the same stack memory contained in the thread list, + * as well as a 256-byte region around the instruction address that + * was executing when the exception occurred. Stack memory is from + * 4 bytes below a thread's stack pointer up to the top of the + * memory region encompassing the stack. */ + Normal = 0x00000000, + WithDataSegs = 0x00000001, + WithFullMemory = 0x00000002, + WithHandleData = 0x00000004, + FilterMemory = 0x00000008, + ScanMemory = 0x00000010, + WithUnloadedModules = 0x00000020, + WithIndirectlyReferencedMemory = 0x00000040, + FilterModulePaths = 0x00000080, + WithProcessThreadData = 0x00000100, + WithPrivateReadWriteMemory = 0x00000200, + WithoutOptionalData = 0x00000400, + WithFullMemoryInfo = 0x00000800, + WithThreadInfo = 0x00001000, + WithCodeSegs = 0x00002000, + WithoutAuxilliarySegs = 0x00004000, + WithFullAuxilliaryState = 0x00008000, + WithPrivateWriteCopyMemory = 0x00010000, + IgnoreInaccessibleMemory = 0x00020000, + WithTokenInformation = 0x00040000, +} + +/* For (MDRawDirectory).stream_type */ +#[repr(u32)] +pub enum MDStreamType { + UnusedStream = 0, + ReservedStream0 = 1, + ReservedStream1 = 2, + ThreadListStream = 3, /* MDRawThreadList */ + ModuleListStream = 4, /* MDRawModuleList */ + MemoryListStream = 5, /* MDRawMemoryList */ + ExceptionStream = 6, /* MDRawExceptionStream */ + SystemInfoStream = 7, /* MDRawSystemInfo */ + ThreadExListStream = 8, + Memory64ListStream = 9, + CommentStreamA = 10, + CommentStreamW = 11, + HandleDataStream = 12, + FunctionTableStream = 13, + UnloadedModuleListStream = 14, + MiscInfoStream = 15, /* MDRawMiscInfo */ + MemoryInfoListStream = 16, /* MDRawMemoryInfoList */ + ThreadInfoListStream = 17, + HandleOperationListStream = 18, + TokenStream = 19, + JavascriptDataStream = 20, + SystemMemoryInfoStream = 21, + ProcessVmCountersStream = 22, + LastReservedStream = 0x0000ffff, + + /* Breakpad extension types. 0x4767 = "Gg" */ + BreakpadInfoStream = 0x47670001, /* MDRawBreakpadInfo */ + AssertionInfoStream = 0x47670002, /* MDRawAssertionInfo */ + /* These are additional minidump stream values which are specific to + * the linux breakpad implementation. */ + LinuxCpuInfo = 0x47670003, /* /proc/cpuinfo */ + LinuxProcStatus = 0x47670004, /* /proc/$x/status */ + LinuxLsbRelease = 0x47670005, /* /etc/lsb-release */ + LinuxCmdLine = 0x47670006, /* /proc/$x/cmdline */ + LinuxEnviron = 0x47670007, /* /proc/$x/environ */ + LinuxAuxv = 0x47670008, /* /proc/$x/auxv */ + LinuxMaps = 0x47670009, /* /proc/$x/maps */ + LinuxDsoDebug = 0x4767000A, /* MDRawDebug{32,64} */ + + /* Crashpad extension types. 0x4350 = "CP" + * See Crashpad's minidump/minidump_extensions.h. */ + CrashpadInfoStream = 0x43500001, /* MDRawCrashpadInfo */ +} diff --git a/third_party/rust/minidump_writer_linux/src/minidump_writer.rs b/third_party/rust/minidump_writer_linux/src/minidump_writer.rs new file mode 100644 index 000000000000..0f9307671eac --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/minidump_writer.rs @@ -0,0 +1,405 @@ +use crate::app_memory::AppMemoryList; +use crate::crash_context::CrashContext; +use crate::dso_debug; +use crate::linux_ptrace_dumper::LinuxPtraceDumper; +use crate::maps_reader::{MappingInfo, MappingList}; +use crate::minidump_format::*; +use crate::sections::*; +use crate::thread_info::Pid; +use crate::Result; +use std::io::{Cursor, Read, Seek, SeekFrom, Write}; + +pub type DumpBuf = Cursor>; + +#[derive(Debug)] +pub struct DirSection<'a, W> +where + W: Write + Seek, +{ + curr_idx: usize, + section: MemoryArrayWriter, + /// If we have to append to some file, we have to know where we currently are + destination_start_offset: u64, + destination: &'a mut W, + last_position_written_to_file: u64, +} + +impl<'a, W> DirSection<'a, W> +where + W: Write + Seek, +{ + fn new(buffer: &mut DumpBuf, index_length: u32, destination: &'a mut W) -> Result { + let dir_section = + MemoryArrayWriter::::alloc_array(buffer, index_length as usize)?; + Ok(DirSection { + curr_idx: 0, + section: dir_section, + destination_start_offset: destination.seek(SeekFrom::Current(0))?, + destination, + last_position_written_to_file: 0, + }) + } + + fn position(&self) -> u32 { + self.section.position + } + + fn dump_dir_entry(&mut self, buffer: &mut DumpBuf, dirent: MDRawDirectory) -> Result<()> { + self.section.set_value_at(buffer, dirent, self.curr_idx)?; + + // Now write it to file + + // First get all the positions + let curr_file_pos = self.destination.seek(SeekFrom::Current(0))?; + let idx_pos = self.section.location_of_index(self.curr_idx); + self.curr_idx += 1; + + self.destination.seek(std::io::SeekFrom::Start( + self.destination_start_offset + idx_pos.rva as u64, + ))?; + let start = idx_pos.rva as usize; + let end = (idx_pos.rva + idx_pos.data_size) as usize; + self.destination.write_all(&buffer.get_ref()[start..end])?; + + // Reset file-position + self.destination + .seek(std::io::SeekFrom::Start(curr_file_pos))?; + + Ok(()) + } + + /// Writes 2 things to file: + /// 1. The given dirent into the dir section in the header (if any is given) + /// 2. Everything in the in-memory buffer that was added since the last call to this function + fn write_to_file( + &mut self, + buffer: &mut DumpBuf, + dirent: Option, + ) -> Result<()> { + if let Some(dirent) = dirent { + self.dump_dir_entry(buffer, dirent)?; + } + + let start_pos = self.last_position_written_to_file as usize; + self.destination.write_all(&buffer.get_ref()[start_pos..])?; + self.last_position_written_to_file = buffer.position(); + Ok(()) + } +} + +pub enum CrashingThreadContext { + None, + CrashContext(MDLocationDescriptor), + CrashContextPlusAddress((MDLocationDescriptor, usize)), +} + +pub struct MinidumpWriter { + pub process_id: Pid, + pub blamed_thread: Pid, + pub minidump_size_limit: Option, + pub skip_stacks_if_mapping_unreferenced: bool, + pub principal_mapping_address: Option, + pub user_mapping_list: MappingList, + pub app_memory: AppMemoryList, + pub memory_blocks: Vec, + pub principal_mapping: Option, + pub sanitize_stack: bool, + pub crash_context: Option, + pub crashing_thread_context: CrashingThreadContext, +} + +// This doesn't work yet: +// https://github.com/rust-lang/rust/issues/43408 +// fn write>(path: P, value: T) -> Result<()> { +// let mut file = std::fs::File::open(path)?; +// let bytes: [u8; size_of::()] = unsafe { transmute(value) }; +// file.write_all(&bytes)?; +// Ok(()) +// } + +impl MinidumpWriter { + pub fn new(process: Pid, blamed_thread: Pid) -> Self { + MinidumpWriter { + process_id: process, + blamed_thread, + minidump_size_limit: None, + skip_stacks_if_mapping_unreferenced: false, + principal_mapping_address: None, + user_mapping_list: MappingList::new(), + app_memory: AppMemoryList::new(), + memory_blocks: Vec::new(), + principal_mapping: None, + sanitize_stack: false, + crash_context: None, + crashing_thread_context: CrashingThreadContext::None, + } + } + + pub fn set_minidump_size_limit(&mut self, limit: u64) -> &mut Self { + self.minidump_size_limit = Some(limit); + self + } + + pub fn set_user_mapping_list(&mut self, user_mapping_list: MappingList) -> &mut Self { + self.user_mapping_list = user_mapping_list; + self + } + + pub fn set_principal_mapping_address(&mut self, principal_mapping_address: usize) -> &mut Self { + self.principal_mapping_address = Some(principal_mapping_address); + self + } + + pub fn set_app_memory(&mut self, app_memory: AppMemoryList) -> &mut Self { + self.app_memory = app_memory; + self + } + + pub fn set_crash_context(&mut self, crash_context: CrashContext) -> &mut Self { + self.crash_context = Some(crash_context); + self + } + + pub fn skip_stacks_if_mapping_unreferenced(&mut self) -> &mut Self { + self.skip_stacks_if_mapping_unreferenced = true; // Off by default + self + } + + pub fn sanitize_stack(&mut self) -> &mut Self { + self.sanitize_stack = true; // Off by default + self + } + + /// Generates a minidump and writes to the destination provided. Returns the in-memory + /// version of the minidump as well. + pub fn dump(&mut self, destination: &mut (impl Write + Seek)) -> Result> { + let mut dumper = LinuxPtraceDumper::new(self.process_id)?; + dumper.suspend_threads()?; + // TODO: Doesn't exist yet + //self.dumper.late_init()?; + + if self.skip_stacks_if_mapping_unreferenced { + if let Some(address) = self.principal_mapping_address { + self.principal_mapping = dumper.find_mapping_no_bias(address).cloned(); + } + + if !self.crash_thread_references_principal_mapping(&dumper) { + return Err("!crash_thread_references_principal_mapping".into()); + } + } + + let mut buffer = Cursor::new(Vec::new()); + self.generate_dump(&mut buffer, &mut dumper, destination)?; + + // dumper would resume threads in drop() automatically, + // but in case there is an error, we want to catch it + dumper.resume_threads()?; + + Ok(buffer.into_inner()) + } + + fn crash_thread_references_principal_mapping(&self, dumper: &LinuxPtraceDumper) -> bool { + if self.crash_context.is_none() || self.principal_mapping.is_none() { + return false; + } + + let low_addr = self + .principal_mapping + .as_ref() + .unwrap() + .system_mapping_info + .start_address; + let high_addr = self + .principal_mapping + .as_ref() + .unwrap() + .system_mapping_info + .end_address; + + let pc = self + .crash_context + .as_ref() + .unwrap() + .get_instruction_pointer(); + let stack_pointer = self.crash_context.as_ref().unwrap().get_stack_pointer(); + + if pc >= low_addr as libc::greg_t && pc < high_addr as libc::greg_t { + return true; + } + + let (stack_ptr, stack_len) = match dumper.get_stack_info(stack_pointer as usize) { + Ok(x) => x, + Err(_) => { + return false; + } + }; + let stack_copy = match LinuxPtraceDumper::copy_from_process( + self.blamed_thread, + stack_ptr as *mut libc::c_void, + stack_len as isize, + ) { + Ok(x) => x, + Err(_) => { + return false; + } + }; + + let sp_offset = stack_pointer as usize - stack_ptr; + self.principal_mapping + .as_ref() + .unwrap() + .stack_has_pointer_to_mapping(&stack_copy, sp_offset) + } + + fn generate_dump( + &mut self, + buffer: &mut DumpBuf, + dumper: &mut LinuxPtraceDumper, + destination: &mut (impl Write + Seek), + ) -> Result<()> { + // A minidump file contains a number of tagged streams. This is the number + // of stream which we write. + let num_writers = 13u32; + + let mut header_section = MemoryWriter::::alloc(buffer)?; + + let mut dir_section = DirSection::new(buffer, num_writers, destination)?; + + let header = MDRawHeader { + signature: MD_HEADER_SIGNATURE, + version: MD_HEADER_VERSION, + stream_count: num_writers, + // header.get()->stream_directory_rva = dir.position(); + stream_directory_rva: dir_section.position(), + checksum: 0, /* Can be 0. In fact, that's all that's + * been found in minidump files. */ + time_date_stamp: std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH)? + .as_secs() as u32, // TODO: This is not Y2038 safe, but thats how its currently defined as + flags: 0, + }; + header_section.set_value(buffer, header)?; + + // Ensure the header gets flushed. If we crash somewhere below, + // we should have a mostly-intact dump + dir_section.write_to_file(buffer, None)?; + + let dirent = thread_list_stream::write(self, buffer, &dumper)?; + // Write section to file + dir_section.write_to_file(buffer, Some(dirent))?; + + let dirent = mappings::write(self, buffer, dumper)?; + // Write section to file + dir_section.write_to_file(buffer, Some(dirent))?; + + let _ = app_memory::write(self, buffer)?; + // Write section to file + dir_section.write_to_file(buffer, None)?; + + let dirent = memory_list_stream::write(self, buffer)?; + // Write section to file + dir_section.write_to_file(buffer, Some(dirent))?; + + let dirent = exception_stream::write(self, buffer)?; + // Write section to file + dir_section.write_to_file(buffer, Some(dirent))?; + + let dirent = systeminfo_stream::write(buffer)?; + // Write section to file + dir_section.write_to_file(buffer, Some(dirent))?; + + let dirent = match self.write_file(buffer, "/proc/cpuinfo") { + Ok(location) => MDRawDirectory { + stream_type: MDStreamType::LinuxCpuInfo as u32, + location, + }, + Err(_) => Default::default(), + }; + // Write section to file + dir_section.write_to_file(buffer, Some(dirent))?; + + let dirent = match self.write_file(buffer, &format!("/proc/{}/status", self.blamed_thread)) + { + Ok(location) => MDRawDirectory { + stream_type: MDStreamType::LinuxProcStatus as u32, + location, + }, + Err(_) => Default::default(), + }; + // Write section to file + dir_section.write_to_file(buffer, Some(dirent))?; + + let dirent = match self + .write_file(buffer, "/etc/lsb-release") + .or_else(|_| self.write_file(buffer, "/etc/os-release")) + { + Ok(location) => MDRawDirectory { + stream_type: MDStreamType::LinuxLsbRelease as u32, + location, + }, + Err(_) => Default::default(), + }; + // Write section to file + dir_section.write_to_file(buffer, Some(dirent))?; + + let dirent = match self.write_file(buffer, &format!("/proc/{}/cmdline", self.blamed_thread)) + { + Ok(location) => MDRawDirectory { + stream_type: MDStreamType::LinuxCmdLine as u32, + location, + }, + Err(_) => Default::default(), + }; + // Write section to file + dir_section.write_to_file(buffer, Some(dirent))?; + + let dirent = match self.write_file(buffer, &format!("/proc/{}/environ", self.blamed_thread)) + { + Ok(location) => MDRawDirectory { + stream_type: MDStreamType::LinuxEnviron as u32, + location, + }, + Err(_) => Default::default(), + }; + // Write section to file + dir_section.write_to_file(buffer, Some(dirent))?; + + let dirent = match self.write_file(buffer, &format!("/proc/{}/auxv", self.blamed_thread)) { + Ok(location) => MDRawDirectory { + stream_type: MDStreamType::LinuxAuxv as u32, + location, + }, + Err(_) => Default::default(), + }; + // Write section to file + dir_section.write_to_file(buffer, Some(dirent))?; + + let dirent = match self.write_file(buffer, &format!("/proc/{}/maps", self.blamed_thread)) { + Ok(location) => MDRawDirectory { + stream_type: MDStreamType::LinuxMaps as u32, + location, + }, + Err(_) => Default::default(), + }; + // Write section to file + dir_section.write_to_file(buffer, Some(dirent))?; + + let dirent = dso_debug::write_dso_debug_stream(buffer, self.blamed_thread, &dumper.auxv) + .unwrap_or_default(); + // Write section to file + dir_section.write_to_file(buffer, Some(dirent))?; + + // If you add more directory entries, don't forget to update kNumWriters, + // above. + Ok(()) + } + + fn write_file(&self, buffer: &mut DumpBuf, filename: &str) -> Result { + let mut file = std::fs::File::open(std::path::PathBuf::from(filename))?; + let mut content = Vec::new(); + file.read_to_end(&mut content)?; + + let section = MemoryArrayWriter::::alloc_from_array(buffer, &content)?; + Ok(section.location()) + } +} diff --git a/third_party/rust/minidump_writer_linux/src/sections/app_memory.rs b/third_party/rust/minidump_writer_linux/src/sections/app_memory.rs new file mode 100644 index 000000000000..93644119cfb4 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/sections/app_memory.rs @@ -0,0 +1,25 @@ +use crate::linux_ptrace_dumper::LinuxPtraceDumper; +use crate::minidump_format::*; +use crate::minidump_writer::{DumpBuf, MinidumpWriter}; +use crate::sections::MemoryArrayWriter; +use crate::Result; +use std::convert::TryInto; + +/// Write application-provided memory regions. +pub fn write(config: &mut MinidumpWriter, buffer: &mut DumpBuf) -> Result<()> { + for app_memory in &config.app_memory { + let data_copy = LinuxPtraceDumper::copy_from_process( + config.blamed_thread, + app_memory.ptr as *mut libc::c_void, + app_memory.length.try_into()?, + )?; + + let section = MemoryArrayWriter::::alloc_from_array(buffer, &data_copy)?; + let desc = MDMemoryDescriptor { + start_of_memory_range: app_memory.ptr as u64, + memory: section.location(), + }; + config.memory_blocks.push(desc); + } + Ok(()) +} diff --git a/third_party/rust/minidump_writer_linux/src/sections/exception_stream.rs b/third_party/rust/minidump_writer_linux/src/sections/exception_stream.rs new file mode 100644 index 000000000000..9dd027b5f940 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/sections/exception_stream.rs @@ -0,0 +1,95 @@ +use crate::minidump_format::*; +use crate::minidump_writer::{CrashingThreadContext, DumpBuf, MinidumpWriter}; +use crate::sections::MemoryWriter; +use crate::Result; + +#[allow(non_camel_case_types, unused)] +#[repr(u32)] +enum MDExceptionCodeLinux { + MD_EXCEPTION_CODE_LIN_SIGHUP = 1, /* Hangup (POSIX) */ + MD_EXCEPTION_CODE_LIN_SIGINT = 2, /* Interrupt (ANSI) */ + MD_EXCEPTION_CODE_LIN_SIGQUIT = 3, /* Quit (POSIX) */ + MD_EXCEPTION_CODE_LIN_SIGILL = 4, /* Illegal instruction (ANSI) */ + MD_EXCEPTION_CODE_LIN_SIGTRAP = 5, /* Trace trap (POSIX) */ + MD_EXCEPTION_CODE_LIN_SIGABRT = 6, /* Abort (ANSI) */ + MD_EXCEPTION_CODE_LIN_SIGBUS = 7, /* BUS error (4.2 BSD) */ + MD_EXCEPTION_CODE_LIN_SIGFPE = 8, /* Floating-point exception (ANSI) */ + MD_EXCEPTION_CODE_LIN_SIGKILL = 9, /* Kill, unblockable (POSIX) */ + MD_EXCEPTION_CODE_LIN_SIGUSR1 = 10, /* User-defined signal 1 (POSIX). */ + MD_EXCEPTION_CODE_LIN_SIGSEGV = 11, /* Segmentation violation (ANSI) */ + MD_EXCEPTION_CODE_LIN_SIGUSR2 = 12, /* User-defined signal 2 (POSIX) */ + MD_EXCEPTION_CODE_LIN_SIGPIPE = 13, /* Broken pipe (POSIX) */ + MD_EXCEPTION_CODE_LIN_SIGALRM = 14, /* Alarm clock (POSIX) */ + MD_EXCEPTION_CODE_LIN_SIGTERM = 15, /* Termination (ANSI) */ + MD_EXCEPTION_CODE_LIN_SIGSTKFLT = 16, /* Stack faultd */ + MD_EXCEPTION_CODE_LIN_SIGCHLD = 17, /* Child status has changed (POSIX) */ + MD_EXCEPTION_CODE_LIN_SIGCONT = 18, /* Continue (POSIX) */ + MD_EXCEPTION_CODE_LIN_SIGSTOP = 19, /* Stop, unblockable (POSIX) */ + MD_EXCEPTION_CODE_LIN_SIGTSTP = 20, /* Keyboard stop (POSIX) */ + MD_EXCEPTION_CODE_LIN_SIGTTIN = 21, /* Background read from tty (POSIX) */ + MD_EXCEPTION_CODE_LIN_SIGTTOU = 22, /* Background write to tty (POSIX) */ + MD_EXCEPTION_CODE_LIN_SIGURG = 23, + /* Urgent condition on socket (4.2 BSD) */ + MD_EXCEPTION_CODE_LIN_SIGXCPU = 24, /* CPU limit exceeded (4.2 BSD) */ + MD_EXCEPTION_CODE_LIN_SIGXFSZ = 25, + /* File size limit exceeded (4.2 BSD) */ + MD_EXCEPTION_CODE_LIN_SIGVTALRM = 26, /* Virtual alarm clock (4.2 BSD) */ + MD_EXCEPTION_CODE_LIN_SIGPROF = 27, /* Profiling alarm clock (4.2 BSD) */ + MD_EXCEPTION_CODE_LIN_SIGWINCH = 28, /* Window size change (4.3 BSD, Sun) */ + MD_EXCEPTION_CODE_LIN_SIGIO = 29, /* I/O now possible (4.2 BSD) */ + MD_EXCEPTION_CODE_LIN_SIGPWR = 30, /* Power failure restart (System V) */ + MD_EXCEPTION_CODE_LIN_SIGSYS = 31, /* Bad system call */ + MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED = 0xFFFFFFFF, /* No exception, + dump requested. */ +} + +pub fn write(config: &mut MinidumpWriter, buffer: &mut DumpBuf) -> Result { + let exception = if let Some(context) = &config.crash_context { + MDException { + exception_code: context.siginfo.si_signo as u32, + exception_flags: context.siginfo.si_code as u32, + exception_record: 0, + exception_address: unsafe { context.siginfo.si_addr() } as u64, + number_parameters: 0, + __align: 0, + exception_information: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + } + } else { + let addr = match config.crashing_thread_context { + CrashingThreadContext::CrashContextPlusAddress((_, addr)) => addr, + _ => 0, + }; + MDException { + exception_code: MDExceptionCodeLinux::MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED as u32, + exception_flags: 0, + exception_record: 0, + exception_address: addr as u64, + number_parameters: 0, + __align: 0, + exception_information: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + } + }; + + let thread_context = match config.crashing_thread_context { + CrashingThreadContext::CrashContextPlusAddress((ctx, _)) => ctx, + CrashingThreadContext::CrashContext(ctx) => ctx, + CrashingThreadContext::None => MDLocationDescriptor { + data_size: 0, + rva: 0, + }, + }; + + let stream = MDRawExceptionStream { + thread_id: config.blamed_thread as u32, + exception_record: exception, + __align: 0, + thread_context, + }; + let exc = MemoryWriter::alloc_with_val(buffer, stream)?; + let dirent = MDRawDirectory { + stream_type: MDStreamType::ExceptionStream as u32, + location: exc.location(), + }; + + Ok(dirent) +} diff --git a/third_party/rust/minidump_writer_linux/src/sections/mappings.rs b/third_party/rust/minidump_writer_linux/src/sections/mappings.rs new file mode 100644 index 000000000000..912eb0f3ddcb --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/sections/mappings.rs @@ -0,0 +1,102 @@ +use crate::linux_ptrace_dumper::LinuxPtraceDumper; +use crate::maps_reader::MappingInfo; +use crate::minidump_format::*; +use crate::minidump_writer::{DumpBuf, MinidumpWriter}; +use crate::sections::{write_string_to_location, MemoryArrayWriter, MemoryWriter}; +use crate::Result; + +/// Write information about the mappings in effect. Because we are using the +/// minidump format, the information about the mappings is pretty limited. +/// Because of this, we also include the full, unparsed, /proc/$x/maps file in +/// another stream in the file. +pub fn write( + config: &mut MinidumpWriter, + buffer: &mut DumpBuf, + dumper: &mut LinuxPtraceDumper, +) -> Result { + let mut num_output_mappings = config.user_mapping_list.len(); + + for mapping in &dumper.mappings { + // If the mapping is uninteresting, or if + // there is caller-provided information about this mapping + // in the user_mapping_list list, skip it + if mapping.is_interesting() && !mapping.is_contained_in(&config.user_mapping_list) { + num_output_mappings += 1; + } + } + + let list_header = MemoryWriter::::alloc_with_val(buffer, num_output_mappings as u32)?; + + let mut dirent = MDRawDirectory { + stream_type: MDStreamType::ModuleListStream as u32, + location: list_header.location(), + }; + + // In case of num_output_mappings == 0, this call doesn't allocate any memory in the buffer + let mut mapping_list = + MemoryArrayWriter::::alloc_array(buffer, num_output_mappings)?; + dirent.location.data_size += mapping_list.location().data_size; + + // First write all the mappings from the dumper + let mut idx = 0; + for map_idx in 0..dumper.mappings.len() { + if !dumper.mappings[map_idx].is_interesting() + || dumper.mappings[map_idx].is_contained_in(&config.user_mapping_list) + { + continue; + } + // Note: elf_identifier_for_mapping_index() can manipulate the |mapping.name|. + let identifier = dumper + .elf_identifier_for_mapping_index(map_idx) + .unwrap_or(Default::default()); + let module = fill_raw_module(buffer, &dumper.mappings[map_idx], &identifier)?; + mapping_list.set_value_at(buffer, module, idx)?; + idx += 1; + } + + // Next write all the mappings provided by the caller + for user in &config.user_mapping_list { + // GUID was provided by caller. + let module = fill_raw_module(buffer, &user.mapping, &user.identifier)?; + mapping_list.set_value_at(buffer, module, idx)?; + idx += 1; + } + Ok(dirent) +} + +fn fill_raw_module( + buffer: &mut DumpBuf, + mapping: &MappingInfo, + identifier: &[u8], +) -> Result { + let cv_record: MDLocationDescriptor; + if identifier.is_empty() { + // Just zeroes + cv_record = Default::default(); + } else { + let cv_signature = MD_CVINFOELF_SIGNATURE; + let array_size = std::mem::size_of_val(&cv_signature) + identifier.len(); + + let mut sig_section = MemoryArrayWriter::::alloc_array(buffer, array_size)?; + for (index, val) in cv_signature + .to_ne_bytes() + .iter() + .chain(identifier.iter()) + .enumerate() + { + sig_section.set_value_at(buffer, *val, index)?; + } + cv_record = sig_section.location(); + } + + let (file_path, _) = mapping.get_mapping_effective_name_and_path()?; + let name_header = write_string_to_location(buffer, &file_path)?; + + Ok(MDRawModule { + base_of_image: mapping.start_address as u64, + size_of_image: mapping.size as u32, + cv_record, + module_name_rva: name_header.rva, + ..Default::default() + }) +} diff --git a/third_party/rust/minidump_writer_linux/src/sections/memory_list_stream.rs b/third_party/rust/minidump_writer_linux/src/sections/memory_list_stream.rs new file mode 100644 index 000000000000..a40d2a6b13c6 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/sections/memory_list_stream.rs @@ -0,0 +1,21 @@ +use crate::minidump_format::*; +use crate::minidump_writer::{DumpBuf, MinidumpWriter}; +use crate::sections::{MemoryArrayWriter, MemoryWriter}; +use crate::Result; + +pub fn write(config: &mut MinidumpWriter, buffer: &mut DumpBuf) -> Result { + let list_header = + MemoryWriter::::alloc_with_val(buffer, config.memory_blocks.len() as u32)?; + + let mut dirent = MDRawDirectory { + stream_type: MDStreamType::MemoryListStream as u32, + location: list_header.location(), + }; + + let block_list = + MemoryArrayWriter::::alloc_from_array(buffer, &config.memory_blocks)?; + + dirent.location.data_size += block_list.location().data_size; + + Ok(dirent) +} diff --git a/third_party/rust/minidump_writer_linux/src/sections/mod.rs b/third_party/rust/minidump_writer_linux/src/sections/mod.rs new file mode 100644 index 000000000000..bcddeeef0acc --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/sections/mod.rs @@ -0,0 +1,191 @@ +pub mod app_memory; +pub mod exception_stream; +pub mod mappings; +pub mod memory_list_stream; +pub mod systeminfo_stream; +pub mod thread_list_stream; + +use crate::minidump_format::*; +use crate::Result; +use std::convert::TryInto; +use std::io::{Cursor, Write}; + +#[derive(Debug, PartialEq)] +pub struct MemoryWriter { + pub position: MDRVA, + pub size: usize, + phantom: std::marker::PhantomData, +} + +impl MemoryWriter +where + T: Default + Sized, +{ + /// Create a slot for a type T in the buffer, we can fill right now with real values. + pub fn alloc_with_val(buffer: &mut Cursor>, val: T) -> Result { + // Get position of this value (e.g. before we add ourselves there) + let position = buffer.position(); + let size = std::mem::size_of::(); + let bytes = unsafe { std::slice::from_raw_parts(&val as *const T as *const u8, size) }; + buffer.write_all(bytes)?; + + Ok(MemoryWriter { + position: position as u32, + size, + phantom: std::marker::PhantomData:: {}, + }) + } + + /// Create a slot for a type T in the buffer, we can fill later with real values. + /// This function fills it with `Default::default()`, which is less performant than + /// using uninitialized memory, but safe. + pub fn alloc(buffer: &mut Cursor>) -> Result { + // Filling out the buffer with default-values + let val: T = Default::default(); + Self::alloc_with_val(buffer, val) + } + + /// Write actual values in the buffer-slot we got during `alloc()` + pub fn set_value(&mut self, buffer: &mut Cursor>, val: T) -> Result<()> { + // Save whereever the current cursor stands in the buffer + let curr_pos = buffer.position(); + + // Write the actual value we want at our position that + // was determined by `alloc()` into the buffer + buffer.set_position(self.position as u64); + let bytes = unsafe { + std::slice::from_raw_parts(&val as *const T as *const u8, std::mem::size_of::()) + }; + let res = buffer.write_all(bytes); + + // Resetting whereever we were before updating this + // regardless of the write-result + buffer.set_position(curr_pos); + + res?; + Ok(()) + } + + pub fn location(&self) -> MDLocationDescriptor { + MDLocationDescriptor { + data_size: std::mem::size_of::() as u32, + rva: self.position, + } + } +} + +#[derive(Debug, PartialEq)] +pub struct MemoryArrayWriter { + pub position: MDRVA, + array_size: usize, + phantom: std::marker::PhantomData, +} + +impl MemoryArrayWriter +where + T: Default + Sized, +{ + /// Create a slot for a type T in the buffer, we can fill in the values in one go. + pub fn alloc_from_array(buffer: &mut Cursor>, array: &[T]) -> Result { + // Get position of this value (e.g. before we add ourselves there) + let position = buffer.position(); + for val in array { + let bytes = unsafe { + std::slice::from_raw_parts(val as *const T as *const u8, std::mem::size_of::()) + }; + buffer.write_all(bytes)?; + } + + Ok(MemoryArrayWriter { + position: position as u32, + array_size: array.len(), + phantom: std::marker::PhantomData:: {}, + }) + } + + /// Create a slot for a type T in the buffer, we can fill later with real values. + /// This function fills it with `Default::default()`, which is less performant than + /// using uninitialized memory, but safe. + pub fn alloc_array(buffer: &mut Cursor>, array_size: usize) -> Result { + // Get position of this value (e.g. before we add ourselves there) + let position = buffer.position(); + for _ in 0..array_size { + // Filling out the buffer with default-values + let val: T = Default::default(); + let bytes = unsafe { + std::slice::from_raw_parts(&val as *const T as *const u8, std::mem::size_of::()) + }; + buffer.write_all(bytes)?; + } + + Ok(MemoryArrayWriter { + position: position as u32, + array_size, + phantom: std::marker::PhantomData:: {}, + }) + } + + /// Write actual values in the buffer-slot we got during `alloc()` + pub fn set_value_at( + &mut self, + buffer: &mut Cursor>, + val: T, + index: usize, + ) -> Result<()> { + // Save whereever the current cursor stands in the buffer + let curr_pos = buffer.position(); + + // Write the actual value we want at our position that + // was determined by `alloc()` into the buffer + buffer.set_position(self.position as u64 + (std::mem::size_of::() * index) as u64); + let bytes = unsafe { + std::slice::from_raw_parts(&val as *const T as *const u8, std::mem::size_of::()) + }; + let res = buffer.write_all(bytes); + + // Resetting whereever we were before updating this + // regardless of the write-result + buffer.set_position(curr_pos); + + res?; + Ok(()) + } + + pub fn location(&self) -> MDLocationDescriptor { + MDLocationDescriptor { + data_size: (self.array_size * std::mem::size_of::()) as u32, + rva: self.position, + } + } + + pub fn location_of_index(&self, idx: usize) -> MDLocationDescriptor { + MDLocationDescriptor { + data_size: std::mem::size_of::() as u32, + rva: self.position + (std::mem::size_of::() * idx) as u32, + } + } +} + +pub fn write_string_to_location( + buffer: &mut Cursor>, + text: &str, +) -> Result { + let letters: Vec = text.encode_utf16().collect(); + + // First write size of the string (x letters in u16, times the size of u16) + let text_header = MemoryWriter::::alloc_with_val( + buffer, + (letters.len() * std::mem::size_of::()).try_into()?, + )?; + + // Then write utf-16 letters after that + let mut text_section = MemoryArrayWriter::::alloc_array(buffer, letters.len())?; + for (index, letter) in letters.iter().enumerate() { + text_section.set_value_at(buffer, *letter, index)?; + } + + let mut location = text_header.location(); + location.data_size += text_section.location().data_size; + + Ok(location) +} diff --git a/third_party/rust/minidump_writer_linux/src/sections/systeminfo_stream.rs b/third_party/rust/minidump_writer_linux/src/sections/systeminfo_stream.rs new file mode 100644 index 000000000000..2d17edbcd7d4 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/sections/systeminfo_stream.rs @@ -0,0 +1,19 @@ +use crate::dumper_cpu_info::{write_cpu_information, write_os_information}; +use crate::minidump_format::*; +use crate::minidump_writer::DumpBuf; +use crate::sections::MemoryWriter; +use crate::Result; + +pub fn write(buffer: &mut DumpBuf) -> Result { + let mut info_section = MemoryWriter::::alloc(buffer)?; + let dirent = MDRawDirectory { + stream_type: MDStreamType::SystemInfoStream as u32, + location: info_section.location(), + }; + let mut info: MDRawSystemInfo = Default::default(); + write_cpu_information(&mut info)?; + write_os_information(buffer, &mut info)?; + + info_section.set_value(buffer, info)?; + Ok(dirent) +} diff --git a/third_party/rust/minidump_writer_linux/src/sections/thread_list_stream.rs b/third_party/rust/minidump_writer_linux/src/sections/thread_list_stream.rs new file mode 100644 index 000000000000..0abd436c2c4b --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/sections/thread_list_stream.rs @@ -0,0 +1,235 @@ +use crate::linux_ptrace_dumper::LinuxPtraceDumper; +use crate::minidump_cpu::RawContextCPU; +use crate::minidump_format::*; +use crate::minidump_writer::{CrashingThreadContext, DumpBuf, MinidumpWriter}; +use crate::sections::{MemoryArrayWriter, MemoryWriter}; +use crate::Result; +use std::convert::TryInto; +use std::io::Write; + +// The following kLimit* constants are for when minidump_size_limit_ is set +// and the minidump size might exceed it. +// +// Estimate for how big each thread's stack will be (in bytes). +const LIMIT_AVERAGE_THREAD_STACK_LENGTH: usize = 8 * 1024; +// Number of threads whose stack size we don't want to limit. These base +// threads will simply be the first N threads returned by the dumper (although +// the crashing thread will never be limited). Threads beyond this count are +// the extra threads. +const LIMIT_BASE_THREAD_COUNT: usize = 20; +// Maximum stack size to dump for any extra thread (in bytes). +const LIMIT_MAX_EXTRA_THREAD_STACK_LEN: usize = 2 * 1024; +// Make sure this number of additional bytes can fit in the minidump +// (exclude the stack data). +const LIMIT_MINIDUMP_FUDGE_FACTOR: u64 = 64 * 1024; + +#[derive(Debug, Clone, Copy)] +enum MaxStackLen { + None, + Len(usize), +} + +pub fn write( + config: &mut MinidumpWriter, + buffer: &mut DumpBuf, + dumper: &LinuxPtraceDumper, +) -> Result { + let num_threads = dumper.threads.len(); + // Memory looks like this: + // ... + + let list_header = MemoryWriter::::alloc_with_val(buffer, num_threads as u32)?; + + let mut dirent = MDRawDirectory { + stream_type: MDStreamType::ThreadListStream as u32, + location: list_header.location(), + }; + + let mut thread_list = MemoryArrayWriter::::alloc_array(buffer, num_threads)?; + dirent.location.data_size += thread_list.location().data_size; + // If there's a minidump size limit, check if it might be exceeded. Since + // most of the space is filled with stack data, just check against that. + // If this expects to exceed the limit, set extra_thread_stack_len such + // that any thread beyond the first kLimitBaseThreadCount threads will + // have only kLimitMaxExtraThreadStackLen bytes dumped. + let mut extra_thread_stack_len = MaxStackLen::None; // default to no maximum + if let Some(minidump_size_limit) = config.minidump_size_limit { + let estimated_total_stack_size = (num_threads * LIMIT_AVERAGE_THREAD_STACK_LENGTH) as u64; + let curr_pos = buffer.position(); + let estimated_minidump_size = + curr_pos + estimated_total_stack_size + LIMIT_MINIDUMP_FUDGE_FACTOR; + if estimated_minidump_size > minidump_size_limit { + extra_thread_stack_len = MaxStackLen::Len(LIMIT_MAX_EXTRA_THREAD_STACK_LEN); + } + } + + for (idx, item) in dumper.threads.clone().iter().enumerate() { + let mut thread = MDRawThread::default(); + thread.thread_id = (*item).try_into()?; + + // We have a different source of information for the crashing thread. If + // we used the actual state of the thread we would find it running in the + // signal handler with the alternative stack, which would be deeply + // unhelpful. + if config.crash_context.is_some() && thread.thread_id == config.blamed_thread as u32 { + let crash_context = config.crash_context.as_ref().unwrap(); + let instruction_ptr = crash_context.get_instruction_pointer() as usize; + let stack_pointer = crash_context.get_stack_pointer() as usize; + fill_thread_stack( + config, + buffer, + dumper, + &mut thread, + instruction_ptr, + stack_pointer, + MaxStackLen::None, + )?; + // Copy 256 bytes around crashing instruction pointer to minidump. + let ip_memory_size: usize = 256; + // Bound it to the upper and lower bounds of the memory map + // it's contained within. If it's not in mapped memory, + // don't bother trying to write it. + for mapping in &dumper.mappings { + if instruction_ptr < mapping.start_address + || instruction_ptr >= mapping.start_address + mapping.size + { + continue; + } + + let mut ip_memory_d: MDMemoryDescriptor = Default::default(); + + // Try to get 128 bytes before and after the IP, but + // settle for whatever's available. + ip_memory_d.start_of_memory_range = + std::cmp::max(mapping.start_address, instruction_ptr - ip_memory_size / 2) + as u64; + let end_of_range = std::cmp::min( + mapping.start_address + mapping.size, + instruction_ptr + ip_memory_size / 2, + ) as u64; + ip_memory_d.memory.data_size = + (end_of_range - ip_memory_d.start_of_memory_range) as u32; + + let memory_copy = LinuxPtraceDumper::copy_from_process( + thread.thread_id as i32, + ip_memory_d.start_of_memory_range as *mut libc::c_void, + ip_memory_d.memory.data_size as isize, + )?; + + let mem_section = MemoryArrayWriter::alloc_from_array(buffer, &memory_copy)?; + ip_memory_d.memory = mem_section.location(); + config.memory_blocks.push(ip_memory_d); + + break; + } + // let cpu = MemoryWriter::alloc(buffer, &memory_copy)?; + let mut cpu: RawContextCPU = Default::default(); + let crash_context = config.crash_context.as_ref().unwrap(); + crash_context.fill_cpu_context(&mut cpu); + let cpu_section = MemoryWriter::alloc_with_val(buffer, cpu)?; + thread.thread_context = cpu_section.location(); + + config.crashing_thread_context = + CrashingThreadContext::CrashContext(cpu_section.location()); + } else { + let info = dumper.get_thread_info_by_index(idx)?; + let max_stack_len = + if config.minidump_size_limit.is_some() && idx >= LIMIT_BASE_THREAD_COUNT { + extra_thread_stack_len + } else { + MaxStackLen::None // default to no maximum for this thread + }; + let instruction_ptr = info.get_instruction_pointer() as usize; + fill_thread_stack( + config, + buffer, + dumper, + &mut thread, + instruction_ptr, + info.stack_pointer, + max_stack_len, + )?; + + let mut cpu = RawContextCPU::default(); + info.fill_cpu_context(&mut cpu); + let cpu_section = MemoryWriter::::alloc_with_val(buffer, cpu)?; + thread.thread_context = cpu_section.location(); + if item == &config.blamed_thread { + // This is the crashing thread of a live process, but + // no context was provided, so set the crash address + // while the instruction pointer is already here. + config.crashing_thread_context = CrashingThreadContext::CrashContextPlusAddress(( + cpu_section.location(), + info.get_instruction_pointer(), + )); + } + } + thread_list.set_value_at(buffer, thread, idx)?; + } + Ok(dirent) +} + +fn fill_thread_stack( + config: &mut MinidumpWriter, + buffer: &mut DumpBuf, + dumper: &LinuxPtraceDumper, + thread: &mut MDRawThread, + instruction_ptr: usize, + stack_ptr: usize, + max_stack_len: MaxStackLen, +) -> Result<()> { + thread.stack.start_of_memory_range = stack_ptr.try_into()?; + thread.stack.memory.data_size = 0; + thread.stack.memory.rva = buffer.position() as u32; + + if let Ok((mut stack, mut stack_len)) = dumper.get_stack_info(stack_ptr) { + if let MaxStackLen::Len(max_stack_len) = max_stack_len { + if stack_len > max_stack_len { + stack_len = max_stack_len; + + // Skip empty chunks of length max_stack_len. + // Meaning != 0 + if stack_len > 0 { + while stack + stack_len < stack_ptr { + stack += stack_len; + } + } + } + } + + let mut stack_bytes = LinuxPtraceDumper::copy_from_process( + thread.thread_id.try_into()?, + stack as *mut libc::c_void, + stack_len.try_into()?, + )?; + let stack_pointer_offset = stack_ptr - stack; + if config.skip_stacks_if_mapping_unreferenced { + if let Some(principal_mapping) = &config.principal_mapping { + let low_addr = principal_mapping.system_mapping_info.start_address; + let high_addr = principal_mapping.system_mapping_info.end_address; + if (instruction_ptr < low_addr || instruction_ptr > high_addr) + && !principal_mapping + .stack_has_pointer_to_mapping(&stack_bytes, stack_pointer_offset) + { + return Ok(()); + } + } else { + return Ok(()); + } + } + + if config.sanitize_stack { + dumper.sanitize_stack_copy(&mut stack_bytes, stack_ptr, stack_pointer_offset)?; + } + + let stack_location = MDLocationDescriptor { + data_size: stack_bytes.len() as u32, + rva: buffer.position() as u32, + }; + buffer.write_all(&stack_bytes)?; + thread.stack.start_of_memory_range = stack as u64; + thread.stack.memory = stack_location; + config.memory_blocks.push(thread.stack.clone()); + } + Ok(()) +} diff --git a/third_party/rust/minidump_writer_linux/src/thread_info/mod.rs b/third_party/rust/minidump_writer_linux/src/thread_info/mod.rs new file mode 100644 index 000000000000..94a78d33b3e4 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/thread_info/mod.rs @@ -0,0 +1,159 @@ +use crate::Result; +use nix::errno::Errno; +use nix::sys::ptrace; +use nix::unistd; +use std::convert::TryInto; +use std::io::{self, BufRead}; +use std::path; + +pub type Pid = i32; + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[path = "thread_info_x86.rs"] +mod imp; +#[cfg(target_arch = "arm")] +#[path = "thread_info_arm.rs"] +mod imp; +#[cfg(target_arch = "aarch64")] +#[path = "thread_info_aarch64.rs"] +mod imp; +#[cfg(target_arch = "mips")] +#[path = "thread_info_mips.rs"] +mod imp; + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub type ThreadInfo = imp::ThreadInfoX86; +#[cfg(target_arch = "arm")] +pub type ThreadInfo = imp::ThreadInfoArm; +#[cfg(target_arch = "aarch64")] +pub type ThreadInfo = imp::ThreadInfoAarch64; +#[cfg(target_arch = "mips")] +pub type ThreadInfo = imp::ThreadInfoMips; + +#[derive(Debug)] +#[allow(non_camel_case_types)] +enum NT_Elf { + NT_NONE = 0, + NT_PRSTATUS = 1, + NT_PRFPREG = 2, + //NT_PRPSINFO = 3, + //NT_TASKSTRUCT = 4, + //NT_AUXV = 6, +} + +pub fn to_u128(slice: &[u32]) -> Vec { + let mut res = Vec::new(); + for chunk in slice.chunks_exact(4) { + let value = u128::from_ne_bytes( + chunk + .iter() + .map(|x| x.to_ne_bytes().to_vec()) + .flatten() + .collect::>() + .as_slice() + .try_into() // Make slice into fixed size array + .unwrap(), // Which has to work as we know the numbers work out + ); + res.push(value) + } + res +} + +trait CommonThreadInfo { + fn get_ppid_and_tgid(tid: Pid) -> Result<(Pid, Pid)> { + let mut ppid = -1; + let mut tgid = -1; + + let status_path = path::PathBuf::from(format!("/proc/{}/status", tid)); + let status_file = std::fs::File::open(status_path)?; + for line in io::BufReader::new(status_file).lines() { + let l = line?; + match &l[0..6] { + "Tgid:\t" => tgid = l[6..].parse::()?, + "PPid:\t" => ppid = l[6..].parse::()?, + _ => continue, + } + } + if ppid == -1 || tgid == -1 { + return Err("ppid or tgid is -1".into()); + } + Ok((ppid, tgid)) + } + + /// SLIGHTLY MODIFIED COPY FROM CRATE nix + /// Function for ptrace requests that return values from the data field. + /// Some ptrace get requests populate structs or larger elements than `c_long` + /// and therefore use the data field to return values. This function handles these + /// requests. + fn ptrace_get_data( + request: ptrace::Request, + flag: Option, + pid: nix::unistd::Pid, + ) -> Result { + let mut data = std::mem::MaybeUninit::uninit(); + let res = unsafe { + libc::ptrace( + request as ptrace::RequestType, + libc::pid_t::from(pid), + flag.unwrap_or(NT_Elf::NT_NONE), + data.as_mut_ptr() as *const _ as *const libc::c_void, + ) + }; + Errno::result(res)?; + Ok(unsafe { data.assume_init() }) + } + + /// SLIGHTLY MODIFIED COPY FROM CRATE nix + /// Function for ptrace requests that return values from the data field. + /// Some ptrace get requests populate structs or larger elements than `c_long` + /// and therefore use the data field to return values. This function handles these + /// requests. + fn ptrace_get_data_via_io( + request: ptrace::Request, + flag: Option, + pid: nix::unistd::Pid, + ) -> Result { + let mut data = std::mem::MaybeUninit::uninit(); + let io = libc::iovec { + iov_base: data.as_mut_ptr() as *mut libc::c_void, + iov_len: std::mem::size_of::(), + }; + let res = unsafe { + libc::ptrace( + request as ptrace::RequestType, + libc::pid_t::from(pid), + flag.unwrap_or(NT_Elf::NT_NONE), + &io as *const _ as *const libc::c_void, + ) + }; + Errno::result(res)?; + Ok(unsafe { data.assume_init() }) + } + + /// COPY FROM CRATE nix BECAUSE ITS NOT PUBLIC + fn ptrace_peek( + request: ptrace::Request, + pid: unistd::Pid, + addr: ptrace::AddressType, + data: *mut libc::c_void, + ) -> nix::Result { + let ret = unsafe { + Errno::clear(); + libc::ptrace( + request as ptrace::RequestType, + libc::pid_t::from(pid), + addr, + data, + ) + }; + match Errno::result(ret) { + Ok(..) | Err(nix::Error::Sys(Errno::UnknownErrno)) => Ok(ret), + err @ Err(..) => err, + } + } +} +impl ThreadInfo { + pub fn create(pid: Pid, tid: Pid) -> Result { + Self::create_impl(pid, tid) + } +} diff --git a/third_party/rust/minidump_writer_linux/src/thread_info/thread_info_aarch64.rs b/third_party/rust/minidump_writer_linux/src/thread_info/thread_info_aarch64.rs new file mode 100644 index 000000000000..70099cd0c8a1 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/thread_info/thread_info_aarch64.rs @@ -0,0 +1,45 @@ +use super::Pid; +use crate::minidump_cpu::imp::{MDARM64RegisterNumbers, MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT}; +use libc; + +#[cfg(target_arch = "aarch64")] +#[derive(Debug)] +pub struct ThreadInfoAarch64 { + pub stack_pointer: libc::c_ulonglong, + pub tgid: Pid, // thread group id + pub ppid: Pid, // parent process + pub regs: libc::user_regs_struct, + pub fpregs: libc::user_fpsimd_struct, +} + +impl ThreadInfoAarch64 { + pub fn get_instruction_pointer(&self) -> libc::c_ulonglong { + self.regs.pc + } + + pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { + // out->context_flags = MD_CONTEXT_ARM64_FULL_OLD; + out.cpsr = self.regs.pstate as u32; + for idx in 0..MDARM64RegisterNumbers::MD_CONTEXT_ARM64_REG_SP { + out.iregs[idx] = self.regs.regs[idx]; + } + out.iregs[MDARM64RegisterNumbers::MD_CONTEXT_ARM64_REG_SP] = self.regs.sp; + out.iregs[MDARM64RegisterNumbers::MD_CONTEXT_ARM64_REG_PC] = self.regs.pc; + out.float_save.fpsr = self.fpregs.fpsr; + out.float_save.fpcr = self.fpregs.fpcr; + + // my_memcpy(&out->float_save.regs, &fpregs.vregs, + // MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT * 16); + out.float_save.regs = self + .fpregs + .vregs + .iter() + .map(|x| x.to_ne_bytes().to_vec()) + .flatten() + .take(MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT * 16) + .collect::>() + .as_slice() + .try_into() // Make slice into fixed size array + .unwrap(); // Which has to work as we know the numbers work out + } +} diff --git a/third_party/rust/minidump_writer_linux/src/thread_info/thread_info_arm.rs b/third_party/rust/minidump_writer_linux/src/thread_info/thread_info_arm.rs new file mode 100644 index 000000000000..afe40003b641 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/thread_info/thread_info_arm.rs @@ -0,0 +1,35 @@ +use super::Pid; +use crate::minidump_cpu::imp::MD_CONTEXT_ARM_GPR_COUNT; +use libc; + +#[derive(Debug)] +pub struct ThreadInfoArm { + pub stack_pointer: libc::c_ulonglong, + pub tgid: Pid, // thread group id + pub ppid: Pid, // parent process + pub regs: libc::user_regs, + pub fpregs: libc::user_fpregs, +} + +impl ThreadInfoArm { + pub fn get_instruction_pointer(&self) -> libc::c_ulonglong { + self.regs.uregs[15] + } + + pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { + // out->context_flags = MD_CONTEXT_ARM_FULL; + for idx in 0..MD_CONTEXT_ARM_GPR_COUNT { + out.iregs[idx] = self.regs.uregs[idx]; + } + // No CPSR register in ThreadInfo(it's not accessible via ptrace) + out.cpsr = 0; + + #[not(cfg(target_os = "android"))] + { + out.float_save.fpscr = self.fpregs.fpsr | (self.fpregs.fpcr as u64) << 32; + // TODO: + // my_memset(&out->float_save.regs, 0, sizeof(out->float_save.regs)); + // my_memset(&out->float_save.extra, 0, sizeof(out->float_save.extra)); + } + } +} diff --git a/third_party/rust/minidump_writer_linux/src/thread_info/thread_info_mips.rs b/third_party/rust/minidump_writer_linux/src/thread_info/thread_info_mips.rs new file mode 100644 index 000000000000..edbd2d0e93d4 --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/thread_info/thread_info_mips.rs @@ -0,0 +1,53 @@ +use super::Pid; +use libc; + +#[derive(Debug)] +pub struct ThreadInfoMips { + pub stack_pointer: libc::c_ulonglong, + pub tgid: Pid, // thread group id + pub ppid: Pid, // parent process + // Use the structure defined in + pub mcontext: libc::mcontext_t, +} + +impl ThreadInfoMips { + pub fn get_instruction_pointer(&self) -> libc::c_ulonglong { + self.mcontext.pc + } + + pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { + // #if _MIPS_SIM == _ABI64 + // out->context_flags = MD_CONTEXT_MIPS64_FULL; + // #elif _MIPS_SIM == _ABIO32 + // out->context_flags = MD_CONTEXT_MIPS_FULL; + for idx in 0..MD_CONTEXT_MIPS_GPR_COUNT { + out.iregs[idx] = self.mcontext.gregs[idx]; + } + + out.mdhi = self.mcontext.mdhi; + out.mdlo = self.mcontext.mdlo; + out.dsp_control = self.mcontext.dsp; + + out.hi[0] = self.mcontext.hi1; + out.lo[0] = self.mcontext.lo1; + out.hi[1] = self.mcontext.hi2; + out.lo[1] = self.mcontext.lo2; + out.hi[2] = self.mcontext.hi3; + out.lo[2] = self.mcontext.lo3; + + out.epc = self.mcontext.pc; + out.badvaddr = 0; // Not stored in mcontext + out.status = 0; // Not stored in mcontext + out.cause = 0; // Not stored in mcontext + + for idx in 0..MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT { + out.float_save.regs[idx] = self.mcontext.fpregs.fp_r.fp_fregs[idx]._fp_fregs; + } + + out.float_save.fpcsr = mcontext.fpc_csr; + + // #if _MIPS_SIM == _ABIO32 + // out.float_save.fir = self.mcontext.fpc_eir; + // #endif + } +} diff --git a/third_party/rust/minidump_writer_linux/src/thread_info/thread_info_x86.rs b/third_party/rust/minidump_writer_linux/src/thread_info/thread_info_x86.rs new file mode 100644 index 000000000000..394c39b7d1aa --- /dev/null +++ b/third_party/rust/minidump_writer_linux/src/thread_info/thread_info_x86.rs @@ -0,0 +1,324 @@ +use super::{CommonThreadInfo, NT_Elf, Pid}; +use crate::minidump_cpu::imp::*; +use crate::minidump_cpu::RawContextCPU; +#[cfg(target_arch = "x86_64")] +use crate::thread_info::to_u128; +use crate::Result; +use core::mem::size_of_val; +use libc; +use libc::user; +use memoffset; +use nix::sys::ptrace; +use nix::unistd; +#[cfg(target_arch = "x86")] +use std::convert::TryInto; + +const NUM_DEBUG_REGISTERS: usize = 8; + +pub struct ThreadInfoX86 { + pub stack_pointer: usize, + pub tgid: Pid, // thread group id + pub ppid: Pid, // parent process + pub regs: libc::user_regs_struct, + pub fpregs: libc::user_fpregs_struct, + #[cfg(target_arch = "x86_64")] + pub dregs: [libc::c_ulonglong; NUM_DEBUG_REGISTERS], + #[cfg(target_arch = "x86")] + pub dregs: [libc::c_int; NUM_DEBUG_REGISTERS], + #[cfg(target_arch = "x86")] + pub fpxregs: libc::user_fpxregs_struct, +} + +impl CommonThreadInfo for ThreadInfoX86 {} + +impl ThreadInfoX86 { + // nix currently doesn't support PTRACE_GETREGSET, so we have to do it ourselves + fn getregset(pid: Pid) -> Result { + Self::ptrace_get_data_via_io::( + ptrace::Request::PTRACE_GETREGSET, + Some(NT_Elf::NT_PRSTATUS), + nix::unistd::Pid::from_raw(pid), + ) + } + + // nix currently doesn't support PTRACE_GETREGSET, so we have to do it ourselves + fn getfpregset(pid: Pid) -> Result { + Self::ptrace_get_data_via_io::( + ptrace::Request::PTRACE_GETREGSET, + Some(NT_Elf::NT_PRFPREG), + nix::unistd::Pid::from_raw(pid), + ) + } + + // nix currently doesn't support PTRACE_GETFPREGS, so we have to do it ourselves + fn getfpregs(pid: Pid) -> Result { + Self::ptrace_get_data::( + ptrace::Request::PTRACE_GETFPREGS, + None, + nix::unistd::Pid::from_raw(pid), + ) + } + + // nix currently doesn't support PTRACE_GETFPXREGS, so we have to do it ourselves + #[cfg(target_arch = "x86")] + fn getfpxregs(pid: Pid) -> Result { + Self::ptrace_get_data::( + ptrace::Request::PTRACE_GETFPXREGS, + None, + nix::unistd::Pid::from_raw(pid), + ) + } + + fn peek_user(pid: Pid, addr: ptrace::AddressType) -> nix::Result { + Self::ptrace_peek( + ptrace::Request::PTRACE_PEEKUSER, + nix::unistd::Pid::from_raw(pid), + addr, + std::ptr::null_mut(), + ) + } + + pub fn create_impl(_pid: Pid, tid: Pid) -> Result { + let (ppid, tgid) = Self::get_ppid_and_tgid(tid)?; + let regs = Self::getregset(tid).or_else(|_| ptrace::getregs(unistd::Pid::from_raw(tid)))?; + let fpregs = Self::getfpregset(tid).or_else(|_| Self::getfpregs(tid))?; + #[cfg(target_arch = "x86")] + let fpxregs: libc::user_fpxregs_struct; + #[cfg(target_arch = "x86")] + { + if cfg!(target_feature = "fxsr") { + fpxregs = Self::getfpxregs(tid)?; + } else { + fpxregs = unsafe { std::mem::zeroed() }; + } + } + + #[cfg(target_arch = "x86_64")] + let mut dregs: [libc::c_ulonglong; NUM_DEBUG_REGISTERS] = [0; NUM_DEBUG_REGISTERS]; + #[cfg(target_arch = "x86")] + let mut dregs: [libc::c_int; NUM_DEBUG_REGISTERS] = [0; NUM_DEBUG_REGISTERS]; + + let debug_offset = memoffset::offset_of!(user, u_debugreg); + let elem_offset = size_of_val(&dregs[0]); + for idx in 0..NUM_DEBUG_REGISTERS { + let chunk = Self::peek_user( + tid, + (debug_offset + idx * elem_offset) as ptrace::AddressType, + )?; + #[cfg(target_arch = "x86_64")] + { + dregs[idx] = chunk as u64; // libc / ptrace is very messy wrt int types used... + } + #[cfg(target_arch = "x86")] + { + dregs[idx] = chunk as i32; // libc / ptrace is very messy wrt int types used... + } + } + + #[cfg(target_arch = "x86_64")] + let stack_pointer = regs.rsp as usize; + #[cfg(target_arch = "x86")] + let stack_pointer = regs.esp as usize; + + Ok(ThreadInfoX86 { + stack_pointer, + tgid, + ppid, + regs, + fpregs, + dregs, + #[cfg(target_arch = "x86")] + fpxregs, + }) + } + + #[cfg(target_arch = "x86_64")] + pub fn get_instruction_pointer(&self) -> usize { + self.regs.rip as usize + } + + #[cfg(target_arch = "x86")] + pub fn get_instruction_pointer(&self) -> usize { + self.regs.eip as usize + } + + #[cfg(target_arch = "x86_64")] + pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { + out.context_flags = MD_CONTEXT_AMD64_FULL | MD_CONTEXT_AMD64_SEGMENTS; + + out.cs = self.regs.cs as u16; // TODO: This is u64, do we loose information by doing this? + + out.ds = self.regs.ds as u16; // TODO: This is u64, do we loose information by doing this? + out.es = self.regs.es as u16; // TODO: This is u64, do we loose information by doing this? + out.fs = self.regs.fs as u16; // TODO: This is u64, do we loose information by doing this? + out.gs = self.regs.gs as u16; // TODO: This is u64, do we loose information by doing this? + + out.ss = self.regs.ss as u16; // TODO: This is u64, do we loose information by doing this? + out.eflags = self.regs.eflags as u32; // TODO: This is u64, do we loose information by doing this? + + out.dr0 = self.dregs[0]; + out.dr1 = self.dregs[1]; + out.dr2 = self.dregs[2]; + out.dr3 = self.dregs[3]; + // 4 and 5 deliberatly omitted because they aren't included in the minidump + // format. + out.dr6 = self.dregs[6]; + out.dr7 = self.dregs[7]; + + out.rax = self.regs.rax; + out.rcx = self.regs.rcx; + out.rdx = self.regs.rdx; + out.rbx = self.regs.rbx; + + out.rsp = self.regs.rsp; + + out.rbp = self.regs.rbp; + out.rsi = self.regs.rsi; + out.rdi = self.regs.rdi; + out.r8 = self.regs.r8; + out.r9 = self.regs.r9; + out.r10 = self.regs.r10; + out.r11 = self.regs.r11; + out.r12 = self.regs.r12; + out.r13 = self.regs.r13; + out.r14 = self.regs.r14; + out.r15 = self.regs.r15; + + out.rip = self.regs.rip; + + out.flt_save.control_word = self.fpregs.cwd; + out.flt_save.status_word = self.fpregs.swd; + out.flt_save.tag_word = self.fpregs.ftw as u8; // TODO: This is u16, do we loose information by doing this? + out.flt_save.error_opcode = self.fpregs.fop; + out.flt_save.error_offset = self.fpregs.rip as u32; // TODO: This is u64, do we loose information by doing this? + out.flt_save.error_selector = 0; // We don't have this. + out.flt_save.data_offset = self.fpregs.rdp as u32; // TODO: This is u64, do we loose information by doing this? + out.flt_save.data_selector = 0; // We don't have this. + out.flt_save.mx_csr = self.fpregs.mxcsr; + out.flt_save.mx_csr_mask = self.fpregs.mxcr_mask; + + let data = to_u128(&self.fpregs.st_space); + for idx in 0..data.len() { + out.flt_save.float_registers[idx] = data[idx]; + } + + let data = to_u128(&self.fpregs.xmm_space); + for idx in 0..data.len() { + out.flt_save.xmm_registers[idx] = data[idx]; + } + // my_memcpy(&out.flt_save.float_registers, &self.fpregs.st_space, 8 * 16); + // my_memcpy(&out.flt_save.xmm_registers, &self.fpregs.xmm_space, 16 * 16); + } + + #[cfg(target_arch = "x86")] + pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { + out.context_flags = MD_CONTEXT_X86_ALL; + + out.dr0 = self.dregs[0] as u32; + out.dr3 = self.dregs[3] as u32; + out.dr1 = self.dregs[1] as u32; + out.dr2 = self.dregs[2] as u32; + // 4 and 5 deliberatly omitted because they aren't included in the minidump + // format. + out.dr6 = self.dregs[6] as u32; + out.dr7 = self.dregs[7] as u32; + + out.gs = self.regs.xgs as u32; + out.fs = self.regs.xfs as u32; + out.es = self.regs.xes as u32; + out.ds = self.regs.xds as u32; + + out.edi = self.regs.edi as u32; + out.esi = self.regs.esi as u32; + out.ebx = self.regs.ebx as u32; + out.edx = self.regs.edx as u32; + out.ecx = self.regs.ecx as u32; + out.eax = self.regs.eax as u32; + + out.ebp = self.regs.ebp as u32; + out.eip = self.regs.eip as u32; + out.cs = self.regs.xcs as u32; + out.eflags = self.regs.eflags as u32; + out.esp = self.regs.esp as u32; + out.ss = self.regs.xss as u32; + + out.float_save.control_word = self.fpregs.cwd as u32; + out.float_save.status_word = self.fpregs.swd as u32; + out.float_save.tag_word = self.fpregs.twd as u32; + out.float_save.error_offset = self.fpregs.fip as u32; + out.float_save.error_selector = self.fpregs.fcs as u32; + out.float_save.data_offset = self.fpregs.foo as u32; + out.float_save.data_selector = self.fpregs.fos as u32; + + // 8 registers * 10 bytes per register. + // my_memcpy(out->float_save.register_area, fpregs.st_space, 10 * 8); + out.float_save.register_area = self + .fpregs + .st_space + .iter() + .map(|x| x.to_ne_bytes().to_vec()) + .flatten() + .take(MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE) + .collect::>() + .as_slice() + .try_into() // Make slice into fixed size array + .unwrap(); // Which has to work as we know the numbers work out + + // This matches the Intel fpsave format. + let mut idx = 0; + for val in &(self.fpregs.cwd as u16).to_ne_bytes() { + out.extended_registers[idx] = *val; + idx += 1; + } + for val in &(self.fpregs.swd as u16).to_ne_bytes() { + out.extended_registers[idx] = *val; + idx += 1; + } + for val in &(self.fpregs.twd as u16).to_ne_bytes() { + out.extended_registers[idx] = *val; + idx += 1; + } + for val in &(self.fpxregs.fop as u16).to_ne_bytes() { + out.extended_registers[idx] = *val; + idx += 1; + } + for val in &(self.fpxregs.fip as u32).to_ne_bytes() { + out.extended_registers[idx] = *val; + idx += 1; + } + for val in &(self.fpxregs.fcs as u16).to_ne_bytes() { + out.extended_registers[idx] = *val; + idx += 1; + } + for val in &(self.fpregs.foo as u32).to_ne_bytes() { + out.extended_registers[idx] = *val; + idx += 1; + } + for val in &(self.fpregs.fos as u16).to_ne_bytes() { + out.extended_registers[idx] = *val; + idx += 1; + } + for val in &(self.fpxregs.mxcsr as u32).to_ne_bytes() { + out.extended_registers[idx] = *val; + idx += 1; + } + + // my_memcpy(out->extended_registers + 32, &fpxregs.st_space, 128); + idx = 32; + for val in &self.fpxregs.st_space { + for byte in &val.to_ne_bytes() { + out.extended_registers[idx] = *byte; + idx += 1; + } + } + + // my_memcpy(out->extended_registers + 160, &fpxregs.xmm_space, 128); + idx = 160; + for val in &self.fpxregs.xmm_space { + for byte in &val.to_ne_bytes() { + out.extended_registers[idx] = *byte; + idx += 1; + } + } + } +} diff --git a/third_party/rust/minidump_writer_linux/tests/common/mod.rs b/third_party/rust/minidump_writer_linux/tests/common/mod.rs new file mode 100644 index 000000000000..9014abfa2bde --- /dev/null +++ b/third_party/rust/minidump_writer_linux/tests/common/mod.rs @@ -0,0 +1,79 @@ +use std::io::{BufRead, BufReader, Write}; +use std::process::{Child, Command, Stdio}; + +#[allow(unused)] +pub fn spawn_child(command: &str, args: &[&str]) { + let mut cmd_object = Command::new("cargo"); + let mut cmd_ref = cmd_object + .env("RUST_BACKTRACE", "1") + .arg("run") + .arg("-q") + .arg("--bin") + .arg("test") + .arg("--") + .arg(command); + for arg in args { + cmd_ref = cmd_ref.arg(arg); + } + let child = cmd_ref.output().expect("failed to execute child"); + + println!("Child output:"); + std::io::stdout().write_all(&child.stdout).unwrap(); + std::io::stdout().write_all(&child.stderr).unwrap(); + assert_eq!(child.status.code().expect("No return value"), 0); +} + +#[allow(unused)] +pub fn start_child_and_wait_for_threads(num: usize) -> Child { + let mut child = Command::new("cargo") + .env("RUST_BACKTRACE", "1") + .arg("run") + .arg("-q") + .arg("--bin") + .arg("test") + .arg("--") + .arg("spawn_and_wait") + .arg(format!("{}", num)) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute child"); + + wait_for_threads(&mut child, num); + child +} + +#[allow(unused)] +pub fn wait_for_threads(child: &mut Child, num: usize) { + let mut f = BufReader::new(child.stdout.as_mut().expect("Can't open stdout")); + let mut lines = 0; + while lines < num { + let mut buf = String::new(); + match f.read_line(&mut buf) { + Ok(_) => { + if buf == "1\n" { + lines += 1; + } + } + Err(e) => { + panic!(e); + } + } + } +} + +#[allow(unused)] +pub fn start_child_and_return(command: &str) -> Child { + let mut child = Command::new("cargo") + .env("RUST_BACKTRACE", "1") + .arg("run") + .arg("-q") + .arg("--bin") + .arg("test") + .arg("--") + .arg(format!("{}", command)) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute child"); + + child +} diff --git a/third_party/rust/minidump_writer_linux/tests/minidump_writer.rs b/third_party/rust/minidump_writer_linux/tests/minidump_writer.rs new file mode 100644 index 000000000000..f31dc5bcaf6a --- /dev/null +++ b/third_party/rust/minidump_writer_linux/tests/minidump_writer.rs @@ -0,0 +1,583 @@ +use minidump::*; +use minidump_common::format::{GUID, MINIDUMP_STREAM_TYPE::*}; +use minidump_writer_linux::app_memory::AppMemory; +use minidump_writer_linux::crash_context::{fpstate_t, CrashContext}; +use minidump_writer_linux::linux_ptrace_dumper::LinuxPtraceDumper; +use minidump_writer_linux::maps_reader::{MappingEntry, MappingInfo, SystemMappingInfo}; +use minidump_writer_linux::minidump_writer::MinidumpWriter; +use minidump_writer_linux::thread_info::Pid; +use minidump_writer_linux::Result; +use nix::errno::Errno; +use nix::sys::signal::Signal; +use std::convert::TryInto; +use std::io::{BufRead, BufReader}; +use std::os::unix::process::ExitStatusExt; +use std::process::{Command, Stdio}; +use std::str::FromStr; + +mod common; +use common::*; + +#[derive(Debug, PartialEq)] +enum Context { + With, + Without, +} + +fn get_ucontext() -> Result { + let mut context = std::mem::MaybeUninit::::uninit(); + let res = unsafe { libc::getcontext(context.as_mut_ptr()) }; + Errno::result(res)?; + unsafe { Ok(context.assume_init()) } +} + +fn get_crash_context(tid: Pid) -> CrashContext { + let context = get_ucontext().expect("Failed to get ucontext"); + let siginfo: libc::siginfo_t = unsafe { std::mem::zeroed() }; + let float_state: fpstate_t = unsafe { std::mem::zeroed() }; + CrashContext { + siginfo, + tid, + context, + float_state, + } +} + +fn test_write_dump_helper(context: Context) { + let num_of_threads = 3; + let mut child = start_child_and_wait_for_threads(num_of_threads); + let pid = child.id() as i32; + + let mut tmpfile = tempfile::Builder::new() + .prefix("write_dump") + .tempfile() + .unwrap(); + + let mut tmp = MinidumpWriter::new(pid, pid); + if context == Context::With { + let crash_context = get_crash_context(pid); + tmp.set_crash_context(crash_context); + } + let in_memory_buffer = tmp.dump(&mut tmpfile).expect("Could not write minidump"); + child.kill().expect("Failed to kill process"); + + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + let meta = std::fs::metadata(tmpfile.path()).expect("Couldn't get metadata for tempfile"); + assert!(meta.len() > 0); + + let mem_slice = std::fs::read(tmpfile.path()).expect("Failed to minidump"); + assert_eq!(mem_slice.len(), in_memory_buffer.len()); + assert_eq!(mem_slice, in_memory_buffer); +} +#[test] +fn test_write_dump() { + test_write_dump_helper(Context::Without) +} +#[test] +fn test_write_dump_with_context() { + test_write_dump_helper(Context::With) +} + +fn test_write_and_read_dump_from_parent_helper(context: Context) { + let mut child = start_child_and_return("spawn_mmap_wait"); + let pid = child.id() as i32; + + let mut tmpfile = tempfile::Builder::new() + .prefix("write_and_read_dump") + .tempfile() + .unwrap(); + + let mut f = BufReader::new(child.stdout.as_mut().expect("Can't open stdout")); + let mut buf = String::new(); + let _ = f + .read_line(&mut buf) + .expect("Couldn't read address provided by child"); + let mut output = buf.split_whitespace(); + let mmap_addr = usize::from_str(output.next().unwrap()).expect("unable to parse mmap_addr"); + let memory_size = usize::from_str(output.next().unwrap()).expect("unable to parse memory_size"); + // Add information about the mapped memory. + let mapping = MappingInfo { + start_address: mmap_addr, + size: memory_size, + offset: 0, + executable: false, + name: Some("a fake mapping".to_string()), + system_mapping_info: SystemMappingInfo { + start_address: mmap_addr, + end_address: mmap_addr + memory_size, + }, + }; + + let identifier = vec![ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, + 0xFF, + ]; + let entry = MappingEntry { + mapping, + identifier, + }; + + let mut tmp = MinidumpWriter::new(pid, pid); + if context == Context::With { + let crash_context = get_crash_context(pid); + tmp.set_crash_context(crash_context); + } + + tmp.set_user_mapping_list(vec![entry]) + .dump(&mut tmpfile) + .expect("Could not write minidump"); + + child.kill().expect("Failed to kill process"); + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); + let module_list: MinidumpModuleList = dump + .get_stream() + .expect("Couldn't find stream MinidumpModuleList"); + let module = module_list + .module_at_address(mmap_addr as u64) + .expect("Couldn't find user mapping module"); + assert_eq!(module.base_address(), mmap_addr as u64); + assert_eq!(module.size(), memory_size as u64); + assert_eq!(module.code_file(), "a fake mapping"); + assert_eq!( + module.debug_identifier(), + Some("33221100554477668899AABBCCDDEEFF0".into()) + ); + + let _: MinidumpException = dump.get_stream().expect("Couldn't find MinidumpException"); + let _: MinidumpThreadList = dump.get_stream().expect("Couldn't find MinidumpThreadList"); + let _: MinidumpMemoryList = dump.get_stream().expect("Couldn't find MinidumpMemoryList"); + let _: MinidumpException = dump.get_stream().expect("Couldn't find MinidumpException"); + let _: MinidumpSystemInfo = dump.get_stream().expect("Couldn't find MinidumpSystemInfo"); + let _ = dump + .get_raw_stream(LinuxCpuInfo) + .expect("Couldn't find LinuxCpuInfo"); + let _ = dump + .get_raw_stream(LinuxProcStatus) + .expect("Couldn't find LinuxProcStatus"); + let _ = dump + .get_raw_stream(LinuxCmdLine) + .expect("Couldn't find LinuxCmdLine"); + let _ = dump + .get_raw_stream(LinuxEnviron) + .expect("Couldn't find LinuxEnviron"); + let _ = dump + .get_raw_stream(LinuxAuxv) + .expect("Couldn't find LinuxAuxv"); + let _ = dump + .get_raw_stream(LinuxMaps) + .expect("Couldn't find LinuxMaps"); + let _ = dump + .get_raw_stream(LinuxDsoDebug) + .expect("Couldn't find LinuxDsoDebug"); +} +#[test] +fn test_write_and_read_dump_from_parent() { + test_write_and_read_dump_from_parent_helper(Context::Without) +} +#[test] +fn test_write_and_read_dump_from_parent_with_context() { + test_write_and_read_dump_from_parent_helper(Context::With) +} + +fn test_write_with_additional_memory_helper(context: Context) { + let mut child = start_child_and_return("spawn_alloc_wait"); + let pid = child.id() as i32; + + let mut tmpfile = tempfile::Builder::new() + .prefix("additional_memory") + .tempfile() + .unwrap(); + + let mut f = BufReader::new(child.stdout.as_mut().expect("Can't open stdout")); + let mut buf = String::new(); + let _ = f + .read_line(&mut buf) + .expect("Couldn't read address provided by child"); + let mut output = buf.split_whitespace(); + let memory_addr = usize::from_str_radix(output.next().unwrap().trim_start_matches("0x"), 16) + .expect("unable to parse mmap_addr"); + let memory_size = usize::from_str(output.next().unwrap()).expect("unable to parse memory_size"); + + let app_memory = AppMemory { + ptr: memory_addr, + length: memory_size, + }; + + let mut tmp = MinidumpWriter::new(pid, pid); + if context == Context::With { + let crash_context = get_crash_context(pid); + tmp.set_crash_context(crash_context); + } + + tmp.set_app_memory(vec![app_memory]) + .dump(&mut tmpfile) + .expect("Could not write minidump"); + + child.kill().expect("Failed to kill process"); + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + // Read dump file and check its contents + let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); + + let section: MinidumpMemoryList = dump.get_stream().expect("Couldn't find MinidumpMemoryList"); + let region = section + .memory_at_address(memory_addr as u64) + .expect("Couldn't find memory region"); + + assert_eq!(region.base_address, memory_addr as u64); + assert_eq!(region.size, memory_size as u64); + + let mut values = Vec::::with_capacity(memory_size); + for idx in 0..memory_size { + values.push((idx % 255) as u8); + } + + // Verify memory contents. + assert_eq!(region.bytes, values); +} +#[test] +fn test_write_with_additional_memory() { + test_write_with_additional_memory_helper(Context::Without) +} +#[test] +fn test_write_with_additional_memory_with_context() { + test_write_with_additional_memory_helper(Context::With) +} + +#[test] +fn test_minidump_size_limit() { + let num_of_threads = 40; + let mut child = start_child_and_wait_for_threads(num_of_threads); + let pid = child.id() as i32; + + let mut total_normal_stack_size = 0; + let normal_file_size; + // First, write a minidump with no size limit. + { + let mut tmpfile = tempfile::Builder::new() + .prefix("write_dump_unlimited") + .tempfile() + .unwrap(); + + MinidumpWriter::new(pid, pid) + .dump(&mut tmpfile) + .expect("Could not write minidump"); + + let meta = std::fs::metadata(tmpfile.path()).expect("Couldn't get metadata for tempfile"); + assert!(meta.len() > 0); + + normal_file_size = meta.len(); + + // Read dump file and check its contents + let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); + let thread_list: MinidumpThreadList = + dump.get_stream().expect("Couldn't find MinidumpThreadList"); + for thread in thread_list.threads { + assert!(thread.raw.thread_id > 0); + assert!(thread.raw.stack.memory.data_size > 0); + total_normal_stack_size += thread.raw.stack.memory.data_size; + } + } + + // Second, write a minidump with a size limit big enough to not trigger + // anything. + { + // Set size limit arbitrarily 1MB larger than the normal file size -- such + // that the limiting code will not kick in. + let minidump_size_limit = normal_file_size + 1024 * 1024; + + let mut tmpfile = tempfile::Builder::new() + .prefix("write_dump_pseudolimited") + .tempfile() + .unwrap(); + + MinidumpWriter::new(pid, pid) + .set_minidump_size_limit(minidump_size_limit) + .dump(&mut tmpfile) + .expect("Could not write minidump"); + + let meta = std::fs::metadata(tmpfile.path()).expect("Couldn't get metadata for tempfile"); + + // Make sure limiting wasn't actually triggered. NOTE: If you fail this, + // first make sure that "minidump_size_limit" above is indeed set to a + // large enough value -- the limit-checking code in minidump_writer.rs + // does just a rough estimate. + assert_eq!(meta.len(), normal_file_size); + } + + // Third, write a minidump with a size limit small enough to be triggered. + { + // Set size limit to some arbitrary amount, such that the limiting code + // will kick in. The equation used to set this value was determined by + // simply reversing the size-limit logic a little bit in order to pick a + // size we know will trigger it. + + // Copyied from sections/thread_list_stream.rs + const LIMIT_AVERAGE_THREAD_STACK_LENGTH: u64 = 8 * 1024; + let mut minidump_size_limit = LIMIT_AVERAGE_THREAD_STACK_LENGTH * 40; + + // If, in reality, each of the threads' stack is *smaller* than + // kLimitAverageThreadStackLength, the normal file size could very well be + // smaller than the arbitrary limit that was just set. In that case, + // either of these numbers should trigger the size-limiting code, but we + // might as well pick the smallest. + if normal_file_size < minidump_size_limit { + minidump_size_limit = normal_file_size; + } + + let mut tmpfile = tempfile::Builder::new() + .prefix("write_dump_limited") + .tempfile() + .unwrap(); + + MinidumpWriter::new(pid, pid) + .set_minidump_size_limit(minidump_size_limit) + .dump(&mut tmpfile) + .expect("Could not write minidump"); + + let meta = std::fs::metadata(tmpfile.path()).expect("Couldn't get metadata for tempfile"); + assert!(meta.len() > 0); + // Make sure the file size is at least smaller than the original. If this + // fails because it's the same size, then the size-limit logic didn't kick + // in like it was supposed to. + assert!(meta.len() < normal_file_size); + + let mut total_limit_stack_size = 0; + // Read dump file and check its contents + let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); + let thread_list: MinidumpThreadList = + dump.get_stream().expect("Couldn't find MinidumpThreadList"); + for thread in thread_list.threads { + assert!(thread.raw.thread_id > 0); + assert!(thread.raw.stack.memory.data_size > 0); + total_limit_stack_size += thread.raw.stack.memory.data_size; + } + + // Make sure stack size shrunk by at least 1KB per extra thread. + // Note: The 1KB is arbitrary, and assumes that the thread stacks are big + // enough to shrink by that much. For example, if each thread stack was + // originally only 2KB, the current size-limit logic wouldn't actually + // shrink them because that's the size to which it tries to shrink. If + // you fail this part of the test due to something like that, the test + // logic should probably be improved to account for your situation. + + // Copyied from sections/thread_list_stream.rs + const LIMIT_BASE_THREAD_COUNT: usize = 20; + const MIN_PER_EXTRA_THREAD_STACK_REDUCTION: usize = 1024; + let min_expected_reduction = + (40 - LIMIT_BASE_THREAD_COUNT) * MIN_PER_EXTRA_THREAD_STACK_REDUCTION; + assert!(total_limit_stack_size < total_normal_stack_size - min_expected_reduction as u32); + } + + child.kill().expect("Failed to kill process"); + + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); +} + +#[test] +fn test_with_deleted_binary() { + let num_of_threads = 1; + let binary_copy_dir = tempfile::Builder::new() + .prefix("deleted_binary") + .tempdir() + .unwrap(); + let binary_copy = binary_copy_dir.as_ref().join("binary_copy"); + + let path: &'static str = std::env!("CARGO_BIN_EXE_test"); + + std::fs::copy(path, &binary_copy).expect("Failed to copy binary"); + let mem_slice = std::fs::read(&binary_copy).expect("Failed to read binary"); + + let mut child = Command::new(&binary_copy) + .env("RUST_BACKTRACE", "1") + .arg("spawn_and_wait") + .arg(format!("{}", num_of_threads)) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute child"); + wait_for_threads(&mut child, num_of_threads); + + let pid = child.id() as i32; + + let build_id = LinuxPtraceDumper::elf_file_identifier_from_mapped_file(&mem_slice) + .expect("Failed to get build_id"); + + let guid = GUID { + data1: u32::from_ne_bytes(build_id[0..4].try_into().unwrap()), + data2: u16::from_ne_bytes(build_id[4..6].try_into().unwrap()), + data3: u16::from_ne_bytes(build_id[6..8].try_into().unwrap()), + data4: build_id[8..16].try_into().unwrap(), + }; + + // guid_to_string() is not public in minidump, so copied it here + // And append a zero, because module IDs include an "age" field + // which is always zero on Linux. + let filtered = format!( + "{:08X}{:04X}{:04X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}0", + guid.data1, + guid.data2, + guid.data3, + guid.data4[0], + guid.data4[1], + guid.data4[2], + guid.data4[3], + guid.data4[4], + guid.data4[5], + guid.data4[6], + guid.data4[7], + ); + // Strip out dashes + //let mut filtered: String = identifier.chars().filter(|x| *x != '-').collect(); + + std::fs::remove_file(&binary_copy).expect("Failed to remove binary"); + + let mut tmpfile = tempfile::Builder::new() + .prefix("deleted_binary") + .tempfile() + .unwrap(); + + MinidumpWriter::new(pid, pid) + .dump(&mut tmpfile) + .expect("Could not write minidump"); + + child.kill().expect("Failed to kill process"); + + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + // Begin checks on dump + let meta = std::fs::metadata(tmpfile.path()).expect("Couldn't get metadata for tempfile"); + assert!(meta.len() > 0); + + let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); + let module_list: MinidumpModuleList = dump + .get_stream() + .expect("Couldn't find stream MinidumpModuleList"); + let main_module = module_list + .main_module() + .expect("Could not get main module"); + assert_eq!(main_module.code_file(), binary_copy.to_string_lossy()); + assert_eq!( + main_module.debug_identifier(), + Some(std::borrow::Cow::from(filtered.as_str())) + ); +} + +fn test_skip_if_requested_helper(context: Context) { + let num_of_threads = 1; + let mut child = start_child_and_wait_for_threads(num_of_threads); + let pid = child.id() as i32; + + let mut tmpfile = tempfile::Builder::new() + .prefix("skip_if_requested") + .tempfile() + .unwrap(); + + let mut tmp = MinidumpWriter::new(pid, pid); + if context == Context::With { + let crash_context = get_crash_context(pid); + tmp.set_crash_context(crash_context); + } + + let res = tmp + .skip_stacks_if_mapping_unreferenced() + .set_principal_mapping_address(0x0102030405060708) + .dump(&mut tmpfile); + child.kill().expect("Failed to kill process"); + + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + assert!(res.is_err()); +} +#[test] +fn test_skip_if_requested() { + test_skip_if_requested_helper(Context::Without) +} +#[test] +fn test_skip_if_requested_with_context() { + test_skip_if_requested_helper(Context::With) +} + +fn test_sanitized_stacks_helper(context: Context) { + let num_of_threads = 1; + let mut child = start_child_and_wait_for_threads(num_of_threads); + let pid = child.id() as i32; + + let mut tmpfile = tempfile::Builder::new() + .prefix("skip_if_requested") + .tempfile() + .unwrap(); + + let mut tmp = MinidumpWriter::new(pid, pid); + if context == Context::With { + let crash_context = get_crash_context(pid); + tmp.set_crash_context(crash_context); + } + tmp.sanitize_stack() + .dump(&mut tmpfile) + .expect("Faild to dump minidump"); + child.kill().expect("Failed to kill process"); + + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + // Read dump file and check its contents + let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); + let dump_array = std::fs::read(tmpfile.path()).expect("Failed to read minidump as vec"); + let thread_list: MinidumpThreadList = + dump.get_stream().expect("Couldn't find MinidumpThreadList"); + + let defaced = if cfg!(target_pointer_width = "64") { + 0x0defaced0defacedusize.to_ne_bytes() + } else { + 0x0defacedusize.to_ne_bytes() + }; + + for thread in thread_list.threads { + let mem = thread.raw.stack.memory; + let start = mem.rva as usize; + let end = (mem.rva + mem.data_size) as usize; + let slice = &dump_array.as_slice()[start..end]; + assert!(slice + .windows(defaced.len()) + .position(|window| window == defaced) + .is_some()); + } +} +#[test] +fn test_sanitized_stacks() { + test_sanitized_stacks_helper(Context::Without) +} +#[test] +fn test_sanitized_stacks_with_context() { + test_sanitized_stacks_helper(Context::Without) +} diff --git a/third_party/rust/minidump_writer_linux/tests/ptrace_dumper.rs b/third_party/rust/minidump_writer_linux/tests/ptrace_dumper.rs new file mode 100644 index 000000000000..59ef840bf55e --- /dev/null +++ b/third_party/rust/minidump_writer_linux/tests/ptrace_dumper.rs @@ -0,0 +1,300 @@ +use minidump_writer_linux::linux_ptrace_dumper; +use nix::sys::mman::{mmap, MapFlags, ProtFlags}; +use nix::sys::signal::Signal; +use std::io::{BufRead, BufReader}; +use std::mem::size_of; +use std::os::unix::io::AsRawFd; +use std::os::unix::process::ExitStatusExt; + +mod common; +use common::*; + +#[test] +fn test_setup() { + spawn_child("setup", &[]); +} + +#[test] +fn test_thread_list_from_child() { + // Child spawns and looks in the parent (== this process) for its own thread-ID + spawn_child("thread_list", &[]); +} + +#[test] +fn test_thread_list_from_parent() { + let num_of_threads = 5; + let mut child = start_child_and_wait_for_threads(num_of_threads); + let pid = child.id() as i32; + let mut dumper = + linux_ptrace_dumper::LinuxPtraceDumper::new(pid).expect("Couldn't init dumper"); + assert_eq!(dumper.threads.len(), num_of_threads); + dumper.suspend_threads().expect("Could not suspend threads"); + + // let mut matching_threads = 0; + for (idx, curr_thread) in dumper.threads.iter().enumerate() { + println!("curr_thread: {}", curr_thread); + let info = dumper + .get_thread_info_by_index(idx) + .expect("Could not get thread info by index"); + let (_stack_ptr, stack_len) = dumper + .get_stack_info(info.stack_pointer) + .expect("Could not get stack_pointer"); + assert!(stack_len > 0); + + // TODO: I currently know of no way to write the thread_id into the registers using Rust, + // so this check is deactivated for now, because it always fails + /* + // In the helper program, we stored a pointer to the thread id in a + // specific register. Check that we can recover its value. + #[cfg(target_arch = "x86_64")] + let process_tid_location = info.regs.rcx; + #[cfg(target_arch = "x86")] + let process_tid_location = info.regs.ecx; + #[cfg(target_arch = "arm")] + let process_tid_location = info.regs.uregs[3]; + #[cfg(target_arch = "aarch64")] + let process_tid_location = info.regs.regs[3]; + #[cfg(target_arch = "mips")] + let process_tid_location = info.mcontext.gregs[1]; + + let thread_id_data = LinuxPtraceDumper::copy_from_process( + *curr_thread, + process_tid_location as *mut libc::c_void, + 4, + ) + .expect("Could not copy from process"); + let found_thread_id = i32::from_ne_bytes( + thread_id_data + .as_slice() + .try_into() + .expect("couldn't parse i32 from read data"), + ); + matching_threads += if *curr_thread == found_thread_id { + 1 + } else { + 0 + }; */ + } + dumper.resume_threads().expect("Failed to resume threads"); + child.kill().expect("Failed to kill process"); + + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + // We clean up the child process before checking the final result + // TODO: I currently know of no way to write the thread_id into the registers using Rust, + // so this check is deactivated for now, because it always fails + // assert_eq!(matching_threads, num_of_threads); +} + +// #[cfg(not(any(target_arch = "mips", target_arch = "arm-eabi"))] +#[cfg(not(target_arch = "mips"))] +#[test] +// Ensure that the linux-gate VDSO is included in the mapping list. +fn test_mappings_include_linux_gate() { + spawn_child("mappings_include_linux_gate", &[]); +} + +#[test] +fn test_linux_gate_mapping_id() { + spawn_child("linux_gate_mapping_id", &[]); +} + +#[test] +fn test_merged_mappings() { + let page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE).unwrap(); + let page_size = page_size.unwrap() as usize; + let map_size = 3 * page_size; + + let path: &'static str = std::env!("CARGO_BIN_EXE_test"); + let file = std::fs::File::open(path).unwrap(); + + // mmap two segments out of the helper binary, one + // enclosed in the other, but with different protections. + let mapped_mem = unsafe { + mmap( + std::ptr::null_mut(), + map_size, + ProtFlags::PROT_READ, + MapFlags::MAP_SHARED, + file.as_raw_fd(), + 0, + ) + .unwrap() + }; + + // Carve a page out of the first mapping with different permissions. + let _inside_mapping = unsafe { + mmap( + (mapped_mem as usize + 2 * page_size) as *mut libc::c_void, + page_size, + ProtFlags::PROT_NONE, + MapFlags::MAP_SHARED | MapFlags::MAP_FIXED, + file.as_raw_fd(), + // Map a different offset just to + // better test real-world conditions. + page_size as i64, + ) + }; + + spawn_child( + "merged_mappings", + &[ + path, + &format!("{}", mapped_mem as usize), + &format!("{}", map_size), + ], + ); +} + +#[test] +// Ensure that the linux-gate VDSO is included in the mapping list. +fn test_file_id() { + spawn_child("file_id", &[]); +} + +#[test] +fn test_find_mapping() { + spawn_child( + "find_mappings", + &[ + &format!("{}", libc::printf as *const () as usize), + &format!("{}", String::new as *const () as usize), + ], + ); +} + +#[test] +fn test_copy_from_process_self() { + let stack_var: libc::c_long = 0x11223344; + let heap_var: Box = Box::new(0x55667788); + spawn_child( + "copy_from_process", + &[ + &format!("{}", &stack_var as *const libc::c_long as usize), + &format!("{}", heap_var.as_ref() as *const libc::c_long as usize), + ], + ); +} + +#[test] +fn test_sanitize_stack_copy() { + let num_of_threads = 1; + let mut child = start_child_and_return("spawn_alloc_wait"); + let pid = child.id() as i32; + + let mut f = BufReader::new(child.stdout.as_mut().expect("Can't open stdout")); + let mut buf = String::new(); + let _ = f + .read_line(&mut buf) + .expect("Couldn't read address provided by child"); + let mut output = buf.split_whitespace(); + let heap_addr = usize::from_str_radix(output.next().unwrap().trim_start_matches("0x"), 16) + .expect("unable to parse mmap_addr"); + + let mut dumper = + linux_ptrace_dumper::LinuxPtraceDumper::new(pid).expect("Couldn't init dumper"); + assert_eq!(dumper.threads.len(), num_of_threads); + dumper.suspend_threads().expect("Could not suspend threads"); + let thread_info = dumper + .get_thread_info_by_index(0) + .expect("Couldn't find thread_info"); + + let defaced = if cfg!(target_pointer_width = "64") { + 0x0defaced0defacedusize.to_ne_bytes() + } else { + 0x0defacedusize.to_ne_bytes() + }; + + let mut simulated_stack = vec![0xffu8; 2 * size_of::()]; + // Pointers into the stack shouldn't be sanitized. + simulated_stack[size_of::()..].copy_from_slice(&thread_info.stack_pointer.to_ne_bytes()); + + dumper + .sanitize_stack_copy( + &mut simulated_stack, + thread_info.stack_pointer, + size_of::(), + ) + .expect("Could not sanitize stack"); + + assert!(simulated_stack[size_of::()..] != defaced); + // Memory prior to the stack pointer should be cleared. + assert_eq!( + &simulated_stack[0..size_of::()], + vec![0u8; size_of::()].as_slice() + ); + + // Small integers should not be sanitized. + for ii in -4096..=4096isize { + simulated_stack = vec![0u8; 2 * size_of::()]; + simulated_stack[0..size_of::()].copy_from_slice(&(ii as usize).to_ne_bytes()); + dumper + .sanitize_stack_copy(&mut simulated_stack, thread_info.stack_pointer, 0) + .expect("Failed to sanitize with small integers"); + assert!(simulated_stack[size_of::()..] != defaced); + } + + // The instruction pointer definitely should point into an executable mapping. + let instr_ptr = thread_info.get_instruction_pointer() as usize; + let mapping_info = dumper + .find_mapping_no_bias(instr_ptr) + .expect("Failed to find mapping info"); + assert!(mapping_info.executable); + + // Pointers to code shouldn't be sanitized. + simulated_stack = vec![0u8; 2 * size_of::()]; + simulated_stack[size_of::()..].copy_from_slice(&instr_ptr.to_ne_bytes()); + dumper + .sanitize_stack_copy(&mut simulated_stack, thread_info.stack_pointer, 0) + .expect("Failed to sanitize with instr_ptr"); + assert!(simulated_stack[0..size_of::()] != defaced); + assert!(simulated_stack[size_of::()..] != defaced); + + // String fragments should be sanitized. + let junk = "abcdefghijklmnop".as_bytes(); + simulated_stack.copy_from_slice(&junk[0..2 * size_of::()]); + dumper + .sanitize_stack_copy(&mut simulated_stack, thread_info.stack_pointer, 0) + .expect("Failed to sanitize with junk"); + assert_eq!(simulated_stack[0..size_of::()], defaced); + assert_eq!(simulated_stack[size_of::()..], defaced); + + // Heap pointers should be sanititzed. + + // NOTE: The original test used the heap-address below, but here thread_info.regs.rcx + // is the instruction pointer, and thus in direct conflict with the "instruction pointer" + // testcase. + // Instead we just allocate something on the heap in the child and pass that address to this test. + // #[cfg(target_arch = "x86_64")] + // let heap_addr = thread_info.regs.rcx as usize; + // #[cfg(target_arch = "x86")] + // let heap_addr = thread_info.regs.ecx as usize; + // #[cfg(target_arch = "arm")] + // let heap_addr = thread_info.regs.uregs[3] as usize; + // #[cfg(target_arch = "aarch64")] + // let heap_addr = thread_info.regs.regs[3] as usize; + // #[cfg(target_arch = "mips")] + // let heap_addr = thread_info.mcontext.gregs[1] as usize; + + simulated_stack = vec![0u8; 2 * size_of::()]; + + simulated_stack[0..size_of::()].copy_from_slice(&heap_addr.to_ne_bytes()); + dumper + .sanitize_stack_copy(&mut simulated_stack, thread_info.stack_pointer, 0) + .expect("Failed to sanitize with heap addr"); + + assert_eq!(simulated_stack[0..size_of::()], defaced); + + dumper.resume_threads().expect("Failed to resume threads"); + child.kill().expect("Failed to kill process"); + + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); +} diff --git a/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc index 7d51e0ecb1a7..19f7a94aa5b7 100644 --- a/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc +++ b/toolkit/crashreporter/breakpad-client/linux/crash_generation/crash_generation_server.cc @@ -51,6 +51,10 @@ #include "common/linux/eintr_wrapper.h" #include "common/linux/safe_readlink.h" +#if !defined(__ANDROID__) +#include "mozilla/toolkit/crashreporter/rust_minidump_writer_linux_ffi_generated.h" +#endif + static const char kCommandQuit = 'x'; namespace google_breakpad { @@ -266,12 +270,20 @@ CrashGenerationServer::ClientEvent(short revents) if (!MakeMinidumpFilename(minidump_filename)) return true; +#if defined(__ANDROID__) if (!google_breakpad::WriteMinidump(minidump_filename.c_str(), crashing_pid, crash_context, kCrashContextSize)) { close(signal_fd); return true; } +#else + if (!write_minidump_linux_with_context(minidump_filename.c_str(), + crashing_pid, crash_context)) { + close(signal_fd); + return true; + } +#endif ClientInfo info(crashing_pid, this); if (dump_callback_) { diff --git a/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc index c8509c2d5363..cb0195621fdc 100644 --- a/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc +++ b/toolkit/crashreporter/breakpad-client/linux/handler/exception_handler.cc @@ -97,6 +97,9 @@ #include "common/linux/eintr_wrapper.h" #include "third_party/lss/linux_syscall_support.h" #include "prenv.h" +#if !defined(__ANDROID__) +#include "mozilla/toolkit/crashreporter/rust_minidump_writer_linux_ffi_generated.h" +#endif #ifdef MOZ_PHC #include "replace_malloc_bridge.h" @@ -849,10 +852,15 @@ bool ExceptionHandler::WriteMinidumpForChild(pid_t child, // This function is not run in a compromised context. MinidumpDescriptor descriptor(dump_path); descriptor.UpdatePath(); +#if defined(__ANDROID__) if (!google_breakpad::WriteMinidump(descriptor.path(), child, child_blamed_thread)) return false; +#else + if (!write_minidump_linux(descriptor.path(), child, child_blamed_thread)) + return false; +#endif // nullptr here for phc::AddrInfo* is ok because this is not a crash. return callback ? callback(descriptor, callback_context, nullptr, true) diff --git a/toolkit/crashreporter/moz.build b/toolkit/crashreporter/moz.build index 8d354675eab1..f69235382cfc 100644 --- a/toolkit/crashreporter/moz.build +++ b/toolkit/crashreporter/moz.build @@ -57,6 +57,10 @@ if CONFIG["MOZ_CRASHREPORTER"]: "google-breakpad/src/common/linux", "google-breakpad/src/processor", ] + + if CONFIG["OS_TARGET"] != "Android": + DIRS += ["rust_minidump_writer_linux"] + if not CONFIG["DUMP_SYMS"] and CONFIG["HOST_OS_ARCH"] == "Linux": DIRS += [ "google-breakpad/src/tools/linux/dump_syms", diff --git a/toolkit/crashreporter/rust_minidump_writer_linux/Cargo.toml b/toolkit/crashreporter/rust_minidump_writer_linux/Cargo.toml new file mode 100644 index 000000000000..f1c86d543325 --- /dev/null +++ b/toolkit/crashreporter/rust_minidump_writer_linux/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "rust_minidump_writer_linux" +version = "0.1.0" +authors = ["Martin Sirringhaus"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +minidump_writer_linux = "0.1.0" +libc = "0.2.74" diff --git a/toolkit/crashreporter/rust_minidump_writer_linux/cbindgen.toml b/toolkit/crashreporter/rust_minidump_writer_linux/cbindgen.toml new file mode 100644 index 000000000000..215bb5620d6c --- /dev/null +++ b/toolkit/crashreporter/rust_minidump_writer_linux/cbindgen.toml @@ -0,0 +1,10 @@ +header = """/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */""" +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */ +""" +include_version = true +braces = "SameLine" +line_length = 100 +tab_width = 2 +language = "C++" diff --git a/toolkit/crashreporter/rust_minidump_writer_linux/moz.build b/toolkit/crashreporter/rust_minidump_writer_linux/moz.build new file mode 100644 index 000000000000..834a97de28c5 --- /dev/null +++ b/toolkit/crashreporter/rust_minidump_writer_linux/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +if CONFIG["COMPILE_ENVIRONMENT"]: + # This tells mach to run cbindgen and that this header-file should be created + CbindgenHeader( + "rust_minidump_writer_linux_ffi_generated.h", + inputs=["/toolkit/crashreporter/rust_minidump_writer_linux"], + ) + + # This tells mach to copy that generated file to obj/dist/includes/mozilla/toolkit/crashreporter/ + EXPORTS.mozilla.toolkit.crashreporter += [ + "!rust_minidump_writer_linux_ffi_generated.h", + ] diff --git a/toolkit/crashreporter/rust_minidump_writer_linux/src/lib.rs b/toolkit/crashreporter/rust_minidump_writer_linux/src/lib.rs new file mode 100644 index 000000000000..dd6ed9a32b1a --- /dev/null +++ b/toolkit/crashreporter/rust_minidump_writer_linux/src/lib.rs @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +extern crate minidump_writer_linux; + +use libc::pid_t; +use std::ffi::CStr; +use std::os::raw::{c_char, c_void}; + +use minidump_writer_linux::crash_context::CrashContext; +use minidump_writer_linux::minidump_writer::MinidumpWriter; + +// This function will be exposed to C++ +#[no_mangle] +pub unsafe extern "C" fn write_minidump_linux( + dump_path: *const c_char, + child: pid_t, + child_blamed_thread: pid_t, +) -> bool { + assert!(!dump_path.is_null()); + let c_path = CStr::from_ptr(dump_path); + + let path = match c_path.to_str() { + Ok(s) => s, + Err(_) => { + return false; + } + }; + + let mut dump_file = match std::fs::OpenOptions::new() + .create(true) // Create file if it doesn't exist + .write(true) // Truncate file + .open(path) + { + Ok(f) => f, + Err(_) => { + return false; + } + }; + + MinidumpWriter::new(child, child_blamed_thread) + .dump(&mut dump_file) + .is_ok() +} + +// This function will be exposed to C++ +#[no_mangle] +pub unsafe extern "C" fn write_minidump_linux_with_context( + dump_path: *const c_char, + child: pid_t, + context: *const c_void, +) -> bool { + assert!(!dump_path.is_null()); + let c_path = CStr::from_ptr(dump_path); + + assert!(!context.is_null()); + let cc = (&*(context as *const CrashContext)).clone(); + + let path = match c_path.to_str() { + Ok(s) => s, + Err(_) => { + return false; + } + }; + + let mut dump_file = match std::fs::OpenOptions::new() + .create(true) // Create file if it doesn't exist + .write(true) // Truncate file + .open(path) + { + Ok(f) => f, + Err(_) => { + return false; + } + }; + + MinidumpWriter::new(child, cc.tid) + .set_crash_context(cc) + .dump(&mut dump_file) + .is_ok() +} diff --git a/toolkit/library/rust/shared/Cargo.toml b/toolkit/library/rust/shared/Cargo.toml index 59924d307ef7..f0ceaa20e3f7 100644 --- a/toolkit/library/rust/shared/Cargo.toml +++ b/toolkit/library/rust/shared/Cargo.toml @@ -74,6 +74,9 @@ qcms = { path = "../../../../gfx/qcms", features = ["c_bindings"] } viaduct = { git = "https://github.com/mozilla/application-services", rev = "8a576fbe79199fa8664f64285524017f74ebcc5f"} webext_storage_bridge = { path = "../../../components/extensions/storage/webext_storage_bridge" } +[target.'cfg(target_os = "linux")'.dependencies] +rust_minidump_writer_linux = { path = "../../../../toolkit/crashreporter/rust_minidump_writer_linux" } + [build-dependencies] rustc_version = "0.2" diff --git a/toolkit/library/rust/shared/lib.rs b/toolkit/library/rust/shared/lib.rs index fd9ff0121966..35f242756395 100644 --- a/toolkit/library/rust/shared/lib.rs +++ b/toolkit/library/rust/shared/lib.rs @@ -88,6 +88,9 @@ extern crate remote; extern crate gecko_logger; +#[cfg(target_os = "linux")] +extern crate rust_minidump_writer_linux; + extern crate log; use log::info;