尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Rust 编译器调试指南:用 LLDB Python Providers 编写 Rust 类型可视化器(Synthetic / Summary)

Rust 编译器调试指南:用 LLDB Python Providers 编写 Rust 类型可视化器(Synthetic / Summary) Rust 编译器调试指南用 LLDB Python Providers 编写 Rust 类型可视化器Synthetic / Summary【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rustRust 标准库类型如VecT、String、HashMap在调试器中的裸内存布局与用户期望的抽象形态相去甚远。本文以 rustc 开发者指南中 LLDB - Python Providers 一章为骨架系统讲解 LLDB 提供的三类输出定制机制Formats、Synthetic Providers、Summary Providers深入剖析 Synthetic Provider 的完整接口契约、update缓存语义、实例级 Summary 的实现技巧并结合仓库内真实实现 lldb_providers.py 与 lldb_lookup.py 给出可运行的VecT可视化器完整示例与前后对比输出。读完本文你将具备为任意 Rust 自定义类型编写高质量 LLDB 可视化器的完整能力。前置阅读本文是 rustc-dev-guide 调试信息debuginfo系列的一部分建议先阅读 调试器可视化器总览 与 LLDB 内部机制它们介绍了可视化器在整个调试信息管线中的位置、LLDB 的TypeSystem架构以及 DWARF/PDB 的差异背景。环境前提Python 版本与 LLDB 的绑定LLDB 的 C ↔ Python FFI 层期望的 Python 版本是在 LLDB 编译时就已指定的。LLDB 会尽量把该版本对应到主流 Linux 与 macOS 发行版中常见的最低 Python 版本但在 Windows 上目前没有简单的解决方案——如果你遇到_lldb不存在的导入错误很可能是 Python 版本不匹配导致的。LLDB 社区正在考虑解决该问题相关讨论见 LLVM Discourse 与 llvm-project issue。截至 2025 年 11 月LLDB 支持的最低 Python 版本为3.8并有计划视外部因素升级到 3.9 或 3.10。因此Rust 官方可视化器脚本在编写时尽量只使用最低支持版本内可用的特性。实用提示LLDB 的 Python 包路径可以通过 CLI 命令lldb -P获取。LLDB 输出定制的三种机制LLDB 提供了三种定制变量输出的机制机制作用官方文档主题Formats格式设置基本类型的默认打印格式type-formatSynthetic providers合成子项通过 Python 类为类型提供合成子项把底层内存重构成对用户友好的结构synthetic-childrenSummary providers摘要通过 Python 函数为类型生成一行摘要字符串type-summaryFormats修复 8 位整数的默认打印Formats 允许为基本类型设置默认打印格式例如把25u8打印为十进制25、十六进制0x19或二进制00011001。Rust 几乎总是需要覆盖unsigned char、signed char、char、u8、i8这几种类型把它们强制改为无符号十进制格式。原因很直观调试器源自 C 生态默认把 8 位整数当作字符打印而 Rust 语义中它们就是整数。仓库中的真实实现印证了这一点——lldb_lookup.py 的register_providers_compatibility中用SBTypeFormat为u8、unsigned char注册了eFormatUnsigned为i8、signed char、char注册了eFormatDecimal并且显式跳过指针与引用eTypeOptionSkipPointers | eTypeOptionSkipReferences避免影响指向这些类型的指针的显示注释特别说明i8在 MSVC 上会翻译为signed char而 Rust 的char最终类型名是char32_t与 C 的char不冲突因此可以直接覆盖。Synthetic Provider接口契约与每个方法的使用要点Synthetic Provider 是一个遵循特定接口编写的Python 类与一个或多个 Rust 类型关联。它包装SBValue对象LLDB 在检查变量时会调用该类的函数。官方文档synthetic-children中部分信息含糊、过时甚至缺失因此 rustc 团队在实践中整理出了下述精确契约。被包装的值仍然是SBValue但调用例如SBValue.GetChildAtIndex时内部实际会调用SyntheticProvider.get_child_at_index。你可以通过以下方式探查SBValue.IsSynthetic()判断该值是否挂有 synthetic providerSBValue.GetTypeSynthetic()获取挂载的 synthetic 类型信息SBValue.GetNonSyntheticValue()获取底层非 synthetic 的值。接口原型如下class SyntheticProvider: def __init__(self, valobj: SBValue, _lldb_internal): ... # optional def update(self) - bool: ... # optional def has_children(self) - bool: ... # optional def num_children(self, max_children: int) - int: ... def get_child_index(self, name: str) - int: ... def get_child_at_index(self, index: int) - SBValue: ... # optional def get_type_name(self) - str: ... # optional def get_value(self) - SBValue: ...下面逐方法说明其语义、坑点与惯用法。若某方法重写了SBValue的对应方法会一并标注。__init__只做一件事每个对象调用一次。必须把valobj存到 Python 类字段中供其他方法访问。除此之外应尽量少做其他事重型、易变的信息应放在update中。可选update刷新状态并决定缓存是否可用在 LLDB 与变量交互之前、__init__之后调用。LLDB 会跟踪update是否已被调用如果已调用过、且变量不可能发生变化例如未步进就再次检查同一变量则会省略对update的调用。它有两大职责存储/更新自上次update以来可能变化的信息告知 LLDB 子项是否发生变化、是否需要刷新子项缓存。典型操作包括存储Vec的堆指针、长度、容量和元素类型确定枚举变量的变体variant检查HashMap的哪些槽位被占用。update的缓存语义关键LLDB 会尽可能缓存值包括子项。这个缓存本质上就是子对象的数量 子对象所对应的被调试进程内存地址。update的返回值含义如下返回True告诉 LLDB 子项数量与子项地址自上次update以来没有变化可以复用缓存中的子项。在错误场景下返回True会导致调试器输出错误信息。比如子项数量或底层内存地址变了却仍返回True输出就会失真。返回False表示有变化缓存被清空子项全部重新获取。这是更安全的选择不确定时请返回False或None。要点只关心父子关系孙项是否命中缓存只取决于其直接父级的update与祖父级无关。把子项缓存理解为指向内存的指针以切片为例只要data_ptr与length没变返回True就是合适的。即使切片是可变的、元素内容被覆盖如slice[0] 15由于缓存的是指针它们依然能反映该内存位置的新数据。反之若data_ptr变化说明指向了新的内存位置旧的子指针全部失效必须刷新缓存若length变化说明子项个数变了也必须刷新。若length变了而data_ptr没变可以把旧子项保存在SyntheticProvider内部例如list[SBValue]按需复用而不是全部重建。截至 2025 年 11 月仓库中的可视化器还没有任何update返回True的实现但随调试信息测试套件的改进这可能改变。测试缓存行为的注意点不要依赖 LLDB 步进时保留变量的启发式行为。应先把变量存进 Python 对象例如v lldb.frame.var(var_name)步进后再检查这个已存储的变量。可选has_children低成本的是否有子项判断重写SBValue.MightHaveChildren。这是 LLDB 用来判断值是否有子项的快捷方式避免做潜在昂贵的计数计算例如链表求长度。通常一行搞定return True、return False或return self.valobj.MightHaveChildren()。可选num_children子项总数重写SBValue.GetNumChildren。返回 LLDB 打印该类型时应尝试访问的子项总数。这个数字不必等于合成子项的总数。若计算子项数量开销很大如链表可直接返回max_children参数无此顾虑时可以省略该参数。隐藏字段技巧可以有意识地让某些字段对 LLDB 隐藏但用户仍可访问。例如希望vec![1, 2, 3]只显示元素但len和capacity仍然可按需访问num_children返回3即可把显示限制为[1, 2, 3]而用户仍可直接访问v.len、v.capacity。这正是 VecT 示例 采用的方案。get_child_index按名字解析索引重写SBValue.GetIndexOfChildWithName影响SBValue.GetChildMemberWithName。给定一个名字返回该子项应被访问的索引。返回值应直接传给get_child_at_index。与num_children一样这里返回的值可以是任意的只要与get_child_at_index协调一致。有一个特殊值$$dereference$$。处理这个伪字段可以让 LLDB 把get_child_at_index返回的SBValue当作表达式解析器中解引用的结果例如*val和val-field。get_child_at_index按索引生成子项重写SBValue.GetChildAtIndex。给定索引返回一个子SBValue。常用生成方式SBValue.CreateValueFromAddress最常见从地址构造较少用SBValue.CreateChildAtOffset、SBValue.CreateValueFromExpression、SBValue.CreateValueFromData。这些函数比较挑剔可能需要反复调试才能得到想要的输出。某些场景下SBValue.Clone更合适它创建一个已有子项的精确副本但换上新名字。典型用途是元组——其字段名是__0、__1风格而我们希望显示为0、1。返回前可以对子项做小幅修改。典型场景是str/String我们更希望子项以lldb.eFormatBytesWithASCII显示而不是十进制数值。可选get_type_name覆盖类型显示名重写SBValue.GetDisplayTypeName。覆盖类型的显示名称。注意对于类型名被覆盖的 syntheticSBValue原始类型名仍可通过SBValue.GetTypeName()和SBValue.GetType().GetName()取回。典型用途缩短标准库长类型名如std::collections::hash::map::HashMapK, V, std::hash::random::RandomState→HashMapK, V归一化 MSVC 类型名如ref$str$→str。字符串处理可能比较棘手尤其是 MSVC 上无法方便地获取类型的泛型参数时。可选get_value提供表达式求值用的值重写SBValue.GetValue()、SBValue.GetValueAsUnsigned()、SBValue.GetValueAsSigned()、SBValue.GetValueAsAddress()。返回的SBValue应为基本类型或指针在表达式中被视为该变量的值。重要 Bug 提醒返回的SBValue必须保存在SyntheticProvider中。截至 2025 年 11 月存在一个已知 bug如果在get_value内获取SBValue却没有存储到任何地方LLDB 访问该值时 Python 会段错误segfault。Summary Provider一行摘要Summary provider 是如下形式的 Python 函数def SummaryProvider(valobj: SBValue, _lldb_internal) - str: ...返回的字符串会原样呈现给用户。如果返回值不是字符串会被朴素地转换为字符串例如return None会打印None而不是空字符串。如果传入的SBValue类型挂有 Synthetic Provider则valobj.IsSynthetic()返回True且会使用 synthetic 对应的函数。若不需要这种间接可通过valobj.GetNonSyntheticValue()取回原始值。这在String等场景很关键逐个调用GetChildAtIndex循环取字符比直接读取堆指针、一次性读取被调试进程内存中的整个字节数组、再用 Python 的bytes.decode()慢得多。实例级 SummaryInstance Summaries访问 provider 内部状态普通SummaryProvider函数拿到的是一个不透明的SBValue。该SBValue若类型挂有SyntheticProvider会反映其效果但无法访问SyntheticProvider实例本身及其内部实现细节。在摘要需要这些内部细节时就很麻烦截至 2025 年 11 月仓库中的做法是synth SyntheticProvider(valobj.GetNonSyntheticValue(), _dict)把非合成值重新跑一遍 synthetic显然不够优雅后续有计划改进。更好的方案利用 Python 模块级状态实现实例摘要。这一技术早有先例旧版 CodeLLDB 的 Rust 可视化脚本。核心思路每个SyntheticProvider的__init__中把唯一 ID 与自身的弱引用存进一个全局字典SyntheticProvider类额外实现get_summary函数该类型的SummaryProvider用唯一 ID 查字典取回实例后调用其get_summary。import weakref SYNTH_BY_ID weakref.WeakValueDictionary() class SyntheticProvider: valobj: SBValue # slots requires opting-in to __weakref__ __slots__ (valobj, __weakref__) def __init__(valobj: SBValue, _dict): SYNTH_BY_ID[valobj.GetID()] self self.valobj valobj def get_summary(self) - str: ... def InstanceSummaryProvider(valobj: SBValue, _dict) - str: # GetNonSyntheticValue should never fail as InstanceSummaryProvider implies an instance of a # SyntheticProvider. No non-synthetic types should ever have this summary assigned to them # We use GetNonSyntheticValue because the synthetic vaobj has its own unique ID return SYNTH_BY_ID[valobj.GetNonSyntheticValue().GetID()].get_summary()一个典型应用是枚举的 synthetic provider摘要需要访问变体名variant name但类型名或合成子项难以方便地反映这一点。通过实例摘要可以在self.variant.GetTypeName()基础上做字符串处理取出变体名。编写可视化器脚本加载与注册重要与 GDB 和 CDB 不同LLDB 可以调试携带DWARF 或 PDB调试信息的可执行文件。可视化器必须尽可能同时兼容两种格式差异概览参见 rust-codegenDWARF vs PDB。脚本通过 CLI 命令注入 LLDBcommand script import path-to-script.py注入后用type synthetic add和type summary add分别把类和函数加入 synthetic/summary 池。摘要和合成器可以关联到category类别通常以目标语言命名。Rust 使用的类别名为Rust。提示所有 LLDB 命令都可以用help前缀获取简要说明、参数列表与示例例如help type synthetic add。历史上 rustc 用command source ...从lldb_commands文件执行一串 CLI 命令来注册 provider该文件相当笨重已被下述 Python API 方案取代。__lldb_init_module脚本初始化钩子这是可选函数形式如下def __lldb_init_module(debugger: SBDebugger, _lldb_internal) - None: ...它在command script import ...结束时、控制权交还 CLI 之前被调用允许脚本初始化自身状态。关键是它拿到了 debugger 本身的引用从而可以创建Rust类别并向其中添加 provider也可以根据检测到的 LLDB 版本有条件地切换使用的 provider——这在开始使用 recognizer 函数后至关重要因为recognizer 是 LLDB 19.0 才引入的。仓库实现 lldb_lookup.py 中的__lldb_init_module正是如此获取或创建Rust类别并SetEnabled(True)随后调用register_providers_compatibility()完成全部注册。该文件还详细记录了注册细节默认类型选项DEFAULT_TYPE_OPTIONS组合了eTypeOptionCascade沿 typedef 链生效、eTypeOptionHideEmptyAggregates与eTypeOptionFrontEndWantsDereference让 provider 拿到解引用后的类型方便按 pointee 类型推理注册顺序至关重要LLDB 匹配 provider 时按逆序迭代已有 provider从而允许新注册的 provider 覆盖旧注册的。文件头注释警告修改注册顺序要格外小心为每种标准库类型注册了对应的合成器与摘要器String、str/Boxstr、切片、OsString、Vec、VecDeque、HashMap/HashSet、Rc/Arc、Cell/RefCell、NonZero、Path/PathBuf、MSVC 枚举与元组等并针对 GNUDWARF与 MSVCPDB分别设计了不同的正则。可视化器解析顺序可视化器的解析顺序详见 LLDB 官方finding-formatters-101文档为若有精确匹配非正则名称、recognizer 函数、或已匹配过 provider 的类型使用之若对象是指针/引用尝试用解引用后类型的 formatter若是 typedef检查底层类型是否有 formatter若以上都不行遍历正则类型匹配器。在上述每一步中迭代都是逆序的让新命令能覆盖旧命令。这对BoxstrvsBoxT这类场景很重要前者希望有专门的 synthetic后者用更通用的 synthetic。仓库代码同样遵循此规则注册了^(alloc::([a-z_]::))Boxstr,.*$专用于Boxstr与更宽泛的Vec.、Rc.等正则在无 recognizer 的老版本 LLDB 上还会用.*兜底注册synthetic_lookup分发函数把未匹配类型按RustType分类struct/union/tuple/enum/empty 等路由到相应 provider。杂项细节MinutiaeLLDB 的 Python API 很强大但存在一些陷阱与非直观行为。Python 实现可以在lldb -P返回路径下的lldb\__init__.py中查看。除了 LLDB 仓库中的 synthetic 示例还有 C 可视化器可作参考例如LibCxxVector即VecT的 C 对应物——虽然 C 可视化器用 C 编写且能访问 LLDB 内部但 API 与通用实践非常相似。SBValue注意事项指针/引用SBValue在部分场景会自动解引用表现得像被指向对象的子项就是它自己的子项。非函数字段通常是property()字段直接指向对应函数如SBValue.type property(GetType, None)。通过这些简写访问比直接调用函数慢应避免。部分属性会返回带特殊行为的对象如SBValue.member返回类似dict[str, SBValue]的对象用于访问子项内部这些特殊对象往往只是再分配一个新类实例并调用SBValue的函数造成额外性能损失如SBValue.member的__getitem__本质上就是一行return self.valobj.GetChildMemberWithName(name)。SBValue.GetID为每个值返回一个调试会话内唯一的int。SyntheticSBValue的 ID 与底层SBValue不同底层 ID 可通过SBValue.GetNonSyntheticValue().GetID()获取。手动计算地址时应优先用SBValue.GetValueAsAddress而非SBValue.GetValueAsUnsigned因为存在目标相关的特殊行为。获取SBValue的字符串表示很棘手GetSummary需要 summary providerGetValue要求类型能用基本类型表示。两者都不满足时类型几乎都是用户自定义结构体可以交给StructSummaryProvider处理。SBType注意事项聚合类型Aggregate type指非基本类型的 struct/class/unionTemplate 等价于 Generic泛型类型可通过SBTarget.FindFirstType(type_name)按名查找SBTarget可用SBValue.GetTarget获取SBType.template_args在类型无泛型时返回None而不是空列表有时需要借助SBType.GetArrayType、SBType.GetPointerType等函数把类型变换为目标类型。这些函数不会失败它们直接询问底层 LLDBTypeSystem插件取类型完全绕过调试信息——即使调试信息中根本不存在该类型也能创建出合适的类型SBType.GetCanonicalType等价于SBType.GetTypedefedTypeSBType.GetUnqualifiedType。与GetTypedefedType不同无论原SBType是否为 typedef它总是返回有效的SBTypeSBType.GetStaticFieldWithName是 LLDB 18 才加入的。由于静态字段除此之外完全无法访问向后兼容并非总是可行。仓库中的 lldb_providers.py 对上述细节有大量实战印证例如get_template_args用字符串解析替代SBType.template_argsLLDB 对 PDB 调试信息无法填充该字段同时用于手动改写泛型类型名如Vecref$str$ →Vecstrresolve_msvc_template_arg递归处理ref$/array$/slice2$等 MSVC 类型包装LLDB 内部把引用、指针、数组按 C 风格解释u8→u8 *按名字查找仍不可用于是改用GetPointerType()/GetArrayType()直接向 clang 请求类型节点通过LLDBFeature位标志检测 LLDB 版本能力GetStaticFieldWithName→LLDB 18、recognizer→19、Float128→22.1、GetParent→23.1 等并在detect_features中用getattr探测 API 是否存在避免依赖 Apple LLDB 与 LLVM 版本号体系不一致的问题。完整示例VecTProvider以下示例来自 rustc-dev-guide 原文是理解上述全部接口的最佳范本。仓库中功能等价的正式实现为StdVecSyntheticProvider见 lldb_providers.py它额外处理了RawVec/Unique/NonNull在不同 Rust 版本间的字段布局差异docstring 中注释了 rust 1.31.1 / 1.33.0 / 1.62.0 / 1.75 / 1.76 的结构变化并带有capacity 高位为 1 即视为悬空 Vec的保护逻辑。SyntheticProvider典型的 prelude 使用__slots__因为字段已知。除了对象本身还需要存储元素类型Vec的堆指针是*mut u8而非*mut T。Rust 是静态类型语言T永不改变因此可以在初始化时存储而堆指针、长度、容量会变化这里先做默认初始化。import lldb class VecSyntheticProvider: valobj: SBValue data_ptr: SBValue len: int cap: int element_type: SBType __slots__ ( valobj, data_ptr, len, cap, element_type, __weakref__, ) def __init__(valobj: SBValue, _dict) - None: self.valobj valobj # invalid type is a better default than None self.element_type SBType() # special handling to account for DWARF/PDB differences if (arg : valobj.GetType().GetTemplateArgumentType(0)): self.element_type arg else: arg_name next(get_template_args(valobj.GetTypeName())) self.element_type resolve_msvc_template_arg(arg_name, valobj.GetTarget())get_template_args与resolve_msvc_template_arg的完整实现见仓库 lldb_providers.py。接下来是update函数。我们检查指针与长度是否变化可以省略容量检查——子项数量在len不变时不会变化而容量变化若引发重分配data_ptr的地址必然不同。若data_ptr与length都没变可以利用 LLDB 的缓存直接提前返回若变了则存下新值并通知 LLDB 刷新缓存。def update(self): ptr self.valobj.GetChildMemberWithName(data_ptr) len self.valobj.GetChildMemberWithName(length).GetValueAsUnsigned() if ( self.data_ptr.GetValueAsAddress() ptr.GetValueAsAddress() and self.len len ): # Our child address offsets and child count are still valid # so we can reuse cached children return True self.data_ptr ptr self.len len return Falsehas_children与num_children都很直接def has_children(self) - bool: return True def num_children(self) - int: return self.len访问元素时我们期望[0]、[1]这样的名字来模拟索引。同时用户仍应能快速访问长度与容量调试时非常有用。为它们分配u32::MAX - 1与u32::MAX - 2两个索引几乎可以保证不会与元素索引重叠。注意同时兼容cap简写与完整capacity名字。def get_child_index(self, name: str) - int: index name.lstrip([).rstrip(]) if index.isdigit(): return int(index) if name len: return lldb.UINT32_MAX - 1 if name cap or name capacity: return lldb.UINT32_MAX - 2 return -1现在协调get_child_at_index让元素、长度、容量都可访问def get_child_at_index(self, index: int) - SBValue: if index UINT32_MAX - 1: return self.valobj.GetChildMemberWithName(len) if index UINT32_MAX - 2: return ( self.valobj.GetChildMemberWithName(buf) .GetChildMemberWithName(inner) .GetChildMemberWithName(cap) .GetChildAtIndex(0) .Clone(capacity) ) addr self.data_ptr.GetValueAsAddress() addr index * self.element_type.GetByteSize() return self.valobj.CreateValueFromAddress(f[{index}], addr, self.element_type)对于类型显示名可以剥掉路径限定符。用户自定义的名为Vec的类型最终会带上完整限定名因此不存在歧义。还可以去掉分配器泛型参数极少有用。用get_template_args而非self.element_type.GetName()有三个原因若元素类型解析失败self.valobj的类型名仍能让用户知道元素的真实类型类型名不受 DWARF 与 PDB 节点的限制名字中的模板类型能反映*const/*mut、/mut等细节截至 2025 年 11 月我们尚未归一化 MSVC 类型名一旦开始归一化就必须处理类型的字符串名——而字符串到字符串的转换比SBType到字符串的转换更容易缓存。def get_type_name(self) - str: return fVec{next(get_template_args(self.valobj))}Vec没有合适的基本值可以表示因此省略get_value函数。SummaryProvider得益于 synthetic provider摘要函数非常简单。唯一的麻烦是GetSummary仅在对象类型挂有SummaryProvider时才返回值否则返回空字符串这不理想。在完整的一套可视化脚本中可以确保所有既没有GetSummary()也没有GetValue()的类型都是结构体然后把它们委托给通用的StructSummaryProvider。示例中略过这一细节def VecSummaryProvider(valobj: SBValue, _lldb_internal) - str: children [] for i in range(valobj.GetNumChildren()): child valobj.GetChildAtIndex(i) summary child.GetSummary() if summary is None: summary child.GetValue() if summary is None: summary {...} children.append(summary) return fvec![{, .join(children)}]启用 provider假设该 synthetic 已被导入到lldb_lookup.py中。方式一CLI 命令type synthetic add -l lldb_lookup.synthetic_lookup -x ^(alloc::([a-z_]::))Vec.$ --category Rust type summary add -F lldb_lookup.summary_lookup -x ^(alloc::([a-z_]::))Vec.$ --category Rust方式二__lldb_init_moduledef __lldb_init_module(debugger: SBDebugger, _dict: LLDBOpaque): # Ensure the category exists and is enabled rust_cat debugger.GetCategory(Rust) if not rust_cat.IsValid(): rust_cat debugger.CreateCategory(Rust) rust_cat.SetEnabled(True) # Register Vec providers vec_regex r^(alloc::([a-z_]::))Vec.$ sb_name lldb.SBTypeNameSpecifier(vec_regex, is_regexTrue) sb_synth lldb.SBTypeSynthetic.CreateWithClassName(lldb_lookup.VecSyntheticProvider) sb_synth.SetOptions(lldb.eTypeOptionCascade) sb_summary lldb.SBTypeSummary.CreateWithFunctionName(lldb_lookup.VecSummaryProvider) sb_summary.SetOptions(lldb.eTypeOptionCascade) rust_cat.AddTypeSynthetic(sb_name, sb_synth) rust_cat.AddSummary(sb_name, sb_summary)仓库真实代码 lldb_lookup.py 中的register_synth/register_summary辅助函数与此完全同构用SBTypeSynthetic.CreateWithClassName/SBTypeSummary.CreateWithFunctionName创建 provider 描述、SetOptions设置选项、RUST_CATEGORY.AddTypeSynthetic/AddTypeSummary注册并检查返回值、失败时打印警告。注册Vec时实际使用的正则为^(alloc::([a-z_]::))Vec.$摘要器用的是SizeSummaryProvider输出sizeN。输出对比未启用 provider 时(lldb) v vec_v (alloc::vec::Vecint, alloc::alloc::Global) vec_v { buf { inner { ptr { pointer (pointer \n) _marker {} } cap (__0 5) alloc {} } _marker {} } len 5 } (lldb) v vec_v[0] error: user expression 0:1:6: subscripted value is not an array or pointer 1 | vec_v[0] | ^启用 provider 后v var_name打印摘要加所有子项(lldb) v vec_v (Vecint) vec_v vec![10, 20, 30, 40, 50] { [0] 10 [1] 20 [2] 30 [3] 40 [4] 50 } (lldb) v vec_v[0] (int) vec_v[0] 10同时可以确认隐藏的长度与容量仍可访问(lldb) v vec_v.len (unsigned long long) vec_v.len 5 (lldb) v vec_v.capacity (unsigned long long) vec_v.capacity 5 (lldb) v vec_v.cap (unsigned long long) vec_v.cap 5延伸阅读与仓库资源完整可视化器脚本src/etc/lldb_providers.pyprovider 实现与 src/etc/lldb_lookup.py类型匹配与注册调试信息系列文档调试器可视化器总览、LLDB 内部机制、可视化器测试tests/debuginfo测试套件与repr指令、--bless流程用于验证本文所述的 provider 行为若需在调试会话中加载这些脚本可借助随工具链分发的rust-lldb支持脚本见 debugger-visualizers.md它会定位调试器与工具链的可视化器脚本并自动加载【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表