wasmer_compiler/
elf.rs

1//! Helpers shared by the object-based compiler backends (Cranelift and
2//! Singlepass) for emitting per-function relocatable ELF objects and linking
3//! them into the final module image.
4
5use crate::compiler::{CompiledObjects, emit_metadata_and_link};
6use crate::dwarf::{EhRelocation, EhTarget};
7use crate::misc::{CompiledFunctionExt, CompiledKind};
8use crate::object::get_object_for_target;
9use crate::types::function::{Compilation, FunctionBody};
10use crate::types::relocation::{Relocation, RelocationKind, RelocationTarget};
11use crate::types::section::CustomSection;
12use object::{
13    RelocationEncoding, RelocationFlags, RelocationKind as ObjectRelocationKind, SectionKind,
14    SymbolFlags, SymbolKind, SymbolScope, elf,
15    write::{
16        Object, Relocation as ObjectRelocation, SectionId, StandardSection, StandardSegment,
17        Symbol, SymbolId, SymbolSection,
18    },
19};
20use std::{
21    fs::OpenOptions,
22    io::Read,
23    path::{Path, PathBuf},
24};
25use tempfile::NamedTempFile;
26use wasmer_types::{
27    CompileError, LibCall, LocalFunctionIndex, TrapInformation, entity::PrimaryMap, target::Target,
28};
29use wasmer_types::{FunctionIndex, FunctionType};
30
31/// The result of compiling a single unit (function or trampoline): either an
32/// in-memory body for the classic artifact format, or a relocatable object
33/// file on disk for the ELF artifact format (together with the maximum stack
34/// usage, when known).
35pub enum CompileOutput<T> {
36    /// The compiled body, kept in memory.
37    InMemory(T),
38    /// Path to the emitted relocatable object file and the unit's maximum
39    /// stack usage, when known.
40    Object(PathBuf, Option<usize>),
41}
42
43impl<T: crate::compiler::CompiledFunction> crate::compiler::CompiledFunction for CompileOutput<T> {}
44
45/// Extract the object-file paths from ELF-mode compile outputs.
46pub fn compile_output_paths<T>(outputs: Vec<CompileOutput<T>>) -> Vec<PathBuf> {
47    outputs
48        .into_iter()
49        .map(|output| match output {
50            CompileOutput::Object(path, _) => path,
51            CompileOutput::InMemory(_) => unreachable!(),
52        })
53        .collect()
54}
55
56/// Extract the in-memory bodies from classic-mode compile outputs.
57pub fn compile_output_in_memory<T>(outputs: Vec<CompileOutput<T>>) -> Vec<T> {
58    outputs
59        .into_iter()
60        .map(|output| match output {
61            CompileOutput::InMemory(body) => body,
62            CompileOutput::Object(..) => unreachable!(),
63        })
64        .collect()
65}
66
67/// Write a finished object into `build_directory` under `filename`.
68pub fn save_object(
69    object: Object<'static>,
70    build_directory: &Path,
71    filename: String,
72) -> Result<PathBuf, CompileError> {
73    let path = build_directory.join(filename);
74    let mut file = OpenOptions::new()
75        .write(true)
76        .create(true)
77        .truncate(true)
78        .open(&path)
79        .map_err(|e| {
80            CompileError::Codegen(format!("failed to create object {}: {e}", path.display()))
81        })?;
82    object.write_stream(&mut file).map_err(|e| {
83        CompileError::Codegen(format!("failed to write object {}: {e}", path.display()))
84    })?;
85    Ok(path)
86}
87
88/// Declare an undefined text symbol resolved when the objects are linked.
89pub fn add_undefined_symbol(object: &mut Object<'static>, name: String) -> SymbolId {
90    object.add_symbol(Symbol {
91        name: name.into_bytes(),
92        value: 0,
93        size: 0,
94        kind: SymbolKind::Text,
95        scope: SymbolScope::Linkage,
96        weak: false,
97        section: SymbolSection::Undefined,
98        flags: SymbolFlags::None,
99    })
100}
101
102/// Declare an undefined dynamic symbol for a libcall, resolved by the runtime
103/// loader through a dynamic relocation.
104pub fn add_libcall_symbol(object: &mut Object<'static>, libcall: LibCall) -> SymbolId {
105    object.add_symbol(Symbol {
106        name: libcall.to_function_name().to_string().into_bytes(),
107        value: 0,
108        size: 0,
109        kind: SymbolKind::Unknown,
110        scope: SymbolScope::Dynamic,
111        weak: false,
112        section: SymbolSection::Undefined,
113        flags: SymbolFlags::None,
114    })
115}
116
117/// Map a Wasmer relocation kind onto the corresponding object-file relocation
118/// flags.
119pub fn relocation_kind_to_flags(kind: RelocationKind) -> Result<RelocationFlags, CompileError> {
120    use ObjectRelocationKind as K;
121    Ok(match kind {
122        RelocationKind::Abs4 => RelocationFlags::Generic {
123            kind: K::Absolute,
124            encoding: RelocationEncoding::Generic,
125            size: 32,
126        },
127        RelocationKind::Abs8 => RelocationFlags::Generic {
128            kind: K::Absolute,
129            encoding: RelocationEncoding::Generic,
130            size: 64,
131        },
132        RelocationKind::PCRel4 => RelocationFlags::Generic {
133            kind: K::Relative,
134            encoding: RelocationEncoding::Generic,
135            size: 32,
136        },
137        RelocationKind::X86CallPCRel4 => RelocationFlags::Generic {
138            kind: K::Relative,
139            encoding: RelocationEncoding::X86Branch,
140            size: 32,
141        },
142        RelocationKind::X86CallPLTRel4 => RelocationFlags::Generic {
143            kind: K::PltRelative,
144            encoding: RelocationEncoding::X86Branch,
145            size: 32,
146        },
147        RelocationKind::X86GOTPCRel4 => RelocationFlags::Generic {
148            kind: K::GotRelative,
149            encoding: RelocationEncoding::Generic,
150            size: 32,
151        },
152        RelocationKind::Arm64Call => RelocationFlags::Elf {
153            r_type: elf::R_AARCH64_CALL26,
154        },
155        // For RISC-V relocations, please refer to:
156        // https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/2484f950a551c653f1823f1bd11926bf5a57fae3/riscv-elf.adoc#relocations
157        RelocationKind::RiscvPCRelHi20 => RelocationFlags::Elf {
158            r_type: elf::R_RISCV_PCREL_HI20,
159        },
160        RelocationKind::RiscvPCRelLo12I => RelocationFlags::Elf {
161            r_type: elf::R_RISCV_PCREL_LO12_I,
162        },
163        RelocationKind::RiscvCall => RelocationFlags::Elf {
164            r_type: elf::R_RISCV_CALL_PLT,
165        },
166        kind => {
167            return Err(CompileError::Codegen(format!(
168                "unsupported ELF relocation kind: {kind:?}"
169            )));
170        }
171    })
172}
173
174/// Apply the compiled code's relocations to `section`, declaring the referenced symbols.
175pub fn add_relocations(
176    object: &mut Object<'static>,
177    section: SectionId,
178    relocations: &[Relocation],
179    local_symbol: Option<(LocalFunctionIndex, SymbolId)>,
180) -> Result<(), CompileError> {
181    for relocation in relocations {
182        let symbol = match relocation.reloc_target {
183            RelocationTarget::LocalFunc(index) => local_symbol
184                .filter(|(local_index, _)| *local_index == index)
185                .map_or_else(
186                    || {
187                        add_undefined_symbol(
188                            object,
189                            CompiledKind::Local(index, String::new()).linkage_name(),
190                        )
191                    },
192                    |(_, symbol)| symbol,
193                ),
194            RelocationTarget::CustomSection(index) => add_undefined_symbol(
195                object,
196                CompiledKind::ImportFunctionTrampoline(
197                    FunctionIndex::from_u32(index.as_u32()),
198                    FunctionType::default(),
199                )
200                .linkage_name(),
201            ),
202            RelocationTarget::LibCall(libcall) => add_libcall_symbol(object, libcall),
203            RelocationTarget::DynamicTrampoline(index) => add_undefined_symbol(
204                object,
205                CompiledKind::DynamicFunctionTrampoline(index, FunctionType::default())
206                    .linkage_name(),
207            ),
208        };
209        let flags = relocation_kind_to_flags(relocation.kind)?;
210        object
211            .add_relocation(
212                section,
213                ObjectRelocation {
214                    offset: relocation.offset as u64,
215                    flags,
216                    symbol,
217                    addend: relocation.addend,
218                },
219            )
220            .map_err(|e| CompileError::Codegen(format!("failed to add ELF relocation: {e}")))?;
221    }
222    Ok(())
223}
224
225/// Emit the per-function trap table into a `.w.traps` section, under a weak
226/// data symbol so the metadata object can reference it per function.
227pub fn emit_trap_section(
228    object: &mut Object<'static>,
229    kind: &CompiledKind,
230    traps: &[TrapInformation],
231) {
232    let mut trap_data = Vec::with_capacity(traps.len() * 8 + size_of::<u32>());
233    trap_data.extend_from_slice(&(traps.len() as u32).to_le_bytes());
234    for trap in traps {
235        trap_data.extend_from_slice(&trap.code_offset.to_le_bytes());
236        trap_data.extend_from_slice(&(trap.trap_code as u32).to_le_bytes());
237    }
238    let traps_section = object.add_section(
239        object.segment_name(StandardSegment::Data).to_vec(),
240        crate::WASMER_TRAPS_SECTION_NAME.to_vec(),
241        SectionKind::Other,
242    );
243    let traps_symbol = object.add_symbol(Symbol {
244        name: kind.traps_name().into_bytes(),
245        value: 0,
246        size: trap_data.len() as u64,
247        kind: SymbolKind::Data,
248        scope: SymbolScope::Linkage,
249        weak: true,
250        section: SymbolSection::Section(traps_section),
251        flags: SymbolFlags::None,
252    });
253    object.add_symbol_data(traps_symbol, traps_section, &trap_data, 4);
254}
255
256/// Emit a serialized `.eh_frame` blob into its own section, resolving the
257/// recorded relocations against the function's text symbol, the exception
258/// personality routine and the function's LSDA section.
259pub fn emit_eh_frame_section(
260    object: &mut Object<'static>,
261    eh_frame_bytes: &[u8],
262    relocations: &[EhRelocation],
263    function_symbol: SymbolId,
264    lsda_section_symbol: Option<SymbolId>,
265) -> Result<(), CompileError> {
266    let section = object.add_section(
267        object.segment_name(StandardSegment::Debug).to_vec(),
268        crate::EH_FRAME_SECTION_NAME.to_vec(),
269        SectionKind::Other,
270    );
271    let data_offset = object.append_section_data(section, eh_frame_bytes, 4);
272
273    // The personality symbol is added lazily, the first time a relocation
274    // references it.
275    let mut personality_symbol = None;
276    for relocation in relocations {
277        let symbol = match relocation.target {
278            EhTarget::Function => function_symbol,
279            EhTarget::Personality => *personality_symbol
280                .get_or_insert_with(|| add_libcall_symbol(object, LibCall::EHPersonality)),
281            EhTarget::Lsda => lsda_section_symbol.ok_or_else(|| {
282                CompileError::Codegen(
283                    ".eh_frame references an LSDA but none was emitted".to_string(),
284                )
285            })?,
286        };
287        object
288            .add_relocation(
289                section,
290                ObjectRelocation {
291                    offset: data_offset + relocation.offset,
292                    flags: RelocationFlags::Generic {
293                        kind: relocation.kind,
294                        encoding: RelocationEncoding::Generic,
295                        size: 8 * relocation.size,
296                    },
297                    symbol,
298                    addend: relocation.addend,
299                },
300            )
301            .map_err(|e| {
302                CompileError::Codegen(format!("failed to add .eh_frame relocation: {e}"))
303            })?;
304    }
305    Ok(())
306}
307
308/// Serialize a trampoline's body into its own relocatable object file.
309pub fn emit_function_body(
310    target: &Target,
311    build_directory: &Path,
312    kind: &CompiledKind,
313    body: &FunctionBody,
314) -> Result<PathBuf, CompileError> {
315    let mut object = get_object_for_target(target.triple())
316        .map_err(|e| CompileError::Codegen(format!("cannot create object: {e}")))?;
317    let symbol = object.add_symbol(Symbol {
318        name: kind.linkage_name().into_bytes(),
319        value: 0,
320        size: body.body.len() as u64,
321        kind: SymbolKind::Text,
322        scope: SymbolScope::Linkage,
323        weak: false,
324        section: SymbolSection::Undefined,
325        flags: SymbolFlags::None,
326    });
327    let text = object.section_id(StandardSection::Text);
328    object.add_symbol_data(symbol, text, &body.body, 4);
329    save_object(object, build_directory, kind.object_filename())
330}
331
332/// Serialize an import trampoline's custom section into its own relocatable object file.
333pub fn emit_import_trampoline(
334    target: &Target,
335    build_directory: &Path,
336    kind: &CompiledKind,
337    section: &CustomSection,
338) -> Result<PathBuf, CompileError> {
339    let mut object = get_object_for_target(target.triple())
340        .map_err(|e| CompileError::Codegen(format!("cannot create object: {e}")))?;
341    let symbol = object.add_symbol(Symbol {
342        name: kind.linkage_name().into_bytes(),
343        value: 0,
344        size: section.bytes.len() as u64,
345        kind: SymbolKind::Text,
346        scope: SymbolScope::Linkage,
347        weak: false,
348        section: SymbolSection::Undefined,
349        flags: SymbolFlags::None,
350    });
351    let text = object.section_id(StandardSection::Text);
352    object.add_symbol_data(symbol, text, section.bytes.as_slice(), 4);
353    save_object(object, build_directory, kind.object_filename())
354}
355
356/// Link all the per-function and trampoline objects (plus the Wasmer metadata
357/// object) into the final shared-object module image.
358#[allow(clippy::too_many_arguments)]
359pub fn link_module(
360    target: &Target,
361    compile_info_blob: &[u8],
362    build_directory: &Path,
363    object_files: &[PathBuf],
364    import_trampoline_objects: &[PathBuf],
365    trampoline_objects: &[PathBuf],
366    dynamic_trampoline_objects: &[PathBuf],
367    debug_dir: Option<PathBuf>,
368    module_hash: Option<String>,
369    function_max_stack_usage: PrimaryMap<LocalFunctionIndex, Option<usize>>,
370) -> Result<Compilation, CompileError> {
371    let module_file = NamedTempFile::new_in(build_directory)
372        .map_err(|e| CompileError::Codegen(format!("cannot create temporary module file: {e}")))?;
373    let mut module_file = emit_metadata_and_link(
374        target,
375        compile_info_blob,
376        build_directory,
377        module_file,
378        &CompiledObjects {
379            object_files,
380            import_trampoline_object_files: import_trampoline_objects,
381            trampoline_object_files: trampoline_objects,
382            dynamic_trampoline_object_files: dynamic_trampoline_objects,
383        },
384        debug_dir,
385        module_hash,
386    )?;
387    let mut elf = Vec::new();
388    module_file
389        .read_to_end(&mut elf)
390        .map_err(|e| CompileError::Codegen(format!("cannot read linked module artifact: {e}")))?;
391    Ok(Compilation::Elf {
392        data: elf,
393        function_max_stack_usage,
394    })
395}