use crate::shape::{Global, Local, Place, Scope, Shape, Sig};
use crate::tree;
use rucc_object::{Chunk, Info, Reference, Reloc};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Unit {
pub name: String,
pub dir: String,
pub producer: String,
pub files: Vec<String>,
pub types: Vec<Shape>,
pub funcs: Vec<Function>,
pub globals: Vec<Global>,
pub pointer: u8,
pub frames: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Function {
pub name: String,
pub len: u64,
pub rows: Vec<Row>,
pub decl: Option<Place>,
pub sig: Option<Sig>,
pub external: bool,
pub locals: Vec<Local>,
pub scopes: Vec<Scope>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Row {
pub at: u64,
pub file: usize,
pub line: u32,
pub column: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
Refused {
why: String,
},
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Refused { why } => {
write!(f, "the debug writer refused what it was given: {why}")
}
}
}
}
impl std::error::Error for Error {}
#[derive(Debug, Clone)]
struct Section {
bytes: gimli::write::EndianVec<gimli::LittleEndian>,
relocs: Vec<gimli::write::Relocation>,
}
impl Default for Section {
fn default() -> Self {
Self { bytes: gimli::write::EndianVec::new(gimli::LittleEndian), relocs: Vec::new() }
}
}
impl gimli::write::RelocateWriter for Section {
type Writer = gimli::write::EndianVec<gimli::LittleEndian>;
fn writer(&self) -> &Self::Writer {
&self.bytes
}
fn writer_mut(&mut self) -> &mut Self::Writer {
&mut self.bytes
}
fn relocate(&mut self, relocation: gimli::write::Relocation) {
self.relocs.push(relocation);
}
}
pub fn write(unit: &Unit) -> Result<Info, Error> {
if unit.funcs.iter().all(|func| func.rows.is_empty()) {
return Ok(Info::default());
}
let encoding =
gimli::Encoding { format: gimli::Format::Dwarf32, version: 5, address_size: unit.pointer };
let mut dwarf = gimli::write::DwarfUnit::new(encoding);
let dir = text(&unit.dir, encoding, &mut dwarf.line_strings);
let name = text(&unit.name, encoding, &mut dwarf.line_strings);
let mut program =
gimli::write::LineProgram::new(encoding, gimli::LineEncoding::default(), dir, name, None);
let under = program.default_directory();
let files: Vec<gimli::write::FileId> = unit
.files
.iter()
.map(|file| {
let file = text(file, encoding, &mut dwarf.line_strings);
program.add_file(file, under, None)
})
.collect();
for (index, func) in unit.funcs.iter().enumerate() {
if func.rows.is_empty() {
continue;
}
program.begin_sequence(Some(gimli::write::Address::Symbol { symbol: index, addend: 0 }));
let mut said: Option<(usize, u32, u32)> = None;
for row in &func.rows {
let now = (row.file, row.line, row.column);
if said == Some(now) {
continue;
}
said = Some(now);
let Some(&file) = files.get(row.file) else {
let why = format!("row at {} names file {}, which is not one", row.at, row.file);
return Err(Error::Refused { why });
};
let state = program.row();
state.address_offset = row.at;
state.file = file;
state.line = u64::from(row.line);
state.column = u64::from(row.column);
state.is_statement = true;
program.generate_row();
}
program.end_sequence(func.len);
}
let ranges = unit
.funcs
.iter()
.enumerate()
.filter(|(_, func)| !func.rows.is_empty())
.map(|(index, func)| gimli::write::Range::StartLength {
begin: gimli::write::Address::Symbol { symbol: index, addend: 0 },
length: func.len,
})
.collect();
dwarf.unit.line_program = program;
let covers = dwarf.unit.ranges.add(gimli::write::RangeList(ranges));
let root = dwarf.unit.root();
let producer = text(&unit.producer, encoding, &mut dwarf.line_strings);
let name = text(&unit.name, encoding, &mut dwarf.line_strings);
let dir = text(&unit.dir, encoding, &mut dwarf.line_strings);
let root = dwarf.unit.get_mut(root);
root.set(gimli::DW_AT_producer, gimli::write::AttributeValue::LineStringRef(held(producer)?));
root.set(gimli::DW_AT_language, gimli::write::AttributeValue::Language(gimli::DW_LANG_C11));
root.set(gimli::DW_AT_name, gimli::write::AttributeValue::LineStringRef(held(name)?));
root.set(gimli::DW_AT_comp_dir, gimli::write::AttributeValue::LineStringRef(held(dir)?));
root.set(gimli::DW_AT_stmt_list, gimli::write::AttributeValue::LineProgramRef);
root.set(gimli::DW_AT_ranges, gimli::write::AttributeValue::RangeListRef(covers));
tree::describe(&mut dwarf, &unit.types, &files, &unit.funcs, &unit.globals, unit.frames)?;
let mut sections = gimli::write::Sections::new(Section::default());
dwarf.write(&mut sections).map_err(refused)?;
let mut info = Info::default();
let named = |target: gimli::write::RelocationTarget| match target {
gimli::write::RelocationTarget::Symbol(index) => match unit.funcs.get(index) {
Some(func) => func.name.clone(),
None => unit.globals[index - unit.funcs.len()].name.clone(),
},
gimli::write::RelocationTarget::Section(id) => id.name().to_owned(),
};
sections.for_each(|id, section| {
if section.bytes.slice().is_empty() {
return Ok(());
}
let relocs = section
.relocs
.iter()
.map(|reloc| Reloc {
at: reloc.offset,
symbol: named(reloc.target),
kind: Reference::Address { bytes: reloc.size },
addend: reloc.addend,
after: 0,
})
.collect();
info.chunks.push(Chunk {
name: id.name().to_owned(),
bytes: section.bytes.slice().to_vec(),
relocs,
});
Ok::<(), Error>(())
})?;
Ok(info)
}
fn text(
val: &str,
encoding: gimli::Encoding,
strings: &mut gimli::write::LineStringTable,
) -> gimli::write::LineString {
let val: Vec<u8> = val.bytes().filter(|&byte| byte != 0).collect();
gimli::write::LineString::new(val, encoding, strings)
}
fn held(string: gimli::write::LineString) -> Result<gimli::write::LineStringId, Error> {
match string {
gimli::write::LineString::LineStringRef(id) => Ok(id),
_ => Err(Error::Refused {
why: "a string meant for the line string section was written another way".to_owned(),
}),
}
}
fn refused(why: gimli::write::Error) -> Error {
Error::Refused { why: why.to_string() }
}
#[cfg(test)]
mod tests {
use super::*;
fn one() -> Unit {
Unit {
name: "a.c".to_owned(),
dir: "/tmp".to_owned(),
producer: "rucc".to_owned(),
files: vec!["a.c".to_owned()],
types: Vec::new(),
funcs: vec![Function {
name: "f".to_owned(),
len: 16,
rows: vec![
Row { at: 0, file: 0, line: 3, column: 1 },
Row { at: 8, file: 0, line: 4, column: 5 },
],
..Function::default()
}],
globals: Vec::new(),
pointer: 8,
frames: true,
}
}
#[test]
fn a_unit_with_rows_writes_the_four_sections_a_reader_needs() {
let info = write(&one()).expect("sections");
let names: Vec<&str> = info.chunks.iter().map(|chunk| chunk.name.as_str()).collect();
assert_eq!(
names,
[".debug_abbrev", ".debug_line_str", ".debug_line", ".debug_rnglists", ".debug_info"]
);
assert!(info.chunks.iter().all(|chunk| !chunk.bytes.is_empty()));
}
#[test]
fn a_sequence_asks_the_linker_where_its_function_went() {
let info = write(&one()).expect("sections");
let line = info.chunks.iter().find(|chunk| chunk.name == ".debug_line").expect("a table");
let address = line.relocs.iter().find(|reloc| reloc.symbol == "f").expect("an address");
assert_eq!(address.kind, Reference::Address { bytes: 8 });
assert_eq!(address.addend, 0);
let rest = line.relocs.iter().filter(|reloc| reloc.symbol != "f");
assert!(rest.clone().count() > 0);
assert!(rest.clone().all(|reloc| reloc.symbol == ".debug_line_str"));
assert!(rest.clone().all(|reloc| reloc.kind == Reference::Address { bytes: 4 }));
}
#[test]
fn a_unit_with_no_rows_writes_nothing() {
let mut unit = one();
unit.funcs[0].rows.clear();
assert_eq!(write(&unit).expect("sections"), Info::default());
}
#[test]
fn a_row_naming_a_file_that_is_not_there_is_refused() {
let mut unit = one();
unit.funcs[0].rows[1].file = 7;
assert!(write(&unit).is_err());
}
}