Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions crates/environ/src/compile/module_artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,32 @@ impl<'a> ObjectBuilder<'a> {
dwarf.push((T::id() as u8, offset..offset + data.len() as u64));
}

/// Appends the original Wasm bytecode for one or more core modules as a
/// pair of new ELF sections.
///
/// `modules` is an iterator of raw Wasm binary slices, one per core
/// module, in `StaticModuleIndex` order.
pub fn append_wasm_bytecode<'b>(&mut self, modules: impl IntoIterator<Item = &'b [u8]>) {
let bytecode_id = self.obj.add_section(
self.obj.segment_name(StandardSegment::Data).to_vec(),
obj::ELF_WASMTIME_WASM_BYTECODE.as_bytes().to_vec(),
SectionKind::ReadOnlyData,
);
let ends_id = self.obj.add_section(
self.obj.segment_name(StandardSegment::Data).to_vec(),
obj::ELF_WASMTIME_WASM_BYTECODE_ENDS.as_bytes().to_vec(),
SectionKind::ReadOnlyData,
);
let mut end: u32 = 0;
for wasm in modules {
self.obj.append_section_data(bytecode_id, wasm, 1);
end = end
.checked_add(u32::try_from(wasm.len()).expect("module bytecode exceeds 4 GiB"))
.expect("total bytecode exceeds 4 GiB");
self.obj.append_section_data(ends_id, &end.to_le_bytes(), 4);
}
Comment thread
cfallin marked this conversation as resolved.
}

/// Creates the `ELF_WASMTIME_INFO` section from the given serializable data
/// structure.
pub fn serialize_info<T>(&mut self, info: &T)
Expand Down
18 changes: 18 additions & 0 deletions crates/environ/src/obj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,24 @@ pub const ELF_NAME_DATA: &'static str = ".name.wasm";
/// metadata.
pub const ELF_WASMTIME_DWARF: &str = ".wasmtime.dwarf";

/// This is the name of the section in the final ELF image which contains the
/// original Wasm bytecode for the module, preserved verbatim to support
/// debugger access to the source bytecode.
///
/// This section is only emitted when the `guest-debug` tunable is enabled at
/// compile time. Its contents are the concatenated raw bytes of all core
/// module Wasm binaries in the artifact.
pub const ELF_WASMTIME_WASM_BYTECODE: &str = ".wasmtime.wasm_bytecode";

/// This is the name of the companion section to [`ELF_WASMTIME_WASM_BYTECODE`]
/// that stores the end-offset table used to locate individual module bytecodes
/// within the concatenated data.
///
/// The section contains one little-endian `u32` per core module in
/// the artifact giving the *end* of that module's bytecode in the
/// concatenated bytecode section above.
pub const ELF_WASMTIME_WASM_BYTECODE_ENDS: &str = ".wasmtime.wasm_bytecode_ends";

/// Workaround to implement `core::error::Error` until
/// gimli-rs/object#747 is settled.
pub struct ObjectCrateErrorWrapper(pub object::Error);
Expand Down
19 changes: 19 additions & 0 deletions crates/wasmtime/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ pub(crate) fn build_module_artifacts<T: FinishedObject>(
dwarf_package,
)?;

if tunables.debug_guest {
object.append_wasm_bytecode(std::iter::once(wasm));
}

let (info, index) = compilation_artifacts.unwrap_as_module_info();
let types = types.finish();
object.serialize_info(&(&info, &index, &types));
Expand Down Expand Up @@ -181,6 +185,16 @@ pub(crate) fn build_component_artifacts<T: FinishedObject>(
t.module.needs_gc_heap |= needs_gc_heap
}

// Collect bytecode slices here before moving `module_translations` below.
let module_wasms = if tunables.debug_guest {
module_translations
.values()
.map(|t| t.wasm)
.collect::<Vec<_>>()
} else {
vec![]
};

let mut object = compiler.object(ObjectKind::Component)?;
engine.append_compiler_info(&mut object)?;
engine.append_bti(&mut object);
Expand All @@ -192,6 +206,11 @@ pub(crate) fn build_component_artifacts<T: FinishedObject>(
module_translations,
None, // TODO: Support dwarf packages for components.
)?;

if tunables.debug_guest {
object.append_wasm_bytecode(module_wasms);
}

let (types, ty) = types.finish(&component.component);

