Add Rust Formatters to Debugger (Vector, Map etc.) (#2219)

This PR adds LLDB formatters so that variables are human-readable when debugging
Rust code in VS Code. This includes Tuple, Slice, String, Vector, Map, Enum etc.

It also distributes a standalone Python version with LLDB. This solution enables high
portability, so Ubuntu 20.04 and 22.04 can for example still be supported with the
same build since glibc is statically linked in the Python build, also making it easier to
support more operating systems in the future.

Known Issues: Enum types are not displayed correctly. 

For more details, refer to:
https://github.com/bytecodealliance/wasm-micro-runtime/pull/2219
This commit is contained in:
Ben Riegel 2023-06-14 12:53:51 +01:00 committed by GitHub
parent 1456512754
commit fca81fcd98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 827 additions and 22 deletions

View File

@ -36,6 +36,11 @@ jobs:
needs: try_reuse
if: needs.try_reuse.outputs.result != 'hit'
runs-on: ${{ inputs.runner }}
env:
PYTHON_VERSION: '3.10'
PYTHON_UBUNTU_STANDALONE_BUILD: https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11+20230507-x86_64-unknown-linux-gnu-install_only.tar.gz
PYTHON_MACOS_STANDALONE_BUILD: https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11+20230507-x86_64-apple-darwin-install_only.tar.gz
steps:
- uses: actions/checkout@v3
@ -63,10 +68,12 @@ jobs:
- name: install utils macos
if: steps.lldb_build_cache.outputs.cache-hit != 'true' && contains(inputs.runner, 'macos')
run: |
brew install swig cmake ninja libedit
brew remove swig
brew install swig@3 cmake ninja libedit
brew link --overwrite swig@3
sudo rm -rf /Library/Developer/CommandLineTools
- name: intsall utils ubuntu
- name: install utils ubuntu
if: steps.lldb_build_cache.outputs.cache-hit != 'true' && contains(inputs.runner, 'ubuntu')
run: sudo apt update && sudo apt-get install -y lld ninja-build
@ -88,6 +95,20 @@ jobs:
git apply ../../../build-scripts/lldb-wasm.patch
working-directory: core/deps/llvm-project
- name: get stand-alone python ubuntu
if: steps.lldb_build_cache.outputs.cache-hit != 'true' && contains(inputs.runner, 'ubuntu')
run: |
wget ${{ env.PYTHON_UBUNTU_STANDALONE_BUILD }} -O python.tar.gz
tar -xvf python.tar.gz
working-directory: core/deps
- name: get stand-alone python macos
if: steps.lldb_build_cache.outputs.cache-hit != 'true' && contains(inputs.runner, 'macos')
run: |
wget ${{ env.PYTHON_MACOS_STANDALONE_BUILD }} -O python.tar.gz
tar -xvf python.tar.gz
working-directory: core/deps
- name: build lldb ubuntu
if: steps.lldb_build_cache.outputs.cache-hit != 'true' && contains(inputs.runner, 'ubuntu')
run: |
@ -102,17 +123,21 @@ jobs:
-DLLVM_TARGETS_TO_BUILD:STRING="X86;WebAssembly" \
-DLLVM_BUILD_BENCHMARKS:BOOL=OFF \
-DLLVM_BUILD_DOCS:BOOL=OFF \
-DLLVM_BUILD_EXAMPLES:BOOL=OFF \
-DLLVM_BUILD_EXAMPLES:BOOL=OFF \
-DLLVM_BUILD_LLVM_DYLIB:BOOL=OFF \
-DLLVM_BUILD_TESTS:BOOL=OFF \
-DLLVM_INCLUDE_BENCHMARKS:BOOL=OFF \
-DLLVM_BUILD_TESTS:BOOL=OFF \
-DLLVM_INCLUDE_BENCHMARKS:BOOL=OFF \
-DLLVM_INCLUDE_DOCS:BOOL=OFF \
-DLLVM_INCLUDE_EXAMPLES:BOOL=OFF \
-DLLVM_INCLUDE_TESTS:BOOL=OFF \
-DLLVM_ENABLE_BINDINGS:BOOL=OFF \
-DLLVM_ENABLE_LIBXML2:BOOL=ON \
-DLLDB_ENABLE_PYTHON:BOOL=OFF \
-DLLVM_ENABLE_LLD:BOOL=ON
-DLLVM_ENABLE_LLD:BOOL=ON \
-DLLDB_ENABLE_PYTHON:BOOL=ON \
-DLLDB_EMBED_PYTHON_HOME=ON \
-DLLDB_PYTHON_HOME=.. \
-DLLDB_PYTHON_RELATIVE_PATH=lib/lldb-python \
-DPython3_EXECUTABLE="$(pwd)/../python/bin/python${{ env.PYTHON_VERSION }}"
cmake --build build --target lldb install --parallel $(nproc)
working-directory: core/deps/llvm-project
@ -130,20 +155,21 @@ jobs:
-DLLVM_TARGETS_TO_BUILD:STRING="X86;WebAssembly" \
-DLLVM_BUILD_BENCHMARKS:BOOL=OFF \
-DLLVM_BUILD_DOCS:BOOL=OFF \
-DLLVM_BUILD_EXAMPLES:BOOL=OFF \
-DLLVM_BUILD_EXAMPLES:BOOL=OFF \
-DLLVM_BUILD_LLVM_DYLIB:BOOL=OFF \
-DLLVM_BUILD_TESTS:BOOL=OFF \
-DLLVM_INCLUDE_BENCHMARKS:BOOL=OFF \
-DLLVM_BUILD_TESTS:BOOL=OFF \
-DLLVM_INCLUDE_BENCHMARKS:BOOL=OFF \
-DLLVM_INCLUDE_DOCS:BOOL=OFF \
-DLLVM_INCLUDE_EXAMPLES:BOOL=OFF \
-DLLVM_INCLUDE_TESTS:BOOL=OFF \
-DLLVM_BUILD_BENCHMARKS:BOOL=OFF \
-DLLVM_BUILD_DOCS:BOOL=OFF \
-DLLVM_BUILD_LLVM_DYLIB:BOOL=OFF \
-DLLVM_ENABLE_BINDINGS:BOOL=OFF \
-DLLVM_ENABLE_LIBXML2:BOOL=ON \
-DLLDB_ENABLE_PYTHON:BOOL=OFF \
-DLLDB_BUILD_FRAMEWORK:BOOL=OFF
-DLLDB_BUILD_FRAMEWORK:BOOL=OFF \
-DLLDB_ENABLE_PYTHON:BOOL=ON \
-DLLDB_EMBED_PYTHON_HOME=ON \
-DLLDB_PYTHON_HOME=.. \
-DLLDB_PYTHON_RELATIVE_PATH=lib/lldb-python \
-DPython3_EXECUTABLE="$(pwd)/../python/bin/python${{ env.PYTHON_VERSION }}"
cmake --build build --target lldb install --parallel $(nproc)
working-directory: core/deps/llvm-project
@ -162,12 +188,20 @@ jobs:
run: |
cp build/lib/liblldb*.so wamr-lldb/lib
cp build/lib/liblldb*.so.* wamr-lldb/lib
cp -R build/lib/lldb-python wamr-lldb/lib
cp -R ../python/lib/python* wamr-lldb/lib
cp ../python/lib/libpython${{ env.PYTHON_VERSION }}.so.1.0 wamr-lldb/lib
working-directory: core/deps/llvm-project
- name: pack macos specific libraries
if: steps.lldb_build_cache.outputs.cache-hit != 'true' && contains(inputs.runner, 'macos')
run: |
cp build/lib/liblldb*.dylib wamr-lldb/lib
cp -R build/lib/lldb-python wamr-lldb/lib
cp -R ../python/lib/python* wamr-lldb/lib
cp ../python/lib/libpython*.dylib wamr-lldb/lib
install_name_tool -change /install/lib/libpython${{ env.PYTHON_VERSION }}.dylib @rpath/libpython${{ env.PYTHON_VERSION }}.dylib wamr-lldb/lib/liblldb.*.dylib
# Patch path of python library -> https://github.com/indygreg/python-build-standalone/blob/85923ca3911784e6978b85d56e06e9ae75cb2dc4/docs/quirks.rst?plain=1#L412-L446
working-directory: core/deps/llvm-project
- name: compress the binary

View File

@ -0,0 +1,747 @@
'''
Copyright (c) 2016 Vadim Chugunov
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
'''
from __future__ import print_function, division
import sys
import logging
import lldb
import weakref
if sys.version_info[0] == 2:
# python2-based LLDB accepts utf8-encoded ascii strings only.
def to_lldb_str(s): return s.encode('utf8', 'backslashreplace') if isinstance(s, unicode) else s
range = xrange
else:
to_lldb_str = str
log = logging.getLogger(__name__)
module = sys.modules[__name__]
rust_category = None
def initialize_category(debugger, internal_dict):
global module, rust_category
rust_category = debugger.CreateCategory('Rust')
# rust_category.AddLanguage(lldb.eLanguageTypeRust)
rust_category.SetEnabled(True)
attach_summary_to_type(tuple_summary_provider, r'^\(.*\)$', True)
attach_synthetic_to_type(MsvcTupleSynthProvider, r'^tuple\$?<.+>$',
True) # *-windows-msvc uses this name since 1.47
attach_synthetic_to_type(StrSliceSynthProvider, '&str')
attach_synthetic_to_type(StrSliceSynthProvider, 'str*')
attach_synthetic_to_type(StrSliceSynthProvider, 'str') # *-windows-msvc uses this name since 1.5?
attach_synthetic_to_type(StdStringSynthProvider, '^(collections|alloc)::string::String$', True)
attach_synthetic_to_type(StdVectorSynthProvider, r'^(collections|alloc)::vec::Vec<.+>$', True)
attach_synthetic_to_type(StdVecDequeSynthProvider,
r'^(collections|alloc::collections)::vec_deque::VecDeque<.+>$', True)
attach_synthetic_to_type(MsvcEnumSynthProvider, r'^enum\$<.+>$', True)
attach_synthetic_to_type(MsvcEnum2SynthProvider, r'^enum2\$<.+>$', True)
attach_synthetic_to_type(SliceSynthProvider, r'^&(mut *)?\[.*\]$', True)
attach_synthetic_to_type(MsvcSliceSynthProvider, r'^(mut *)?slice\$?<.+>.*$', True)
attach_synthetic_to_type(StdCStringSynthProvider, '^(std|alloc)::ffi::c_str::CString$', True)
attach_synthetic_to_type(StdCStrSynthProvider, '^&?(std|core)::ffi::c_str::CStr$', True)
attach_synthetic_to_type(StdOsStringSynthProvider, 'std::ffi::os_str::OsString')
attach_synthetic_to_type(StdOsStrSynthProvider, '^&?std::ffi::os_str::OsStr', True)
attach_synthetic_to_type(StdPathBufSynthProvider, 'std::path::PathBuf')
attach_synthetic_to_type(StdPathSynthProvider, '^&?std::path::Path', True)
attach_synthetic_to_type(StdRcSynthProvider, r'^alloc::rc::Rc<.+>$', True)
attach_synthetic_to_type(StdRcSynthProvider, r'^alloc::rc::Weak<.+>$', True)
attach_synthetic_to_type(StdArcSynthProvider, r'^alloc::(sync|arc)::Arc<.+>$', True)
attach_synthetic_to_type(StdArcSynthProvider, r'^alloc::(sync|arc)::Weak<.+>$', True)
attach_synthetic_to_type(StdMutexSynthProvider, r'^std::sync::mutex::Mutex<.+>$', True)
attach_synthetic_to_type(StdCellSynthProvider, r'^core::cell::Cell<.+>$', True)
attach_synthetic_to_type(StdRefCellSynthProvider, r'^core::cell::RefCell<.+>$', True)
attach_synthetic_to_type(StdRefCellBorrowSynthProvider, r'^core::cell::Ref<.+>$', True)
attach_synthetic_to_type(StdRefCellBorrowSynthProvider, r'^core::cell::RefMut<.+>$', True)
attach_synthetic_to_type(StdHashMapSynthProvider, r'^std::collections::hash::map::HashMap<.+>$', True)
attach_synthetic_to_type(StdHashSetSynthProvider, r'^std::collections::hash::set::HashSet<.+>$', True)
attach_synthetic_to_type(GenericEnumSynthProvider, r'^core::option::Option<.+>$', True)
attach_synthetic_to_type(GenericEnumSynthProvider, r'^core::result::Result<.+>$', True)
attach_synthetic_to_type(GenericEnumSynthProvider, r'^alloc::borrow::Cow<.+>$', True)
if 'rust' in internal_dict.get('source_languages', []):
lldb.SBDebugger.SetInternalVariable('target.process.thread.step-avoid-regexp',
'^<?(std|core|alloc)::', debugger.GetInstanceName())
def attach_synthetic_to_type(synth_class, type_name, is_regex=False):
global module, rust_category
# log.debug('attaching synthetic %s to "%s", is_regex=%s', synth_class.__name__, type_name, is_regex)
synth = lldb.SBTypeSynthetic.CreateWithClassName(__name__ + '.' + synth_class.__name__)
synth.SetOptions(lldb.eTypeOptionCascade)
rust_category.AddTypeSynthetic(lldb.SBTypeNameSpecifier(type_name, is_regex), synth)
def summary_fn(valobj, dict): return get_synth_summary(synth_class, valobj, dict)
# LLDB accesses summary fn's by name, so we need to create a unique one.
summary_fn.__name__ = '_get_synth_summary_' + synth_class.__name__
setattr(module, summary_fn.__name__, summary_fn)
attach_summary_to_type(summary_fn, type_name, is_regex)
def attach_summary_to_type(summary_fn, type_name, is_regex=False):
global module, rust_category
# log.debug('attaching summary %s to "%s", is_regex=%s', summary_fn.__name__, type_name, is_regex)
summary = lldb.SBTypeSummary.CreateWithFunctionName(__name__ + '.' + summary_fn.__name__)
summary.SetOptions(lldb.eTypeOptionCascade)
rust_category.AddTypeSummary(lldb.SBTypeNameSpecifier(type_name, is_regex), summary)
# 'get_summary' is annoyingly not a part of the standard LLDB synth provider API.
# This trick allows us to share data extraction logic between synth providers and their sibling summary providers.
def get_synth_summary(synth_class, valobj, dict):
try:
obj_id = valobj.GetIndexOfChildWithName('$$object-id$$')
summary = RustSynthProvider.synth_by_id[obj_id].get_summary()
return to_lldb_str(summary)
except Exception as e:
log.exception('%s', e)
raise
# Chained GetChildMemberWithName lookups
def gcm(valobj, *chain):
for name in chain:
valobj = valobj.GetChildMemberWithName(name)
return valobj
# Get a pointer out of core::ptr::Unique<T>
def read_unique_ptr(valobj):
pointer = valobj.GetChildMemberWithName('pointer')
if pointer.TypeIsPointerType(): # Between 1.33 and 1.63 pointer was just *const T
return pointer
return pointer.GetChildAtIndex(0)
def string_from_ptr(pointer, length):
if length <= 0:
return u''
error = lldb.SBError()
process = pointer.GetProcess()
data = process.ReadMemory(pointer.GetValueAsUnsigned(), length, error)
if error.Success():
return data.decode('utf8', 'replace')
else:
log.error('ReadMemory error: %s', error.GetCString())
def get_template_params(type_name):
params = []
level = 0
start = 0
for i, c in enumerate(type_name):
if c == '<':
level += 1
if level == 1:
start = i + 1
elif c == '>':
level -= 1
if level == 0:
params.append(type_name[start:i].strip())
elif c == ',' and level == 1:
params.append(type_name[start:i].strip())
start = i + 1
return params
def obj_summary(valobj, unavailable='{...}'):
summary = valobj.GetSummary()
if summary is not None:
return summary
summary = valobj.GetValue()
if summary is not None:
return summary
return unavailable
def sequence_summary(childern, maxsize=32):
s = ''
for child in childern:
if len(s) > 0:
s += ', '
s += obj_summary(child)
if len(s) > maxsize:
s += ', ...'
break
return s
def tuple_summary(obj, skip_first=0):
fields = [obj_summary(obj.GetChildAtIndex(i)) for i in range(skip_first, obj.GetNumChildren())]
return '(%s)' % ', '.join(fields)
# ----- Summaries -----
def tuple_summary_provider(valobj, dict={}):
return tuple_summary(valobj)
# ----- Synth providers ------
class RustSynthProvider(object):
synth_by_id = weakref.WeakValueDictionary()
next_id = 0
def __init__(self, valobj, dict={}):
self.valobj = valobj
self.obj_id = RustSynthProvider.next_id
RustSynthProvider.synth_by_id[self.obj_id] = self
RustSynthProvider.next_id += 1
def update(self):
return True
def has_children(self):
return False
def num_children(self):
return 0
def get_child_at_index(self, index):
return None
def get_child_index(self, name):
if name == '$$object-id$$':
return self.obj_id
try:
return self.get_index_of_child(name)
except Exception as e:
log.exception('%s', e)
raise
def get_summary(self):
return None
class ArrayLikeSynthProvider(RustSynthProvider):
'''Base class for providers that represent array-like objects'''
def update(self):
self.ptr, self.len = self.ptr_and_len(self.valobj) # type: ignore
self.item_type = self.ptr.GetType().GetPointeeType()
self.item_size = self.item_type.GetByteSize()
def ptr_and_len(self, obj):
pass # abstract
def num_children(self):
return self.len
def has_children(self):
return True
def get_child_at_index(self, index):
try:
if not 0 <= index < self.len:
return None
offset = index * self.item_size
return self.ptr.CreateChildAtOffset('[%s]' % index, offset, self.item_type)
except Exception as e:
log.exception('%s', e)
raise
def get_index_of_child(self, name):
return int(name.lstrip('[').rstrip(']'))
def get_summary(self):
return '(%d)' % (self.len,)
class StdVectorSynthProvider(ArrayLikeSynthProvider):
def ptr_and_len(self, vec):
return (
read_unique_ptr(gcm(vec, 'buf', 'ptr')),
gcm(vec, 'len').GetValueAsUnsigned()
)
def get_summary(self):
return '(%d) vec![%s]' % (self.len, sequence_summary((self.get_child_at_index(i) for i in range(self.len))))
class StdVecDequeSynthProvider(RustSynthProvider):
def update(self):
self.ptr = read_unique_ptr(gcm(self.valobj, 'buf', 'ptr'))
self.cap = gcm(self.valobj, 'buf', 'cap').GetValueAsUnsigned()
head = gcm(self.valobj, 'head').GetValueAsUnsigned()
# rust 1.67 changed from a head, tail implementation to a head, length impl
# https://github.com/rust-lang/rust/pull/102991
vd_len = gcm(self.valobj, 'len')
if vd_len.IsValid():
self.len = vd_len.GetValueAsUnsigned()
self.startptr = head
else:
tail = gcm(self.valobj, 'tail').GetValueAsUnsigned()
self.len = head - tail
self.startptr = tail
self.item_type = self.ptr.GetType().GetPointeeType()
self.item_size = self.item_type.GetByteSize()
def num_children(self):
return self.len
def has_children(self):
return True
def get_child_at_index(self, index):
try:
if not 0 <= index < self.num_children():
return None
offset = ((self.startptr + index) % self.cap) * self.item_size
return self.ptr.CreateChildAtOffset('[%s]' % index, offset, self.item_type)
except Exception as e:
log.exception('%s', e)
raise
def get_index_of_child(self, name):
return int(name.lstrip('[').rstrip(']'))
def get_summary(self):
return '(%d) VecDeque[%s]' % (self.num_children(), sequence_summary((self.get_child_at_index(i) for i in range(self.num_children()))))
##################################################################################################################
class SliceSynthProvider(ArrayLikeSynthProvider):
def ptr_and_len(self, vec):
return (
gcm(vec, 'data_ptr'),
gcm(vec, 'length').GetValueAsUnsigned()
)
def get_summary(self):
return '(%d) &[%s]' % (self.len, sequence_summary((self.get_child_at_index(i) for i in range(self.len))))
class MsvcSliceSynthProvider(SliceSynthProvider):
def get_type_name(self):
tparams = get_template_params(self.valobj.GetTypeName())
return '&[' + tparams[0] + ']'
# Base class for *String providers
class StringLikeSynthProvider(ArrayLikeSynthProvider):
def get_child_at_index(self, index):
ch = ArrayLikeSynthProvider.get_child_at_index(self, index)
ch.SetFormat(lldb.eFormatChar)
return ch
def get_summary(self):
# Limit string length to 1000 characters to cope with uninitialized values whose
# length field contains garbage.
strval = string_from_ptr(self.ptr, min(self.len, 1000))
if strval == None:
return None
if self.len > 1000:
strval += u'...'
return u'"%s"' % strval
class StrSliceSynthProvider(StringLikeSynthProvider):
def ptr_and_len(self, valobj):
return (
gcm(valobj, 'data_ptr'),
gcm(valobj, 'length').GetValueAsUnsigned()
)
class StdStringSynthProvider(StringLikeSynthProvider):
def ptr_and_len(self, valobj):
vec = gcm(valobj, 'vec')
return (
read_unique_ptr(gcm(vec, 'buf', 'ptr')),
gcm(vec, 'len').GetValueAsUnsigned()
)
class StdCStringSynthProvider(StringLikeSynthProvider):
def ptr_and_len(self, valobj):
vec = gcm(valobj, 'inner')
return (
gcm(vec, 'data_ptr'),
gcm(vec, 'length').GetValueAsUnsigned() - 1
)
class StdOsStringSynthProvider(StringLikeSynthProvider):
def ptr_and_len(self, valobj):
vec = gcm(valobj, 'inner', 'inner')
tmp = gcm(vec, 'bytes') # Windows OSString has an extra layer
if tmp.IsValid():
vec = tmp
return (
read_unique_ptr(gcm(vec, 'buf', 'ptr')),
gcm(vec, 'len').GetValueAsUnsigned()
)
class FFISliceSynthProvider(StringLikeSynthProvider):
def ptr_and_len(self, valobj):
process = valobj.GetProcess()
slice_ptr = valobj.GetLoadAddress()
data_ptr_type = valobj.GetTarget().GetBasicType(lldb.eBasicTypeChar).GetPointerType()
# Unsized slice objects have incomplete debug info, so here we just assume standard slice
# reference layout: [<pointer to data>, <data size>]
error = lldb.SBError()
pointer = valobj.CreateValueFromAddress('data', slice_ptr, data_ptr_type)
length = process.ReadPointerFromMemory(slice_ptr + process.GetAddressByteSize(), error)
return pointer, length
class StdCStrSynthProvider(FFISliceSynthProvider):
def ptr_and_len(self, valobj):
ptr, len = FFISliceSynthProvider.ptr_and_len(self, valobj)
return (ptr, len-1) # drop terminaing '\0'
class StdOsStrSynthProvider(FFISliceSynthProvider):
pass
class StdPathBufSynthProvider(StdOsStringSynthProvider):
def ptr_and_len(self, valobj):
return StdOsStringSynthProvider.ptr_and_len(self, gcm(valobj, 'inner'))
class StdPathSynthProvider(FFISliceSynthProvider):
pass
##################################################################################################################
class DerefSynthProvider(RustSynthProvider):
deref = lldb.SBValue()
def has_children(self):
return self.deref.MightHaveChildren()
def num_children(self):
return self.deref.GetNumChildren()
def get_child_at_index(self, index):
return self.deref.GetChildAtIndex(index)
def get_index_of_child(self, name):
return self.deref.GetIndexOfChildWithName(name)
def get_summary(self):
return obj_summary(self.deref)
# Base for Rc and Arc
class StdRefCountedSynthProvider(DerefSynthProvider):
weak = 0
strong = 0
def get_summary(self):
if self.weak != 0:
s = '(refs:%d,weak:%d) ' % (self.strong, self.weak)
else:
s = '(refs:%d) ' % self.strong
if self.strong > 0:
s += obj_summary(self.deref)
else:
s += '<disposed>'
return s
class StdRcSynthProvider(StdRefCountedSynthProvider):
def update(self):
inner = read_unique_ptr(gcm(self.valobj, 'ptr'))
self.strong = gcm(inner, 'strong', 'value', 'value').GetValueAsUnsigned()
self.weak = gcm(inner, 'weak', 'value', 'value').GetValueAsUnsigned()
if self.strong > 0:
self.deref = gcm(inner, 'value')
self.weak -= 1 # There's an implicit weak reference communally owned by all the strong pointers
else:
self.deref = lldb.SBValue()
self.deref.SetPreferSyntheticValue(True)
class StdArcSynthProvider(StdRefCountedSynthProvider):
def update(self):
inner = read_unique_ptr(gcm(self.valobj, 'ptr'))
self.strong = gcm(inner, 'strong', 'v', 'value').GetValueAsUnsigned()
self.weak = gcm(inner, 'weak', 'v', 'value').GetValueAsUnsigned()
if self.strong > 0:
self.deref = gcm(inner, 'data')
self.weak -= 1 # There's an implicit weak reference communally owned by all the strong pointers
else:
self.deref = lldb.SBValue()
self.deref.SetPreferSyntheticValue(True)
class StdMutexSynthProvider(DerefSynthProvider):
def update(self):
self.deref = gcm(self.valobj, 'data', 'value')
self.deref.SetPreferSyntheticValue(True)
class StdCellSynthProvider(DerefSynthProvider):
def update(self):
self.deref = gcm(self.valobj, 'value', 'value')
self.deref.SetPreferSyntheticValue(True)
class StdRefCellSynthProvider(DerefSynthProvider):
def update(self):
self.deref = gcm(self.valobj, 'value', 'value')
self.deref.SetPreferSyntheticValue(True)
def get_summary(self):
borrow = gcm(self.valobj, 'borrow', 'value', 'value').GetValueAsSigned()
s = ''
if borrow < 0:
s = '(borrowed:mut) '
elif borrow > 0:
s = '(borrowed:%d) ' % borrow
return s + obj_summary(self.deref)
class StdRefCellBorrowSynthProvider(DerefSynthProvider):
def update(self):
self.deref = gcm(self.valobj, 'value', 'pointer').Dereference()
self.deref.SetPreferSyntheticValue(True)
##################################################################################################################
class EnumSynthProvider(RustSynthProvider):
variant = lldb.SBValue()
summary = ''
skip_first = 0
def has_children(self):
return self.variant.MightHaveChildren()
def num_children(self):
return self.variant.GetNumChildren() - self.skip_first
def get_child_at_index(self, index):
return self.variant.GetChildAtIndex(index + self.skip_first)
def get_index_of_child(self, name):
return self.variant.GetIndexOfChildWithName(name) - self.skip_first
def get_summary(self):
return self.summary
class GenericEnumSynthProvider(EnumSynthProvider):
def update(self):
dyn_type_name = self.valobj.GetTypeName()
variant_name = dyn_type_name[dyn_type_name.rfind(':')+1:]
self.variant = self.valobj
if self.variant.IsValid() and self.variant.GetNumChildren() > self.skip_first:
if self.variant.GetChildAtIndex(self.skip_first).GetName() in ['0', '__0']:
self.summary = variant_name + tuple_summary(self.variant)
else:
self.summary = variant_name + '{...}'
else:
self.summary = variant_name
class MsvcTupleSynthProvider(RustSynthProvider):
def update(self):
tparams = get_template_params(self.valobj.GetTypeName())
self.type_name = '(' + ', '.join(tparams) + ')'
def has_children(self):
return self.valobj.MightHaveChildren()
def num_children(self):
return self.valobj.GetNumChildren()
def get_child_at_index(self, index):
child = self.valobj.GetChildAtIndex(index)
return child.CreateChildAtOffset(str(index), 0, child.GetType())
def get_index_of_child(self, name):
return str(name)
def get_summary(self):
return tuple_summary(self.valobj)
def get_type_name(self):
return self.type_name
class MsvcEnumSynthProvider(EnumSynthProvider):
is_tuple_variant = False
def update(self):
tparams = get_template_params(self.valobj.GetTypeName())
if len(tparams) == 1: # Regular enum
discr = gcm(self.valobj, 'discriminant')
self.variant = gcm(self.valobj, 'variant' + str(discr.GetValueAsUnsigned()))
variant_name = discr.GetValue()
else: # Niche enum
dataful_min = int(tparams[1])
dataful_max = int(tparams[2])
dataful_var = tparams[3]
discr = gcm(self.valobj, 'discriminant')
if dataful_min <= discr.GetValueAsUnsigned() <= dataful_max:
self.variant = gcm(self.valobj, 'dataful_variant')
variant_name = dataful_var
else:
variant_name = discr.GetValue()
self.type_name = tparams[0]
if self.variant.IsValid() and self.variant.GetNumChildren() > self.skip_first:
if self.variant.GetChildAtIndex(self.skip_first).GetName() == '__0':
self.is_tuple_variant = True
self.summary = variant_name + tuple_summary(self.variant, skip_first=self.skip_first)
else:
self.summary = variant_name + '{...}'
else:
self.summary = variant_name
def get_child_at_index(self, index):
child = self.variant.GetChildAtIndex(index + self.skip_first)
if self.is_tuple_variant:
return child.CreateChildAtOffset(str(index), 0, child.GetType())
else:
return child
def get_index_of_child(self, name):
if self.is_tuple_variant:
return int(name)
else:
return self.variant.GetIndexOfChildWithName(name) - self.skip_first
def get_type_name(self):
return self.type_name
class MsvcEnum2SynthProvider(EnumSynthProvider):
is_tuple_variant = False
def update(self):
tparams = get_template_params(self.valobj.GetTypeName())
self.type_name = tparams[0]
def get_child_at_index(self, index):
return self.valobj.GetChildAtIndex(index)
def get_index_of_child(self, name):
return self.valobj.GetChildIndex(name)
def get_type_name(self):
return self.type_name
##################################################################################################################
class StdHashMapSynthProvider(RustSynthProvider):
def update(self):
self.initialize_table(gcm(self.valobj, 'base', 'table'))
def initialize_table(self, table):
assert table.IsValid()
if table.type.GetNumberOfTemplateArguments() > 0:
item_ty = table.type.GetTemplateArgumentType(0)
else: # we must be on windows-msvc - try to look up item type by name
table_ty_name = table.GetType().GetName() # "hashbrown::raw::RawTable<ITEM_TY>"
item_ty_name = get_template_params(table_ty_name)[0]
item_ty = table.GetTarget().FindTypes(item_ty_name).GetTypeAtIndex(0)
if item_ty.IsTypedefType():
item_ty = item_ty.GetTypedefedType()
inner_table = table.GetChildMemberWithName('table')
if inner_table.IsValid():
self.initialize_hashbrown_v2(inner_table, item_ty) # 1.52 <= std_version
else:
if not table.GetChildMemberWithName('data'):
self.initialize_hashbrown_v2(table, item_ty) # ? <= std_version < 1.52
else:
self.initialize_hashbrown_v1(table, item_ty) # 1.36 <= std_version < ?
def initialize_hashbrown_v2(self, table, item_ty):
self.num_buckets = gcm(table, 'bucket_mask').GetValueAsUnsigned() + 1
ctrl_ptr = gcm(table, 'ctrl', 'pointer')
ctrl = ctrl_ptr.GetPointeeData(0, self.num_buckets)
# Buckets are located above `ctrl`, in reverse order.
start_addr = ctrl_ptr.GetValueAsUnsigned() - item_ty.GetByteSize() * self.num_buckets
buckets_ty = item_ty.GetArrayType(self.num_buckets)
self.buckets = self.valobj.CreateValueFromAddress('data', start_addr, buckets_ty)
error = lldb.SBError()
self.valid_indices = []
for i in range(self.num_buckets):
if ctrl.GetUnsignedInt8(error, i) & 0x80 == 0:
self.valid_indices.append(self.num_buckets - 1 - i)
def initialize_hashbrown_v1(self, table, item_ty):
self.num_buckets = gcm(table, 'bucket_mask').GetValueAsUnsigned() + 1
ctrl_ptr = gcm(table, 'ctrl', 'pointer')
ctrl = ctrl_ptr.GetPointeeData(0, self.num_buckets)
buckets_ty = item_ty.GetArrayType(self.num_buckets)
self.buckets = gcm(table, 'data', 'pointer').Dereference().Cast(buckets_ty)
error = lldb.SBError()
self.valid_indices = []
for i in range(self.num_buckets):
if ctrl.GetUnsignedInt8(error, i) & 0x80 == 0:
self.valid_indices.append(i)
def has_children(self):
return True
def num_children(self):
return len(self.valid_indices)
def get_child_at_index(self, index):
bucket_idx = self.valid_indices[index]
item = self.buckets.GetChildAtIndex(bucket_idx)
return item.CreateChildAtOffset('[%d]' % index, 0, item.GetType())
def get_index_of_child(self, name):
return int(name.lstrip('[').rstrip(']'))
def get_summary(self):
return 'size=%d, capacity=%d' % (self.num_children(), self.num_buckets)
class StdHashSetSynthProvider(StdHashMapSynthProvider):
def update(self):
table = gcm(self.valobj, 'base', 'map', 'table') # std_version >= 1.48
if not table.IsValid():
table = gcm(self.valobj, 'map', 'base', 'table') # std_version < 1.48
self.initialize_table(table)
def get_child_at_index(self, index):
bucket_idx = self.valid_indices[index]
item = self.buckets.GetChildAtIndex(bucket_idx).GetChildAtIndex(0)
return item.CreateChildAtOffset('[%d]' % index, 0, item.GetType())
##################################################################################################################
def __lldb_init_module(debugger_obj, internal_dict):
log.info('Initializing')
initialize_category(debugger_obj, internal_dict)

View File

@ -6,7 +6,7 @@
},
"displayName": "WAMR-IDE",
"description": "An Integrated Development Environment for WASM",
"version": "1.2.1",
"version": "1.2.2",
"engines": {
"vscode": "^1.59.0"
},

