Skip to content

Commit

Permalink
Utility methods for artificial debug types in the generated DWARF (#1482
Browse files Browse the repository at this point in the history
)

* add operator*
* add operator->
* add ptr() unwrap method
* comments/refactor
* macro_rules
* external symbols workaround
  • Loading branch information
yurydelendik committed Apr 10, 2020
1 parent 0aa9465 commit a88e26c
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 99 deletions.
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

0 comments on commit a88e26c

Please sign in to comment.