Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Utility methods for artificial debug types in the generated DWARF #1482

Merged
merged 12 commits into from
Apr 10, 2020
3 changes: 2 additions & 1 deletion crates/api/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use wasmtime_environ::CacheConfig;
use wasmtime_environ::Tunables;
use wasmtime_jit::{native, CompilationStrategy, Compiler};
use wasmtime_profiling::{JitDumpAgent, NullProfilerAgent, ProfilingAgent, VTuneAgent};
use wasmtime_runtime::RuntimeMemoryCreator;
use wasmtime_runtime::{debug_builtins, RuntimeMemoryCreator};

// Runtime Environment

Expand Down Expand Up @@ -465,6 +465,7 @@ impl Engine {
/// Creates a new [`Engine`] with the specified compilation and
/// configuration settings.
pub fn new(config: &Config) -> Engine {
debug_builtins::ensure_exported();
Engine {
config: Arc::new(config.clone()),
}
Expand Down
167 changes: 126 additions & 41 deletions crates/debug/src/transform/unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,23 @@ where
Ok(String::from("??"))
}

/// Replaces WebAssembly pointer type DIE with the wrapper
/// which natively represented by offset in a Wasm memory.
///
/// `pointer_type_entry` is an DW_TAG_pointer_type entry (e.g. `T*`),
/// which refers its base type (e.g. `T`).
///
/// The generated wrapper is a structure that contains only the
/// `__ptr` field. The utility operators overloads is added to
/// provide better debugging experience.
///
/// Notice that "resolve_vmctx_memory_ptr" is external/builtin
/// subprogram that is not part of Wasm code.
fn replace_pointer_type<R>(
parent_id: write::UnitEntryId,
comp_unit: &mut write::Unit,
wp_die_id: write::UnitEntryId,
entry: &DebuggingInformationEntry<R>,
pointer_type_entry: &DebuggingInformationEntry<R>,
unit: &Unit<R, R::Offset>,
context: &DebugInputContext<R>,
out_strings: &mut write::StringTable,
Expand All @@ -95,48 +107,121 @@ fn replace_pointer_type<R>(
where
R: Reader,
{
let die_id = comp_unit.add(parent_id, gimli::DW_TAG_structure_type);
let die = comp_unit.get_mut(die_id);

let name = format!(
"WebAssemblyPtrWrapper<{}>",
get_base_type_name(entry, unit, context)?
);
die.set(
gimli::DW_AT_name,
write::AttributeValue::StringRef(out_strings.add(name.as_str())),
);
die.set(gimli::DW_AT_byte_size, write::AttributeValue::Data1(4));

let p_die_id = comp_unit.add(die_id, gimli::DW_TAG_template_type_parameter);
let p_die = comp_unit.get_mut(p_die_id);
p_die.set(
gimli::DW_AT_name,
write::AttributeValue::StringRef(out_strings.add("T")),
);
p_die.set(
gimli::DW_AT_type,
write::AttributeValue::ThisUnitEntryRef(wp_die_id),
);
if let Some(AttributeValue::UnitRef(ref offset)) = entry.attr_value(gimli::DW_AT_type)? {
pending_die_refs.insert(p_die_id, gimli::DW_AT_type, *offset);
const WASM_PTR_LEN: u8 = 4;

macro_rules! add_tag {
($parent_id:ident, $tag:expr => $die:ident as $die_id:ident { $($a:path = $v:expr),* }) => {
let $die_id = comp_unit.add($parent_id, $tag);
#[allow(unused_variables)]
let $die = comp_unit.get_mut($die_id);
$( $die.set($a, $v); )*
};
}

// Build DW_TAG_structure_type for the wrapper:
// .. DW_AT_name = "WebAssemblyPtrWrapper<T>",
// .. DW_AT_byte_size = 4,
add_tag!(parent_id, gimli::DW_TAG_structure_type => wrapper_die as wrapper_die_id {
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add(format!(
"WebAssemblyPtrWrapper<{}>",
get_base_type_name(pointer_type_entry, unit, context)?
).as_str())),
gimli::DW_AT_byte_size = write::AttributeValue::Data1(WASM_PTR_LEN)
});

// Build DW_TAG_pointer_type for `WebAssemblyPtrWrapper<T>*`:
// .. DW_AT_type = <wrapper_die>
add_tag!(parent_id, gimli::DW_TAG_pointer_type => wrapper_ptr_type as wrapper_ptr_type_id {
gimli::DW_AT_type = write::AttributeValue::ThisUnitEntryRef(wrapper_die_id)
});

let base_type_id = pointer_type_entry.attr_value(gimli::DW_AT_type)?;
// Build DW_TAG_reference_type for `T&`:
// .. DW_AT_type = <base_type>
add_tag!(parent_id, gimli::DW_TAG_reference_type => ref_type as ref_type_id {});
if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
pending_die_refs.insert(ref_type_id, gimli::DW_AT_type, *offset);
}

// Build DW_TAG_pointer_type for `T*`:
// .. DW_AT_type = <base_type>
add_tag!(parent_id, gimli::DW_TAG_pointer_type => ptr_type as ptr_type_id {});
if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
pending_die_refs.insert(ptr_type_id, gimli::DW_AT_type, *offset);
}

// Build wrapper_die's DW_TAG_template_type_parameter:
// .. DW_AT_name = "T"
// .. DW_AT_type = <base_type>
add_tag!(wrapper_die_id, gimli::DW_TAG_template_type_parameter => t_param_die as t_param_die_id {
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("T"))
});
if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
pending_die_refs.insert(t_param_die_id, gimli::DW_AT_type, *offset);
}

