Skip to main content

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