Skip to content

Commit f5b5a61

Browse files
AetiasHaxencounter
andauthored
Display correct line numbers for multiple .text sections (#63)
* Support multiple DWARF sequences * Rework DWARF line info parsing - Properly handles multiple sections in DWARF 1 - line_info moved into ObjSection - DWARF 2 parser no longer errors with no .text section - Both parsers properly skip empty sections * Simplify line_info (no Option) --------- Co-authored-by: Luke Street <luke.street@encounterpc.com>
1 parent 22a24f3 commit f5b5a61

File tree

5 files changed

+71
-42
lines changed

5 files changed

+71
-42
lines changed

objdiff-core/src/arch/mips.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,7 @@ impl ObjArch for ObjArchMips {
112112
}
113113
}
114114
}
115-
let line = obj
116-
.line_info
117-
.as_ref()
118-
.and_then(|map| map.range(..=cur_addr as u64).last().map(|(_, &b)| b));
115+
let line = section.line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
119116
insts.push(ObjIns {
120117
address: cur_addr as u64,
121118
size: 4,

objdiff-core/src/arch/ppc.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,7 @@ impl ObjArch for ObjArchPpc {
133133
}
134134

135135
ops.push(ins.op as u16);
136-
let line = obj
137-
.line_info
138-
.as_ref()
139-
.and_then(|map| map.range(..=cur_addr as u64).last().map(|(_, &b)| b));
136+
let line = section.line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
140137
insts.push(ObjIns {
141138
address: cur_addr as u64,
142139
size: 4,

objdiff-core/src/arch/x86.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ impl ObjArch for ObjArchX86 {
7474
.relocations
7575
.iter()
7676
.find(|r| r.address >= address && r.address < address + instruction.len() as u64);
77+
let line = section.line_info.range(..=address).last().map(|(_, &b)| b);
7778
output.ins = ObjIns {
7879
address,
7980
size: instruction.len() as u8,
@@ -82,7 +83,7 @@ impl ObjArch for ObjArchX86 {
8283
args: vec![],
8384
reloc: reloc.cloned(),
8485
branch_dest: None,
85-
line: obj.line_info.as_ref().and_then(|m| m.get(&address).cloned()),
86+
line,
8687
formatted: String::new(),
8788
orig: None,
8889
};

objdiff-core/src/obj/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ pub struct ObjSection {
3939
pub symbols: Vec<ObjSymbol>,
4040
pub relocations: Vec<ObjReloc>,
4141
pub virtual_address: Option<u64>,
42+
/// Line number info (.line or .debug_line section)
43+
pub line_info: BTreeMap<u64, u64>,
4244
}
4345

4446
#[derive(Debug, Clone, Eq, PartialEq)]
@@ -128,8 +130,6 @@ pub struct ObjInfo {
128130
pub sections: Vec<ObjSection>,
129131
/// Common BSS symbols
130132
pub common: Vec<ObjSymbol>,
131-
/// Line number info (.line or .debug_line section)
132-
pub line_info: Option<BTreeMap<u64, u64>>,
133133
/// Split object metadata (.note.split section)
134134
pub split_meta: Option<SplitMeta>,
135135
}

objdiff-core/src/obj/read.rs

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{collections::BTreeMap, fs, io::Cursor, path::Path};
1+
use std::{fs, io::Cursor, path::Path};
22

33
use anyhow::{anyhow, bail, ensure, Context, Result};
44
use byteorder::{BigEndian, ReadBytesExt};
@@ -111,6 +111,7 @@ fn filter_sections(obj_file: &File<'_>, split_meta: Option<&SplitMeta>) -> Resul
111111
symbols: Vec::new(),
112112
relocations: Vec::new(),
113113
virtual_address,
114+
line_info: Default::default(),
114115
});
115116
}
116117
result.sort_by(|a, b| a.name.cmp(&b.name));
@@ -268,26 +269,40 @@ fn relocations_by_section(
268269
Ok(relocations)
269270
}
270271

271-
fn line_info(obj_file: &File<'_>) -> Result<Option<BTreeMap<u64, u64>>> {
272+
fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection]) -> Result<()> {
272273
// DWARF 1.1
273-
let mut map = BTreeMap::new();
274274
if let Some(section) = obj_file.section_by_name(".line") {
275-
if section.size() == 0 {
276-
return Ok(None);
277-
}
278275
let data = section.uncompressed_data()?;
279276
let mut reader = Cursor::new(data.as_ref());
280277

281-
let size = reader.read_u32::<BigEndian>()?;
282-
let base_address = reader.read_u32::<BigEndian>()? as u64;
283-
while reader.position() < size as u64 {
284-
let line_number = reader.read_u32::<BigEndian>()? as u64;
285-
let statement_pos = reader.read_u16::<BigEndian>()?;
286-
if statement_pos != 0xFFFF {
287-
log::warn!("Unhandled statement pos {}", statement_pos);
278+
let mut text_sections = obj_file.sections().filter(|s| s.kind() == SectionKind::Text);
279+
while reader.position() < data.len() as u64 {
280+
let text_section_index = text_sections
281+
.next()
282+
.ok_or_else(|| anyhow!("Next text section not found for line info"))?
283+
.index()
284+
.0;
285+
let start = reader.position();
286+
let size = reader.read_u32::<BigEndian>()?;
287+
let base_address = reader.read_u32::<BigEndian>()? as u64;
288+
let Some(out_section) =
289+
sections.iter_mut().find(|s| s.orig_index == text_section_index)
290+
else {
291+
// Skip line info for sections we filtered out
292+
reader.set_position(start + size as u64);
293+
continue;
294+
};
295+
let end = start + size as u64;
296+
while reader.position() < end {
297+
let line_number = reader.read_u32::<BigEndian>()? as u64;
298+
let statement_pos = reader.read_u16::<BigEndian>()?;
299+
if statement_pos != 0xFFFF {
300+
log::warn!("Unhandled statement pos {}", statement_pos);
301+
}
302+
let address_delta = reader.read_u32::<BigEndian>()? as u64;
303+
out_section.line_info.insert(base_address + address_delta, line_number);
304+
log::debug!("Line: {:#x} -> {}", base_address + address_delta, line_number);
288305
}
289-
let address_delta = reader.read_u32::<BigEndian>()? as u64;
290-
map.insert(base_address + address_delta, line_number);
291306
}
292307
}
293308

@@ -308,22 +323,48 @@ fn line_info(obj_file: &File<'_>) -> Result<Option<BTreeMap<u64, u64>>> {
308323
};
309324
let dwarf = dwarf_cow.borrow(|section| gimli::EndianSlice::new(section, endian));
310325
let mut iter = dwarf.units();
311-
while let Some(header) = iter.next()? {
326+
if let Some(header) = iter.next()? {
312327
let unit = dwarf.unit(header)?;
313328
if let Some(program) = unit.line_program.clone() {
329+
let mut text_sections =
330+
obj_file.sections().filter(|s| s.kind() == SectionKind::Text);
331+
let section_index = text_sections
332+
.next()
333+
.ok_or_else(|| anyhow!("Next text section not found for line info"))?
334+
.index()
335+
.0;
336+
let mut lines = sections
337+
.iter_mut()
338+
.find(|s| s.orig_index == section_index)
339+
.map(|s| &mut s.line_info);
340+
314341
let mut rows = program.rows();
315342
while let Some((_header, row)) = rows.next_row()? {
316-
if let Some(line) = row.line() {
317-
map.insert(row.address(), line.get());
343+
if let (Some(line), Some(lines)) = (row.line(), &mut lines) {
344+
lines.insert(row.address(), line.get());
345+
}
346+
if row.end_sequence() {
347+
// The next row is the start of a new sequence, which means we must
348+
// advance to the next .text section.
349+
let section_index = text_sections
350+
.next()
351+
.ok_or_else(|| anyhow!("Next text section not found for line info"))?
352+
.index()
353+
.0;
354+
lines = sections
355+
.iter_mut()
356+
.find(|s| s.orig_index == section_index)
357+
.map(|s| &mut s.line_info);
318358
}
319359
}
320360
}
321361
}
362+
if iter.next()?.is_some() {
363+
log::warn!("Multiple units found in DWARF data, only processing the first");
364+
}
322365
}
323-
if map.is_empty() {
324-
return Ok(None);
325-
}
326-
Ok(Some(map))
366+
367+
Ok(())
327368
}
328369

329370
pub fn read(obj_path: &Path) -> Result<ObjInfo> {
@@ -342,16 +383,9 @@ pub fn read(obj_path: &Path) -> Result<ObjInfo> {
342383
section.relocations =
343384
relocations_by_section(arch.as_ref(), &obj_file, section, split_meta.as_ref())?;
344385
}
386+
line_info(&obj_file, &mut sections)?;
345387
let common = common_symbols(arch.as_ref(), &obj_file, split_meta.as_ref())?;
346-
Ok(ObjInfo {
347-
arch,
348-
path: obj_path.to_owned(),
349-
timestamp,
350-
sections,
351-
common,
352-
line_info: line_info(&obj_file)?,
353-
split_meta,
354-
})
388+
Ok(ObjInfo { arch, path: obj_path.to_owned(), timestamp, sections, common, split_meta })
355389
}
356390

357391
pub fn has_function(obj_path: &Path, symbol_name: &str) -> Result<bool> {

0 commit comments

Comments
 (0)