View File

@ -6,23 +6,47 @@
import * as vscode from 'vscode';
import * as os from 'os';
/* see https://github.com/llvm/llvm-project/tree/main/lldb/tools/lldb-vscode#attaching-settings */
export interface WasmDebugConfig {
type: string,
name: string,
request: string,
program? : string,
pid?: string,
stopOnEntry?: boolean,
waitFor?: boolean,
initCommands?: string[],
preRunCommands?: string[],
stopCommands?: string[],
exitCommands?: string[],
terminateCommands?: string[],
attachCommands?: string[]
}
export class WasmDebugConfigurationProvider
implements vscode.DebugConfigurationProvider {
private wasmDebugConfig = {
private wasmDebugConfig: WasmDebugConfig = {
type: 'wamr-debug',
name: 'Attach',
request: 'attach',
stopOnEntry: true,
initCommands: os.platform() === 'win32' || os.platform() === 'darwin' ?
/* linux and windows has different debug configuration */
['platform select remote-linux'] :
undefined,
attachCommands: [
/* default port 1234 */
'process connect -p wasm connect://127.0.0.1:1234',
]
};
constructor(extensionPath: string) {
this.wasmDebugConfig.initCommands = [
/* Add rust formatters -> https://lldb.llvm.org/use/variable.html */
`command script import ${extensionPath}/formatters/rust.py`
];
if (os.platform() === 'win32' || os.platform() === 'darwin') {
this.wasmDebugConfig.initCommands.push('platform select remote-linux');
}
}
public resolveDebugConfiguration(
_: vscode.WorkspaceFolder | undefined,
debugConfiguration: vscode.DebugConfiguration,

View File

@ -170,7 +170,7 @@ export async function activate(context: vscode.ExtensionContext) {
}
/* register debug configuration */
wasmDebugConfigProvider = new WasmDebugConfigurationProvider();
wasmDebugConfigProvider = new WasmDebugConfigurationProvider(context.extensionPath);
vscode.debug.registerDebugConfigurationProvider(
'wamr-debug',