Skip to content

Commit fbe57b5

Browse files
committed
Finally seems to work.
1 parent 4f89d07 commit fbe57b5

File tree

1 file changed

+167
-9
lines changed

1 file changed

+167
-9
lines changed

src/symbolizer.rs

Lines changed: 167 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
use std::borrow::Cow;
22
use std::fs;
33
use std::collections::HashMap;
4+
use std::path::Path;
45

56
use object::{Object, ObjectSection, ObjectSymbol, SymbolKind};
67

78
use crate::{SymbolizedFrame, MemoryMapping};
89

910
struct LibraryInfo {
10-
context: addr2line::Context<gimli::EndianRcSlice<gimli::RunTimeEndian>>,
11+
loader: Option<addr2line::Loader>,
12+
context: Option<addr2line::Context<gimli::EndianRcSlice<gimli::RunTimeEndian>>>,
1113
_file_data: &'static [u8],
1214
_base_address: u64,
1315
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
1418
}
1519

1620
#[derive(Clone, PartialEq, Eq)]
@@ -71,6 +75,47 @@ impl Symbolizer {
7175
})
7276
}
7377

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+
74119
fn create_context_from_object(object: &object::File) -> Result<addr2line::Context<gimli::EndianRcSlice<gimli::RunTimeEndian>>, Box<dyn std::error::Error>> {
75120
let endian = if object.is_little_endian() {
76121
gimli::RunTimeEndian::Little
@@ -167,17 +212,52 @@ impl Symbolizer {
167212
Err(_) => return Ok(()), // Skip if we can't parse
168213
};
169214

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+
}
175253

176254
let mut lib_info = LibraryInfo {
255+
loader,
177256
context,
178257
_file_data: static_file_data,
179-
_base_address : base_address,
258+
_base_address: base_address,
180259
symbols: vec![],
260+
build_id,
181261
};
182262

183263
eprintln!("Loading symbols for library {path}...");
@@ -217,8 +297,18 @@ impl Symbolizer {
217297
if let Some(adr) = self.file_base_addresses.get(&pathname) {
218298
let relative_address = lookup_address - adr;
219299

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+
}
222312
}
223313

224314
// Fall back to symbol table lookup
@@ -262,6 +352,74 @@ impl Symbolizer {
262352
result
263353
}
264354

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+
265423
fn try_symbolize_with_context(&self, context: &addr2line::Context<gimli::EndianRcSlice<gimli::RunTimeEndian>>, lookup_address: u64, original_address: u64) -> Option<SymbolizedFrame> {
266424
let mut result = SymbolizedFrame {
267425
address: original_address,

0 commit comments

Comments
 (0)