let m_die_id = comp_unit.add(die_id, gimli::DW_TAG_member);
let m_die = comp_unit.get_mut(m_die_id);
m_die.set(
gimli::DW_AT_name,
write::AttributeValue::StringRef(out_strings.add("__ptr")),
);
m_die.set(
gimli::DW_AT_type,
write::AttributeValue::ThisUnitEntryRef(wp_die_id),
);
m_die.set(
gimli::DW_AT_data_member_location,
write::AttributeValue::Data1(0),
);
Ok(die_id)
// Build wrapper_die's DW_TAG_member for `__ptr`:
// .. DW_AT_name = "__ptr"
// .. DW_AT_type = <wp_die>
// .. DW_AT_location = 0
add_tag!(wrapper_die_id, gimli::DW_TAG_member => m_die as m_die_id {
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("__ptr")),
gimli::DW_AT_type = write::AttributeValue::ThisUnitEntryRef(wp_die_id),
gimli::DW_AT_data_member_location = write::AttributeValue::Data1(0)
});

// Build wrapper_die's DW_TAG_subprogram for `ptr()`:
// .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr"
// .. DW_AT_name = "ptr"
// .. DW_AT_type = <ptr_type>
// .. DW_TAG_formal_parameter
// .. .. DW_AT_type = <wrapper_ptr_type>
// .. .. DW_AT_artificial = 1
add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add("resolve_vmctx_memory_ptr")),
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("ptr")),
gimli::DW_AT_type = write::AttributeValue::ThisUnitEntryRef(ptr_type_id)
});
add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
gimli::DW_AT_type = write::AttributeValue::ThisUnitEntryRef(wrapper_ptr_type_id),
gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
});

// Build wrapper_die's DW_TAG_subprogram for `operator*`:
// .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr"
// .. DW_AT_name = "operator*"
// .. DW_AT_type = <ref_type>
// .. DW_TAG_formal_parameter
// .. .. DW_AT_type = <wrapper_ptr_type>
// .. .. DW_AT_artificial = 1
add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add("resolve_vmctx_memory_ptr")),
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("operator*")),
gimli::DW_AT_type = write::AttributeValue::ThisUnitEntryRef(ref_type_id)
});
add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
gimli::DW_AT_type = write::AttributeValue::ThisUnitEntryRef(wrapper_ptr_type_id),
gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
});

// Build wrapper_die's DW_TAG_subprogram for `operator->`:
// .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr"
// .. DW_AT_name = "operator->"
// .. DW_AT_type = <ptr_type>
// .. DW_TAG_formal_parameter
// .. .. DW_AT_type = <wrapper_ptr_type>
// .. .. DW_AT_artificial = 1
add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add("resolve_vmctx_memory_ptr")),
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("operator->")),
gimli::DW_AT_type = write::AttributeValue::ThisUnitEntryRef(ptr_type_id)
});
add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
gimli::DW_AT_type = write::AttributeValue::ThisUnitEntryRef(wrapper_ptr_type_id),
gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
});

Ok(wrapper_die_id)
}