let info = CompiledComponentInfo {
Expand Down
8 changes: 8 additions & 0 deletions crates/wasmtime/src/runtime/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use alloc::sync::Arc;
use core::ops::{Add, Range, Sub};
use wasmtime_environ::DefinedFuncIndex;
use wasmtime_environ::ModuleTypes;
use wasmtime_environ::StaticModuleIndex;
#[cfg(feature = "component-model")]
use wasmtime_environ::component::ComponentTypes;

Expand Down Expand Up @@ -227,6 +228,13 @@ impl EngineCode {
self.original_code.wasm_dwarf()
}

/// Returns the original Wasm bytecode section if preserved in the
/// compiled artifact.
#[inline]
pub fn wasm_bytecode_for_module(&self, module: StaticModuleIndex) -> Option<&[u8]> {
self.original_code.wasm_bytecode_for_module(module)
}

/// Returns the raw image as bytes (in our internal image format).
pub fn image(&self) -> &[u8] {
&self.original_code.mmap()[..]
Expand Down
52 changes: 51 additions & 1 deletion crates/wasmtime/src/runtime/code_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ use crate::prelude::*;
use crate::runtime::vm::MmapVec;
use alloc::sync::Arc;
use core::ops::Range;
use object::SectionIndex;
use object::read::elf::SectionTable;
use object::{LittleEndian, SectionIndex, U32Bytes};
use object::{
elf::{FileHeader64, SectionHeader64},
endian::Endianness,
read::elf::{FileHeader as _, SectionHeader as _},
};
use wasmtime_environ::StaticModuleIndex;
use wasmtime_environ::{Trap, lookup_trap_code, obj};
use wasmtime_unwinder::ExceptionTable;

Expand Down Expand Up @@ -45,6 +46,8 @@ pub struct CodeMemory {
func_name_data: Range<usize>,
info_data: Range<usize>,
wasm_dwarf: Range<usize>,
wasm_bytecode: Range<usize>,
wasm_bytecode_ends: Range<usize>,
}

impl Drop for CodeMemory {
Expand Down Expand Up @@ -153,6 +156,8 @@ impl CodeMemory {
let mut func_name_data = 0..0;
let mut info_data = 0..0;
let mut wasm_dwarf = 0..0;
let mut wasm_bytecode = 0..0;
let mut wasm_bytecode_ends = 0..0;
for section_header in sections.iter() {
let data = section_header
.data(endian, mmap_data)
Expand Down Expand Up @@ -212,6 +217,8 @@ impl CodeMemory {
obj::ELF_NAME_DATA => func_name_data = range,
obj::ELF_WASMTIME_INFO => info_data = range,
obj::ELF_WASMTIME_DWARF => wasm_dwarf = range,
obj::ELF_WASMTIME_WASM_BYTECODE => wasm_bytecode = range,
obj::ELF_WASMTIME_WASM_BYTECODE_ENDS => wasm_bytecode_ends = range,

#[cfg(feature = "debug-builtins")]
".debug_info" => has_native_debug_info = true,
Expand Down Expand Up @@ -269,6 +276,8 @@ impl CodeMemory {
wasm_dwarf,
info_data,
wasm_data,
wasm_bytecode,
wasm_bytecode_ends,
})
}

Expand Down Expand Up @@ -332,6 +341,17 @@ impl CodeMemory {
&self.mmap[self.frame_tables_data.clone()]
}

/// Returns the concatenated Wasm bytecode section, or an empty slice if
/// the artifact was not compiled with `guest-debug` enabled.
pub fn wasm_bytecode(&self) -> &[u8] {
&self.mmap[self.wasm_bytecode.clone()]
}

/// Returns the Wasm bytecode section end-offset array.
pub fn wasm_bytecode_ends(&self) -> &[u8] {
&self.mmap[self.wasm_bytecode_ends.clone()]
}

/// Returns the contents of the `ELF_WASMTIME_INFO` section, or an empty
/// slice if it wasn't found.
#[inline]
Expand All @@ -346,6 +366,36 @@ impl CodeMemory {
&self.mmap[self.trap_data.clone()]
}

/// Returns the Wasm bytecode section end-offset for a given core
/// module, or `None` if no bytecode is present.
///
/// # Panics
///
/// Panics if index is out-of-range.
fn wasm_bytecode_end_for_module(&self, index: StaticModuleIndex) -> Option<usize> {
if self.wasm_bytecode_ends().is_empty() {
return None;
}
let ends = self.wasm_bytecode_ends();
let count = ends.len() / core::mem::size_of::<u32>();
let (ends, _) = object::slice_from_bytes::<U32Bytes<LittleEndian>>(ends, count)
.expect("Invalid alignment of `ends` section");
let index = usize::try_from(index.as_u32()).unwrap();
Some(usize::try_from(ends[index].get(LittleEndian)).unwrap())
}

/// Returns the Wasm bytecode for the a core module in this
/// artifact, or `None` if bytecode was not preserved.
pub(crate) fn wasm_bytecode_for_module(&self, index: StaticModuleIndex) -> Option<&[u8]> {
let start = if index.as_u32() == 0 {
0
} else {
self.wasm_bytecode_end_for_module(StaticModuleIndex::from_u32(index.as_u32() - 1))?
};
let end = self.wasm_bytecode_end_for_module(index)?;
Some(&self.wasm_bytecode()[start..end])
}

/// Publishes the internal ELF image to be ready for execution.
///
/// This method can only be when the image is not published (its
Expand Down
6 changes: 6 additions & 0 deletions crates/wasmtime/src/runtime/instantiate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,12 @@ impl CompiledModule {
pub fn has_address_map(&self) -> bool {
!self.engine_code.address_map_data().is_empty()
}

/// Returns the original Wasm bytecode for this module, if it is available.
pub fn bytecode(&self) -> Option<&[u8]> {
self.engine_code
.wasm_bytecode_for_module(self.module.module_index)
}
}

#[cfg(feature = "addr2line")]
Expand Down
12 changes: 12 additions & 0 deletions crates/wasmtime/src/runtime/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,18 @@ impl Module {
Some(&module.strings[name])
}

/// Returns the original Wasm bytecode for this module, if it is
/// available.
///
/// Bytecode is only retained when the [`Engine`] was configured with
/// `guest-debug` support enabled (see [`Config::guest_debug`]). Returns
/// `None` when the module was compiled without that option.
///
/// [`Config::guest_debug`]: crate::Config::guest_debug
pub fn debug_bytecode(&self) -> Option<&[u8]> {
self.compiled_module().bytecode()
}

/// Returns the list of imports that this [`Module`] has and must be
/// satisfied.
///
Expand Down
84 changes: 84 additions & 0 deletions tests/all/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1116,3 +1116,87 @@ async fn invalidated_frame_handles_in_dropped_future() -> wasmtime::Result<()> {

Ok(())
}

#[test]
#[cfg_attr(miri, ignore)]
fn module_bytecode() -> wasmtime::Result<()> {
let wasm = wat::parse_str(
r#"
(module
(func (export "add") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
)
)
"#,
)
.unwrap();

let mut config = Config::default();
config.guest_debug(true);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, &wasm)?;

assert_eq!(module.debug_bytecode(), Some(&wasm[..]));

Ok(())
}

#[test]
#[cfg_attr(miri, ignore)]
fn module_bytecode_absent_without_debug() -> wasmtime::Result<()> {
let wasm = wat::parse_str("(module)").unwrap();

let mut config = Config::default();
config.guest_debug(false);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, &wasm)?;

assert_eq!(module.debug_bytecode(), None);

Ok(())
}

#[test]
#[cfg_attr(miri, ignore)]
fn component_bytecode() -> wasmtime::Result<()> {
use wasmtime::component::{Component, Linker};

// Build the bytecode for each core module by compiling them
// standalone.
let m1_body = r#"(func (export "f1") (result i32) i32.const 42)"#;
let m2_body = r#"(func (export "f2") (result i32) i32.const 99)"#;
let m1_wasm = wat::parse_str(&format!("(module $m1 {m1_body})")).unwrap();
let m2_wasm = wat::parse_str(&format!("(module $m2 {m2_body})")).unwrap();

// Build a component that embeds both core modules inline.
let component_wasm = wat::parse_str(&format!(
r#"(component
(core module $m1 {m1_body})
(core instance $i1 (instantiate (module $m1)))
(core module $m2 {m2_body})
(core instance $i2 (instantiate (module $m2))))
"#,
))
.unwrap();

let mut config = Config::default();
config.guest_debug(true);
let engine = Engine::new(&config)?;

let component = Component::new(&engine, &component_wasm)?;
let linker: Linker<()> = Linker::new(&engine);
let mut store = Store::new(&engine, ());
linker.instantiate(&mut store, &component)?;

let modules = store.debug_all_modules();
assert_eq!(modules.len(), 2);

// Modules should be registered in offset order. The API doesn't
// guarantee this, but this suffices for a test.
assert_eq!(modules[0].debug_bytecode().unwrap(), &m1_wasm[..]);
assert_eq!(modules[1].debug_bytecode().unwrap(), &m2_wasm[..]);

Ok(())
}