use gimli::{
Encoding, Format, LineEncoding, RunTimeEndian, SectionId, constants,
write::{
Address, AttributeValue, DwarfUnit, EndianVec, LineProgram, LineString,
Result as GimliResult, Sections, Writer,
},
};
use object::{
RelocationEncoding, RelocationFlags, RelocationKind, SectionKind,
write::{Object, Relocation, StandardSegment, SymbolId},
};
use wasmer_types::{CompileError, SourceLoc, target::Endianness};
use crate::WasmSourceMap;
#[derive(Clone, Copy, Debug)]
pub enum EhTarget {
Function,
Personality,
Lsda,
}
#[derive(Clone, Debug)]
pub struct EhRelocation {
pub offset: u64,
pub kind: RelocationKind,
pub size: u8,
pub target: EhTarget,
pub addend: i64,
}
#[derive(Clone, Debug)]
pub struct WriterRelocate {
pub relocs: Vec<EhRelocation>,
writer: EndianVec<RunTimeEndian>,
}
impl Default for WriterRelocate {
fn default() -> Self {
Self::new()
}
}
impl WriterRelocate {
pub const FUNCTION_SYMBOL: usize = 0;
pub const PERSONALITY_SYMBOL: usize = 1;
pub const LSDA_SYMBOL: usize = 2;
pub fn new() -> Self {
Self {
relocs: Vec::new(),
writer: EndianVec::new(RunTimeEndian::Little),
}
}
pub fn into_bytes(self) -> Vec<u8> {
self.writer.into_vec()
}
fn target_for(symbol: usize) -> GimliResult<EhTarget> {
match symbol {
Self::FUNCTION_SYMBOL => Ok(EhTarget::Function),
Self::PERSONALITY_SYMBOL => Ok(EhTarget::Personality),
Self::LSDA_SYMBOL => Ok(EhTarget::Lsda),
_ => Err(gimli::write::Error::InvalidAddress),
}
}
}
impl Writer for WriterRelocate {
type Endian = RunTimeEndian;
fn endian(&self) -> Self::Endian {
self.writer.endian()
}
fn len(&self) -> usize {
self.writer.len()
}
fn write(&mut self, bytes: &[u8]) -> GimliResult<()> {
self.writer.write(bytes)
}
fn write_at(&mut self, offset: usize, bytes: &[u8]) -> GimliResult<()> {
self.writer.write_at(offset, bytes)
}
fn write_address(&mut self, address: Address, size: u8) -> GimliResult<()> {
match address {
Address::Constant(val) => self.write_udata(val, size),
Address::Symbol { symbol, addend } => {
let target = Self::target_for(symbol)?;
let offset = self.len() as u64;
self.relocs.push(EhRelocation {
offset,
kind: RelocationKind::Absolute,
size,
target,
addend,
});
self.write_udata(0, size)
}
}
}
fn write_eh_pointer(
&mut self,
address: Address,
eh_pe: constants::DwEhPe,
size: u8,
) -> GimliResult<()> {
if eh_pe == constants::DW_EH_PE_absptr {
return self.write_address(address, size);
}
match address {
Address::Constant(_) => self.writer.write_eh_pointer(address, eh_pe, size),
Address::Symbol { symbol, addend }
if eh_pe == (constants::DW_EH_PE_pcrel | constants::DW_EH_PE_sdata4)
&& size == 8 =>
{
let target = Self::target_for(symbol)?;
let offset = self.len() as u64;
self.relocs.push(EhRelocation {
offset,
kind: RelocationKind::Relative,
size: 4,
target,
addend,
});
self.write_udata(0, 4)
}
Address::Symbol { symbol, addend }
if eh_pe
== (constants::DW_EH_PE_indirect
| constants::DW_EH_PE_pcrel
| constants::DW_EH_PE_sdata4)
&& size == 8 =>
{
let target = Self::target_for(symbol)?;
let offset = self.len() as u64;
self.relocs.push(EhRelocation {
offset,
kind: RelocationKind::Relative,
size: 4,
target,
addend,
});
self.write_udata(0, 4)
}
Address::Symbol { .. } => Err(gimli::write::Error::InvalidAddress),
}
}
fn write_offset(&mut self, _val: usize, _section: SectionId, _size: u8) -> GimliResult<()> {
Err(gimli::write::Error::OffsetOutOfBounds)
}
fn write_offset_at(
&mut self,
_offset: usize,
_val: usize,
_section: SectionId,
_size: u8,
) -> GimliResult<()> {
Err(gimli::write::Error::OffsetOutOfBounds)
}
}
#[derive(Clone, Debug)]
struct DebugRelocation {
offset: u64,
size: u8,
target: DebugRelocationTarget,
addend: i64,
}
#[derive(Clone, Debug)]
enum DebugRelocationTarget {
Function,
Section(SectionId),
}
#[derive(Clone, Debug)]
struct DebugWriter {
relocs: Vec<DebugRelocation>,
writer: EndianVec<RunTimeEndian>,
}
impl DebugWriter {
fn new(_endianness: Option<Endianness>) -> Self {
Self {
relocs: Vec::new(),
writer: EndianVec::new(RunTimeEndian::Little),
}
}
fn into_parts(self) -> (Vec<u8>, Vec<DebugRelocation>) {
(self.writer.into_vec(), self.relocs)
}
}
impl Writer for DebugWriter {
type Endian = RunTimeEndian;
fn endian(&self) -> Self::Endian {
self.writer.endian()
}
fn len(&self) -> usize {
self.writer.len()
}
fn write(&mut self, bytes: &[u8]) -> GimliResult<()> {
self.writer.write(bytes)
}
fn write_at(&mut self, offset: usize, bytes: &[u8]) -> GimliResult<()> {
self.writer.write_at(offset, bytes)
}
fn write_address(&mut self, address: Address, size: u8) -> GimliResult<()> {
match address {
Address::Constant(val) => self.write_udata(val, size),
Address::Symbol { addend, .. } => {
let offset = self.len() as u64;
self.relocs.push(DebugRelocation {
offset,
size,
target: DebugRelocationTarget::Function,
addend,
});
self.write_udata(0, size)
}
}
}
fn write_offset(&mut self, val: usize, section: SectionId, size: u8) -> GimliResult<()> {
let offset = self.len() as u64;
self.relocs.push(DebugRelocation {
offset,
size,
target: DebugRelocationTarget::Section(section),
addend: val as i64,
});
self.write_udata(0, size)
}
fn write_offset_at(
&mut self,
offset: usize,
val: usize,
section: SectionId,
size: u8,
) -> GimliResult<()> {
self.relocs.push(DebugRelocation {
offset: offset as u64,
size,
target: DebugRelocationTarget::Section(section),
addend: val as i64,
});
self.write_udata_at(offset, 0, size)
}
}
pub struct DwarfState {
dwarf: DwarfUnit,
file_id: gimli::write::FileId,
subprogram: gimli::write::UnitEntryId,
}
pub fn init_dwarf_unit(
function_name: &str,
module_name: Option<&str>,
producer: &str,
) -> Result<DwarfState, CompileError> {
let encoding = Encoding {
address_size: 8,
format: Format::Dwarf32,
version: 4,
};
let mut dwarf = DwarfUnit::new(encoding);
let comp_dir = dwarf.strings.add(".");
let file_name_str = module_name.unwrap_or("<module>");
let file_name = dwarf.strings.add(file_name_str);
dwarf.unit.line_program = LineProgram::new(
encoding,
LineEncoding::default(),
LineString::String(b".".to_vec()),
None,
LineString::String(file_name_str.as_bytes().to_vec()),
None,
);
let dir_id = dwarf.unit.line_program.default_directory();
let file_id = dwarf.unit.line_program.add_file(
LineString::String(file_name_str.as_bytes().to_vec()),
dir_id,
None,
);
let function_address = Address::Symbol {
symbol: 0,
addend: 0,
};
dwarf
.unit
.line_program
.begin_sequence(Some(function_address));
let root = dwarf.unit.root();
let cu = dwarf.unit.get_mut(root);
cu.set(
gimli::DW_AT_producer,
AttributeValue::String(producer.as_bytes().to_vec()),
);
cu.set(
gimli::DW_AT_language,
AttributeValue::Language(gimli::DW_LANG_C),
);
cu.set(gimli::DW_AT_name, AttributeValue::StringRef(file_name));
cu.set(gimli::DW_AT_comp_dir, AttributeValue::StringRef(comp_dir));
cu.set(
gimli::DW_AT_low_pc,
AttributeValue::Address(function_address),
);
let subprogram = dwarf.unit.add(root, gimli::DW_TAG_subprogram);
let entry = dwarf.unit.get_mut(subprogram);
entry.set(
gimli::DW_AT_name,
AttributeValue::String(function_name.as_bytes().to_vec()),
);
entry.set(
gimli::DW_AT_decl_file,
AttributeValue::FileIndex(Some(file_id)),
);
entry.set(
gimli::DW_AT_low_pc,
AttributeValue::Address(function_address),
);
Ok(DwarfState {
dwarf,
file_id,
subprogram,
})
}
impl DwarfState {
pub fn add_row(&mut self, code_offset: u64, srcloc: SourceLoc) {
if srcloc.is_default() {
return;
}
let row = self.dwarf.unit.line_program.row();
row.address_offset = code_offset;
row.file = self.file_id;
row.line = (srcloc.bits() as u64).saturating_add(1);
row.column = 0;
self.dwarf.unit.line_program.generate_row();
}
pub fn add_source_map_row(
&mut self,
code_offset: u64,
srcloc: SourceLoc,
source_map: &WasmSourceMap,
) {
let Some(location) = source_map.get(srcloc.bits() as usize) else {
self.add_row(code_offset, srcloc);
return;
};
let directory = self
.dwarf
.unit
.line_program
.add_directory(LineString::String(location.directory.as_bytes().to_vec()));
let file = self.dwarf.unit.line_program.add_file(
LineString::String(location.file.as_bytes().to_vec()),
directory,
None,
);
let row = self.dwarf.unit.line_program.row();
row.address_offset = code_offset;
row.file = file;
row.line = u64::from(location.line);
row.column = u64::from(location.column);
self.dwarf.unit.line_program.generate_row();
}
pub fn write_sections(
&mut self,
object: &mut Object<'static>,
function_symbol: SymbolId,
body_len: u64,
endianness: Option<Endianness>,
) -> Result<(), CompileError> {
self.dwarf.unit.line_program.end_sequence(body_len);
let root = self.dwarf.unit.root();
let cu = self.dwarf.unit.get_mut(root);
cu.set(gimli::DW_AT_high_pc, AttributeValue::Data8(body_len));
let entry = self.dwarf.unit.get_mut(self.subprogram);
entry.set(gimli::DW_AT_decl_line, AttributeValue::Udata(1));
entry.set(gimli::DW_AT_high_pc, AttributeValue::Data8(body_len));
let mut sections = Sections::new(DebugWriter::new(endianness));
self.dwarf
.write(&mut sections)
.map_err(|e| CompileError::Codegen(format!("failed to write DWARF debug info: {e}")))?;
let mut object_sections = Vec::new();
sections
.for_each(|id, writer| {
let (bytes, relocs) = writer.clone().into_parts();
if bytes.is_empty() {
object_sections.push((id, None, relocs));
} else {
let section = object.add_section(
object.segment_name(StandardSegment::Debug).to_vec(),
id.name().as_bytes().to_vec(),
SectionKind::Debug,
);
object.append_section_data(section, &bytes, 1);
object_sections.push((id, Some(section), relocs));
}
Ok::<_, gimli::write::Error>(())
})
.map_err(|e| CompileError::Codegen(format!("failed to collect DWARF sections: {e}")))?;
for (_, section, relocs) in object_sections.clone() {
let Some(section) = section else { continue };
for reloc in relocs {
let symbol = match reloc.target {
DebugRelocationTarget::Function => function_symbol,
DebugRelocationTarget::Section(target) => {
let Some((_, Some(target_section), _)) =
object_sections.iter().find(|(id, _, _)| *id == target)
else {
continue;
};
object.section_symbol(*target_section)
}
};
object
.add_relocation(
section,
Relocation {
offset: reloc.offset,
symbol,
addend: reloc.addend,
flags: RelocationFlags::Generic {
kind: RelocationKind::Absolute,
encoding: RelocationEncoding::Generic,
size: u8::checked_mul(reloc.size, 8).ok_or_else(|| {
CompileError::Codegen("unexpected relocation size".to_string())
})?,
},
},
)
.map_err(|e| {
CompileError::Codegen(format!("failed to add DWARF relocation: {e}"))
})?;
}
}
Ok(())
}
}