mirror of
https://github.com/bytecodealliance/wasm-micro-runtime.git
synced 2025-02-06 06:55:07 +00:00
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:
parent
1456512754
commit
fca81fcd98
64
.github/workflows/build_wamr_lldb.yml
vendored
64
.github/workflows/build_wamr_lldb.yml
vendored
|
@ -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
|
||||
|
|
747
test-tools/wamr-ide/VSCode-Extension/formatters/rust.py
Normal file
747
test-tools/wamr-ide/VSCode-Extension/formatters/rust.py
Normal 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)
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in New Issue
Block a user