wasmer_compiler/
dwarf.rs

1//! DWARF / `.eh_frame` emission helpers shared by the object-based compiler
2//! backends (Cranelift and Singlepass).
3//!
4//! Each compiled function is emitted into its own relocatable object file. This
5//! module provides the writers used to serialize the per-function `.eh_frame`
6//! unwind tables and the DWARF line program into those objects. The recorded
7//! relocations are resolved against the function's own text symbol.
8
9use gimli::{
10    Encoding, Format, LineEncoding, RunTimeEndian, SectionId, constants,
11    write::{
12        Address, AttributeValue, DwarfUnit, EndianVec, LineProgram, LineString,
13        Result as GimliResult, Sections, Writer,
14    },
15};
16use object::{
17    RelocationEncoding, RelocationFlags, RelocationKind, SectionKind,
18    write::{Object, Relocation, StandardSegment, SymbolId},
19};
20use wasmer_types::{CompileError, SourceLoc, target::Endianness};
21
22/// The symbolic target of an `.eh_frame` relocation. The gimli `Address`
23/// `symbol` field is used as a discriminant value (see [`WriterRelocate`]).
24#[derive(Clone, Copy, Debug)]
25pub enum EhTarget {
26    /// The function's own text symbol (FDE initial location).
27    Function,
28    /// The exception personality routine (the `EHPersonality` libcall).
29    Personality,
30    /// The function's LSDA in the `.gcc_except_table` section.
31    Lsda,
32}
33
34/// A relocation recorded while serializing the `.eh_frame` section.
35#[derive(Clone, Debug)]
36pub struct EhRelocation {
37    /// Offset of the relocation within the `.eh_frame` section.
38    pub offset: u64,
39    /// The relocation kind to apply.
40    pub kind: RelocationKind,
41    /// Relocation size, in bytes.
42    pub size: u8,
43    /// The symbolic target this relocation resolves against.
44    pub target: EhTarget,
45    /// Addend applied to the target.
46    pub addend: i64,
47}
48
49/// A gimli [`Writer`] for the `.eh_frame` section that records the relocations
50/// required against the function symbol, the personality routine and the LSDA.
51#[derive(Clone, Debug)]
52pub struct WriterRelocate {
53    /// Relocations recorded while writing the `.eh_frame` section.
54    pub relocs: Vec<EhRelocation>,
55    writer: EndianVec<RunTimeEndian>,
56}
57
58impl Default for WriterRelocate {
59    fn default() -> Self {
60        Self::new()
61    }
62}
63
64impl WriterRelocate {
65    /// `Address::Symbol` discriminant for the function's own text symbol.
66    pub const FUNCTION_SYMBOL: usize = 0;
67    /// `Address::Symbol` discriminant for the exception personality routine.
68    pub const PERSONALITY_SYMBOL: usize = 1;
69    /// `Address::Symbol` discriminant for the function's LSDA.
70    pub const LSDA_SYMBOL: usize = 2;
71
72    /// Create an empty `.eh_frame` writer.
73    pub fn new() -> Self {
74        Self {
75            relocs: Vec::new(),
76            writer: EndianVec::new(RunTimeEndian::Little),
77        }
78    }
79
80    /// Consume the writer, returning the serialized `.eh_frame` bytes.
81    pub fn into_bytes(self) -> Vec<u8> {
82        self.writer.into_vec()
83    }
84
85    fn target_for(symbol: usize) -> GimliResult<EhTarget> {
86        match symbol {
87            Self::FUNCTION_SYMBOL => Ok(EhTarget::Function),
88            Self::PERSONALITY_SYMBOL => Ok(EhTarget::Personality),
89            Self::LSDA_SYMBOL => Ok(EhTarget::Lsda),
90            _ => Err(gimli::write::Error::InvalidAddress),
91        }
92    }
93}
94
95impl Writer for WriterRelocate {
96    type Endian = RunTimeEndian;
97
98    fn endian(&self) -> Self::Endian {
99        self.writer.endian()
100    }
101
102    fn len(&self) -> usize {
103        self.writer.len()
104    }
105
106    fn write(&mut self, bytes: &[u8]) -> GimliResult<()> {
107        self.writer.write(bytes)
108    }
109
110    fn write_at(&mut self, offset: usize, bytes: &[u8]) -> GimliResult<()> {
111        self.writer.write_at(offset, bytes)
112    }
113
114    fn write_address(&mut self, address: Address, size: u8) -> GimliResult<()> {
115        match address {
116            Address::Constant(val) => self.write_udata(val, size),
117            Address::Symbol { symbol, addend } => {
118                let target = Self::target_for(symbol)?;
119                let offset = self.len() as u64;
120                self.relocs.push(EhRelocation {
121                    offset,
122                    kind: RelocationKind::Absolute,
123                    size,
124                    target,
125                    addend,
126                });
127                self.write_udata(0, size)
128            }
129        }
130    }
131
132    fn write_eh_pointer(
133        &mut self,
134        address: Address,
135        eh_pe: constants::DwEhPe,
136        size: u8,
137    ) -> GimliResult<()> {
138        if eh_pe == constants::DW_EH_PE_absptr {
139            return self.write_address(address, size);
140        }
141
142        match address {
143            Address::Constant(_) => self.writer.write_eh_pointer(address, eh_pe, size),
144            Address::Symbol { symbol, addend }
145                if eh_pe == (constants::DW_EH_PE_pcrel | constants::DW_EH_PE_sdata4)
146                    && size == 8 =>
147            {
148                let target = Self::target_for(symbol)?;
149                let offset = self.len() as u64;
150                self.relocs.push(EhRelocation {
151                    offset,
152                    kind: RelocationKind::Relative,
153                    size: 4,
154                    target,
155                    addend,
156                });
157                self.write_udata(0, 4)
158            }
159            // GOT-indirect, PC-relative reference (`R_X86_64_GOTPCREL`). Used
160            // for the personality routine, which is an undefined symbol resolved
161            // at load time: routing it through the GOT yields a dynamic
162            // relocation the runtime loader can apply (a plain data relocation
163            // against an undefined symbol would be dropped by the linker).
164            Address::Symbol { symbol, addend }
165                if eh_pe
166                    == (constants::DW_EH_PE_indirect
167                        | constants::DW_EH_PE_pcrel
168                        | constants::DW_EH_PE_sdata4)
169                    && size == 8 =>
170            {
171                let target = Self::target_for(symbol)?;
172                let offset = self.len() as u64;
173                self.relocs.push(EhRelocation {
174                    offset,
175                    kind: RelocationKind::GotRelative,
176                    size: 4,
177                    target,
178                    addend,
179                });
180                self.write_udata(0, 4)
181            }
182            Address::Symbol { .. } => Err(gimli::write::Error::InvalidAddress),
183        }
184    }
185
186    fn write_offset(&mut self, _val: usize, _section: SectionId, _size: u8) -> GimliResult<()> {
187        Err(gimli::write::Error::OffsetOutOfBounds)
188    }
189
190    fn write_offset_at(
191        &mut self,
192        _offset: usize,
193        _val: usize,
194        _section: SectionId,
195        _size: u8,
196    ) -> GimliResult<()> {
197        Err(gimli::write::Error::OffsetOutOfBounds)
198    }
199}
200
201#[derive(Clone, Debug)]
202struct DebugRelocation {
203    offset: u64,
204    size: u8,
205    target: DebugRelocationTarget,
206    addend: i64,
207}
208
209#[derive(Clone, Debug)]
210enum DebugRelocationTarget {
211    Function,
212    Section(SectionId),
213}
214
215#[derive(Clone, Debug)]
216struct DebugWriter {
217    relocs: Vec<DebugRelocation>,
218    writer: EndianVec<RunTimeEndian>,
219}
220
221impl DebugWriter {
222    fn new(_endianness: Option<Endianness>) -> Self {
223        Self {
224            relocs: Vec::new(),
225            writer: EndianVec::new(RunTimeEndian::Little),
226        }
227    }
228
229    fn into_parts(self) -> (Vec<u8>, Vec<DebugRelocation>) {
230        (self.writer.into_vec(), self.relocs)
231    }
232}
233
234impl Writer for DebugWriter {
235    type Endian = RunTimeEndian;
236
237    fn endian(&self) -> Self::Endian {
238        self.writer.endian()
239    }
240
241    fn len(&self) -> usize {
242        self.writer.len()
243    }
244
245    fn write(&mut self, bytes: &[u8]) -> GimliResult<()> {
246        self.writer.write(bytes)
247    }
248
249    fn write_at(&mut self, offset: usize, bytes: &[u8]) -> GimliResult<()> {
250        self.writer.write_at(offset, bytes)
251    }
252
253    fn write_address(&mut self, address: Address, size: u8) -> GimliResult<()> {
254        match address {
255            Address::Constant(val) => self.write_udata(val, size),
256            Address::Symbol { addend, .. } => {
257                let offset = self.len() as u64;
258                self.relocs.push(DebugRelocation {
259                    offset,
260                    size,
261                    target: DebugRelocationTarget::Function,
262                    addend,
263                });
264                self.write_udata(0, size)
265            }
266        }
267    }
268
269    fn write_offset(&mut self, val: usize, section: SectionId, size: u8) -> GimliResult<()> {
270        let offset = self.len() as u64;
271        self.relocs.push(DebugRelocation {
272            offset,
273            size,
274            target: DebugRelocationTarget::Section(section),
275            addend: val as i64,
276        });
277        self.write_udata(0, size)
278    }
279
280    fn write_offset_at(
281        &mut self,
282        offset: usize,
283        val: usize,
284        section: SectionId,
285        size: u8,
286    ) -> GimliResult<()> {
287        self.relocs.push(DebugRelocation {
288            offset: offset as u64,
289            size,
290            target: DebugRelocationTarget::Section(section),
291            addend: val as i64,
292        });
293        self.write_udata_at(offset, 0, size)
294    }
295}
296
297/// DWARF debug info state built incrementally during codegen.
298pub struct DwarfState {
299    dwarf: DwarfUnit,
300    file_id: gimli::write::FileId,
301    subprogram: gimli::write::UnitEntryId,
302}
303
304/// Initialize DWARF debug info for a function. Begins the line program sequence and sets up CU attributes.
305pub fn init_dwarf_unit(
306    function_name: &str,
307    module_name: Option<&str>,
308    producer: &str,
309) -> Result<DwarfState, CompileError> {
310    let encoding = Encoding {
311        address_size: 8,
312        format: Format::Dwarf32,
313        version: 4,
314    };
315    let mut dwarf = DwarfUnit::new(encoding);
316    let comp_dir = dwarf.strings.add(".");
317    let file_name_str = module_name.unwrap_or("<module>");
318    let file_name = dwarf.strings.add(file_name_str);
319    dwarf.unit.line_program = LineProgram::new(
320        encoding,
321        LineEncoding::default(),
322        LineString::String(b".".to_vec()),
323        None,
324        LineString::String(file_name_str.as_bytes().to_vec()),
325        None,
326    );
327    let dir_id = dwarf.unit.line_program.default_directory();
328    let file_id = dwarf.unit.line_program.add_file(
329        LineString::String(file_name_str.as_bytes().to_vec()),
330        dir_id,
331        None,
332    );
333
334    let function_address = Address::Symbol {
335        symbol: 0,
336        addend: 0,
337    };
338    dwarf
339        .unit
340        .line_program
341        .begin_sequence(Some(function_address));
342
343    let root = dwarf.unit.root();
344    let cu = dwarf.unit.get_mut(root);
345    cu.set(
346        gimli::DW_AT_producer,
347        AttributeValue::String(producer.as_bytes().to_vec()),
348    );
349    cu.set(
350        gimli::DW_AT_language,
351        AttributeValue::Language(gimli::DW_LANG_C),
352    );
353    cu.set(gimli::DW_AT_name, AttributeValue::StringRef(file_name));
354    cu.set(gimli::DW_AT_comp_dir, AttributeValue::StringRef(comp_dir));
355    cu.set(
356        gimli::DW_AT_low_pc,
357        AttributeValue::Address(function_address),
358    );
359
360    let subprogram = dwarf.unit.add(root, gimli::DW_TAG_subprogram);
361    let entry = dwarf.unit.get_mut(subprogram);
362    entry.set(
363        gimli::DW_AT_name,
364        AttributeValue::String(function_name.as_bytes().to_vec()),
365    );
366    entry.set(
367        gimli::DW_AT_decl_file,
368        AttributeValue::FileIndex(Some(file_id)),
369    );
370    entry.set(
371        gimli::DW_AT_low_pc,
372        AttributeValue::Address(function_address),
373    );
374
375    Ok(DwarfState {
376        dwarf,
377        file_id,
378        subprogram,
379    })
380}
381
382impl DwarfState {
383    /// Emit a line program row for an instruction at the given code offset.
384    pub fn add_row(&mut self, code_offset: u64, srcloc: SourceLoc) {
385        if srcloc.is_default() {
386            return;
387        }
388        let row = self.dwarf.unit.line_program.row();
389        row.address_offset = code_offset;
390        row.file = self.file_id;
391        row.line = (srcloc.bits() as u64).saturating_add(1);
392        row.column = 0;
393        self.dwarf.unit.line_program.generate_row();
394    }
395
396    /// Finalize DWARF sections and write them into the object.
397    pub fn write_sections(
398        &mut self,
399        object: &mut Object<'static>,
400        function_symbol: SymbolId,
401        body_len: u64,
402        endianness: Option<Endianness>,
403    ) -> Result<(), CompileError> {
404        // End the line program sequence.
405        self.dwarf.unit.line_program.end_sequence(body_len);
406
407        // Set DW_AT_high_pc for CU and subprogram (body_len now known).
408        let root = self.dwarf.unit.root();
409        let cu = self.dwarf.unit.get_mut(root);
410        cu.set(gimli::DW_AT_high_pc, AttributeValue::Data8(body_len));
411        let entry = self.dwarf.unit.get_mut(self.subprogram);
412        entry.set(gimli::DW_AT_decl_line, AttributeValue::Udata(1));
413        entry.set(gimli::DW_AT_high_pc, AttributeValue::Data8(body_len));
414
415        let mut sections = Sections::new(DebugWriter::new(endianness));
416        self.dwarf
417            .write(&mut sections)
418            .map_err(|e| CompileError::Codegen(format!("failed to write DWARF debug info: {e}")))?;
419
420        let mut object_sections = Vec::new();
421        sections
422            .for_each(|id, writer| {
423                let (bytes, relocs) = writer.clone().into_parts();
424                if bytes.is_empty() {
425                    object_sections.push((id, None, relocs));
426                } else {
427                    let section = object.add_section(
428                        object.segment_name(StandardSegment::Debug).to_vec(),
429                        id.name().as_bytes().to_vec(),
430                        SectionKind::Debug,
431                    );
432                    object.append_section_data(section, &bytes, 1);
433                    object_sections.push((id, Some(section), relocs));
434                }
435                Ok::<_, gimli::write::Error>(())
436            })
437            .map_err(|e| CompileError::Codegen(format!("failed to collect DWARF sections: {e}")))?;
438
439        for (_, section, relocs) in object_sections.clone() {
440            let Some(section) = section else { continue };
441            for reloc in relocs {
442                let symbol = match reloc.target {
443                    DebugRelocationTarget::Function => function_symbol,
444                    DebugRelocationTarget::Section(target) => {
445                        let Some((_, Some(target_section), _)) =
446                            object_sections.iter().find(|(id, _, _)| *id == target)
447                        else {
448                            continue;
449                        };
450                        object.section_symbol(*target_section)
451                    }
452                };
453                object
454                    .add_relocation(
455                        section,
456                        Relocation {
457                            offset: reloc.offset,
458                            symbol,
459                            addend: reloc.addend,
460                            flags: RelocationFlags::Generic {
461                                kind: RelocationKind::Absolute,
462                                encoding: RelocationEncoding::Generic,
463                                size: u8::checked_mul(reloc.size, 8).ok_or_else(|| {
464                                    CompileError::Codegen("unexpected relocation size".to_string())
465                                })?,
466                            },
467                        },
468                    )
469                    .map_err(|e| {
470                        CompileError::Codegen(format!("failed to add DWARF relocation: {e}"))
471                    })?;
472            }
473        }
474
475        Ok(())
476    }
477}