|
1 | 1 | use std::borrow::Cow; |
2 | 2 | use std::fs; |
3 | 3 | use std::collections::HashMap; |
| 4 | +use std::path::Path; |
4 | 5 |
|
5 | 6 | use object::{Object, ObjectSection, ObjectSymbol, SymbolKind}; |
6 | 7 |
|
7 | 8 | use crate::{SymbolizedFrame, MemoryMapping}; |
8 | 9 |
|
9 | 10 | struct LibraryInfo { |
10 | | - context: addr2line::Context<gimli::EndianRcSlice<gimli::RunTimeEndian>>, |
| 11 | + loader: Option<addr2line::Loader>, |
| 12 | + context: Option<addr2line::Context<gimli::EndianRcSlice<gimli::RunTimeEndian>>>, |
11 | 13 | _file_data: &'static [u8], |
12 | 14 | _base_address: u64, |
13 | 15 | symbols: Vec<SymbolInfo>, // sorted, addresses relative to base address! |
| 16 | + #[allow(dead_code)] |
| 17 | + build_id: Option<Vec<u8>>, // Stored for potential future use and debugging |
14 | 18 | } |
15 | 19 |
|
16 | 20 | #[derive(Clone, PartialEq, Eq)] |
@@ -71,6 +75,47 @@ impl Symbolizer { |
71 | 75 | }) |
72 | 76 | } |
73 | 77 |
|
| 78 | + fn extract_build_id(object: &object::File) -> Option<Vec<u8>> { |
| 79 | + // Look for the .note.gnu.build-id section |
| 80 | + if let Some(section) = object.section_by_name(".note.gnu.build-id") { |
| 81 | + if let Ok(data) = section.uncompressed_data() { |
| 82 | + // Parse the note structure |
| 83 | + // Note header: namesz (4 bytes), descsz (4 bytes), type (4 bytes) |
| 84 | + // Then name, then description (which contains the build-id) |
| 85 | + if data.len() >= 16 { |
| 86 | + let namesz = u32::from_ne_bytes([data[0], data[1], data[2], data[3]]) as usize; |
| 87 | + let descsz = u32::from_ne_bytes([data[4], data[5], data[6], data[7]]) as usize; |
| 88 | + let note_type = u32::from_ne_bytes([data[8], data[9], data[10], data[11]]); |
| 89 | + |
| 90 | + // GNU build-id note type is 3 |
| 91 | + if note_type == 3 && namesz == 4 { |
| 92 | + // Skip note header (12 bytes) and name ("GNU\0" = 4 bytes) |
| 93 | + let build_id_start = 16; |
| 94 | + if build_id_start + descsz <= data.len() { |
| 95 | + return Some(data[build_id_start..build_id_start + descsz].to_vec()); |
| 96 | + } |
| 97 | + } |
| 98 | + } |
| 99 | + } |
| 100 | + } |
| 101 | + None |
| 102 | + } |
| 103 | + |
| 104 | + fn get_debug_file_path(build_id: &[u8]) -> Option<String> { |
| 105 | + if build_id.len() < 2 { |
| 106 | + return None; |
| 107 | + } |
| 108 | + |
| 109 | + // Convert build-id to hex string |
| 110 | + let hex_string: String = build_id.iter().map(|b| format!("{b:02x}")).collect(); |
| 111 | + |
| 112 | + // Split into first two hex digits and the rest |
| 113 | + let (prefix, suffix) = hex_string.split_at(2); |
| 114 | + |
| 115 | + // Construct path: /usr/lib/debug/xy/name.debug |
| 116 | + Some(format!("/usr/lib/debug/.build-id/{prefix}/{suffix}.debug")) |
| 117 | + } |
| 118 | + |
74 | 119 | fn create_context_from_object(object: &object::File) -> Result<addr2line::Context<gimli::EndianRcSlice<gimli::RunTimeEndian>>, Box<dyn std::error::Error>> { |
75 | 120 | let endian = if object.is_little_endian() { |
76 | 121 | gimli::RunTimeEndian::Little |
@@ -167,17 +212,52 @@ impl Symbolizer { |
167 | 212 | Err(_) => return Ok(()), // Skip if we can't parse |
168 | 213 | }; |
169 | 214 |
|
170 | | - // Create context |
171 | | - let context = match Self::create_context_from_object(&object) { |
172 | | - Ok(ctx) => ctx, |
173 | | - Err(_) => return Ok(()), // Skip if no debug info |
174 | | - }; |
| 215 | + // Extract build-id from the library |
| 216 | + let build_id = Self::extract_build_id(&object); |
| 217 | + |
| 218 | + let mut loader = None; |
| 219 | + let mut context = None; |
| 220 | + |
| 221 | + // If we have a build-id, try to load debug info from the standard path |
| 222 | + if let Some(ref build_id_bytes) = build_id { |
| 223 | + if let Some(debug_path) = Self::get_debug_file_path(build_id_bytes) { |
| 224 | + eprintln!("Trying to load debug info from: {debug_path}"); |
| 225 | + |
| 226 | + // Try to create loader with debug file |
| 227 | + if Path::new(&debug_path).exists() { |
| 228 | + match addr2line::Loader::new(&debug_path) { |
| 229 | + Ok(debug_loader) => { |
| 230 | + eprintln!("Successfully loaded debug info from {debug_path}"); |
| 231 | + loader = Some(debug_loader); |
| 232 | + } |
| 233 | + Err(e) => { |
| 234 | + eprintln!("Failed to load debug info from {debug_path}: {e}"); |
| 235 | + } |
| 236 | + } |
| 237 | + } else { |
| 238 | + eprintln!("Debug file does not exist: {debug_path}"); |
| 239 | + } |
| 240 | + } |
| 241 | + } |
| 242 | + |
| 243 | + // If we couldn't load debug info from the standard path, fall back to the library itself |
| 244 | + if loader.is_none() { |
| 245 | + eprintln!("Falling back to loading debug info from library itself"); |
| 246 | + match Self::create_context_from_object(&object) { |
| 247 | + Ok(ctx) => context = Some(ctx), |
| 248 | + Err(_) => { |
| 249 | + eprintln!("No debug info available for library {path}"); |
| 250 | + } |
| 251 | + } |
| 252 | + } |
175 | 253 |
|
176 | 254 | let mut lib_info = LibraryInfo { |
| 255 | + loader, |
177 | 256 | context, |
178 | 257 | _file_data: static_file_data, |
179 | | - _base_address : base_address, |
| 258 | + _base_address: base_address, |
180 | 259 | symbols: vec![], |
| 260 | + build_id, |
181 | 261 | }; |
182 | 262 |
|
183 | 263 | eprintln!("Loading symbols for library {path}..."); |
@@ -217,8 +297,18 @@ impl Symbolizer { |
217 | 297 | if let Some(adr) = self.file_base_addresses.get(&pathname) { |
218 | 298 | let relative_address = lookup_address - adr; |
219 | 299 |
|
220 | | - if let Some(symbolized) = self.try_symbolize_with_context(&lib_info.context, relative_address, address) { |
221 | | - return symbolized; |
| 300 | + // Try with loader first if available |
| 301 | + if let Some(ref loader) = lib_info.loader { |
| 302 | + if let Some(symbolized) = self.try_symbolize_with_loader(loader, relative_address, address) { |
| 303 | + return symbolized; |
| 304 | + } |
| 305 | + } |
| 306 | + |
| 307 | + // Fall back to context if available |
| 308 | + if let Some(ref context) = lib_info.context { |
| 309 | + if let Some(symbolized) = self.try_symbolize_with_context(context, relative_address, address) { |
| 310 | + return symbolized; |
| 311 | + } |
222 | 312 | } |
223 | 313 |
|
224 | 314 | // Fall back to symbol table lookup |
@@ -262,6 +352,74 @@ impl Symbolizer { |
262 | 352 | result |
263 | 353 | } |
264 | 354 |
|
| 355 | + fn try_symbolize_with_loader(&self, loader: &addr2line::Loader, lookup_address: u64, original_address: u64) -> Option<SymbolizedFrame> { |
| 356 | + let mut result = SymbolizedFrame { |
| 357 | + address: original_address, |
| 358 | + function_name: None, |
| 359 | + file_name: None, |
| 360 | + line_number: None, |
| 361 | + }; |
| 362 | + |
| 363 | + // Try to find location information for this address |
| 364 | + match loader.find_location(lookup_address) { |
| 365 | + Ok(Some(location)) => { |
| 366 | + // Extract file information |
| 367 | + if let Some(file) = location.file { |
| 368 | + result.file_name = Some(file.to_string()); |
| 369 | + } |
| 370 | + |
| 371 | + // Extract line number |
| 372 | + if let Some(line) = location.line { |
| 373 | + result.line_number = Some(line); |
| 374 | + } |
| 375 | + } |
| 376 | + Ok(None) => { |
| 377 | + // No location information found |
| 378 | + } |
| 379 | + Err(_) => { |
| 380 | + // Error finding location - continue to try symbol lookup |
| 381 | + } |
| 382 | + } |
| 383 | + |
| 384 | + // Try to find function information for this address using find_frames |
| 385 | + match loader.find_frames(lookup_address) { |
| 386 | + Ok(mut frames) => { |
| 387 | + if let Ok(Some(frame)) = frames.next() { |
| 388 | + if let Some(function) = frame.function { |
| 389 | + // Get the raw function name |
| 390 | + let raw_name = function.raw_name().unwrap_or(std::borrow::Cow::Borrowed("<unknown>")); |
| 391 | + |
| 392 | + // Try to demangle the name |
| 393 | + let demangled_name = function.demangle().unwrap_or(raw_name); |
| 394 | + result.function_name = Some(demangled_name.to_string()); |
| 395 | + } |
| 396 | + |
| 397 | + // If we didn't get location info before, try to get it from the frame |
| 398 | + if result.file_name.is_none() { |
| 399 | + if let Some(location) = frame.location { |
| 400 | + if let Some(file) = location.file { |
| 401 | + result.file_name = Some(file.to_string()); |
| 402 | + } |
| 403 | + if let Some(line) = location.line { |
| 404 | + result.line_number = Some(line); |
| 405 | + } |
| 406 | + } |
| 407 | + } |
| 408 | + } |
| 409 | + } |
| 410 | + Err(_) => { |
| 411 | + // Error finding frames |
| 412 | + } |
| 413 | + } |
| 414 | + |
| 415 | + // Return the result if we found something |
| 416 | + if result.function_name.is_some() || result.file_name.is_some() { |
| 417 | + Some(result) |
| 418 | + } else { |
| 419 | + None |
| 420 | + } |
| 421 | + } |
| 422 | + |
265 | 423 | fn try_symbolize_with_context(&self, context: &addr2line::Context<gimli::EndianRcSlice<gimli::RunTimeEndian>>, lookup_address: u64, original_address: u64) -> Option<SymbolizedFrame> { |
266 | 424 | let mut result = SymbolizedFrame { |
267 | 425 | address: original_address, |
|
0 commit comments