diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index 4262f5f8730a..6b3390b96281 100644 --- a/crates/cranelift/src/compiler.rs +++ b/crates/cranelift/src/compiler.rs @@ -459,7 +459,11 @@ impl wasmtime_environ::Compiler for Compiler { ); let memory_offset = if ofs.num_imported_memories > 0 { - ModuleMemoryOffset::Imported(ofs.vmctx_vmmemory_import(MemoryIndex::new(0))) + ModuleMemoryOffset::Imported { + offset_to_vm_memory_definition: ofs.vmctx_vmmemory_import(MemoryIndex::new(0)) + + u32::from(ofs.vmmemory_import_from()), + offset_to_memory_base: ofs.ptr.vmmemory_definition_base().into(), + } } else if ofs.num_defined_memories > 0 { // The addition of shared memory makes the following assumption, // "owned memory index = 0", possibly false. If the first memory diff --git a/crates/cranelift/src/debug.rs b/crates/cranelift/src/debug.rs index ca92d77b6cf8..083a3ba4745e 100644 --- a/crates/cranelift/src/debug.rs +++ b/crates/cranelift/src/debug.rs @@ -7,8 +7,15 @@ pub enum ModuleMemoryOffset { None, /// Offset to the defined memory. Defined(u32), - /// Offset to the imported memory. - Imported(#[allow(dead_code)] u32), + /// This memory is imported. + Imported { + /// Offset, in bytes, to the `*mut VMMemoryDefinition` structure within + /// `VMContext`. + offset_to_vm_memory_definition: u32, + /// Offset, in bytes within `VMMemoryDefinition` where the `base` field + /// lies. + offset_to_memory_base: u32, + }, } pub use write_debuginfo::{emit_dwarf, DwarfSectionRelocTarget}; diff --git a/crates/cranelift/src/debug/transform/expression.rs b/crates/cranelift/src/debug/transform/expression.rs index f78b3b745e6f..17c560906d3b 100644 --- a/crates/cranelift/src/debug/transform/expression.rs +++ b/crates/cranelift/src/debug/transform/expression.rs @@ -21,33 +21,25 @@ pub struct FunctionFrameInfo<'a> { pub memory_offset: ModuleMemoryOffset, } -impl<'a> FunctionFrameInfo<'a> { - fn vmctx_memory_offset(&self) -> Option { - match self.memory_offset { - ModuleMemoryOffset::Defined(x) => Some(x as i64), - ModuleMemoryOffset::Imported(_) => { - // TODO implement memory offset for imported memory - None - } - ModuleMemoryOffset::None => None, - } - } -} - struct ExpressionWriter(write::EndianVec); +enum VmctxBase { + Reg(u16), + OnStack, +} + impl ExpressionWriter { - pub fn new() -> Self { + fn new() -> Self { let endian = gimli::RunTimeEndian::Little; let writer = write::EndianVec::new(endian); ExpressionWriter(writer) } - pub fn write_op(&mut self, op: gimli::DwOp) -> write::Result<()> { + fn write_op(&mut self, op: gimli::DwOp) -> write::Result<()> { self.write_u8(op.0 as u8) } - pub fn write_op_reg(&mut self, reg: u16) -> write::Result<()> { + fn write_op_reg(&mut self, reg: u16) -> write::Result<()> { if reg < 32 { self.write_u8(gimli::constants::DW_OP_reg0.0 as u8 + reg as u8) } else { @@ -56,7 +48,7 @@ impl ExpressionWriter { } } - pub fn write_op_breg(&mut self, reg: u16) -> write::Result<()> { + fn write_op_breg(&mut self, reg: u16) -> write::Result<()> { if reg < 32 { self.write_u8(gimli::constants::DW_OP_breg0.0 as u8 + reg as u8) } else { @@ -65,25 +57,71 @@ impl ExpressionWriter { } } - pub fn write_u8(&mut self, b: u8) -> write::Result<()> { + fn write_u8(&mut self, b: u8) -> write::Result<()> { write::Writer::write_u8(&mut self.0, b) } - pub fn write_u32(&mut self, b: u32) -> write::Result<()> { + fn write_u32(&mut self, b: u32) -> write::Result<()> { write::Writer::write_u32(&mut self.0, b) } - pub fn write_uleb128(&mut self, i: u64) -> write::Result<()> { + fn write_uleb128(&mut self, i: u64) -> write::Result<()> { write::Writer::write_uleb128(&mut self.0, i) } - pub fn write_sleb128(&mut self, i: i64) -> write::Result<()> { + fn write_sleb128(&mut self, i: i64) -> write::Result<()> { write::Writer::write_sleb128(&mut self.0, i) } - pub fn into_vec(self) -> Vec { + fn into_vec(self) -> Vec { self.0.into_vec() } + + fn gen_address_of_memory_base_pointer( + &mut self, + vmctx: VmctxBase, + memory_base: &ModuleMemoryOffset, + ) -> write::Result<()> { + match *memory_base { + ModuleMemoryOffset::Defined(offset) => match vmctx { + VmctxBase::Reg(reg) => { + self.write_op_breg(reg)?; + self.write_sleb128(offset.into())?; + } + VmctxBase::OnStack => { + self.write_op(gimli::constants::DW_OP_consts)?; + self.write_sleb128(offset.into())?; + self.write_op(gimli::constants::DW_OP_plus)?; + } + }, + ModuleMemoryOffset::Imported { + offset_to_vm_memory_definition, + offset_to_memory_base, + } => { + match vmctx { + VmctxBase::Reg(reg) => { + self.write_op_breg(reg)?; + self.write_sleb128(offset_to_vm_memory_definition.into())?; + } + VmctxBase::OnStack => { + if offset_to_vm_memory_definition > 0 { + self.write_op(gimli::constants::DW_OP_consts)?; + self.write_sleb128(offset_to_vm_memory_definition.into())?; + } + self.write_op(gimli::constants::DW_OP_plus)?; + } + } + self.write_op(gimli::constants::DW_OP_deref)?; + if offset_to_memory_base > 0 { + self.write_op(gimli::constants::DW_OP_consts)?; + self.write_sleb128(offset_to_memory_base.into())?; + self.write_op(gimli::constants::DW_OP_plus)?; + } + } + ModuleMemoryOffset::None => return Err(write::Error::InvalidAttributeValue), + } + Ok(()) + } } #[derive(Debug, Clone, PartialEq)] @@ -166,34 +204,16 @@ fn append_memory_deref( isa: &dyn TargetIsa, ) -> Result { let mut writer = ExpressionWriter::new(); - // FIXME for imported memory - match vmctx_loc { - LabelValueLoc::Reg(r) => { - let reg = isa.map_regalloc_reg_to_dwarf(r)?; - writer.write_op_breg(reg)?; - let memory_offset = match frame_info.vmctx_memory_offset() { - Some(offset) => offset, - None => { - return Ok(false); - } - }; - writer.write_sleb128(memory_offset)?; - } + let vmctx_base = match vmctx_loc { + LabelValueLoc::Reg(r) => VmctxBase::Reg(isa.map_regalloc_reg_to_dwarf(r)?), LabelValueLoc::CFAOffset(off) => { writer.write_op(gimli::constants::DW_OP_fbreg)?; writer.write_sleb128(off)?; writer.write_op(gimli::constants::DW_OP_deref)?; - writer.write_op(gimli::constants::DW_OP_consts)?; - let memory_offset = match frame_info.vmctx_memory_offset() { - Some(offset) => offset, - None => { - return Ok(false); - } - }; - writer.write_sleb128(memory_offset)?; - writer.write_op(gimli::constants::DW_OP_plus)?; + VmctxBase::OnStack } - } + }; + writer.gen_address_of_memory_base_pointer(vmctx_base, &frame_info.memory_offset)?; writer.write_op(gimli::constants::DW_OP_deref)?; writer.write_op(gimli::constants::DW_OP_swap)?; writer.write_op(gimli::constants::DW_OP_const4u)?; diff --git a/crates/cranelift/src/debug/transform/utils.rs b/crates/cranelift/src/debug/transform/utils.rs index 7ef6f17f8f69..951833bf4f0d 100644 --- a/crates/cranelift/src/debug/transform/utils.rs +++ b/crates/cranelift/src/debug/transform/utils.rs @@ -92,7 +92,7 @@ pub(crate) fn add_internal_types( gimli::DW_AT_data_member_location = write::AttributeValue::Udata(memory_offset as u64) }); } - ModuleMemoryOffset::Imported(_) => { + ModuleMemoryOffset::Imported { .. } => { // TODO implement convenience pointer to and additional types for VMMemoryImport. } ModuleMemoryOffset::None => (), diff --git a/crates/test-programs/artifacts/build.rs b/crates/test-programs/artifacts/build.rs index a515415b604c..0fa76093ee80 100644 --- a/crates/test-programs/artifacts/build.rs +++ b/crates/test-programs/artifacts/build.rs @@ -33,7 +33,7 @@ fn build_and_generate_tests() { .arg("--target=wasm32-wasi") .arg("--package=test-programs") .env("CARGO_TARGET_DIR", &out_dir) - .env("CARGO_PROFILE_DEV_DEBUG", "1") + .env("CARGO_PROFILE_DEV_DEBUG", "2") .env("RUSTFLAGS", rustflags()) .env_remove("CARGO_ENCODED_RUSTFLAGS"); eprintln!("running: {cmd:?}"); @@ -75,6 +75,7 @@ fn build_and_generate_tests() { s if s.starts_with("api_") => "api", s if s.starts_with("nn_") => "nn", s if s.starts_with("piped_") => "piped", + s if s.starts_with("dwarf_") => "dwarf", // If you're reading this because you hit this panic, either add it // to a test suite above or add a new "suite". The purpose of the // categorization above is to have a static assertion that tests @@ -89,7 +90,7 @@ fn build_and_generate_tests() { } // Generate a component from each test. - if kind == "nn" { + if kind == "nn" || target == "dwarf_imported_memory" { continue; } let adapter = match target.as_str() { diff --git a/crates/test-programs/build.rs b/crates/test-programs/build.rs new file mode 100644 index 000000000000..734cbddbb488 --- /dev/null +++ b/crates/test-programs/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rustc-link-arg-bin=dwarf_imported_memory=--import-memory"); + println!("cargo:rustc-link-arg-bin=dwarf_imported_memory=--export-memory"); +} diff --git a/crates/test-programs/src/bin/dwarf_imported_memory.rs b/crates/test-programs/src/bin/dwarf_imported_memory.rs new file mode 100644 index 000000000000..fe4ca3784c2d --- /dev/null +++ b/crates/test-programs/src/bin/dwarf_imported_memory.rs @@ -0,0 +1 @@ +include!("./dwarf_simple.rs"); diff --git a/crates/test-programs/src/bin/dwarf_simple.rs b/crates/test-programs/src/bin/dwarf_simple.rs new file mode 100644 index 000000000000..ce79fa54e58f --- /dev/null +++ b/crates/test-programs/src/bin/dwarf_simple.rs @@ -0,0 +1,6 @@ +fn main() { + let mut a = 100; + a += 10; + let b = a + 7; + println!("{b}"); +} diff --git a/tests/all/debug/lldb.rs b/tests/all/debug/lldb.rs index b52a6f6aa358..d0c653e0669f 100644 --- a/tests/all/debug/lldb.rs +++ b/tests/all/debug/lldb.rs @@ -34,14 +34,18 @@ fn lldb_with_script(args: &[&str], script: &str) -> Result { cmd.args(args); let output = cmd.output().expect("success"); + let stdout = String::from_utf8(output.stdout)?; + let stderr = String::from_utf8(output.stderr)?; if !output.status.success() { bail!( - "failed to execute {:?}: {}", - cmd, - String::from_utf8_lossy(&output.stderr), + "failed to execute {cmd:?}:\n\ + --- stderr ---\n\ + {stderr}\n\ + --- stdout ---\n\ + {stdout}", ); } - Ok(String::from_utf8(output.stdout)?) + Ok(stdout) } fn check_lldb_output(output: &str, directives: &str) -> Result<()> { @@ -166,7 +170,7 @@ check: Breakpoint 1: no locations (pending) check: stop reason = breakpoint 1.1 check: frame #0 sameln: norm(n=(__ptr = -check: = 27 +check: 27 check: resuming "#, )?; @@ -295,3 +299,75 @@ check: exited with status = 0 )?; Ok(()) } + +#[cfg(all( + any(target_os = "linux", target_os = "macos"), + target_pointer_width = "64" +))] +mod test_programs { + use super::{check_lldb_output, lldb_with_script}; + use anyhow::Result; + use test_programs_artifacts::*; + + macro_rules! assert_test_exists { + ($name:ident) => { + #[allow(unused_imports)] + use self::$name as _; + }; + } + foreach_dwarf!(assert_test_exists); + + fn test_dwarf_simple(wasm: &str, extra_args: &[&str]) -> Result<()> { + println!("testing {wasm:?}"); + let mut args = vec!["-Ccache=n", "-Oopt-level=0", "-Ddebug-info"]; + args.extend(extra_args); + args.push(wasm); + let output = lldb_with_script( + &args, + r#" +breakpoint set --file dwarf_simple.rs --line 3 +breakpoint set --file dwarf_simple.rs --line 5 +r +fr v +c +fr v +c"#, + )?; + + check_lldb_output( + &output, + r#" +check: Breakpoint 1: no locations (pending) +check: Unable to resolve breakpoint to any actual locations. +check: 1 location added to breakpoint 1 +check: stop reason = breakpoint 1.1 +check: dwarf_simple.rs:3 +check: a = 100 +check: dwarf_simple.rs:5 +check: a = 110 +check: b = 117 +check: resuming +check: exited with status = 0 +"#, + )?; + Ok(()) + } + + #[test] + #[ignore] + fn dwarf_simple() -> Result<()> { + for wasm in [DWARF_SIMPLE] { + test_dwarf_simple(wasm, &[])?; + } + Ok(()) + } + + #[test] + #[ignore] + fn dwarf_imported_memory() -> Result<()> { + test_dwarf_simple( + DWARF_IMPORTED_MEMORY, + &["--preload=env=./tests/all/debug/satisfy_memory_import.wat"], + ) + } +} diff --git a/tests/all/debug/satisfy_memory_import.wat b/tests/all/debug/satisfy_memory_import.wat new file mode 100644 index 000000000000..ea6f28c5eaf5 --- /dev/null +++ b/tests/all/debug/satisfy_memory_import.wat @@ -0,0 +1,3 @@ +(module + (memory (export "memory") 100) +)