Skip to content

Commit 8b49d81

Browse files
authored
Better addr2line (#2989)
* better addr2line * delete unused * more * fixer? * lol * class * mm * take care of non pie binary or pie binary * user mode only
1 parent 0aba2c4 commit 8b49d81

File tree

4 files changed

+177
-325
lines changed

4 files changed

+177
-325
lines changed

libafl_qemu/src/modules/usermode/asan.rs

Lines changed: 6 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33

44
use core::{fmt, slice};
55
use std::{
6-
borrow::Cow,
76
env,
8-
fmt::{Debug, Display, Write},
7+
fmt::{Debug, Display},
98
fs,
10-
path::PathBuf,
119
pin::Pin,
1210
sync::Mutex,
1311
};
@@ -21,19 +19,13 @@ use libc::{
2119
};
2220
use meminterval::{Interval, IntervalTree};
2321
use num_enum::{IntoPrimitive, TryFromPrimitive};
24-
use object::Object;
25-
use rangemap::RangeMap;
2622

2723
use crate::{
2824
emu::EmulatorModules,
2925
modules::{
3026
calls::FullBacktraceCollector,
3127
snapshot::SnapshotModule,
32-
utils::{
33-
addr2line_legacy,
34-
filters::{HasAddressFilter, StdAddressFilter},
35-
load_file_section,
36-
},
28+
utils::filters::{HasAddressFilter, StdAddressFilter},
3729
AddressFilter, EmulatorModule, EmulatorModuleTuple,
3830
},
3931
qemu::{Hook, MemAccessInfo, QemuHooks, SyscallHookResult},
@@ -1348,126 +1340,8 @@ where
13481340
/// # Safety
13491341
/// Will access the global [`FullBacktraceCollector`].
13501342
/// Calling this function concurrently might be racey.
1351-
#[expect(clippy::too_many_lines, clippy::unnecessary_cast)]
13521343
pub unsafe fn asan_report(rt: &AsanGiovese, qemu: Qemu, pc: GuestAddr, err: &AsanError) {
1353-
let mut regions = HashMap::new();
1354-
for region in qemu.mappings() {
1355-
if let Some(path) = region.path() {
1356-
let start = region.start();
1357-
let end = region.end();
1358-
let entry = regions.entry(path.to_owned()).or_insert(start..end);
1359-
if start < entry.start {
1360-
*entry = start..entry.end;
1361-
}
1362-
if end > entry.end {
1363-
*entry = entry.start..end;
1364-
}
1365-
}
1366-
}
1367-
1368-
let mut resolvers = vec![];
1369-
let mut images = vec![];
1370-
let mut ranges = RangeMap::new();
1371-
1372-
for (path, rng) in regions {
1373-
let data = fs::read(&path);
1374-
if data.is_err() {
1375-
continue;
1376-
}
1377-
let data = data.unwrap();
1378-
let idx = images.len();
1379-
images.push((path, data));
1380-
ranges.insert(rng, idx);
1381-
}
1382-
1383-
let arena_data = typed_arena::Arena::new();
1384-
1385-
for img in &images {
1386-
if let Ok(obj) = object::read::File::parse(&*img.1) {
1387-
let endian = if obj.is_little_endian() {
1388-
addr2line::gimli::RunTimeEndian::Little
1389-
} else {
1390-
addr2line::gimli::RunTimeEndian::Big
1391-
};
1392-
1393-
let mut load_section = |id: addr2line::gimli::SectionId| -> Result<_, _> {
1394-
load_file_section(id, &obj, endian, &arena_data)
1395-
};
1396-
1397-
let dwarf = addr2line::gimli::Dwarf::load(&mut load_section).unwrap();
1398-
let ctx = addr2line::Context::from_dwarf(dwarf)
1399-
.expect("Failed to create an addr2line context");
1400-
1401-
//let ctx = addr2line::Context::new(&obj).expect("Failed to create an addr2line context");
1402-
resolvers.push(Some((obj, ctx)));
1403-
} else {
1404-
resolvers.push(None);
1405-
}
1406-
}
1407-
1408-
let resolve_addr = |addr: GuestAddr| -> String {
1409-
let mut info = String::new();
1410-
if let Some((rng, idx)) = ranges.get_key_value(&addr) {
1411-
let raddr = (addr - rng.start) as u64;
1412-
if let Some((obj, ctx)) = resolvers[*idx].as_ref() {
1413-
let symbols = obj.symbol_map();
1414-
let mut func = symbols.get(raddr).map(|x| x.name().to_string());
1415-
1416-
if func.is_none() {
1417-
let pathname = PathBuf::from(images[*idx].0.clone());
1418-
let mut split_dwarf_loader = addr2line_legacy::SplitDwarfLoader::new(
1419-
|data, endian| {
1420-
addr2line::gimli::EndianSlice::new(
1421-
arena_data.alloc(Cow::Owned(data.into_owned())),
1422-
endian,
1423-
)
1424-
},
1425-
Some(pathname),
1426-
);
1427-
1428-
let frames = ctx.find_frames(raddr);
1429-
if let Ok(mut frames) = split_dwarf_loader.run(frames) {
1430-
if let Some(frame) = frames.next().unwrap_or(None) {
1431-
if let Some(function) = frame.function {
1432-
if let Ok(name) = function.raw_name() {
1433-
let demangled =
1434-
addr2line::demangle_auto(name, function.language);
1435-
func = Some(demangled.to_string());
1436-
}
1437-
}
1438-
}
1439-
}
1440-
}
1441-
1442-
if let Some(name) = func {
1443-
info += " in ";
1444-
info += &name;
1445-
}
1446-
1447-
if let Some(loc) = ctx.find_location(raddr).unwrap_or(None) {
1448-
if info.is_empty() {
1449-
info += " in";
1450-
}
1451-
info += " ";
1452-
if let Some(file) = loc.file {
1453-
info += file;
1454-
}
1455-
if let Some(line) = loc.line {
1456-
info += ":";
1457-
info += &line.to_string();
1458-
}
1459-
} else {
1460-
let _ = write!(&mut info, " ({}+{raddr:#x})", images[*idx].0);
1461-
}
1462-
}
1463-
if info.is_empty() {
1464-
let _ = write!(&mut info, " ({}+{raddr:#x})", images[*idx].0);
1465-
}
1466-
}
1467-
info
1468-
};
1469-
1470-
// TODO, make a class Resolver for resolving the addresses??
1344+
let resolver = crate::modules::utils::addr2line::AddressResolver::new(&qemu);
14711345
eprintln!("=================================================================");
14721346
let backtrace = FullBacktraceCollector::backtrace()
14731347
.map(|r| {
@@ -1478,7 +1352,7 @@ pub unsafe fn asan_report(rt: &AsanGiovese, qemu: Qemu, pc: GuestAddr, err: &Asa
14781352
.unwrap_or(vec![pc]);
14791353
eprintln!("AddressSanitizer Error: {err}");
14801354
for (i, addr) in backtrace.iter().rev().enumerate() {
1481-
eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr));
1355+
eprintln!("\t#{i} {addr:#x}{}", resolver.resolve(*addr));
14821356
}
14831357
let addr = match err {
14841358
AsanError::Read(addr, _) | AsanError::Write(addr, _) | AsanError::BadFree(addr, _) => {
@@ -1493,13 +1367,13 @@ pub unsafe fn asan_report(rt: &AsanGiovese, qemu: Qemu, pc: GuestAddr, err: &Asa
14931367
} else {
14941368
eprintln!("Freed at:");
14951369
for (i, addr) in item.free_backtrace.iter().rev().enumerate() {
1496-
eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr));
1370+
eprintln!("\t#{i} {addr:#x}{}", resolver.resolve(*addr));
14971371
}
14981372
eprintln!("And previously allocated at:");
14991373
}
15001374

15011375
for (i, addr) in item.backtrace.iter().rev().enumerate() {
1502-
eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr));
1376+
eprintln!("\t#{i} {addr:#x}{}", resolver.resolve(*addr));
15031377
}
15041378
};
15051379

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
//! Utils for addr2line
2+
3+
use std::{borrow::Cow, fmt::Write, fs};
4+
5+
use addr2line::{fallible_iterator::FallibleIterator, Loader};
6+
use goblin::elf::dynamic::{DF_1_PIE, DT_FLAGS_1};
7+
use hashbrown::HashMap;
8+
use libafl_qemu_sys::GuestAddr;
9+
use rangemap::RangeMap;
10+
11+
use crate::Qemu;
12+
// (almost) Copy paste from addr2line/src/bin/addr2line.rs
13+
fn print_function(name: Option<&str>, language: Option<addr2line::gimli::DwLang>) -> String {
14+
let ret = if let Some(name) = name {
15+
addr2line::demangle_auto(Cow::from(name), language).to_string()
16+
} else {
17+
"??".to_string()
18+
};
19+
// println!("{ret:?}");
20+
ret
21+
}
22+
23+
/// check if this binary is pie (for 64bit binary only)
24+
#[must_use]
25+
pub fn is_pie(file: object::File<'_>) -> bool {
26+
let is_pie = match file {
27+
object::File::Elf64(elf) => {
28+
let mut is_pie = false;
29+
let table = elf.elf_section_table();
30+
let dyn_sec = table.dynamic(elf.endian(), elf.data());
31+
if let Ok(Some(d)) = dyn_sec {
32+
let arr = d.0;
33+
for v in arr {
34+
if v.d_tag.get(elf.endian()) == DT_FLAGS_1
35+
&& v.d_val.get(elf.endian()) & DF_1_PIE == DF_1_PIE
36+
{
37+
is_pie = true;
38+
}
39+
}
40+
}
41+
is_pie
42+
}
43+
_ => false,
44+
};
45+
46+
is_pie
47+
}
48+
49+
pub struct AddressResolver {
50+
ranges: RangeMap<GuestAddr, usize>,
51+
images: Vec<(String, Vec<u8>)>,
52+
resolvers: Vec<Option<(Loader, bool)>>,
53+
}
54+
55+
impl AddressResolver {
56+
#[must_use]
57+
pub fn new(qemu: &Qemu) -> Self {
58+
let mut regions = HashMap::new();
59+
for region in qemu.mappings() {
60+
if let Some(path) = region.path() {
61+
let start = region.start();
62+
let end = region.end();
63+
let entry = regions.entry(path.to_owned()).or_insert(start..end);
64+
if start < entry.start {
65+
*entry = start..entry.end;
66+
}
67+
if end > entry.end {
68+
*entry = entry.start..end;
69+
}
70+
}
71+
}
72+
73+
let mut resolvers = vec![];
74+
let mut images = vec![];
75+
let mut ranges: RangeMap<GuestAddr, usize> = RangeMap::new();
76+
77+
for (path, rng) in regions {
78+
let data = fs::read(&path);
79+
if data.is_err() {
80+
continue;
81+
}
82+
let data = data.unwrap();
83+
let idx = images.len();
84+
images.push((path, data));
85+
ranges.insert(rng, idx);
86+
}
87+
88+
for img in &images {
89+
if let Ok(obj) = object::read::File::parse(&*img.1) {
90+
let is_pie = is_pie(obj);
91+
92+
let ctx = Loader::new(img.0.clone()).unwrap();
93+
resolvers.push(Some((ctx, is_pie)));
94+
} else {
95+
resolvers.push(None);
96+
}
97+
}
98+
Self {
99+
ranges,
100+
images,
101+
resolvers,
102+
}
103+
}
104+
105+
#[must_use]
106+
pub fn resolve(&self, pc: GuestAddr) -> String {
107+
let resolve_addr = |addr: GuestAddr| -> String {
108+
let mut info = String::new();
109+
if let Some((range, idx)) = self.ranges.get_key_value(&addr) {
110+
if let Some((ctx, is_pie)) = self.resolvers[*idx].as_ref() {
111+
let raddr = if *is_pie { addr - range.start } else { addr };
112+
let mut frames = ctx.find_frames(raddr.into()).unwrap().peekable();
113+
let mut fname = None;
114+
while let Some(frame) = frames.next().unwrap() {
115+
// Only use the symbol table if this isn't an inlined function.
116+
let symbol = if matches!(frames.peek(), Ok(None)) {
117+
ctx.find_symbol(raddr.into())
118+
} else {
119+
None
120+
};
121+
if symbol.is_some() {
122+
// Prefer the symbol table over the DWARF name because:
123+
// - the symbol can include a clone suffix
124+
// - llvm may omit the linkage name in the DWARF with -g1
125+
fname = Some(print_function(symbol, None));
126+
} else if let Some(func) = frame.function {
127+
fname = Some(print_function(
128+
func.raw_name().ok().as_deref(),
129+
func.language,
130+
));
131+
} else {
132+
fname = Some(print_function(None, None));
133+
}
134+
}
135+
136+
if let Some(name) = fname {
137+
info += " in ";
138+
info += &name;
139+
}
140+
141+
if let Some(loc) = ctx.find_location(raddr.into()).unwrap_or(None) {
142+
if info.is_empty() {
143+
info += " in";
144+
}
145+
info += " ";
146+
if let Some(file) = loc.file {
147+
info += file;
148+
}
149+
if let Some(line) = loc.line {
150+
info += ":";
151+
info += &line.to_string();
152+
}
153+
} else {
154+
let _ = write!(&mut info, " ({}+{addr:#x})", self.images[*idx].0);
155+
}
156+
}
157+
if info.is_empty() {
158+
let _ = write!(&mut info, " ({}+{addr:#x})", self.images[*idx].0);
159+
}
160+
}
161+
info
162+
};
163+
164+
resolve_addr(pc)
165+
}
166+
}

0 commit comments

Comments
 (0)