aya: add symbol lookup in associated debug files

This change enhances the logic for symbol lookup in uprobe or uretprobe.
If the symbol is not found in the original binary, the search continues
in the debug file associated through the debuglink section. Before
searching the symbol table, it compares the build IDs of the two files.
The symbol lookup will only be terminated if both build IDs exist and do
not match. This modification does not affect the existing symbol lookup
logic.

Refs: #936
pull/938/head
swananan 3 months ago
parent e4f9ed8d79
commit e6e1bfeb58

@ -21,7 +21,7 @@ bytes = { workspace = true }
lazy_static = { workspace = true } lazy_static = { workspace = true }
libc = { workspace = true } libc = { workspace = true }
log = { workspace = true } log = { workspace = true }
object = { workspace = true, features = ["elf", "read_core", "std"] } object = { workspace = true, features = ["elf", "read_core", "std", "write"] }
thiserror = { workspace = true } thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt"], optional = true } tokio = { workspace = true, features = ["rt"], optional = true }

@ -6,13 +6,13 @@ use std::{
fs, fs,
io::{self, BufRead, Cursor, Read}, io::{self, BufRead, Cursor, Read},
mem, mem,
os::{fd::AsFd as _, raw::c_char}, os::{fd::AsFd as _, raw::c_char, unix::ffi::OsStrExt},
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
}; };
use libc::pid_t; use libc::pid_t;
use object::{Object, ObjectSection, ObjectSymbol}; use object::{Object, ObjectSection, ObjectSymbol, Symbol};
use thiserror::Error; use thiserror::Error;
use crate::{ use crate::{
@ -432,17 +432,103 @@ enum ResolveSymbolError {
#[error("symbol `{0}` in section `{1:?}` which has no offset")] #[error("symbol `{0}` in section `{1:?}` which has no offset")]
SectionFileRangeNone(String, Result<String, object::Error>), SectionFileRangeNone(String, Result<String, object::Error>),
#[error("failed to access debuglink file `{0}`: `{1}`")]
DebuglinkAccessError(String, io::Error),
#[error("symbol `{0}` not found, mismatched build IDs in main and debug files")]
BuildIdMismatch(String),
}
fn construct_debuglink_path(
filename: &[u8],
main_path: &Path,
) -> Result<PathBuf, ResolveSymbolError> {
let filename_str = OsStr::from_bytes(filename);
let debuglink_path = Path::new(filename_str);
let resolved_path = if debuglink_path.is_relative() {
// If the debug path is relative, resolve it against the parent of the main path
main_path.parent().map_or_else(
|| PathBuf::from(debuglink_path), // Use original if no parent
|parent| parent.join(debuglink_path),
)
} else {
// If the path is not relative, just use original
PathBuf::from(debuglink_path)
};
Ok(resolved_path)
}
fn verify_build_ids<'a>(
main_obj: &'a object::File<'a>,
debug_obj: &'a object::File<'a>,
symbol_name: &str,
) -> Result<(), ResolveSymbolError> {
let main_build_id = main_obj.build_id().ok().flatten();
let debug_build_id = debug_obj.build_id().ok().flatten();
match (debug_build_id, main_build_id) {
(Some(debug_build_id), Some(main_build_id)) => {
// Only perform a comparison if both build IDs are present
if debug_build_id != main_build_id {
return Err(ResolveSymbolError::BuildIdMismatch(symbol_name.to_owned()));
}
Ok(())
}
_ => Ok(()),
}
}
fn find_debug_path_in_object<'a>(
obj: &'a object::File<'a>,
main_path: &Path,
symbol: &str,
) -> Result<PathBuf, ResolveSymbolError> {
match obj.gnu_debuglink() {
Ok(Some((filename, _))) => construct_debuglink_path(filename, main_path),
Ok(None) => Err(ResolveSymbolError::Unknown(symbol.to_string())),
Err(err) => Err(ResolveSymbolError::Object(err)),
}
}
fn find_symbol_in_object<'a>(obj: &'a object::File<'a>, symbol: &str) -> Option<Symbol<'a, 'a>> {
obj.dynamic_symbols()
.chain(obj.symbols())
.find(|sym| sym.name().map(|name| name == symbol).unwrap_or(false))
} }
fn resolve_symbol(path: &Path, symbol: &str) -> Result<u64, ResolveSymbolError> { fn resolve_symbol(path: &Path, symbol: &str) -> Result<u64, ResolveSymbolError> {
let data = fs::read(path)?; let data = fs::read(path)?;
let obj = object::read::File::parse(&*data)?; let obj = object::read::File::parse(&*data)?;
let sym = obj let mut debug_data = Vec::default();
.dynamic_symbols() let mut debug_obj_keeper = None;
.chain(obj.symbols())
.find(|sym| sym.name().map(|name| name == symbol).unwrap_or(false)) let sym = find_symbol_in_object(&obj, symbol).map_or_else(
.ok_or_else(|| ResolveSymbolError::Unknown(symbol.to_string()))?; || {
// Only search in the debug object if the symbol was not found in the main object
let debug_path = find_debug_path_in_object(&obj, path, symbol)?;
debug_data = fs::read(&debug_path).map_err(|e| {
ResolveSymbolError::DebuglinkAccessError(
debug_path
.to_str()
.unwrap_or("Debuglink path missing")
.to_string(),
e,
)
})?;
let debug_obj = object::read::File::parse(&*debug_data)?;
verify_build_ids(&obj, &debug_obj, symbol)?;
debug_obj_keeper = Some(debug_obj);
find_symbol_in_object(debug_obj_keeper.as_ref().unwrap(), symbol)
.ok_or_else(|| ResolveSymbolError::Unknown(symbol.to_string()))
},
Ok,
)?;
let needs_addr_translation = matches!( let needs_addr_translation = matches!(
obj.kind(), obj.kind(),
@ -464,3 +550,141 @@ fn resolve_symbol(path: &Path, symbol: &str) -> Result<u64, ResolveSymbolError>
Ok(sym.address() - section.address() + offset) Ok(sym.address() - section.address() + offset)
} }
} }
#[cfg(test)]
mod tests {
use object::{write::SectionKind, Architecture, BinaryFormat, Endianness};
use super::*;
#[test]
fn test_relative_path_with_parent() {
let filename = b"debug_info";
let main_path = Path::new("/usr/lib/main_binary");
let expected = Path::new("/usr/lib/debug_info");
let result = construct_debuglink_path(filename, main_path).unwrap();
assert_eq!(
result, expected,
"The debug path should resolve relative to the main path's parent"
);
}
#[test]
fn test_relative_path_without_parent() {
let filename = b"debug_info";
let main_path = Path::new("main_binary");
let expected = Path::new("debug_info");
let result = construct_debuglink_path(filename, main_path).unwrap();
assert_eq!(
result, expected,
"The debug path should be the original path as there is no parent"
);
}
#[test]
fn test_absolute_path() {
let filename = b"/absolute/path/to/debug_info";
let main_path = Path::new("/usr/lib/main_binary");
let expected = Path::new("/absolute/path/to/debug_info");
let result = construct_debuglink_path(filename, main_path).unwrap();
assert_eq!(
result, expected,
"The debug path should be the same as the input absolute path"
);
}
fn create_elf_with_debuglink(
debug_filename: &[u8],
crc: u32,
) -> Result<Vec<u8>, object::write::Error> {
let mut obj =
object::write::Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little);
let section_name = b".gnu_debuglink";
let section_id = obj.add_section(vec![], section_name.to_vec(), SectionKind::Note);
let mut debuglink_data = Vec::new();
debuglink_data.extend_from_slice(debug_filename);
debuglink_data.push(0); // Null terminator
while debuglink_data.len() % 4 != 0 {
debuglink_data.push(0);
}
debuglink_data.extend(&crc.to_le_bytes());
obj.append_section_data(section_id, &debuglink_data, 4 /* align */);
obj.write()
}
fn create_elf_with_build_id(build_id: &[u8]) -> Result<Vec<u8>, object::write::Error> {
let mut obj =
object::write::Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little);
let section_name = b".note.gnu.build-id";
let section_id = obj.add_section(vec![], section_name.to_vec(), SectionKind::Note);
let mut note_data = Vec::new();
let build_id_name = b"GNU";
note_data.extend(&(build_id_name.len() as u32 + 1).to_le_bytes());
note_data.extend(&(build_id.len() as u32).to_le_bytes());
note_data.extend(&3u32.to_le_bytes());
note_data.extend_from_slice(build_id_name);
note_data.push(0); // Null terminator
note_data.extend_from_slice(build_id);
obj.append_section_data(section_id, &note_data, 4 /* align */);
obj.write()
}
#[test]
fn test_find_debug_path_success() {
let debug_filepath = b"main.debug";
let main_bytes = create_elf_with_debuglink(debug_filepath, 0x123 /* fake CRC */)
.expect("got main_bytes");
let main_obj = object::File::parse(&*main_bytes).expect("got main obj");
let main_path = Path::new("/path/to/main");
let result = find_debug_path_in_object(&main_obj, main_path, "symbol");
assert_eq!(result.unwrap(), Path::new("/path/to/main.debug"));
}
#[test]
fn test_verify_build_ids_same() {
let build_id = b"test_build_id";
let main_bytes = create_elf_with_build_id(build_id).expect("got main_bytes");
let main_obj = object::File::parse(&*main_bytes).expect("got main obj");
let debug_build_id = b"test_build_id";
let debug_bytes = create_elf_with_build_id(debug_build_id).expect("got debug bytes");
let debug_obj = object::File::parse(&*debug_bytes).expect("got debug obj");
assert!(verify_build_ids(&main_obj, &debug_obj, "symbol_name").is_ok());
}
#[test]
fn test_verify_build_ids_different() {
let build_id = b"main_build_id";
let main_bytes = create_elf_with_build_id(build_id).expect("got main_bytes");
let main_obj = object::File::parse(&*main_bytes).expect("got main obj");
let debug_build_id = b"debug_build_id";
let debug_bytes = create_elf_with_build_id(debug_build_id).expect("got debug bytes");
let debug_obj = object::File::parse(&*debug_bytes).expect("got debug obj");
assert!(matches!(
verify_build_ids(&main_obj, &debug_obj, "symbol_name"),
Err(ResolveSymbolError::BuildIdMismatch(_))
));
}
}

Loading…
Cancel
Save