|
@@ -6,13 +6,13 @@ use std::{
|
|
|
fs,
|
|
|
io::{self, BufRead, Cursor, Read},
|
|
|
mem,
|
|
|
- os::{fd::AsFd as _, raw::c_char},
|
|
|
+ os::{fd::AsFd as _, raw::c_char, unix::ffi::OsStrExt},
|
|
|
path::{Path, PathBuf},
|
|
|
sync::Arc,
|
|
|
};
|
|
|
|
|
|
use libc::pid_t;
|
|
|
-use object::{Object, ObjectSection, ObjectSymbol};
|
|
|
+use object::{Object, ObjectSection, ObjectSymbol, Symbol};
|
|
|
use thiserror::Error;
|
|
|
|
|
|
use crate::{
|
|
@@ -432,17 +432,103 @@ enum ResolveSymbolError {
|
|
|
|
|
|
#[error("symbol `{0}` in section `{1:?}` which has no offset")]
|
|
|
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> {
|
|
|
let data = fs::read(path)?;
|
|
|
let obj = object::read::File::parse(&*data)?;
|
|
|
|
|
|
- let sym = obj
|
|
|
- .dynamic_symbols()
|
|
|
- .chain(obj.symbols())
|
|
|
- .find(|sym| sym.name().map(|name| name == symbol).unwrap_or(false))
|
|
|
- .ok_or_else(|| ResolveSymbolError::Unknown(symbol.to_string()))?;
|
|
|
+ let mut debug_data = Vec::default();
|
|
|
+ let mut debug_obj_keeper = None;
|
|
|
+
|
|
|
+ let sym = find_symbol_in_object(&obj, symbol).map_or_else(
|
|
|
+ || {
|
|
|
+ // 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!(
|
|
|
obj.kind(),
|
|
@@ -464,3 +550,141 @@ fn resolve_symbol(path: &Path, symbol: &str) -> Result<u64, ResolveSymbolError>
|
|
|
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, ¬e_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(_))
|
|
|
+ ));
|
|
|
+ }
|
|
|
+}
|