| from __future__ import annotations |
| from typing import TYPE_CHECKING, List, Callable |
| |
| import lldb |
| |
| from lldb_providers import ( |
| LLDBFeature, |
| FEATURE_FLAGS, |
| LLDBOpaque, |
| StructSyntheticProvider, |
| StdHashMapSyntheticProvider, |
| StdOldHashMapSyntheticProvider, |
| StdRcSyntheticProvider, |
| TupleSyntheticProvider, |
| EmptySyntheticProvider, |
| ClangEncodedEnumProvider, |
| DefaultSyntheticProvider, |
| StdStringSyntheticProvider, |
| StdStringSummaryProvider, |
| StdSliceSyntheticProvider, |
| StdStrSummaryProvider, |
| MSVCStrSyntheticProvider, |
| SizeSummaryProvider, |
| MSVCStdSliceSyntheticProvider, |
| StdSliceSummaryProvider, |
| StdOsStringSummaryProvider, |
| StdVecSyntheticProvider, |
| StdVecDequeSyntheticProvider, |
| StdRcSummaryProvider, |
| StdCellSyntheticProvider, |
| StdRefSyntheticProvider, |
| StdRefSummaryProvider, |
| StdNonZeroNumberSummaryProvider, |
| StdPathBufSummaryProvider, |
| StdPathSummaryProvider, |
| MSVCEnumSyntheticProvider, |
| MSVCEnumSummaryProvider, |
| TupleSummaryProvider, |
| MSVCTupleSyntheticProvider, |
| ClangEncodedEnumSummaryProvider, |
| StructSummaryProvider, |
| ) |
| from rust_types import ( |
| ENUM_DISR_FIELD_NAME, |
| ENUM_LLDB_ENCODED_VARIANTS, |
| RustType, |
| classify_union, |
| is_tuple_fields, |
| ) |
| |
| if TYPE_CHECKING: |
| from lldb import SBValue |
| |
| #################################################################################################### |
| # This file contains lookup functions that associate rust types with their synthetic/summary |
| # providers. |
| # |
| # LLDB caches the results of the the commands in `lldb_commands`, but that caching is "shallow". It |
| # purely associates the type with the function given, whether it is a regular function or a class |
| # constructor. If the function makes decisions about what type of SyntheticProvider to return, that |
| # processing is done **each time a value of that type is encountered**. |
| # |
| # To reiterate, inspecting a `vec![T; 100_000]` will call `T`'s lookup function/constructor 100,000 |
| # times. This can lead to significant delays in value visualization if the lookup logic is complex. |
| # |
| # As such, lookup functions should be kept as minimal as possible. LLDB technically expects a |
| # SyntheticProvider class constructor. If you can provide just a class constructor, that should be |
| # preferred. If extra processing must be done, try to keep it as minimal and as targeted as possible |
| # (see: `classify_hashmap()` vs `classify_hashset()`). |
| #################################################################################################### |
| |
| RUST_CATEGORY: lldb.SBTypeCategory = lldb.SBTypeCategory() |
| MOD_PREFIX = f"{__name__}." |
| |
| DEFAULT_TYPE_OPTIONS: int = ( |
| # ensure it applies through typedef chains |
| lldb.eTypeOptionCascade |
| | lldb.eTypeOptionHideEmptyAggregates |
| # helps us reason about what types can be put through the synthetic provider. |
| # this is important because of the `update` logic as well as when working with |
| # type information, since we almost always want to work on the pointee type/pointee's |
| # template args. These options allow us to know that the type we have is never a pointer-to |
| # the type we want |
| | lldb.eTypeOptionFrontEndWantsDereference |
| ) |
| |
| |
| def __lldb_init_module(debugger: lldb.SBDebugger, _dict: LLDBOpaque): |
| global RUST_CATEGORY |
| RUST_CATEGORY = debugger.GetCategory("Rust") |
| |
| if not RUST_CATEGORY.IsValid(): |
| RUST_CATEGORY = debugger.CreateCategory("Rust") |
| |
| RUST_CATEGORY.SetEnabled(True) |
| |
| # BUG: This specifier is used to determine whether or not visualizers in this category shoud be |
| # used for the given executable. It defaults to `lldb.eLanguageTypeUnknown` which will be active |
| # regardless of the executable's reported language, but setting it to `rust` would be ideal |
| # so that our visualizers do not apply to, for example, C++ code when debugging across FFI |
| # boundaries. |
| # We cannot currently enable this though, as this query defers to the `TypeSystem` (in our case, |
| # `TypeSystemClang`). `TypeSystemClang::SupportsLanguage` includes Rust, which allows Rust |
| # support in LLDB, but SBTypeCategory instead checks |
| # `TypeSystemClang::GetSupportedLanguagesForTypes` which only includes various C and C++ |
| # versions. I'm not sure if Rust should be added to that function or not, but in the meantime |
| # cannot directly specify that Rust is the intended language for this category. |
| |
| # RUST_CATEGORY.AddLanguage(lldb.eLanguageTypeRust) |
| |
| global FEATURE_FLAGS |
| # Most feature checks should be possible via simple "does this API exist at all" checks. |
| if getattr(lldb.SBType, "GetStaticFieldWithName", None) is not None: |
| FEATURE_FLAGS |= LLDBFeature.StaticFields |
| if getattr(lldb, "eFormatterMatchCallback", None) is not None: |
| FEATURE_FLAGS |= LLDBFeature.TypeRecognizers |
| if getattr(lldb, "eBasicTypeFloat128", None) is not None: |
| FEATURE_FLAGS |= LLDBFeature.Float128 |
| |
| register_providers_compatibility() |
| |
| |
| def register_providers_compatibility(): |
| """ |
| Adds providers to global `RUST_CATEGORY`. Does not attempt to use type recognizers |
| |
| The order that providers are added matters. Existing providers are iterated through in **reverse |
| order** when finding a match, allowing new providers to "overwrite" old providers. Be very |
| careful when modifying the order that providers are added. |
| """ |
| |
| global RUST_CATEGORY |
| |
| if LLDBFeature.TypeRecognizers in FEATURE_FLAGS: |
| # FIXME: this can be removed once full support for type recognizers is added. |
| # This prevents a semi-unfixable regression for CodeLLDB |
| register_synth( |
| synthetic_lookup, |
| lldb.SBTypeNameSpecifier( |
| MOD_PREFIX + is_udt.__name__, |
| lldb.eFormatterMatchCallback, |
| ), |
| lldb.eTypeOptionCascade, |
| ) |
| |
| # enforce uniform aggregate formatting |
| register_summary( |
| StructSummaryProvider, |
| lldb.SBTypeNameSpecifier( |
| MOD_PREFIX + is_udt.__name__, |
| lldb.eFormatterMatchCallback, |
| ), |
| lldb.eTypeOptionCascade |
| | lldb.eTypeOptionHideEmptyAggregates |
| | lldb.eTypeOptionHideChildren, |
| ) |
| else: |
| # Need to toss any remaining types through this so that GNU enums are caught |
| register_synth( |
| synthetic_lookup, |
| lldb.SBTypeNameSpecifier(r".*", True), |
| ) |
| |
| # String |
| register( |
| StdStringSyntheticProvider, |
| StdStringSummaryProvider, |
| r"^(alloc::([a-z_]+::)+)String$", |
| ) |
| |
| # str GNU |
| register( |
| StdSliceSyntheticProvider, |
| StdStrSummaryProvider, |
| r"^&(mut )?str$", |
| ) |
| |
| # str MSVC |
| register( |
| MSVCStrSyntheticProvider, |
| StdStrSummaryProvider, |
| r"^ref(_mut)?\$<str\$>$", |
| ) |
| |
| # slice GNU |
| register( |
| StdSliceSyntheticProvider, |
| SizeSummaryProvider, |
| r"^&(mut )?\[.+\]$", |
| ) |
| |
| # slice MSVC |
| register( |
| MSVCStdSliceSyntheticProvider, |
| StdSliceSummaryProvider, |
| r"^ref(_mut)?\$<slice2\$<.+> >", |
| ) |
| |
| # OsString |
| register_summary( |
| StdOsStringSummaryProvider, |
| lldb.SBTypeNameSpecifier(r"^(std::ffi::([a-z_]+::)+)OsString$", True), |
| ) |
| |
| # Vec |
| register( |
| StdVecSyntheticProvider, |
| SizeSummaryProvider, |
| r"^(alloc::([a-z_]+::)+)Vec<.+>$", |
| ) |
| |
| # VecDeque |
| register( |
| StdVecDequeSyntheticProvider, |
| SizeSummaryProvider, |
| r"^(alloc::([a-z_]+::)+)VecDeque<.+>$", |
| ) |
| |
| # HashMap |
| register( |
| classify_hashmap, |
| SizeSummaryProvider, |
| r"^(std::collections::([a-z_]+::)+)HashMap<.+>$", |
| ) |
| |
| # HashSet |
| register( |
| classify_hashset, |
| SizeSummaryProvider, |
| r"^(std::collections::([a-z_]+::)+)HashSet<.+>$", |
| ) |
| |
| # Rc |
| register( |
| StdRcSyntheticProvider, |
| StdRcSummaryProvider, |
| r"^(alloc::([a-z_]+::)+)Rc<.+>$", |
| ) |
| |
| # Arc |
| register( |
| arc_synthetic, |
| StdRcSummaryProvider, |
| r"^(alloc::([a-z_]+::)+)Arc<.+>$", |
| ) |
| |
| # Cell |
| register_synth( |
| StdCellSyntheticProvider, |
| lldb.SBTypeNameSpecifier( |
| r"^(core::([a-z_]+::)+)Cell<.+>$", |
| True, |
| ), |
| ) |
| |
| # RefCell |
| register( |
| StdRefSyntheticProvider, |
| StdRefSummaryProvider, |
| r"^(core::([a-z_]+::)+)Ref(Cell|Mut)?<.+>$", |
| ) |
| |
| # NonZero |
| register_summary( |
| StdNonZeroNumberSummaryProvider, |
| lldb.SBTypeNameSpecifier( |
| r"^(core::([a-z_]+::)+)NonZero(<.+>|I\d{0,3}|U\d{0,3})$", |
| True, |
| ), |
| ) |
| |
| # PathBuf |
| register_summary( |
| StdPathBufSummaryProvider, |
| lldb.SBTypeNameSpecifier( |
| r"^(std::([a-z_]+::)+)PathBuf$", |
| True, |
| ), |
| ) |
| |
| # Path |
| register_summary( |
| StdPathSummaryProvider, |
| lldb.SBTypeNameSpecifier( |
| r"^&(mut )?(std::([a-z_]+::)+)Path$", |
| True, |
| ), |
| ) |
| |
| # Enum MSVC |
| register( |
| MSVCEnumSyntheticProvider, |
| MSVCEnumSummaryProvider, |
| r"^enum2\$<.+>$", |
| ) |
| |
| # Tuple GNU |
| register( |
| TupleSyntheticProvider, |
| TupleSummaryProvider, |
| r"^\(.*\)$", |
| type_options=DEFAULT_TYPE_OPTIONS | lldb.eTypeOptionHideChildren, |
| ) |
| |
| # Tuple MSVC |
| register( |
| MSVCTupleSyntheticProvider, |
| TupleSummaryProvider, |
| r"^tuple\$<.+>$", |
| ) |
| |
| |
| def register( |
| synth_provider: Callable[[SBValue, LLDBOpaque], object], |
| summary_provider: Callable[[SBValue, LLDBOpaque], str], |
| match_str: str, |
| regex: bool = True, |
| type_options: int = DEFAULT_TYPE_OPTIONS, |
| ): |
| global RUST_CATEGORY |
| sb_name: lldb.SBTypeNameSpecifier = lldb.SBTypeNameSpecifier(match_str, regex) |
| |
| register_synth(synth_provider, sb_name, type_options) |
| register_summary(summary_provider, sb_name, type_options) |
| |
| |
| def register_synth( |
| provider: Callable[[SBValue, LLDBOpaque], object], |
| sb_name: lldb.SBTypeNameSpecifier, |
| type_options: int = DEFAULT_TYPE_OPTIONS, |
| ): |
| sb_synth: lldb.SBTypeSynthetic = lldb.SBTypeSynthetic.CreateWithClassName( |
| MOD_PREFIX + provider.__name__ |
| ) |
| sb_synth.SetOptions(type_options) |
| |
| global RUST_CATEGORY |
| # returns false for failures, does not provide any info to determine what the failure was |
| |
| res: bool = RUST_CATEGORY.AddTypeSynthetic(sb_name, sb_synth) |
| |
| if not res: |
| print( |
| "Warning: unable to register summary: " |
| + f"{MOD_PREFIX + provider.__name__} with specifier '{sb_name.GetName()}'" |
| ) |
| |
| |
| def register_summary( |
| provider: Callable[[SBValue, LLDBOpaque], object], |
| sb_name: lldb.SBTypeNameSpecifier, |
| type_options: int = DEFAULT_TYPE_OPTIONS, |
| ): |
| sb_summary: lldb.SBTypeSummary = lldb.SBTypeSummary.CreateWithFunctionName( |
| MOD_PREFIX + provider.__name__ |
| ) |
| |
| sb_summary.SetOptions(type_options) |
| |
| global RUST_CATEGORY |
| # returns false for failures, does not provide any info to determine what the failure was |
| res: bool = RUST_CATEGORY.AddTypeSummary(sb_name, sb_summary) |
| |
| if not res: |
| print( |
| "Warning: unable to register summary: " |
| + f"{MOD_PREFIX + provider.__name__} with specifier '{sb_name.GetName()}'" |
| ) |
| |
| |
| # BACKCOMPAT: rust 1.35 |
| def is_hashbrown_hashmap(hash_map: lldb.SBValue) -> bool: |
| return len(hash_map.type.fields) == 1 |
| |
| |
| def classify_hashmap(valobj: lldb.SBValue, _dict: LLDBOpaque) -> object: |
| if is_hashbrown_hashmap(valobj): |
| return StdHashMapSyntheticProvider(valobj, _dict) |
| else: |
| return StdOldHashMapSyntheticProvider(valobj, _dict) |
| |
| |
| def classify_hashset(valobj: lldb.SBValue, _dict: LLDBOpaque) -> object: |
| hash_map = valobj.GetChildAtIndex(0) |
| if is_hashbrown_hashmap(hash_map): |
| return StdHashMapSyntheticProvider(valobj, _dict, show_values=False) |
| else: |
| return StdOldHashMapSyntheticProvider(hash_map, _dict, show_values=False) |
| |
| |
| def arc_synthetic(valobj: lldb.SBValue, _dict: LLDBOpaque) -> object: |
| return StdRcSyntheticProvider(valobj, _dict, is_atomic=True) |
| |
| |
| def is_udt(type: lldb.SBType, _dict: LLDBOpaque) -> bool: |
| return ( |
| type.GetBasicType() == lldb.eBasicTypeInvalid |
| and not type.GetEnumerationIntegerType().IsValid() |
| and not type.IsPointerType() |
| and not type.IsArrayType() |
| ) |
| |
| |
| def classify_rust_type(type: lldb.SBType, is_msvc: bool) -> RustType: |
| if type.IsPointerType(): |
| return RustType.Indirection |
| |
| # there is a bit of code duplication here because we don't want to check all of the standard |
| # library regexes since LLDB handles that for us |
| type_class = type.GetTypeClass() |
| if type_class == lldb.eTypeClassStruct: |
| fields: List[lldb.SBTypeMember] = type.fields |
| if len(fields) == 0: |
| return RustType.Empty |
| |
| # <<variant>> is emitted by GDB while LLDB(18.1+) emits "$variants$" |
| if ( |
| fields[0].name == ENUM_DISR_FIELD_NAME |
| or fields[0].name == ENUM_LLDB_ENCODED_VARIANTS |
| ): |
| return RustType.Enum |
| |
| if is_tuple_fields(fields): |
| return RustType.Tuple |
| |
| return RustType.Struct |
| if type_class == lldb.eTypeClassUnion: |
| # If we're debugging msvc, sum-type enums should have been caught by the regex in lldb |
| # commands since they all start with "enum2$<" |
| if is_msvc: |
| return RustType.Union |
| return classify_union(type.fields) |
| |
| return RustType.Other |
| |
| |
| def synthetic_lookup(valobj: lldb.SBValue, _dict: LLDBOpaque) -> object: |
| """Returns the synthetic provider for the given value""" |
| |
| # small hack to check for the DWARF debug info section, since SBTarget.triple and |
| # SBProcess.triple report lldb's target rather than the executable's. SBProcessInfo.triple |
| # returns a triple without the ABI. It is also possible for any of those functions to return a |
| # None object. |
| # Instead, we look for the GNU `.debug_info` section, as MSVC does not have one with the same |
| # name |
| # FIXME: I don't know if this works when the DWARF lives in a separate file |
| # (see: https://gcc.gnu.org/wiki/DebugFissionDWP). Splitting the DWARF is very uncommon afaik so |
| # it should be okay for the time being. |
| is_msvc = not valobj.GetFrame().GetModule().FindSection(".debug_info").IsValid() |
| |
| rust_type = classify_rust_type(valobj.GetType(), is_msvc) |
| |
| if rust_type == RustType.Struct or rust_type == RustType.Union: |
| return StructSyntheticProvider(valobj, _dict) |
| if rust_type == RustType.StructVariant: |
| return StructSyntheticProvider(valobj, _dict, is_variant=True) |
| if rust_type == RustType.Tuple: |
| return TupleSyntheticProvider(valobj, _dict) |
| if rust_type == RustType.TupleVariant: |
| return TupleSyntheticProvider(valobj, _dict, is_variant=True) |
| if rust_type == RustType.Empty: |
| return EmptySyntheticProvider(valobj, _dict) |
| if rust_type == RustType.RegularEnum: |
| discriminant = valobj.GetChildAtIndex(0).GetChildAtIndex(0).GetValueAsUnsigned() |
| return synthetic_lookup(valobj.GetChildAtIndex(discriminant), _dict) |
| if rust_type == RustType.SingletonEnum: |
| return synthetic_lookup(valobj.GetChildAtIndex(0), _dict) |
| if rust_type == RustType.Enum: |
| # this little trick lets us treat `synthetic_lookup` as a "recognizer function" for the enum |
| # summary providers, reducing the number of lookups we have to do. This is a huge time save |
| # because there's no way (via type name) to recognize sum-type enums on `*-gnu` targets. The |
| # alternative would be to shove every single type through `summary_lookup`, which is |
| # incredibly wasteful. Once these scripts are updated for LLDB 19.0 and we can use |
| # `--recognizer-function`, this hack will only be needed for backwards compatibility. |
| summary: lldb.SBTypeSummary = valobj.GetTypeSummary() |
| if ( |
| summary.summary_data is None |
| or summary.summary_data.strip() |
| != "lldb_lookup.ClangEncodedEnumSummaryProvider(valobj,internal_dict)" |
| ): |
| sb_summary = lldb.SBTypeSummary().CreateWithFunctionName( |
| MOD_PREFIX + ClangEncodedEnumSummaryProvider.__name__ |
| ) |
| sb_summary.SetOptions(DEFAULT_TYPE_OPTIONS) |
| |
| RUST_CATEGORY.AddTypeSummary( |
| lldb.SBTypeNameSpecifier(valobj.GetTypeName()), |
| sb_summary, |
| ) |
| |
| return ClangEncodedEnumProvider(valobj, _dict) |
| |
| return DefaultSyntheticProvider(valobj, _dict) |