pub(crate) fn clone_unit<'a, R>(
Expand Down
135 changes: 78 additions & 57 deletions crates/debug/src/transform/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,70 @@ use wasmtime_environ::isa::TargetIsa;
use wasmtime_environ::wasm::DefinedFuncIndex;
use wasmtime_environ::{ModuleMemoryOffset, ModuleVmctxInfo, ValueLabelsRanges};

/// Adds internal Wasm utility types DIEs such as WebAssemblyPtr and
/// WasmtimeVMContext.
///
/// For unwrapping Wasm pointer, the WasmtimeVMContext has the `set()` method
/// that allows to contol current Wasm memory to inspect.
/// Notice that "set_vmctx_memory" is an external/builtin subprogram that
/// is not part of Wasm code.
pub(crate) fn add_internal_types(
comp_unit: &mut write::Unit,
root_id: write::UnitEntryId,
out_strings: &mut write::StringTable,
module_info: &ModuleVmctxInfo,
) -> (write::UnitEntryId, write::UnitEntryId) {
let wp_die_id = comp_unit.add(root_id, gimli::DW_TAG_base_type);
let wp_die = comp_unit.get_mut(wp_die_id);
wp_die.set(
gimli::DW_AT_name,
write::AttributeValue::StringRef(out_strings.add("WebAssemblyPtr")),
);
wp_die.set(gimli::DW_AT_byte_size, write::AttributeValue::Data1(4));
wp_die.set(
gimli::DW_AT_encoding,
write::AttributeValue::Encoding(gimli::DW_ATE_unsigned),
);
const WASM_PTR_LEN: u8 = 4;

let memory_byte_die_id = comp_unit.add(root_id, gimli::DW_TAG_base_type);
let memory_byte_die = comp_unit.get_mut(memory_byte_die_id);
memory_byte_die.set(
gimli::DW_AT_name,
write::AttributeValue::StringRef(out_strings.add("u8")),
);
memory_byte_die.set(
gimli::DW_AT_encoding,
write::AttributeValue::Encoding(gimli::DW_ATE_unsigned),
);
memory_byte_die.set(gimli::DW_AT_byte_size, write::AttributeValue::Data1(1));
macro_rules! add_tag {
($parent_id:ident, $tag:expr => $die:ident as $die_id:ident { $($a:path = $v:expr),* }) => {
let $die_id = comp_unit.add($parent_id, $tag);
let $die = comp_unit.get_mut($die_id);
$( $die.set($a, $v); )*
};
}

let memory_bytes_die_id = comp_unit.add(root_id, gimli::DW_TAG_pointer_type);
let memory_bytes_die = comp_unit.get_mut(memory_bytes_die_id);
memory_bytes_die.set(
gimli::DW_AT_name,
write::AttributeValue::StringRef(out_strings.add("u8*")),
);
memory_bytes_die.set(
gimli::DW_AT_type,
write::AttributeValue::ThisUnitEntryRef(memory_byte_die_id),
);
// Build DW_TAG_base_type for generic `WebAssemblyPtr`.
// .. DW_AT_name = "WebAssemblyPtr"
// .. DW_AT_byte_size = 4
// .. DW_AT_encoding = DW_ATE_unsigned
// let wp_die_id = comp_unit.add(root_id, gimli::DW_TAG_base_type);
// let wp_die = comp_unit.get_mut(wp_die_id);
add_tag!(root_id, gimli::DW_TAG_base_type => wp_die as wp_die_id {
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("WebAssemblyPtr")),
gimli::DW_AT_byte_size = write::AttributeValue::Data1(WASM_PTR_LEN),
gimli::DW_AT_encoding = write::AttributeValue::Encoding(gimli::DW_ATE_unsigned)
});

// Build DW_TAG_base_type for Wasm byte:
// .. DW_AT_name = u8
// .. DW_AT_encoding = DW_ATE_unsigned
// .. DW_AT_byte_size = 1
add_tag!(root_id, gimli::DW_TAG_base_type => memory_byte_die as memory_byte_die_id {
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("u8")),
gimli::DW_AT_encoding = write::AttributeValue::Encoding(gimli::DW_ATE_unsigned),
gimli::DW_AT_byte_size = write::AttributeValue::Data1(1)
});

