#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LineRow {
pub addr: u32,
pub line: u32,
pub file: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SourceLoc {
pub line: u32,
pub file: u32,
}
pub fn op_offsets_to_source(
op_offsets: &[u32],
code_base: u32,
rows: &[LineRow],
) -> Vec<Option<SourceLoc>> {
let mut sorted: Vec<LineRow> = rows.to_vec();
sorted.sort_by_key(|r| r.addr);
op_offsets
.iter()
.map(|&off| {
let a = off.checked_sub(code_base)?;
sorted
.iter()
.rev()
.find(|r| r.addr <= a)
.map(|r| SourceLoc {
line: r.line,
file: r.file,
})
})
.collect()
}
use std::collections::HashMap;
use gimli::{Dwarf, EndianSlice, LittleEndian, SectionId};
use wasmparser::{Parser, Payload};
#[derive(Debug, Default, Clone)]
pub struct InputDwarfLine {
pub rows: Vec<LineRow>,
pub code_base: u32,
}
pub fn read_input_dwarf_line(wasm: &[u8]) -> InputDwarfLine {
let mut sections: HashMap<String, Vec<u8>> = HashMap::new();
let mut code_base = 0u32;
for payload in Parser::new(0).parse_all(wasm) {
match payload {
Ok(Payload::CustomSection(c)) if c.name().starts_with(".debug_") => {
sections.insert(c.name().to_string(), c.data().to_vec());
}
Ok(Payload::CodeSectionStart { range, .. }) => {
code_base = range.start as u32;
}
_ => {}
}
}
if !sections.contains_key(".debug_line") {
return InputDwarfLine {
rows: Vec::new(),
code_base,
};
}
let rows = parse_debug_line_rows(§ions).unwrap_or_default();
InputDwarfLine { rows, code_base }
}
fn parse_debug_line_rows(
sections: &HashMap<String, Vec<u8>>,
) -> Result<Vec<LineRow>, gimli::Error> {
let empty: &[u8] = &[];
let load = |id: SectionId| -> Result<EndianSlice<'_, LittleEndian>, gimli::Error> {
let data = sections.get(id.name()).map_or(empty, |v| v.as_slice());
Ok(EndianSlice::new(data, LittleEndian))
};
let dwarf = Dwarf::load(load)?;
let mut rows = Vec::new();
let mut units = dwarf.units();
while let Some(header) = units.next()? {
let unit = dwarf.unit(header)?;
let Some(program) = unit.line_program.clone() else {
continue;
};
let mut state = program.rows();
while let Some((_, row)) = state.next_row()? {
if row.end_sequence() {
continue;
}
rows.push(LineRow {
addr: row.address() as u32,
line: row.line().map(|l| l.get() as u32).unwrap_or(0),
file: row.file_index() as u32,
});
}
}
Ok(rows)
}
pub fn emit_debug_sections(table: &[(u64, u32)], text_sym: usize) -> Vec<EmittedDwarfSection> {
use gimli::write::{Address, AttributeValue, DwarfUnit, LineProgram, LineString, Sections};
if table.is_empty() {
return Vec::new();
}
let encoding = gimli::Encoding {
format: gimli::Format::Dwarf32,
version: 4,
address_size: 4,
};
let mut dwarf = DwarfUnit::new(encoding);
let high_pc = table.iter().map(|&(a, _)| a).max().unwrap_or(0) + 1;
let mut program = LineProgram::new(
encoding,
gimli::LineEncoding::default(),
LineString::String(b"/synth".to_vec()),
None,
LineString::String(b"synth.wasm".to_vec()),
None,
);
let dir = program.default_directory();
let fid = program.add_file(LineString::String(b"synth.wasm".to_vec()), dir, None);
let text_base = Address::Symbol {
symbol: text_sym,
addend: 0,
};
program.begin_sequence(Some(text_base));
for &(addr, line) in table {
let row = program.row();
row.address_offset = addr;
row.file = fid;
row.line = line as u64;
program.generate_row();
}
program.end_sequence(high_pc);
dwarf.unit.line_program = program;
{
let name_id = dwarf.strings.add("synth.wasm");
let root = dwarf.unit.root();
let root_die = dwarf.unit.get_mut(root);
root_die.set(gimli::DW_AT_name, AttributeValue::StringRef(name_id));
root_die.set(gimli::DW_AT_low_pc, AttributeValue::Address(text_base));
root_die.set(gimli::DW_AT_high_pc, AttributeValue::Udata(high_pc));
}
let seed = RelocWriter {
inner: gimli::write::EndianVec::new(LittleEndian),
relocs: Vec::new(),
};
let mut sections = Sections::new(seed);
if dwarf.write(&mut sections).is_err() {
return Vec::new();
}
let mut out: Vec<EmittedDwarfSection> = Vec::new();
let _ = sections.for_each(|id, w: &RelocWriter| -> Result<(), ()> {
let bytes = w.inner.slice();
if !bytes.is_empty()
&& let Some(name) = section_name(id)
{
let text_relocs = w
.relocs
.iter()
.map(|&(offset, _addend, size)| DwarfTextReloc {
offset: offset as u32,
size,
})
.collect();
out.push(EmittedDwarfSection {
name,
bytes: bytes.to_vec(),
text_relocs,
});
}
Ok(())
});
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DwarfTextReloc {
pub offset: u32,
pub size: u8,
}
#[derive(Debug, Clone)]
pub struct EmittedDwarfSection {
pub name: &'static str,
pub bytes: Vec<u8>,
pub text_relocs: Vec<DwarfTextReloc>,
}
#[derive(Clone)]
struct RelocWriter {
inner: gimli::write::EndianVec<LittleEndian>,
relocs: Vec<(usize, i64, u8)>,
}
impl gimli::write::Writer for RelocWriter {
type Endian = LittleEndian;
fn endian(&self) -> Self::Endian {
self.inner.endian()
}
fn len(&self) -> usize {
self.inner.len()
}
fn write(&mut self, bytes: &[u8]) -> gimli::write::Result<()> {
self.inner.write(bytes)
}
fn write_at(&mut self, offset: usize, bytes: &[u8]) -> gimli::write::Result<()> {
self.inner.write_at(offset, bytes)
}
fn write_address(
&mut self,
address: gimli::write::Address,
size: u8,
) -> gimli::write::Result<()> {
use gimli::write::Address;
match address {
Address::Constant(val) => self.inner.write_udata(val, size),
Address::Symbol { symbol: _, addend } => {
let offset = self.inner.len();
self.relocs.push((offset, addend, size));
self.inner.write_udata(addend as u64, size)
}
}
}
}
fn section_name(id: SectionId) -> Option<&'static str> {
Some(match id {
SectionId::DebugInfo => ".debug_info",
SectionId::DebugAbbrev => ".debug_abbrev",
SectionId::DebugStr => ".debug_str",
SectionId::DebugLine => ".debug_line",
SectionId::DebugLineStr => ".debug_line_str",
SectionId::DebugRanges => ".debug_ranges",
SectionId::DebugRngLists => ".debug_rnglists",
SectionId::DebugStrOffsets => ".debug_str_offsets",
SectionId::DebugAddr => ".debug_addr",
_ => return None,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn covering_row_lookup() {
let rows = [
LineRow {
addr: 0,
line: 10,
file: 1,
},
LineRow {
addr: 8,
line: 11,
file: 1,
},
LineRow {
addr: 20,
line: 12,
file: 1,
},
];
let got = op_offsets_to_source(&[100, 104, 108, 130], 100, &rows);
assert_eq!(got[0].map(|s| s.line), Some(10)); assert_eq!(got[1].map(|s| s.line), Some(10)); assert_eq!(got[2].map(|s| s.line), Some(11)); assert_eq!(got[3].map(|s| s.line), Some(12)); }
#[test]
fn op_before_first_row_is_none() {
let rows = [LineRow {
addr: 8,
line: 11,
file: 1,
}];
let got = op_offsets_to_source(&[100], 100, &rows);
assert_eq!(got[0], None);
}
#[test]
fn op_before_code_base_is_none() {
let rows = [LineRow {
addr: 0,
line: 1,
file: 1,
}];
let got = op_offsets_to_source(&[50], 100, &rows);
assert_eq!(got[0], None);
}
}