From 4909a1056bd8bde1064a5f8a804aa8b9fc681a8c Mon Sep 17 00:00:00 2001 From: Alexis Beingessner Date: Wed, 2 Mar 2022 01:15:07 -0500 Subject: [PATCH] WIP android64 support --- Cargo.toml | 2 +- src/crash_context/crash_context_aarch64.rs | 16 ++++ src/crash_context/mod.rs | 4 +- src/dumper_cpu_info/cpu_info_arm.rs | 8 +- src/minidump_cpu/minidump_cpu_aarch64.rs | 102 +++++++++++++++++++++ src/minidump_cpu/mod.rs | 2 +- src/sections/thread_list_stream.rs | 4 +- src/thread_info/thread_info_aarch64.rs | 69 +++++++++----- tests/minidump_writer.rs | 15 ++- tests/ptrace_dumper.rs | 12 +-- 10 files changed, 196 insertions(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a8eb7ce1..e6631294 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" [dependencies] tempfile = "3.1.0" nix = "0.23" -libc = "0.2.74" +libc = "0.2.117" memoffset = "0.5.1" byteorder = "1.3.2" memmap2 = "0.2.2" diff --git a/src/crash_context/crash_context_aarch64.rs b/src/crash_context/crash_context_aarch64.rs index e6fb3f9e..af16fbf0 100644 --- a/src/crash_context/crash_context_aarch64.rs +++ b/src/crash_context/crash_context_aarch64.rs @@ -1,3 +1,5 @@ +use crate::minidump_cpu::{RawContextCPU, imp::{MD_CONTEXT_ARM64_ALL_OLD, MDARM64RegisterNumbers}}; + use super::CrashContext; impl CrashContext { @@ -8,4 +10,18 @@ impl CrashContext { pub fn get_stack_pointer(&self) -> usize { self.context.uc_mcontext.pc as usize } + + pub fn fill_cpu_context(&self, out: &mut RawContextCPU) { + out.context_flags = MD_CONTEXT_ARM64_ALL_OLD; + out.cpsr = self.context.uc_mcontext.pstate as u32; + for idx in 0..MDARM64RegisterNumbers::MD_CONTEXT_ARM64_REG_SP as usize { + out.iregs[idx] = self.context.uc_mcontext.regs[idx]; + } + out.iregs[MDARM64RegisterNumbers::MD_CONTEXT_ARM64_REG_SP as usize] = self.context.uc_mcontext.sp; + out.iregs[MDARM64RegisterNumbers::MD_CONTEXT_ARM64_REG_PC as usize] = self.context.uc_mcontext.pc; + out.pc = self.context.uc_mcontext.pc; + out.float_save.fpcr = self.float_state.fpcr; + out.float_save.fpsr = self.float_state.fpsr; + out.float_save.regs = self.float_state.regs; + } } diff --git a/src/crash_context/mod.rs b/src/crash_context/mod.rs index 2be8fb15..0f5f10bd 100644 --- a/src/crash_context/mod.rs +++ b/src/crash_context/mod.rs @@ -16,6 +16,7 @@ pub mod imp; // pub mod imp; #[cfg(target_arch = "arm")] use crate::minidump_cpu::RawContextCPU; +use crate::minidump_cpu::imp::libc_user_fpsimd_struct; #[cfg(target_arch = "arm")] impl CrashContext { pub fn get_instruction_pointer(&self) -> usize { @@ -37,7 +38,8 @@ pub mod imp; pub mod imp; #[cfg(target_arch = "aarch64")] -pub type fpstate_t = libc::fpsimd_context; // Currently not part of libc! This will produce an error. +#[allow(non_camel_case_types)] +pub type fpstate_t = libc_user_fpsimd_struct; // Currently not part of libc! This will produce an error. #[cfg(not(any( target_arch = "aarch64", target_arch = "mips", diff --git a/src/dumper_cpu_info/cpu_info_arm.rs b/src/dumper_cpu_info/cpu_info_arm.rs index ab7da7ae..f943fc9b 100644 --- a/src/dumper_cpu_info/cpu_info_arm.rs +++ b/src/dumper_cpu_info/cpu_info_arm.rs @@ -86,10 +86,10 @@ pub fn write_cpu_information(sys_info: &mut MDRawSystemInfo) -> Result<()> { // The ELF hwcaps are listed in the "Features" entry as textual tags. // This table is used to rebuild them. - let cpu_features_entries; + let cpu_features_entries: &[CpuFeaturesEntry]; #[cfg(target_arch = "arm")] { - cpu_features_entries = [ + cpu_features_entries = &[ CpuFeaturesEntry::new("swp", MDCPUInformationARMElfHwCaps::Swp as u32), CpuFeaturesEntry::new("half", MDCPUInformationARMElfHwCaps::Half as u32), CpuFeaturesEntry::new("thumb", MDCPUInformationARMElfHwCaps::Thumb as u32), @@ -119,7 +119,7 @@ pub fn write_cpu_information(sys_info: &mut MDRawSystemInfo) -> Result<()> { #[cfg(target_arch = "aarch64")] { // No hwcaps on aarch64. - cpu_features_entries = []; + cpu_features_entries = &[]; } // processor_architecture should always be set, do this first @@ -251,7 +251,7 @@ pub fn write_cpu_information(sys_info: &mut MDRawSystemInfo) -> Result<()> { if let Some(val) = value { // Parse each space-separated tag. for tag in val.split_whitespace() { - for entry in &cpu_features_entries { + for entry in cpu_features_entries { if entry.tag == tag { sys_info.cpu.elf_hwcaps |= entry.hwcaps; break; diff --git a/src/minidump_cpu/minidump_cpu_aarch64.rs b/src/minidump_cpu/minidump_cpu_aarch64.rs index c35e2e3c..2b7a56bd 100644 --- a/src/minidump_cpu/minidump_cpu_aarch64.rs +++ b/src/minidump_cpu/minidump_cpu_aarch64.rs @@ -1,12 +1,114 @@ +/// A u128 that matches the layout of uint128_t for C FFI purposes +/// **BUT NOT THE ABI**. This is safe for pass-by-ref but not pass-by-value. +/// +/// Rust underaligns u128 compared to C's ABI due to a long-standing llvm bug. +/// Unfortuantely library code *can't* perfectly work around this, because +/// primitives have magic ABIs. +/// +/// Although repr(transparent) *exists* to preserve the magic ABI of primitives, +/// you can't combine this with other reprs (which makes a kind of sense), +/// and we need to apply repr(align(16)). +/// +/// The upshot of this is that this type (or a struct containing it) can be +/// passed *by-reference* to C, but if you try to pass it *by-value* then +/// the ABI might not match and this value may not be passed to the function +/// right. +/// +/// This is good enough for our purposes, because we largely just want this +/// for APIs like linux's getcontext which does in fact work by-reference. +/// +/// See "i128 / u128 are not compatible with C's definition." +/// https://github.com/rust-lang/rust/issues/54341 +#[repr(C, align(16))] +#[derive(Debug, Copy, Clone, Default)] +#[allow(non_camel_case_types)] +pub struct layout_only_ffi_u128(u128); + +impl layout_only_ffi_u128 { + pub fn to_ne_bytes(self) -> [u8; 16] { + self.0.to_ne_bytes() + } +} + 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. */ +#[allow(non_camel_case_types)] 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, } + +/* Windows only? +#[repr(C)] +#[derive(Default)] +pub struct MDRawContextARM64 { + pub context_flags: u32, + pub cpsr: u32, + pub iregs: [u64; 32], + pub pc: u64, + pub float_save: libc_user_fpsimd_struct, + pub bcr: [u32; 8], + pub bvr: [u64; 8], + pub wcr: [u32; 2], + pub wvr: [u64; 2], +} +*/ + +#[repr(C)] +#[derive(Default)] +pub struct MDRawContextARM64Old { + pub context_flags: u64, + pub iregs: [u64; 32], + pub pc: u64, + pub cpsr: u32, + pub float_save: FloatingSaveAreaARM64Old, +} + +/// aarch64 floating point state +#[repr(C)] +#[derive(Debug, Copy, Clone, Default)] +#[allow(non_camel_case_types)] +pub struct libc_user_fpsimd_struct { + pub regs: [layout_only_ffi_u128; 32usize], + pub fpsr: u32, + pub fpcr: u32, +} + +/// aarch64 floating point state (old) +#[repr(C)] +#[derive(Debug, Clone, Copy, Default)] +pub struct FloatingSaveAreaARM64Old { + pub fpsr: u32, + pub fpcr: u32, + pub regs: [layout_only_ffi_u128; 32usize], +} + +pub const MD_CONTEXT_ARM64: u32 = 0x400000; +pub const MD_CONTEXT_ARM64_OLD: u64 = 0x80000000; + +pub const MD_CONTEXT_ARM64_ALL_OLD: u64 = MD_CONTEXT_ARM64_OLD | 0x2 | 0x4; + +/* +/* For (MDRawContextARM64_Old).context_flags. These values indicate the type of + * context stored in the structure. MD_CONTEXT_ARM64_OLD is Breakpad-defined. + * This value was chosen to avoid likely conflicts with MD_CONTEXT_* + * for other CPUs. */ +#define MD_CONTEXT_ARM64_OLD 0x80000000 +#define MD_CONTEXT_ARM64_INTEGER_OLD (MD_CONTEXT_ARM64_OLD | 0x00000002) +#define MD_CONTEXT_ARM64_FLOATING_POINT_OLD (MD_CONTEXT_ARM64_OLD | 0x00000004) + +#define MD_CONTEXT_ARM64_FULL_OLD (MD_CONTEXT_ARM64_INTEGER_OLD | \ + MD_CONTEXT_ARM64_FLOATING_POINT_OLD) + +#define MD_CONTEXT_ARM64_ALL_OLD (MD_CONTEXT_ARM64_INTEGER_OLD | \ + MD_CONTEXT_ARM64_FLOATING_POINT_OLD) + +*/ \ No newline at end of file diff --git a/src/minidump_cpu/mod.rs b/src/minidump_cpu/mod.rs index ebce0ad5..ce90a2f7 100644 --- a/src/minidump_cpu/mod.rs +++ b/src/minidump_cpu/mod.rs @@ -21,6 +21,6 @@ pub type RawContextCPU = imp::MDRawContextX86; #[cfg(target_arch = "arm")] pub type RawContextCPU = imp::MDRawContextARM; #[cfg(target_arch = "aarch64")] -pub type RawContextCPU = imp::MDRawContextX86; +pub type RawContextCPU = imp::MDRawContextARM64Old; #[cfg(target_arch = "mips")] pub type RawContextCPU = i32; diff --git a/src/sections/thread_list_stream.rs b/src/sections/thread_list_stream.rs index d3672d99..24eb596e 100644 --- a/src/sections/thread_list_stream.rs +++ b/src/sections/thread_list_stream.rs @@ -152,7 +152,7 @@ pub fn write( dumper, &mut thread, instruction_ptr, - info.stack_pointer, + info.stack_pointer as usize, max_stack_len, )?; @@ -166,7 +166,7 @@ pub fn write( // while the instruction pointer is already here. config.crashing_thread_context = CrashingThreadContext::CrashContextPlusAddress(( cpu_section.location(), - info.get_instruction_pointer(), + info.get_instruction_pointer() as usize, )); } } diff --git a/src/thread_info/thread_info_aarch64.rs b/src/thread_info/thread_info_aarch64.rs index cca218f4..a8233b76 100644 --- a/src/thread_info/thread_info_aarch64.rs +++ b/src/thread_info/thread_info_aarch64.rs @@ -1,7 +1,9 @@ -use super::Pid; +use super::{Pid, CommonThreadInfo}; use crate::errors::ThreadInfoError; -use crate::minidump_cpu::imp::{MDARM64RegisterNumbers, MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT}; +use crate::minidump_cpu::imp::{MDARM64RegisterNumbers, MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT, libc_user_fpsimd_struct, MD_CONTEXT_ARM64_OLD, MD_CONTEXT_ARM64_ALL_OLD}; +use crate::minidump_cpu::RawContextCPU; use libc; +use nix::sys::ptrace; type Result = std::result::Result; @@ -12,37 +14,60 @@ pub struct ThreadInfoAarch64 { pub tgid: Pid, // thread group id pub ppid: Pid, // parent process pub regs: libc::user_regs_struct, - pub fpregs: libc::user_fpsimd_struct, + pub fpregs: libc_user_fpsimd_struct, } +impl CommonThreadInfo for ThreadInfoAarch64 {} + impl ThreadInfoAarch64 { + // 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_GETFPREGS, so we have to do it ourselves + fn getregs(pid: Pid) -> Result { + Self::ptrace_get_data::( + ptrace::Request::PTRACE_GETFPREGS, + None, + nix::unistd::Pid::from_raw(pid), + ) + } + 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.context_flags = MD_CONTEXT_ARM64_ALL_OLD; out.cpsr = self.regs.pstate as u32; - for idx in 0..MDARM64RegisterNumbers::MD_CONTEXT_ARM64_REG_SP { + for idx in 0..MDARM64RegisterNumbers::MD_CONTEXT_ARM64_REG_SP as usize { 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.iregs[MDARM64RegisterNumbers::MD_CONTEXT_ARM64_REG_SP as usize] = self.regs.sp; + out.iregs[MDARM64RegisterNumbers::MD_CONTEXT_ARM64_REG_PC as usize] = self.regs.pc; + out.pc = self.regs.pc; out.float_save.fpcr = self.fpregs.fpcr; + out.float_save.fpsr = self.fpregs.fpsr; + out.float_save.regs = self.fpregs.regs; + } + pub fn create_impl(_pid: Pid, tid: Pid) -> Result { + let (ppid, tgid) = Self::get_ppid_and_tgid(tid)?; + let regs = Self::getregs(tid)?; + let fpregs = Self::getfpregs(tid)?; - // 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 + let stack_pointer = regs.uregs[13] as usize; + + Ok(ThreadInfoAarch64 { + stack_pointer, + tgid, + ppid, + regs, + fpregs, + }) } -} +} \ No newline at end of file diff --git a/tests/minidump_writer.rs b/tests/minidump_writer.rs index 95c26fb3..1744419a 100644 --- a/tests/minidump_writer.rs +++ b/tests/minidump_writer.rs @@ -27,7 +27,7 @@ enum Context { Without, } -#[cfg(not(any(target_arch = "mips", target_arch = "arm")))] +#[cfg(not(any(target_arch = "mips", target_arch = "arm", target_arch = "aarch64")))] fn get_ucontext() -> Result { let mut context = std::mem::MaybeUninit::::uninit(); let res = unsafe { libc::getcontext(context.as_mut_ptr()) }; @@ -35,6 +35,19 @@ fn get_ucontext() -> Result { unsafe { Ok(context.assume_init()) } } +#[cfg(target_arch = "aarch64")] +fn get_ucontext() -> Result { + // Libc doesn't define this for arm64 because of the u128 thing + extern "C" { + pub fn getcontext(ucp: *mut libc::ucontext_t) -> libc::c_int; + } + let mut context = std::mem::MaybeUninit::::uninit(); + let res = unsafe { getcontext(context.as_mut_ptr()) }; + Errno::result(res)?; + unsafe { Ok(context.assume_init()) } +} + + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] fn get_crash_context(tid: Pid) -> CrashContext { let siginfo: libc::siginfo_t = unsafe { std::mem::zeroed() }; diff --git a/tests/ptrace_dumper.rs b/tests/ptrace_dumper.rs index c2e8fccf..42fa42ac 100644 --- a/tests/ptrace_dumper.rs +++ b/tests/ptrace_dumper.rs @@ -38,7 +38,7 @@ fn test_thread_list_from_parent() { .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) + .get_stack_info(info.stack_pointer as usize) .expect("Could not get stack_pointer"); assert!(stack_len > 0); @@ -221,7 +221,7 @@ fn test_sanitize_stack_copy() { dumper .sanitize_stack_copy( &mut simulated_stack, - thread_info.stack_pointer, + thread_info.stack_pointer as usize, size_of::(), ) .expect("Could not sanitize stack"); @@ -238,7 +238,7 @@ fn test_sanitize_stack_copy() { 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) + .sanitize_stack_copy(&mut simulated_stack, thread_info.stack_pointer as usize, 0) .expect("Failed to sanitize with small integers"); assert!(simulated_stack[size_of::()..] != defaced); } @@ -254,7 +254,7 @@ fn test_sanitize_stack_copy() { 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) + .sanitize_stack_copy(&mut simulated_stack, thread_info.stack_pointer as usize, 0) .expect("Failed to sanitize with instr_ptr"); assert!(simulated_stack[0..size_of::()] != defaced); assert!(simulated_stack[size_of::()..] != defaced); @@ -263,7 +263,7 @@ fn test_sanitize_stack_copy() { 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) + .sanitize_stack_copy(&mut simulated_stack, thread_info.stack_pointer as usize, 0) .expect("Failed to sanitize with junk"); assert_eq!(simulated_stack[0..size_of::()], defaced); assert_eq!(simulated_stack[size_of::()..], defaced); @@ -289,7 +289,7 @@ fn test_sanitize_stack_copy() { 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) + .sanitize_stack_copy(&mut simulated_stack, thread_info.stack_pointer as usize, 0) .expect("Failed to sanitize with heap addr"); assert_eq!(simulated_stack[0..size_of::()], defaced);