// Build DW_TAG_pointer_type that references Wasm bytes:
// .. DW_AT_name = "u8*"
// .. DW_AT_type = <memory_byte_die>
add_tag!(root_id, gimli::DW_TAG_pointer_type => memory_bytes_die as memory_bytes_die_id {
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("u8*")),
gimli::DW_AT_type = write::AttributeValue::ThisUnitEntryRef(memory_byte_die_id)
});

// Create artificial VMContext type and its reference for convinience viewing
// its fields (such as memory ref) in a debugger.
// its fields (such as memory ref) in a debugger. Build DW_TAG_structure_type:
// .. DW_AT_name = "WasmtimeVMContext"
let vmctx_die_id = comp_unit.add(root_id, gimli::DW_TAG_structure_type);
let vmctx_die = comp_unit.get_mut(vmctx_die_id);
vmctx_die.set(
gimli::DW_AT_name,
write::AttributeValue::StringRef(out_strings.add("WasmtimeVMContext")),
);

// TODO multiple memories
match module_info.memory_offset {
ModuleMemoryOffset::Defined(memory_offset) => {
// The context has defined memory: extend the WasmtimeVMContext size
Expand All @@ -67,37 +81,44 @@ pub(crate) fn add_internal_types(
);

// Define the "memory" field which is a direct pointer to allocated Wasm memory.
let m_die_id = comp_unit.add(vmctx_die_id, gimli::DW_TAG_member);
let m_die = comp_unit.get_mut(m_die_id);
m_die.set(
gimli::DW_AT_name,
write::AttributeValue::StringRef(out_strings.add("memory")),
);
m_die.set(
gimli::DW_AT_type,
write::AttributeValue::ThisUnitEntryRef(memory_bytes_die_id),
);
m_die.set(
gimli::DW_AT_data_member_location,
write::AttributeValue::Udata(memory_offset as u64),
);
// Build DW_TAG_member:
// .. DW_AT_name = "memory"
// .. DW_AT_type = <memory_bytes_die>
// .. DW_AT_data_member_location = `memory_offset`
add_tag!(vmctx_die_id, gimli::DW_TAG_member => m_die as m_die_id {
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("memory")),
gimli::DW_AT_type = write::AttributeValue::ThisUnitEntryRef(memory_bytes_die_id),
gimli::DW_AT_data_member_location = write::AttributeValue::Udata(memory_offset as u64)
});
}
ModuleMemoryOffset::Imported(_) => {
// TODO implement convinience pointer to and additional types for VMMemoryImport.
}
ModuleMemoryOffset::None => (),
}

let vmctx_ptr_die_id = comp_unit.add(root_id, gimli::DW_TAG_pointer_type);
let vmctx_ptr_die = comp_unit.get_mut(vmctx_ptr_die_id);
vmctx_ptr_die.set(
gimli::DW_AT_name,
write::AttributeValue::StringRef(out_strings.add("WasmtimeVMContext*")),
);
vmctx_ptr_die.set(
gimli::DW_AT_type,
write::AttributeValue::ThisUnitEntryRef(vmctx_die_id),
);
// Build DW_TAG_pointer_type for `WasmtimeVMContext*`:
// .. DW_AT_name = "WasmtimeVMContext*"
// .. DW_AT_type = <vmctx_die>
add_tag!(root_id, gimli::DW_TAG_pointer_type => vmctx_ptr_die as vmctx_ptr_die_id {
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("WasmtimeVMContext*")),
gimli::DW_AT_type = write::AttributeValue::ThisUnitEntryRef(vmctx_die_id)
});

// Build vmctx_die's DW_TAG_subprogram for `set` method:
// .. DW_AT_linkage_name = "set_vmctx_memory"
// .. DW_AT_name = "set"
// .. DW_TAG_formal_parameter
// .. .. DW_AT_type = <vmctx_ptr_die>
// .. .. DW_AT_artificial = 1
add_tag!(vmctx_die_id, gimli::DW_TAG_subprogram => vmctx_set as vmctx_set_id {
gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add("set_vmctx_memory")),
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("set"))
});
add_tag!(vmctx_set_id, gimli::DW_TAG_formal_parameter => vmctx_set_this_param as vmctx_set_this_param_id {
gimli::DW_AT_type = write::AttributeValue::ThisUnitEntryRef(vmctx_ptr_die_id),
gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
});

(wp_die_id, vmctx_ptr_die_id)
}
Expand Down
Loading