|
| 1 | +use std::{collections::HashMap, io::Write, rc::Rc}; |
| 2 | + |
| 3 | +use gimli::{Section, UnwindSection}; |
| 4 | +use object::{Object, ObjectSection}; |
| 5 | + |
| 6 | +use crate::cli::monitor::symbols::Symbols; |
| 7 | + |
| 8 | +pub(crate) const MARKER: &str = "STACKDUMP: "; |
| 9 | + |
| 10 | +pub(crate) fn backtrace_from_stack_dump( |
| 11 | + line: &str, |
| 12 | + out: &mut dyn Write, |
| 13 | + elf: Option<&[u8]>, |
| 14 | + symbols: &Symbols<'_>, |
| 15 | +) -> std::io::Result<()> { |
| 16 | + if let Some(elf) = elf { |
| 17 | + if let Some(remaining) = line.to_string().strip_prefix(MARKER) { |
| 18 | + let mut split = remaining.split(" "); |
| 19 | + let (address, stack) = { |
| 20 | + let first = split.next(); |
| 21 | + let second = split.next(); |
| 22 | + |
| 23 | + (first, second) |
| 24 | + }; |
| 25 | + |
| 26 | + if let Some(address) = address { |
| 27 | + if let Some(stack) = stack { |
| 28 | + if stack.len() % 2 != 0 { |
| 29 | + return Ok(()); |
| 30 | + } |
| 31 | + |
| 32 | + let mut pc = u32::from_str_radix(address, 16).unwrap_or_default(); |
| 33 | + let mut stack_bytes = Vec::new(); |
| 34 | + for byte_chars in stack.chars().collect::<Vec<char>>().chunks(2) { |
| 35 | + if byte_chars.len() == 2 { |
| 36 | + stack_bytes.push( |
| 37 | + u8::from_str_radix( |
| 38 | + &format!("{}{}", byte_chars[0], byte_chars[1]), |
| 39 | + 16, |
| 40 | + ) |
| 41 | + .unwrap_or_default(), |
| 42 | + ); |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + let func_info = get_func_info(elf)?; |
| 47 | + |
| 48 | + writeln!(out).ok(); |
| 49 | + let mut index = 0; |
| 50 | + loop { |
| 51 | + let func = func_info.iter().find(|f| f.start <= pc && f.end >= pc); |
| 52 | + if let Some(func) = func { |
| 53 | + if func.stack_frame_size == 0 { |
| 54 | + break; |
| 55 | + } |
| 56 | + |
| 57 | + let lookup_pc = pc as u64 - 4; |
| 58 | + let name = symbols.name(lookup_pc); |
| 59 | + let location = symbols.location(lookup_pc); |
| 60 | + if let Some(name) = name { |
| 61 | + if let Some((file, line_num)) = location { |
| 62 | + writeln!(out, "{name}\r\n at {file}:{line_num}\r\n").ok(); |
| 63 | + } else { |
| 64 | + writeln!(out, "{name}\r\n at ??:??\r\n").ok(); |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + if index + func.stack_frame_size as usize > stack_bytes.len() { |
| 69 | + break; |
| 70 | + } |
| 71 | + |
| 72 | + let next_pc_pos = index + (func.stack_frame_size as usize - 4); |
| 73 | + |
| 74 | + pc = u32::from_le_bytes( |
| 75 | + stack_bytes[next_pc_pos..][..4] |
| 76 | + .try_into() |
| 77 | + .unwrap_or_default(), |
| 78 | + ); |
| 79 | + index += func.stack_frame_size as usize; |
| 80 | + } else { |
| 81 | + break; |
| 82 | + } |
| 83 | + } |
| 84 | + writeln!(out).ok(); |
| 85 | + } |
| 86 | + } |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + Ok(()) |
| 91 | +} |
| 92 | + |
| 93 | +fn get_func_info(elf: &[u8]) -> Result<Vec<FuncInfo>, std::io::Error> { |
| 94 | + let debug_file = object::File::parse(elf).expect("parse file"); |
| 95 | + |
| 96 | + let endian = if debug_file.is_little_endian() { |
| 97 | + gimli::RunTimeEndian::Little |
| 98 | + } else { |
| 99 | + gimli::RunTimeEndian::Big |
| 100 | + }; |
| 101 | + |
| 102 | + let eh_frame = gimli::EhFrame::load(|sect_id| { |
| 103 | + let data = debug_file |
| 104 | + .section_by_name(sect_id.name()) |
| 105 | + .and_then(|section| section.data().ok()); |
| 106 | + |
| 107 | + if let Some(data) = data { |
| 108 | + Ok::<gimli::EndianReader<gimli::RunTimeEndian, Rc<[u8]>>, ()>( |
| 109 | + gimli::EndianRcSlice::new(Rc::from(data), endian), |
| 110 | + ) |
| 111 | + } else { |
| 112 | + Err(()) |
| 113 | + } |
| 114 | + }) |
| 115 | + .map_err(|_| std::io::Error::other("no eh_frame section"))?; |
| 116 | + |
| 117 | + process_eh_frame(&debug_file, eh_frame).map_err(|_| std::io::Error::other("eh_frame error")) |
| 118 | +} |
| 119 | + |
| 120 | +#[derive(Debug)] |
| 121 | +struct FuncInfo { |
| 122 | + start: u32, |
| 123 | + end: u32, |
| 124 | + stack_frame_size: u32, |
| 125 | +} |
| 126 | + |
| 127 | +fn process_eh_frame<R: gimli::Reader<Offset = usize>>( |
| 128 | + file: &object::File<'_>, |
| 129 | + mut eh_frame: gimli::EhFrame<R>, |
| 130 | +) -> Result<Vec<FuncInfo>, gimli::Error> { |
| 131 | + let mut res = Vec::new(); |
| 132 | + |
| 133 | + let address_size = file |
| 134 | + .architecture() |
| 135 | + .address_size() |
| 136 | + .map(|w| w.bytes()) |
| 137 | + .unwrap_or(std::mem::size_of::<usize>() as u8); |
| 138 | + eh_frame.set_address_size(address_size); |
| 139 | + |
| 140 | + let mut bases = gimli::BaseAddresses::default(); |
| 141 | + if let Some(section) = file.section_by_name(".eh_frame") { |
| 142 | + bases = bases.set_eh_frame(section.address()); |
| 143 | + } |
| 144 | + |
| 145 | + let mut cies = HashMap::new(); |
| 146 | + |
| 147 | + let mut entries = eh_frame.entries(&bases); |
| 148 | + loop { |
| 149 | + match entries.next()? { |
| 150 | + None => return Ok(res), |
| 151 | + Some(gimli::CieOrFde::Fde(partial)) => { |
| 152 | + let fde = match partial.parse(|_, bases, o| { |
| 153 | + cies.entry(o) |
| 154 | + .or_insert_with(|| eh_frame.cie_from_offset(bases, o)) |
| 155 | + .clone() |
| 156 | + }) { |
| 157 | + Ok(fde) => fde, |
| 158 | + Err(_) => { |
| 159 | + // ignored |
| 160 | + continue; |
| 161 | + } |
| 162 | + }; |
| 163 | + |
| 164 | + let mut entry = FuncInfo { |
| 165 | + start: fde.initial_address() as u32, |
| 166 | + end: fde.end_address() as u32, |
| 167 | + stack_frame_size: 0u32, |
| 168 | + }; |
| 169 | + |
| 170 | + let instructions = fde.instructions(&eh_frame, &bases); |
| 171 | + let sfs = estimate_stack_frame_size(instructions)?; |
| 172 | + entry.stack_frame_size = sfs; |
| 173 | + res.push(entry); |
| 174 | + } |
| 175 | + _ => (), |
| 176 | + } |
| 177 | + } |
| 178 | +} |
| 179 | + |
| 180 | +fn estimate_stack_frame_size<R: gimli::Reader>( |
| 181 | + mut insns: gimli::CallFrameInstructionIter<'_, R>, |
| 182 | +) -> Result<u32, gimli::Error> { |
| 183 | + use gimli::CallFrameInstruction::*; |
| 184 | + |
| 185 | + let mut sfs = 0; |
| 186 | + |
| 187 | + loop { |
| 188 | + match insns.next() { |
| 189 | + Err(_e) => { |
| 190 | + break; |
| 191 | + } |
| 192 | + Ok(None) => { |
| 193 | + break; |
| 194 | + } |
| 195 | + Ok(Some(op)) => { |
| 196 | + if let DefCfaOffset { offset } = op { |
| 197 | + sfs = u32::max(sfs, offset as u32); |
| 198 | + } |
| 199 | + } |
| 200 | + } |
| 201 | + } |
| 202 | + |
| 203 | + Ok(sfs) |
| 204 | +} |
0 commit comments