Skip to main content

wasmer_compiler_cranelift/
elf.rs

1//! Emission of per-function relocatable ELF objects for the experimental
2//! ELF artifact format.
3
4#[cfg(feature = "unwind")]
5use crate::eh::FunctionLsdaData;
6#[cfg(feature = "unwind")]
7use cranelift_codegen::gimli::{
8    RunTimeEndian, SectionId as GimliSectionId, constants,
9    write::{
10        Address, EhFrame, EndianVec, FrameDescriptionEntry, FrameTable, Result as GimliResult,
11        Writer,
12    },
13};
14#[cfg(feature = "unwind")]
15use cranelift_codegen::isa::TargetIsa;
16#[cfg(feature = "unwind")]
17use object::RelocationKind as ObjectRelocationKind;
18use object::{
19    RelocationEncoding, RelocationFlags, SectionKind, SymbolFlags, SymbolKind, SymbolScope,
20    write::{
21        Object, Relocation as ObjectRelocation, StandardSection, StandardSegment, Symbol,
22        SymbolSection,
23    },
24};
25#[cfg(feature = "unwind")]
26use std::collections::HashMap;
27#[cfg(feature = "unwind")]
28use wasmer_compiler::dwarf::{EhRelocation, EhTarget, WriterRelocate};
29#[cfg(feature = "unwind")]
30use wasmer_compiler::elf::emit_eh_frame_section;
31use wasmer_compiler::{WasmSourceMap, dwarf::init_dwarf_unit};
32use wasmer_compiler::{
33    elf::{add_relocations, emit_trap_section},
34    misc::{CompiledFunctionExt, CompiledKind},
35    object::get_object_for_target,
36    types::function::CompiledFunction,
37};
38use wasmer_types::{CompileError, LocalFunctionIndex, target::Target};
39
40/// A gimli [`Writer`] for the `.eh_frame` section that records the relocations
41/// required against the function symbol, the personality routine and the LSDA.
42///
43/// This mirrors [`wasmer_compiler::dwarf::WriterRelocate`], but implements the
44/// `Writer` trait of the gimli version re-exported by `cranelift-codegen`
45/// (which differs from the workspace gimli version), so that Cranelift's
46/// `FrameTable`/`FrameDescriptionEntry` types can be serialized with it. The
47/// recorded relocations use the shared [`EhRelocation`] representation.
48#[cfg(feature = "unwind")]
49struct EhFrameWriter {
50    relocs: Vec<EhRelocation>,
51    writer: EndianVec<RunTimeEndian>,
52}
53
54#[cfg(feature = "unwind")]
55impl EhFrameWriter {
56    fn new() -> Self {
57        Self {
58            relocs: Vec::new(),
59            writer: EndianVec::new(RunTimeEndian::Little),
60        }
61    }
62
63    fn into_bytes(self) -> Vec<u8> {
64        self.writer.into_vec()
65    }
66
67    fn target_for(symbol: usize) -> GimliResult<EhTarget> {
68        match symbol {
69            WriterRelocate::FUNCTION_SYMBOL => Ok(EhTarget::Function),
70            WriterRelocate::PERSONALITY_SYMBOL => Ok(EhTarget::Personality),
71            WriterRelocate::LSDA_SYMBOL => Ok(EhTarget::Lsda),
72            _ => Err(cranelift_codegen::gimli::write::Error::InvalidAddress),
73        }
74    }
75}
76
77#[cfg(feature = "unwind")]
78impl Writer for EhFrameWriter {
79    type Endian = RunTimeEndian;
80
81    fn endian(&self) -> Self::Endian {
82        self.writer.endian()
83    }
84
85    fn len(&self) -> usize {
86        self.writer.len()
87    }
88
89    fn write(&mut self, bytes: &[u8]) -> GimliResult<()> {
90        self.writer.write(bytes)
91    }
92
93    fn write_at(&mut self, offset: usize, bytes: &[u8]) -> GimliResult<()> {
94        self.writer.write_at(offset, bytes)
95    }
96
97    fn write_address(&mut self, address: Address, size: u8) -> GimliResult<()> {
98        match address {
99            Address::Constant(val) => self.write_udata(val, size),
100            Address::Symbol { symbol, addend } => {
101                let target = Self::target_for(symbol)?;
102                let offset = self.len() as u64;
103                self.relocs.push(EhRelocation {
104                    offset,
105                    kind: ObjectRelocationKind::Absolute,
106                    size,
107                    target,
108                    addend,
109                });
110                self.write_udata(0, size)
111            }
112        }
113    }
114
115    fn write_eh_pointer(
116        &mut self,
117        address: Address,
118        eh_pe: constants::DwEhPe,
119        size: u8,
120    ) -> GimliResult<()> {
121        if eh_pe == constants::DW_EH_PE_absptr {
122            return self.write_address(address, size);
123        }
124
125        match address {
126            Address::Constant(_) => self.writer.write_eh_pointer(address, eh_pe, size),
127            Address::Symbol { symbol, addend }
128                if eh_pe == (constants::DW_EH_PE_pcrel | constants::DW_EH_PE_sdata4)
129                    && size == 8 =>
130            {
131                let target = Self::target_for(symbol)?;
132                let offset = self.len() as u64;
133                self.relocs.push(EhRelocation {
134                    offset,
135                    kind: ObjectRelocationKind::Relative,
136                    size: 4,
137                    target,
138                    addend,
139                });
140                self.write_udata(0, 4)
141            }
142            // Indirect, PC-relative reference to the personality pointer. The
143            // ELF emitter places the pointer in relocatable read-only data and
144            // resolves this relocation against that local slot. This is the
145            // architecture-independent equivalent of a `DW.ref.*` symbol.
146            Address::Symbol { symbol, addend }
147                if eh_pe
148                    == (constants::DW_EH_PE_indirect
149                        | constants::DW_EH_PE_pcrel
150                        | constants::DW_EH_PE_sdata4)
151                    && size == 8 =>
152            {
153                let target = Self::target_for(symbol)?;
154                let offset = self.len() as u64;
155                self.relocs.push(EhRelocation {
156                    offset,
157                    kind: ObjectRelocationKind::Relative,
158                    size: 4,
159                    target,
160                    addend,
161                });
162                self.write_udata(0, 4)
163            }
164            Address::Symbol { .. } => Err(cranelift_codegen::gimli::write::Error::InvalidAddress),
165        }
166    }
167
168    fn write_offset(
169        &mut self,
170        _val: usize,
171        _section: GimliSectionId,
172        _size: u8,
173    ) -> GimliResult<()> {
174        Err(cranelift_codegen::gimli::write::Error::OffsetOutOfBounds)
175    }
176
177    fn write_offset_at(
178        &mut self,
179        _offset: usize,
180        _val: usize,
181        _section: GimliSectionId,
182        _size: u8,
183    ) -> GimliResult<()> {
184        Err(cranelift_codegen::gimli::write::Error::OffsetOutOfBounds)
185    }
186}
187
188/// Emit a per-object section holding the exception tag constants referenced by
189/// a function's LSDA type table, returning a section symbol and a tag->offset
190/// map. Returns `None` when the LSDA references no tags.
191#[cfg(feature = "unwind")]
192fn emit_eh_tag_section(
193    object: &mut Object<'static>,
194    lsda: &FunctionLsdaData,
195) -> Option<(object::write::SymbolId, HashMap<u32, u32>)> {
196    let mut tags: Vec<u32> = lsda.relocations.iter().map(|r| r.tag).collect();
197    tags.sort_unstable();
198    tags.dedup();
199    if tags.is_empty() {
200        return None;
201    }
202
203    let mut bytes = Vec::with_capacity(tags.len() * size_of::<u32>());
204    let mut offsets = HashMap::new();
205    for tag in tags {
206        offsets.insert(tag, bytes.len() as u32);
207        bytes.extend_from_slice(&tag.to_ne_bytes());
208    }
209
210    let section = object.add_section(
211        object.segment_name(StandardSegment::Data).to_vec(),
212        b".wasmer.eh_tags".to_vec(),
213        SectionKind::ReadOnlyData,
214    );
215    object.append_section_data(section, &bytes, 4);
216    Some((object.section_symbol(section), offsets))
217}
218
219/// Serialize a single compiled function into its own relocatable object file.
220#[allow(clippy::too_many_arguments)]
221pub(crate) fn emit_local_function(
222    #[cfg(feature = "unwind")] isa: &dyn TargetIsa,
223    target: &Target,
224    index: LocalFunctionIndex,
225    function_name: &str,
226    module_name: Option<&str>,
227    function: &CompiledFunction,
228    source_map: &WasmSourceMap,
229    #[cfg(feature = "unwind")] fde: Option<FrameDescriptionEntry>,
230    #[cfg(feature = "unwind")] lsda: Option<FunctionLsdaData>,
231) -> Result<Vec<u8>, CompileError> {
232    let kind = CompiledKind::Local(index, String::new());
233    let mut object = get_object_for_target(target.triple())
234        .map_err(|e| CompileError::Codegen(format!("cannot create object: {e}")))?;
235
236    // Emit the function body into the text section.
237    let function_symbol = object.add_symbol(Symbol {
238        name: kind.linkage_name().into_bytes(),
239        value: 0,
240        size: function.body.body.len() as u64,
241        kind: SymbolKind::Text,
242        scope: SymbolScope::Linkage,
243        weak: false,
244        section: SymbolSection::Undefined,
245        flags: SymbolFlags::None,
246    });
247    let text = object.section_id(StandardSection::Text);
248    object.add_symbol_data(function_symbol, text, &function.body.body, 16);
249    add_relocations(
250        &mut object,
251        text,
252        &function.relocations,
253        Some((index, function_symbol)),
254    )?;
255
256    // Populate DWARF line info from the address map.
257    if let Ok(mut dwarf_state) = init_dwarf_unit(function_name, module_name, "Wasmer (Cranelift)") {
258        for instruction in &function.frame_info.address_map.instructions {
259            dwarf_state.add_source_map_row(
260                instruction.code_offset as u64,
261                instruction.srcloc,
262                source_map,
263            );
264        }
265        dwarf_state.write_sections(
266            &mut object,
267            function_symbol,
268            function.body.body.len() as u64,
269            target.triple().endianness().ok(),
270        )?;
271    }
272
273    emit_trap_section(&mut object, &kind, &function.frame_info.traps);
274
275    // Emit the per-function `.eh_frame` unwind table (and, for functions that
276    // catch exceptions, the matching `.gcc_except_table` LSDA).
277    #[cfg(feature = "unwind")]
278    if let Some(fde) = fde
279        && let Some(mut cie) = isa.create_systemv_cie()
280    {
281        let pointer_bytes = isa.frontend_config().pointer_bytes();
282
283        // Emit the LSDA into `.gcc_except_table`, plus a per-object tag section
284        // holding the exception tag constants referenced by its type table.
285        let lsda_section_symbol = if let Some(lsda) = &lsda {
286            let tag_section_symbol = emit_eh_tag_section(&mut object, lsda);
287
288            let gcc_section = object.add_section(
289                object.segment_name(StandardSegment::Data).to_vec(),
290                b".gcc_except_table".to_vec(),
291                SectionKind::ReadOnlyData,
292            );
293            let lsda_offset =
294                object.append_section_data(gcc_section, &lsda.bytes, u64::from(pointer_bytes));
295            // The type-table slots use `DW_EH_PE_pcrel | sdata4` encoding,
296            // so their relocations are PC-relative 32-bit (`R_X86_64_PC32`).
297            // This keeps `.gcc_except_table` position-independent and read-only.
298            let tag_relocation_flags = RelocationFlags::Generic {
299                kind: object::RelocationKind::Relative,
300                encoding: RelocationEncoding::Generic,
301                size: 32,
302            };
303            for reloc in &lsda.relocations {
304                let (tag_symbol, tag_offset) = tag_section_symbol
305                    .as_ref()
306                    .and_then(|(symbol, offsets)| {
307                        offsets.get(&reloc.tag).map(|offset| (*symbol, *offset))
308                    })
309                    .ok_or_else(|| {
310                        CompileError::Codegen(format!(
311                            "missing exception tag {} for LSDA relocation",
312                            reloc.tag
313                        ))
314                    })?;
315                object
316                    .add_relocation(
317                        gcc_section,
318                        ObjectRelocation {
319                            offset: lsda_offset + reloc.offset as u64,
320                            flags: tag_relocation_flags,
321                            symbol: tag_symbol,
322                            addend: tag_offset as i64,
323                        },
324                    )
325                    .map_err(|e| {
326                        CompileError::Codegen(format!("failed to add LSDA relocation: {e}"))
327                    })?;
328            }
329            Some(object.section_symbol(gcc_section))
330        } else {
331            None
332        };
333
334        // The ELF image may be mapped at any base address, so make the FDE's
335        // function reference position-independent.
336        cie.fde_address_encoding = constants::DW_EH_PE_pcrel | constants::DW_EH_PE_sdata4;
337        let mut fde = fde;
338        if lsda_section_symbol.is_some() {
339            // Reference the personality pointer indirectly and PC-relative.
340            // The shared ELF emitter creates the local pointer slot and its
341            // dynamic relocation. The LSDA lives in the same image and is
342            // referenced directly, PC-relative.
343            cie.personality = Some((
344                constants::DW_EH_PE_indirect
345                    | constants::DW_EH_PE_pcrel
346                    | constants::DW_EH_PE_sdata4,
347                Address::Symbol {
348                    symbol: WriterRelocate::PERSONALITY_SYMBOL,
349                    addend: 0,
350                },
351            ));
352            cie.lsda_encoding = Some(constants::DW_EH_PE_pcrel | constants::DW_EH_PE_sdata4);
353            fde.lsda = Some(Address::Symbol {
354                symbol: WriterRelocate::LSDA_SYMBOL,
355                addend: 0,
356            });
357        }
358
359        let mut frametable = FrameTable::default();
360        let cie_id = frametable.add_cie(cie);
361        frametable.add_fde(cie_id, fde);
362
363        let mut eh_frame = EhFrame(EhFrameWriter::new());
364        frametable
365            .write_eh_frame(&mut eh_frame)
366            .map_err(|e| CompileError::Codegen(format!("failed to write .eh_frame: {e}")))?;
367
368        let relocations = std::mem::take(&mut eh_frame.0.relocs);
369        emit_eh_frame_section(
370            &mut object,
371            &eh_frame.0.into_bytes(),
372            &relocations,
373            function_symbol,
374            lsda_section_symbol,
375        )?;
376    }
377
378    object
379        .write()
380        .map_err(|e| CompileError::Codegen(format!("failed to serialize object: {e}")))
381}