Skip to content

Commit 1a9339e

Browse files
authored
Use low-level ELF-parsing APIs in CodeMemory::new (#12555)
* Use low-level ELF-parsing APIs in `CodeMemory::new` These avoid allocations and should generally be faster because they avoid unnecessary work. * review feedback * fix compilation on certain cfgs * silence warning on build cfg * .pdata is only used as the unwind section on windows, which is covered by a different match arm * cargo fmt
1 parent fcc888a commit 1a9339e

1 file changed

Lines changed: 112 additions & 30 deletions

File tree

crates/wasmtime/src/runtime/code_memory.rs

Lines changed: 112 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ use crate::prelude::*;
55
use crate::runtime::vm::MmapVec;
66
use alloc::sync::Arc;
77
use core::ops::Range;
8-
use object::SectionFlags;
9-
use object::endian::Endianness;
10-
use object::read::{Object, ObjectSection, elf::ElfFile64};
8+
use object::SectionIndex;
9+
use object::read::elf::SectionTable;
10+
use object::{
11+
elf::{FileHeader64, SectionHeader64},
12+
endian::Endianness,
13+
read::elf::{FileHeader as _, SectionHeader as _},
14+
};
1115
use wasmtime_environ::{Trap, lookup_trap_code, obj};
1216
use wasmtime_unwinder::ExceptionTable;
1317

@@ -116,9 +120,23 @@ impl CodeMemory {
116120
/// The returned `CodeMemory` manages the internal `MmapVec` and the
117121
/// `publish` method is used to actually make the memory executable.
118122
pub fn new(engine: &Engine, mmap: MmapVec) -> Result<Self> {
119-
let obj = ElfFile64::<Endianness>::parse(&mmap[..])
123+
let mmap_data = &*mmap;
124+
let header = FileHeader64::<Endianness>::parse(mmap_data)
120125
.map_err(obj::ObjectCrateErrorWrapper)
121-
.with_context(|| "failed to parse internal compilation artifact")?;
126+
.context("failed to parse precompiled artifact as an ELF")?;
127+
let endian = header
128+
.endian()
129+
.context("failed to parse header endianness")?;
130+
131+
let section_headers = header
132+
.section_headers(endian, mmap_data)
133+
.context("failed to parse section headers")?;
134+
let strings = header
135+
.section_strings(endian, mmap_data, section_headers)
136+
.context("failed to parse strings table")?;
137+
let sections = header
138+
.sections(endian, mmap_data)
139+
.context("failed to parse sections table")?;
122140

123141
let mut text = 0..0;
124142
let mut unwind = 0..0;
@@ -135,42 +153,53 @@ impl CodeMemory {
135153
let mut func_name_data = 0..0;
136154
let mut info_data = 0..0;
137155
let mut wasm_dwarf = 0..0;
138-
for section in obj.sections() {
139-
let data = section.data().map_err(obj::ObjectCrateErrorWrapper)?;
140-
let name = section.name().map_err(obj::ObjectCrateErrorWrapper)?;
156+
for section_header in sections.iter() {
157+
let data = section_header
158+
.data(endian, mmap_data)
159+
.map_err(obj::ObjectCrateErrorWrapper)?;
160+
let name = section_name(endian, strings, section_header)?;
141161
let range = subslice_range(data, &mmap);
142162

143163
// Double-check that sections are all aligned properly.
144-
if section.align() != 0 && data.len() != 0 {
145-
if (data.as_ptr() as u64 - mmap.as_ptr() as u64) % section.align() != 0 {
146-
bail!(
147-
"section `{}` isn't aligned to {:#x}",
148-
section.name().unwrap_or("ERROR"),
149-
section.align()
150-
);
151-
}
164+
let section_align = usize::try_from(section_header.sh_addralign(endian))?;
165+
if section_align != 0 && data.len() != 0 {
166+
let section_offset = data.as_ptr().addr() - mmap.as_ptr().addr();
167+
ensure!(
168+
section_offset % section_align == 0,
169+
"section {name:?} isn't aligned to {section_align:#x}",
170+
);
171+
}
172+
173+
// Check that we don't have any relocations, which would make
174+
// loading precompiled Wasm modules slower and also force them to
175+
// get paged into memory from disk.
176+
//
177+
// We avoid using things like Cranelift's `floor`, `ceil`,
178+
// etc... operators in the Wasm-to-CLIF translator specifically to
179+
// avoid having to do any relocations here. This also ensures that
180+
// all builtins use the same trampoline mechanism.
181+
//
182+
// We do, however, allow relocations in `.debug_*` DWARF sections.
183+
if let Some(target_section) = reloc_section_target(&sections, section_header, endian)? {
184+
let target_name = section_name(endian, strings, target_section)?;
185+
ensure!(
186+
target_name.starts_with(".debug_"),
187+
"section {target_name:?} has unexpected relocations \
188+
(defined in section {name:?})",
189+
);
152190
}
153191

154192
match name {
155193
obj::ELF_WASM_BTI => match data.len() {
156194
1 => enable_branch_protection = Some(data[0] != 0),
157-
_ => bail!("invalid `{name}` section"),
195+
_ => bail!("invalid {name:?} section"),
158196
},
159197
".text" => {
160198
text = range;
161199

162-
if let SectionFlags::Elf { sh_flags } = section.flags() {
163-
if sh_flags & obj::SH_WASMTIME_NOT_EXECUTED != 0 {
164-
needs_executable = false;
165-
}
200+
if section_header.sh_flags(endian) & obj::SH_WASMTIME_NOT_EXECUTED != 0 {
201+
needs_executable = false;
166202
}
167-
168-
// Assert that Cranelift hasn't inserted any calls that need to be
169-
// relocated. We avoid using things like Cranelift's floor/ceil/etc.
170-
// operators in the Wasm-to-Cranelift translator specifically to
171-
// avoid having to do any relocations here. This also ensures that
172-
// all builtins use the same trampoline mechanism.
173-
assert!(section.relocations().next().is_none());
174203
}
175204
#[cfg(has_host_compiler_backend)]
176205
crate::runtime::vm::UnwindRegistration::SECTION_NAME => unwind = range,
@@ -183,14 +212,24 @@ impl CodeMemory {
183212
obj::ELF_NAME_DATA => func_name_data = range,
184213
obj::ELF_WASMTIME_INFO => info_data = range,
185214
obj::ELF_WASMTIME_DWARF => wasm_dwarf = range,
215+
186216
#[cfg(feature = "debug-builtins")]
187217
".debug_info" => has_native_debug_info = true,
188218

189-
_ => log::debug!("ignoring section {name}"),
219+
// These sections are expected, but we do not need to retain any
220+
// info about them.
221+
"" | ".symtab" | ".strtab" | ".shstrtab" | ".xdata" | obj::ELF_WASM_ENGINE => {
222+
log::debug!("ignoring section {name:?}")
223+
}
224+
_ if name.starts_with(".debug_") || name.starts_with(".rela.debug_") => {
225+
log::debug!("ignoring debug section {name:?}")
226+
}
227+
228+
_ => bail!("unexpected section {name:?} in Wasm compilation artifact"),
190229
}
191230
}
192231

193-
// require mutability even when this is turned off
232+
// Silence unused `mut` warning.
194233
#[cfg(not(has_host_compiler_backend))]
195234
let _ = &mut unwind;
196235

@@ -546,6 +585,49 @@ impl CodeMemory {
546585
}
547586
}
548587

588+
fn section_name<'a>(
589+
endian: Endianness,
590+
strings: object::StringTable<'a>,
591+
section_header: &SectionHeader64<Endianness>,
592+
) -> Result<&'a str> {
593+
let name = section_header
594+
.name(endian, strings)
595+
.map_err(obj::ObjectCrateErrorWrapper)?;
596+
Ok(str::from_utf8(name).context("invalid section name in Wasm compilation artifact")?)
597+
}
598+
599+
fn is_reloc_section(section_header: &SectionHeader64<Endianness>, endian: Endianness) -> bool {
600+
let sh_type = section_header.sh_type(endian);
601+
matches!(
602+
sh_type,
603+
object::elf::SHT_REL | object::elf::SHT_RELA | object::elf::SHT_CREL
604+
)
605+
}
606+
607+
fn reloc_section_target<'a>(
608+
sections: &'a SectionTable<'a, FileHeader64<Endianness>, &'a [u8]>,
609+
section: &'a SectionHeader64<Endianness>,
610+
endian: Endianness,
611+
) -> Result<Option<&'a SectionHeader64<Endianness>>> {
612+
if !is_reloc_section(&section, endian) {
613+
return Ok(None);
614+
}
615+
616+
let sh_info = section.info_link(endian);
617+
618+
// Dynamic relocation.
619+
if sh_info == SectionIndex(0) {
620+
return Ok(None);
621+
}
622+
623+
ensure!(
624+
sh_info.0 < sections.len(),
625+
"invalid ELF `sh_info` for relocation section",
626+
);
627+
628+
Ok(Some(sections.section(sh_info)?))
629+
}
630+
549631
/// Returns the range of `inner` within `outer`, such that `outer[range]` is the
550632
/// same as `inner`.
551633
///

0 commit comments

Comments
 (0)