Skip to content

Commit

Permalink
Better addr2line (#2989)
Browse files Browse the repository at this point in the history
* better addr2line

* delete unused

* more

* fixer?

* lol

* class

* mm

* take care of non pie binary or pie binary

* user mode only
  • Loading branch information
tokatoka authored Feb 16, 2025
1 parent 0aba2c4 commit 8b49d81
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 325 deletions.
138 changes: 6 additions & 132 deletions libafl_qemu/src/modules/usermode/asan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@

use core::{fmt, slice};
use std::{
borrow::Cow,
env,
fmt::{Debug, Display, Write},
fmt::{Debug, Display},
fs,
path::PathBuf,
pin::Pin,
sync::Mutex,
};
Expand All @@ -21,19 +19,13 @@ use libc::{
};
use meminterval::{Interval, IntervalTree};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use object::Object;
use rangemap::RangeMap;

use crate::{
emu::EmulatorModules,
modules::{
calls::FullBacktraceCollector,
snapshot::SnapshotModule,
utils::{
addr2line_legacy,
filters::{HasAddressFilter, StdAddressFilter},
load_file_section,
},
utils::filters::{HasAddressFilter, StdAddressFilter},
AddressFilter, EmulatorModule, EmulatorModuleTuple,
},
qemu::{Hook, MemAccessInfo, QemuHooks, SyscallHookResult},
Expand Down Expand Up @@ -1348,126 +1340,8 @@ where
/// # Safety
/// Will access the global [`FullBacktraceCollector`].
/// Calling this function concurrently might be racey.
#[expect(clippy::too_many_lines, clippy::unnecessary_cast)]
pub unsafe fn asan_report(rt: &AsanGiovese, qemu: Qemu, pc: GuestAddr, err: &AsanError) {
let mut regions = HashMap::new();
for region in qemu.mappings() {
if let Some(path) = region.path() {
let start = region.start();
let end = region.end();
let entry = regions.entry(path.to_owned()).or_insert(start..end);
if start < entry.start {
*entry = start..entry.end;
}
if end > entry.end {
*entry = entry.start..end;
}
}
}

let mut resolvers = vec![];
let mut images = vec![];
let mut ranges = RangeMap::new();

for (path, rng) in regions {
let data = fs::read(&path);
if data.is_err() {
continue;
}
let data = data.unwrap();
let idx = images.len();
images.push((path, data));
ranges.insert(rng, idx);
}

let arena_data = typed_arena::Arena::new();

for img in &images {
if let Ok(obj) = object::read::File::parse(&*img.1) {
let endian = if obj.is_little_endian() {
addr2line::gimli::RunTimeEndian::Little
} else {
addr2line::gimli::RunTimeEndian::Big
};

let mut load_section = |id: addr2line::gimli::SectionId| -> Result<_, _> {
load_file_section(id, &obj, endian, &arena_data)
};

let dwarf = addr2line::gimli::Dwarf::load(&mut load_section).unwrap();
let ctx = addr2line::Context::from_dwarf(dwarf)
.expect("Failed to create an addr2line context");

//let ctx = addr2line::Context::new(&obj).expect("Failed to create an addr2line context");
resolvers.push(Some((obj, ctx)));
} else {
resolvers.push(None);
}
}

let resolve_addr = |addr: GuestAddr| -> String {
let mut info = String::new();
if let Some((rng, idx)) = ranges.get_key_value(&addr) {
let raddr = (addr - rng.start) as u64;
if let Some((obj, ctx)) = resolvers[*idx].as_ref() {
let symbols = obj.symbol_map();
let mut func = symbols.get(raddr).map(|x| x.name().to_string());

if func.is_none() {
let pathname = PathBuf::from(images[*idx].0.clone());
let mut split_dwarf_loader = addr2line_legacy::SplitDwarfLoader::new(
|data, endian| {
addr2line::gimli::EndianSlice::new(
arena_data.alloc(Cow::Owned(data.into_owned())),
endian,
)
},
Some(pathname),
);

let frames = ctx.find_frames(raddr);
if let Ok(mut frames) = split_dwarf_loader.run(frames) {
if let Some(frame) = frames.next().unwrap_or(None) {
if let Some(function) = frame.function {
if let Ok(name) = function.raw_name() {
let demangled =
addr2line::demangle_auto(name, function.language);
func = Some(demangled.to_string());
}
}
}
}
}

if let Some(name) = func {
info += " in ";
info += &name;
}

if let Some(loc) = ctx.find_location(raddr).unwrap_or(None) {
if info.is_empty() {
info += " in";
}
info += " ";
if let Some(file) = loc.file {
info += file;
}
if let Some(line) = loc.line {
info += ":";
info += &line.to_string();
}
} else {
let _ = write!(&mut info, " ({}+{raddr:#x})", images[*idx].0);
}
}
if info.is_empty() {
let _ = write!(&mut info, " ({}+{raddr:#x})", images[*idx].0);
}
}
info
};

// TODO, make a class Resolver for resolving the addresses??
let resolver = crate::modules::utils::addr2line::AddressResolver::new(&qemu);
eprintln!("=================================================================");
let backtrace = FullBacktraceCollector::backtrace()
.map(|r| {
Expand All @@ -1478,7 +1352,7 @@ pub unsafe fn asan_report(rt: &AsanGiovese, qemu: Qemu, pc: GuestAddr, err: &Asa
.unwrap_or(vec![pc]);
eprintln!("AddressSanitizer Error: {err}");
for (i, addr) in backtrace.iter().rev().enumerate() {
eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr));
eprintln!("\t#{i} {addr:#x}{}", resolver.resolve(*addr));
}
let addr = match err {
AsanError::Read(addr, _) | AsanError::Write(addr, _) | AsanError::BadFree(addr, _) => {
Expand All @@ -1493,13 +1367,13 @@ pub unsafe fn asan_report(rt: &AsanGiovese, qemu: Qemu, pc: GuestAddr, err: &Asa
} else {
eprintln!("Freed at:");
for (i, addr) in item.free_backtrace.iter().rev().enumerate() {
eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr));
eprintln!("\t#{i} {addr:#x}{}", resolver.resolve(*addr));
}
eprintln!("And previously allocated at:");
}

for (i, addr) in item.backtrace.iter().rev().enumerate() {
eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr));
eprintln!("\t#{i} {addr:#x}{}", resolver.resolve(*addr));
}
};

Expand Down
166 changes: 166 additions & 0 deletions libafl_qemu/src/modules/utils/addr2line.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
//! Utils for addr2line
use std::{borrow::Cow, fmt::Write, fs};

use addr2line::{fallible_iterator::FallibleIterator, Loader};
use goblin::elf::dynamic::{DF_1_PIE, DT_FLAGS_1};
use hashbrown::HashMap;
use libafl_qemu_sys::GuestAddr;
use rangemap::RangeMap;

use crate::Qemu;
// (almost) Copy paste from addr2line/src/bin/addr2line.rs
fn print_function(name: Option<&str>, language: Option<addr2line::gimli::DwLang>) -> String {
let ret = if let Some(name) = name {
addr2line::demangle_auto(Cow::from(name), language).to_string()
} else {
"??".to_string()
};
// println!("{ret:?}");
ret
}

/// check if this binary is pie (for 64bit binary only)
#[must_use]
pub fn is_pie(file: object::File<'_>) -> bool {
let is_pie = match file {
object::File::Elf64(elf) => {
let mut is_pie = false;
let table = elf.elf_section_table();
let dyn_sec = table.dynamic(elf.endian(), elf.data());
if let Ok(Some(d)) = dyn_sec {
let arr = d.0;
for v in arr {
if v.d_tag.get(elf.endian()) == DT_FLAGS_1
&& v.d_val.get(elf.endian()) & DF_1_PIE == DF_1_PIE
{
is_pie = true;
}
}
}
is_pie
}
_ => false,
};

is_pie
}

pub struct AddressResolver {
ranges: RangeMap<GuestAddr, usize>,
images: Vec<(String, Vec<u8>)>,
resolvers: Vec<Option<(Loader, bool)>>,
}

impl AddressResolver {
#[must_use]
pub fn new(qemu: &Qemu) -> Self {
let mut regions = HashMap::new();
for region in qemu.mappings() {
if let Some(path) = region.path() {
let start = region.start();
let end = region.end();
let entry = regions.entry(path.to_owned()).or_insert(start..end);
if start < entry.start {
*entry = start..entry.end;
}
if end > entry.end {
*entry = entry.start..end;
}
}
}

let mut resolvers = vec![];
let mut images = vec![];
let mut ranges: RangeMap<GuestAddr, usize> = RangeMap::new();

for (path, rng) in regions {
let data = fs::read(&path);
if data.is_err() {
continue;
}
let data = data.unwrap();
let idx = images.len();
images.push((path, data));
ranges.insert(rng, idx);
}

for img in &images {
if let Ok(obj) = object::read::File::parse(&*img.1) {
let is_pie = is_pie(obj);

let ctx = Loader::new(img.0.clone()).unwrap();
resolvers.push(Some((ctx, is_pie)));
} else {
resolvers.push(None);
}
}
Self {
ranges,
images,
resolvers,
}
}

#[must_use]
pub fn resolve(&self, pc: GuestAddr) -> String {
let resolve_addr = |addr: GuestAddr| -> String {
let mut info = String::new();
if let Some((range, idx)) = self.ranges.get_key_value(&addr) {
if let Some((ctx, is_pie)) = self.resolvers[*idx].as_ref() {
let raddr = if *is_pie { addr - range.start } else { addr };
let mut frames = ctx.find_frames(raddr.into()).unwrap().peekable();
let mut fname = None;
while let Some(frame) = frames.next().unwrap() {
// Only use the symbol table if this isn't an inlined function.
let symbol = if matches!(frames.peek(), Ok(None)) {
ctx.find_symbol(raddr.into())
} else {
None
};
if symbol.is_some() {
// Prefer the symbol table over the DWARF name because:
// - the symbol can include a clone suffix
// - llvm may omit the linkage name in the DWARF with -g1
fname = Some(print_function(symbol, None));
} else if let Some(func) = frame.function {
fname = Some(print_function(
func.raw_name().ok().as_deref(),
func.language,
));
} else {
fname = Some(print_function(None, None));
}
}

if let Some(name) = fname {
info += " in ";
info += &name;
}

if let Some(loc) = ctx.find_location(raddr.into()).unwrap_or(None) {
if info.is_empty() {
info += " in";
}
info += " ";
if let Some(file) = loc.file {
info += file;
}
if let Some(line) = loc.line {
info += ":";
info += &line.to_string();
}
} else {
let _ = write!(&mut info, " ({}+{addr:#x})", self.images[*idx].0);
}
}
if info.is_empty() {
let _ = write!(&mut info, " ({}+{addr:#x})", self.images[*idx].0);
}
}
info
};

resolve_addr(pc)
}
}
Loading

0 comments on commit 8b49d81

Please sign in to comment.