//! Object file builder.1//!2//! Creates ELF image based on `Compilation` information. The ELF contains3//! functions and trampolines in the ".text" section. It also contains all4//! relocation records for the linking stage. If DWARF sections exist, their5//! content will be written as well.6//!7//! The object file has symbols for each function and trampoline, as well as8//! symbols that refer to libcalls.9//!10//! The function symbol names have format "_wasm_function_N", where N is11//! `FuncIndex`. The defined wasm function symbols refer to a JIT compiled12//! function body, the imported wasm function do not. The trampolines symbol13//! names have format "_trampoline_N", where N is `SignatureIndex`.1415use crate::CompiledFunction;16use cranelift_codegen::TextSectionBuilder;17use cranelift_codegen::isa::unwind::{UnwindInfo, systemv};18use cranelift_control::ControlPlane;19use gimli::RunTimeEndian;20use gimli::write::{Address, EhFrame, EndianVec, FrameTable, Writer};21use object::write::{Object, SectionId, StandardSegment, Symbol, SymbolId, SymbolSection};22use object::{Architecture, SectionFlags, SectionKind, SymbolFlags, SymbolKind, SymbolScope};23use std::ops::Range;24use wasmtime_environ::error::Result;25use wasmtime_environ::{Compiler, TripleExt};26use wasmtime_environ::{FuncKey, obj};2728const TEXT_SECTION_NAME: &[u8] = b".text";2930fn text_align(compiler: &dyn Compiler) -> u64 {31// text pages will not be made executable with pulley, so the section32// doesn't need to be padded out to page alignment boundaries.33if compiler.triple().is_pulley() {340x135} else {36compiler.page_size_align()37}38}3940/// A helper structure used to assemble the final text section of an executable,41/// plus unwinding information and other related details.42///43/// This builder relies on Cranelift-specific internals but assembles into a44/// generic `Object` which will get further appended to in a compiler-agnostic45/// fashion later.46pub struct ModuleTextBuilder<'a> {47/// The target that we're compiling for, used to query target-specific48/// information as necessary.49compiler: &'a dyn Compiler,5051/// The object file that we're generating code into.52obj: &'a mut Object<'static>,5354/// The WebAssembly module we're generating code for.55text_section: SectionId,5657unwind_info: UnwindInfoBuilder<'a>,5859/// In-progress text section that we're using cranelift's `MachBuffer` to60/// build to resolve relocations (calls) between functions.61text: Box<dyn TextSectionBuilder>,6263ctrl_plane: ControlPlane,64}6566impl<'a> ModuleTextBuilder<'a> {67/// Creates a new builder for the text section of an executable.68///69/// The `.text` section will be appended to the specified `obj` along with70/// any unwinding or such information as necessary. The `num_funcs`71/// parameter indicates the number of times the `append_func` function will72/// be called. The `finish` function will panic if this contract is not met.73pub fn new(74obj: &'a mut Object<'static>,75compiler: &'a dyn Compiler,76text: Box<dyn TextSectionBuilder>,77) -> Self {78// Entire code (functions and trampolines) will be placed79// in the ".text" section.80let text_section = obj.add_section(81obj.segment_name(StandardSegment::Text).to_vec(),82TEXT_SECTION_NAME.to_vec(),83SectionKind::Text,84);8586// If this target is Pulley then flag the text section as not needing the87// executable bit in virtual memory which means that the runtime won't88// try to call `Mmap::make_executable`, which makes Pulley more89// portable.90if compiler.triple().is_pulley() {91let section = obj.section_mut(text_section);92assert!(matches!(section.flags, SectionFlags::None));93section.flags = SectionFlags::Elf {94sh_flags: obj::SH_WASMTIME_NOT_EXECUTED,95};96}9798Self {99compiler,100obj,101text_section,102unwind_info: Default::default(),103text,104ctrl_plane: ControlPlane::default(),105}106}107108/// Appends the `func` specified named `name` to this object.109///110/// The `resolve_reloc_target` closure is used to resolve a relocation111/// target to an adjacent function which has already been added or will be112/// added to this object. The argument is the relocation target specified113/// within `CompiledFunction` and the return value must be an index where114/// the target will be defined by the `n`th call to `append_func`.115///116/// Returns the symbol associated with the function as well as the range117/// that the function resides within the text section.118pub fn append_func(119&mut self,120name: &str,121compiled_func: &'a CompiledFunction,122resolve_reloc_target: impl Fn(wasmtime_environ::FuncKey) -> usize,123) -> (SymbolId, Range<u64>) {124let body = compiled_func.buffer.data();125let alignment = compiled_func.alignment;126let body_len = body.len() as u64;127let off = self128.text129.append(true, &body, alignment, &mut self.ctrl_plane);130131let symbol_id = self.obj.add_symbol(Symbol {132name: name.as_bytes().to_vec(),133value: off,134size: body_len,135kind: SymbolKind::Text,136scope: SymbolScope::Compilation,137weak: false,138section: SymbolSection::Section(self.text_section),139flags: SymbolFlags::None,140});141142if let Some(info) = compiled_func.unwind_info() {143self.unwind_info.push(off, body_len, info);144}145146for r in compiled_func.relocations() {147let reloc_offset = off + u64::from(r.offset);148149// This relocation is used to fill in which hostcall id is150// desired within the `call_indirect_host` opcode of Pulley151// itself. The relocation target is the start of the instruction152// and the goal is to insert the static signature number, `n`,153// into the instruction.154//155// At this time the instruction looks like:156//157// +------+------+------+------+158// | OP | OP_EXTENDED | N |159// +------+------+------+------+160//161// This 4-byte encoding has `OP` indicating this is an "extended162// opcode" where `OP_EXTENDED` is a 16-bit extended opcode.163// The `N` byte is the index of the signature being called and164// is what's b eing filled in.165//166// See the `test_call_indirect_host_width` in167// `pulley/tests/all.rs` for this guarantee as well.168if let FuncKey::PulleyHostCall(host_call) = r.reloc_target {169#[cfg(feature = "pulley")]170{171use pulley_interpreter::encode::Encode;172assert_eq!(pulley_interpreter::CallIndirectHost::WIDTH, 4);173}174let n = host_call.index();175let byte = u8::try_from(n).unwrap();176self.text.write(reloc_offset + 3, &[byte]);177continue;178}179180let target = resolve_reloc_target(r.reloc_target);181if self182.text183.resolve_reloc(reloc_offset, r.reloc, r.addend, target)184{185continue;186}187188panic!("failed to resolve relocation: {r:?} -> {target}");189}190191(symbol_id, off..off + body_len)192}193194/// Forces "veneers" to be used for inter-function calls in the text195/// section which means that in-bounds optimized addresses are never used.196///197/// This is only useful for debugging cranelift itself and typically this198/// option is disabled.199pub fn force_veneers(&mut self) {200self.text.force_veneers();201}202203/// Appends the specified amount of bytes of padding into the text section.204///205/// This is only useful when fuzzing and/or debugging cranelift itself and206/// for production scenarios `padding` is 0 and this function does nothing.207pub fn append_padding(&mut self, padding: usize) {208if padding == 0 {209return;210}211self.text212.append(false, &vec![0; padding], 1, &mut self.ctrl_plane);213}214215/// Indicates that the text section has been written completely and this216/// will finish appending it to the original object.217///218/// Note that this will also write out the unwind information sections if219/// necessary.220pub fn finish(mut self, postprocess_text: impl FnOnce(&mut [u8])) {221// Finish up the text section now that we're done adding functions.222let mut text = self.text.finish(&mut self.ctrl_plane);223224postprocess_text(&mut text[..]);225226self.obj227.section_mut(self.text_section)228.set_data(text, text_align(self.compiler));229230// Append the unwind information for all our functions, if necessary.231self.unwind_info232.append_section(self.compiler, self.obj, self.text_section);233}234}235236/// Builder used to create unwind information for a set of functions added to a237/// text section.238#[derive(Default)]239struct UnwindInfoBuilder<'a> {240windows_xdata: Vec<u8>,241windows_pdata: Vec<RUNTIME_FUNCTION>,242systemv_unwind_info: Vec<(u64, &'a systemv::UnwindInfo)>,243}244245// This is a mirror of `RUNTIME_FUNCTION` in the Windows API, but defined here246// to ensure everything is always `u32` and to have it available on all247// platforms. Note that all of these specifiers here are relative to a "base248// address" which we define as the base of where the text section is eventually249// loaded.250#[expect(non_camel_case_types, reason = "matching Windows style, not Rust")]251struct RUNTIME_FUNCTION {252begin: u32,253end: u32,254unwind_address: u32,255}256257impl<'a> UnwindInfoBuilder<'a> {258/// Pushes the unwind information for a function into this builder.259///260/// The function being described must be located at `function_offset` within261/// the text section itself, and the function's size is specified by262/// `function_len`.263///264/// The `info` should come from Cranelift. and is handled here depending on265/// its flavor.266fn push(&mut self, function_offset: u64, function_len: u64, info: &'a UnwindInfo) {267match info {268// Windows unwind information is stored in two locations:269//270// * First is the actual unwinding information which is stored271// in the `.xdata` section. This is where `info`'s emitted272// information will go into.273// * Second are pointers to connect all this unwind information,274// stored in the `.pdata` section. The `.pdata` section is an275// array of `RUNTIME_FUNCTION` structures.276//277// Due to how these will be loaded at runtime the `.pdata` isn't278// actually assembled byte-wise here. Instead that's deferred to279// happen later during `write_windows_unwind_info` which will apply280// a further offset to `unwind_address`.281//282// FIXME: in theory we could "intern" the `unwind_info` value283// here within the `.xdata` section. Most of our unwind284// information for functions is probably pretty similar in which285// case the `.xdata` could be quite small and `.pdata` could286// have multiple functions point to the same unwinding287// information.288UnwindInfo::WindowsX64(info) => {289let unwind_size = info.emit_size();290let mut unwind_info = vec![0; unwind_size];291info.emit(&mut unwind_info);292293// `.xdata` entries are always 4-byte aligned294while self.windows_xdata.len() % 4 != 0 {295self.windows_xdata.push(0x00);296}297let unwind_address = self.windows_xdata.len();298self.windows_xdata.extend_from_slice(&unwind_info);299300// Record a `RUNTIME_FUNCTION` which this will point to.301self.windows_pdata.push(RUNTIME_FUNCTION {302begin: u32::try_from(function_offset).unwrap(),303end: u32::try_from(function_offset + function_len).unwrap(),304unwind_address: u32::try_from(unwind_address).unwrap(),305});306}307308// See https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling309UnwindInfo::WindowsArm64(info) => {310let code_words = info.code_words();311let mut unwind_codes = vec![0; (code_words * 4) as usize];312info.emit(&mut unwind_codes);313314// `.xdata` entries are always 4-byte aligned315while self.windows_xdata.len() % 4 != 0 {316self.windows_xdata.push(0x00);317}318319// First word:320// 0-17: Function Length321// 18-19: Version (must be 0)322// 20: X bit (is exception data present?)323// 21: E bit (has single packed epilogue?)324// 22-26: Epilogue count325// 27-31: Code words count326let requires_extended_counts = code_words > (1 << 5);327let encoded_function_len = function_len / 4;328assert!(encoded_function_len < (1 << 18), "function too large");329let mut word1 = u32::try_from(encoded_function_len).unwrap();330if !requires_extended_counts {331word1 |= u32::from(code_words) << 27;332}333let unwind_address = self.windows_xdata.len();334self.windows_xdata.extend_from_slice(&word1.to_le_bytes());335336if requires_extended_counts {337// Extended counts word:338// 0-15: Epilogue count339// 16-23: Code words count340let extended_counts_word = (code_words as u32) << 16;341self.windows_xdata342.extend_from_slice(&extended_counts_word.to_le_bytes());343}344345// Skip epilogue information: Per comment on [`UnwindInst`], we346// do not emit information about epilogues.347348// Emit the unwind codes.349self.windows_xdata.extend_from_slice(&unwind_codes);350351// Record a `RUNTIME_FUNCTION` which this will point to.352// NOTE: `end` is not used, so leave it as 0.353self.windows_pdata.push(RUNTIME_FUNCTION {354begin: u32::try_from(function_offset).unwrap(),355end: 0,356unwind_address: u32::try_from(unwind_address).unwrap(),357});358}359360// System-V is different enough that we just record the unwinding361// information to get processed at a later time.362UnwindInfo::SystemV(info) => {363self.systemv_unwind_info.push((function_offset, info));364}365366_ => panic!("some unwind info isn't handled here"),367}368}369370/// Appends the unwind information section, if any, to the `obj` specified.371///372/// This function must be called immediately after the text section was373/// added to a builder. The unwind information section must trail the text374/// section immediately.375///376/// The `text_section`'s section identifier is passed into this function.377fn append_section(378&self,379compiler: &dyn Compiler,380obj: &mut Object<'_>,381text_section: SectionId,382) {383// This write will align the text section to a page boundary and then384// return the offset at that point. This gives us the full size of the385// text section at that point, after alignment.386let text_section_size = obj.append_section_data(text_section, &[], text_align(compiler));387388if self.windows_xdata.len() > 0 {389assert!(self.systemv_unwind_info.len() == 0);390// The `.xdata` section must come first to be just-after the `.text`391// section for the reasons documented in `write_windows_unwind_info`392// below.393let segment = obj.segment_name(StandardSegment::Data).to_vec();394let xdata_id = obj.add_section(segment, b".xdata".to_vec(), SectionKind::ReadOnlyData);395let segment = obj.segment_name(StandardSegment::Data).to_vec();396let pdata_id = obj.add_section(segment, b".pdata".to_vec(), SectionKind::ReadOnlyData);397self.write_windows_unwind_info(obj, xdata_id, pdata_id, text_section_size);398}399400if self.systemv_unwind_info.len() > 0 {401let segment = obj.segment_name(StandardSegment::Data).to_vec();402let section_id =403obj.add_section(segment, b".eh_frame".to_vec(), SectionKind::ReadOnlyData);404self.write_systemv_unwind_info(compiler, obj, section_id, text_section_size)405}406}407408/// This function appends a nonstandard section to the object which is only409/// used during `CodeMemory::publish`.410///411/// This custom section effectively stores a `[RUNTIME_FUNCTION; N]` into412/// the object file itself. This way registration of unwind info can simply413/// pass this slice to the OS itself and there's no need to recalculate414/// anything on the other end of loading a module from a precompiled object.415///416/// Support for reading this is in `crates/jit/src/unwind/winx64.rs`.417fn write_windows_unwind_info(418&self,419obj: &mut Object<'_>,420xdata_id: SectionId,421pdata_id: SectionId,422text_section_size: u64,423) {424// Append the `.xdata` section, or the actual unwinding information425// codes and such which were built as we found unwind information for426// functions.427obj.append_section_data(xdata_id, &self.windows_xdata, 4);428429// Next append the `.pdata` section, or the array of `RUNTIME_FUNCTION`430// structures stored in the binary.431//432// This memory will be passed at runtime to `RtlAddFunctionTable` which433// takes a "base address" and the entries within `RUNTIME_FUNCTION` are434// all relative to this base address. The base address we pass is the435// address of the text section itself so all the pointers here must be436// text-section-relative. The `begin` and `end` fields for the function437// it describes are already text-section-relative, but the438// `unwind_address` field needs to be updated here since the value439// stored right now is `xdata`-section-relative. We know that the440// `xdata` section follows the `.text` section so the441// `text_section_size` is added in to calculate the final442// `.text`-section-relative address of the unwind information.443let xdata_rva = |address| {444let address = u64::from(address);445let address = address + text_section_size;446u32::try_from(address).unwrap()447};448let pdata = match obj.architecture() {449Architecture::X86_64 => {450let mut pdata = Vec::with_capacity(self.windows_pdata.len() * 3 * 4);451for info in self.windows_pdata.iter() {452pdata.extend_from_slice(&info.begin.to_le_bytes());453pdata.extend_from_slice(&info.end.to_le_bytes());454pdata.extend_from_slice(&xdata_rva(info.unwind_address).to_le_bytes());455}456pdata457}458459Architecture::Aarch64 => {460// Windows Arm64 .pdata also supports packed unwind data, but461// we're not currently using that.462let mut pdata = Vec::with_capacity(self.windows_pdata.len() * 2 * 4);463for info in self.windows_pdata.iter() {464pdata.extend_from_slice(&info.begin.to_le_bytes());465pdata.extend_from_slice(&xdata_rva(info.unwind_address).to_le_bytes());466}467pdata468}469470_ => unimplemented!("unsupported architecture for windows unwind info"),471};472obj.append_section_data(pdata_id, &pdata, 4);473}474475/// This function appends a nonstandard section to the object which is only476/// used during `CodeMemory::publish`.477///478/// This will generate a `.eh_frame` section, but not one that can be479/// naively loaded. The goal of this section is that we can create the480/// section once here and never again does it need to change. To describe481/// dynamically loaded functions though each individual FDE needs to talk482/// about the function's absolute address that it's referencing. Naturally483/// we don't actually know the function's absolute address when we're484/// creating an object here.485///486/// To solve this problem the FDE address encoding mode is set to487/// `DW_EH_PE_pcrel`. This means that the actual effective address that the488/// FDE describes is a relative to the address of the FDE itself. By489/// leveraging this relative-ness we can assume that the relative distance490/// between the FDE and the function it describes is constant, which should491/// allow us to generate an FDE ahead-of-time here.492///493/// For now this assumes that all the code of functions will start at a494/// page-aligned address when loaded into memory. The eh_frame encoded here495/// then assumes that the text section is itself page aligned to its size496/// and the eh_frame will follow just after the text section. This means497/// that the relative offsets we're using here is the FDE going backwards498/// into the text section itself.499///500/// Note that the library we're using to create the FDEs, `gimli`, doesn't501/// actually encode addresses relative to the FDE itself. Instead the502/// addresses are encoded relative to the start of the `.eh_frame` section.503/// This makes it much easier for us where we provide the relative offset504/// from the start of `.eh_frame` to the function in the text section, which505/// given our layout basically means the offset of the function in the text506/// section from the end of the text section.507///508/// A final note is that the reason we page-align the text section's size is509/// so the .eh_frame lives on a separate page from the text section itself.510/// This allows `.eh_frame` to have different virtual memory permissions,511/// such as being purely read-only instead of read/execute like the code512/// bits.513fn write_systemv_unwind_info(514&self,515compiler: &dyn Compiler,516obj: &mut Object<'_>,517section_id: SectionId,518text_section_size: u64,519) {520let mut cie = match compiler.create_systemv_cie() {521Some(cie) => cie,522None => return,523};524let mut table = FrameTable::default();525cie.fde_address_encoding = gimli::constants::DW_EH_PE_pcrel;526let cie_id = table.add_cie(cie);527528for (text_section_off, unwind_info) in self.systemv_unwind_info.iter() {529let backwards_off = text_section_size - text_section_off;530let actual_offset = -i64::try_from(backwards_off).unwrap();531// Note that gimli wants an unsigned 64-bit integer here, but532// unwinders just use this constant for a relative addition with the533// address of the FDE, which means that the sign doesn't actually534// matter.535let fde = unwind_info.to_fde(Address::Constant(actual_offset.cast_unsigned()));536table.add_fde(cie_id, fde);537}538let endian = match compiler.triple().endianness().unwrap() {539target_lexicon::Endianness::Little => RunTimeEndian::Little,540target_lexicon::Endianness::Big => RunTimeEndian::Big,541};542let mut eh_frame = EhFrame(MyVec(EndianVec::new(endian)));543table.write_eh_frame(&mut eh_frame).unwrap();544545// Some unwinding implementations expect a terminating "empty" length so546// a 0 is written at the end of the table for those implementations.547let mut endian_vec = (eh_frame.0).0;548endian_vec.write_u32(0).unwrap();549obj.append_section_data(section_id, endian_vec.slice(), 1);550551use gimli::constants;552use gimli::write::Error;553554struct MyVec(EndianVec<RunTimeEndian>);555556impl Writer for MyVec {557type Endian = RunTimeEndian;558559fn endian(&self) -> RunTimeEndian {560self.0.endian()561}562563fn len(&self) -> usize {564self.0.len()565}566567fn write(&mut self, buf: &[u8]) -> Result<(), Error> {568self.0.write(buf)569}570571fn write_at(&mut self, pos: usize, buf: &[u8]) -> Result<(), Error> {572self.0.write_at(pos, buf)573}574575// FIXME(gimli-rs/gimli#576) this is the definition we want for576// `write_eh_pointer` but the default implementation, at the time577// of this writing, uses `offset - val` instead of `val - offset`.578// A PR has been merged to fix this but until that's published we579// can't use it.580fn write_eh_pointer(581&mut self,582address: Address,583eh_pe: constants::DwEhPe,584size: u8,585) -> Result<(), Error> {586let val = match address {587Address::Constant(val) => val,588Address::Symbol { .. } => unreachable!(),589};590assert_eq!(eh_pe.application(), constants::DW_EH_PE_pcrel);591let offset = self.len() as u64;592let val = val.wrapping_sub(offset);593self.write_eh_pointer_data(val, eh_pe.format(), size)594}595}596}597}598599600