Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

LLDB - Python プロバイダー

注: LLDB の C++<->Python FFI は、LLDB のコンパイル時に指定されたバージョンの Python を想定しています。LLDB は、このバージョンが一般的な Linux および macOS ディストリビューションの最小バージョンに対応するよう注意していますが、Windows では簡単な解決策はありません。_lldb が存在しないことに関するインポートエラーが発生した場合、Python バージョンの不一致が原因である可能性が高いです。

LLDB はこの問題の解決策を検討しています。更新については、 この議論およびこの github issueを参照してください

注: 2025 年 11 月時点で、 LLDB がサポートする Python の最小バージョンは 3.8 であり、いくつかの外部要因に応じて 3.9 または 3.10 に更新する計画があります。スクリプトは、サポートされる Python の最小バージョンで利用可能な機能のみを使用して書くのが理想です。詳細については、この議論を参照してください。

注: LLDB の Python パッケージへのパスは、CLI コマンド lldb -P で確認できます

LLDB は出力をカスタマイズするための 3 つの仕組みを提供します。

  • フォーマット
  • Synthetic Provider
  • Summary Provider

フォーマット

公式ドキュメントはこちらです。 要するに、 フォーマットを使用すると、プリミティブ型のデフォルトの出力形式を設定できます(たとえば、25u8 を 10 進数の 25、16 進数の 0x19、または 2 進数の 00011001 として出力するなど)。

Rust ではほぼ常に、unsigned charsigned charcharu8、および i8 を (符号なし)10 進数形式にオーバーライドする必要があります。

Synthetic Provider

公式ドキュメントはこちらですが、 一部の情報は曖昧であったり、古くなっていたり、完全に欠落していたりします。

ユーザーが変数と行うほぼすべてのやり取りは、LLDB の SBValue オブジェクトを通じて行われます。これは Python API の両方で使用され、また LLDB の プラグインおよび CLI を通じて内部的にも使用されます。

Synthetic Provider は、特定のインターフェイスを持つように書かれた Python クラスであり、 1 つ以上の Rust 型に関連付けられます。 Synthetic Provider は SBValue オブジェクトをラップし、LLDB は変数を検査するときに このクラスの関数を呼び出します。

ラップされた値は依然として SBValue ですが、たとえば SBValue.GetChildAtIndex を呼び出すと、 内部的には SyntheticProvider.get_child_at_index が呼び出されます。 値に synthetic provider があるかどうかは SBValue.IsSynthetic() で確認でき、どの synthetic であるかは SBValue.GetTypeSynthetic() で確認できます。 基になる非 synthetic 値を操作したい場合は、 SBValue.GetNonSyntheticValue() を呼び出すことができます。

想定されるインターフェイスは次のとおりです。

class SyntheticProvider:
    def __init__(self, valobj: SBValue, _lldb_internal): ...

    # 任意
    def update(self) -> bool: ...

    # 任意
    def has_children(self) -> bool: ...

    # 任意
    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: ...

    # 任意
    def get_type_name(self) -> str: ...

    # 任意
    def get_value(self) -> SBValue: ...

以下では、各メソッド、その癖、および一般的な使用方法について説明します。 メソッドが SBValue メソッドをオーバーライドする場合、そのメソッドを示します。

__init__

この関数はオブジェクトごとに 1 回呼び出され、valobj を Python クラス内に保存して、 他の場所からアクセスできるようにしなければなりません。 ここではそれ以外のことはほとんど行うべきではありません。

(任意)update

この関数は、LLDB が変数を操作する前、ただし __init__ の後に呼び出されます。 LLDB は、update がすでに呼び出されたかどうかを追跡します。 すでに呼び出されていて、その変数が変更された可能性がない場合 (たとえば、ステップ実行せずに同じ変数を 2 回目に検査する場合)、update の呼び出しは省かれます。

この関数には 2 つの目的があります。

  • 前回 update が実行されてから変更された可能性のある情報を保存/更新する
  • 子に変更があり、子キャッシュをフラッシュすべきであることを LLDB に通知する。

典型的な操作には、Vec のヒープポインター、長さ、容量、要素型を保存すること、 enum 変数のバリアントを判定すること、または HashMap のどのスロットが使用されているかを確認することが含まれます。

この関数から返される bool はやや複雑です。詳細については、以下の update のキャッシュを参照してください。 迷った場合は、False/None を返してください。

2025 年 11 月時点で、

いずれのビジュアライザーも True を返していませんが、デバッグ情報 テストスイートが改善されるにつれて変わる可能性があります。

update のキャッシュ

LLDB は、子の値を含め、可能な場合には値をキャッシュしようとします。 このキャッシュは実質的に、 子オブジェクトの数と、その子オブジェクトが表す基になるデバッグ対象メモリのアドレスです。 True を返すことで、子の数とそれらの子のアドレスが 前回 update が実行されてから変更されていないことを LLDB に示し、キャッシュ済みの子を再利用できることを意味します。

不適切な状況で True を返すと、デバッガーが誤った情報を出力する結果になります

False を返すと、変更があったことを示し、キャッシュはフラッシュされ、 子は最初から取得されます。 不確かな場合はこちらのほうが安全な選択肢です。

重要なのは親から子への関係のみです。 孫は祖父母ではなく、直接の親の update 関数に依存します。

子キャッシュはメモリへのポインターとして見ることが重要です。 たとえば、スライスの data_ptr 値と length が変更されていない場合、True を返すのが適切です。 スライスがミュータブルで、 その要素が上書きされた場合(たとえば slice[0] = 15)であっても、子キャッシュは ポインターで構成されているため、そのメモリ位置にある新しいデータを反映します。

逆に、data_ptr が変更された場合、それはメモリ内の新しい場所を指していることを意味し、 子ポインターは無効であり、キャッシュをフラッシュしなければなりません。 length が変更された場合は、新しい子の数を反映するためにキャッシュをフラッシュする必要があります。 length が変更されたが data_ptr は 変更されていない場合、古い子を SyntheticProvider 自体に保存しておき(たとえば list[SBValue])、それらを最初から生成する代わりに配ることが可能です。新しい子は SyntheticProvider のリストにまだ存在しない場合にのみ作成します。

さらに明確にするには、この議論を参照してください

注: キャッシュ動作をテストする際は、ステップ実行時に変数を永続化する LLDB のヒューリスティックに依存しないでください。 代わりに、変数を Python オブジェクトに保存し(たとえば v = lldb.frame.var("var_name"))、 前方にステップ実行してから、保存した変数を検査してください。

(任意)has_children

SBValue.MightHaveChildren をオーバーライドします これは、値が子をまったく持つかどうかを、子の数を判定するための潜在的に高コストな計算(例: 連結リスト)を行わずに確認するために LLDB が使用するショートカットです。 多くの場合、これは return True/return False、または return self.valobj.MightHaveChildren() の 1 行になります。

(任意)num_children

SBValue.GetNumChildren をオーバーライドします

型を表示する際に LLDB がアクセスを試みるべき子の総数を返します。 この数は、合成された子の総数と一致している必要はありません

子の数を計算するのにコストがかかる可能性がある場合(例: 連結リスト)は、max_children 引数を返すことができます。 これを考慮する必要がない場合は、関数シグネチャから max_children を省略できます。

さらに、フィールドをユーザーからは引き続きアクセス可能にしたまま、LLDB から意図的に「非表示」にできます。 たとえば、vec![1, 2, 3] では要素だけを表示したい一方で、lencapacity の値は要求に応じてアクセス可能にしておきたい場合があります。 num_children から 3 を返すことで、 LLDB に [1, 2, 3] のみを表示するよう制限しつつ、ユーザーは引き続き v.lenv.capacity に直接アクセスできます。 この実装例については、Example Provider: Vec<T> を参照してください。

get_child_index

SBValue.GetIndexOfChildWithName をオーバーライドします

SBValue.GetChildMemberWithName に影響します

名前を指定すると、その子にアクセスすべきインデックスを返します。 この関数の戻り値は get_child_at_index に直接渡されることが想定されています。 num_children と同様に、ここで返される値は、get_child_at_index と適切に連携している限り、任意の値にすることができます

特別な値の 1 つに $$dereference$$ があります。 この疑似フィールドを考慮すると、LLDB は get_child_at_index から返された SBValue を、LLDB の式パーサーによる参照外しの結果として使用できるようになります (例: *val および val->field)。

get_child_at_index

SBValue.GetChildAtIndex をオーバーライドします

インデックスを指定すると、子の SBValue を返します。 多くの場合、これらは SBValue.CreateValueFromAddress によって生成されますが、それほど一般的ではないものとして SBValue.CreateChildAtOffsetSBValue.CreateValueFromExpressionSBValue.CreateValueFromData もあります。 これらの関数はやや扱いづらいことがあるため、目的の出力を得るには調整が必要になる場合があります。

場合によっては、SBValue.Clone が適切です。 これは既存の子の完全なコピーでありながら、新しい名前を持つ新しい子を作成します。 これは、タプルのようにフィールド名が __0__1、… という形式であるものを、01、… という名前にしたい場合に便利です。

返す前に、結果の子に小さな変更を加えることができます。 これは &str/String の場合に便利で、子を単なる 10 進値としてではなく、 lldb.eFormatBytesWithASCII として表示したい場合があります。

(任意)get_type_name

SBValue.GetDisplayTypeName をオーバーライドします

型の表示名をオーバーライドします。 型名がオーバーライドされた合成 SBValue についても、 元の型名は SBValue.GetTypeName() および SBValue.GetType().GetName() で引き続き取得できます。

これは、一般的な標準ライブラリ型の名前を短縮する場合(例: std::collections::hash::map::HashMap<K, V, std::hash::random::RandomState> -> HashMap<K, V>)や、 MSVC の型名を正規化する場合(例: ref$<str$> -> &str)に役立ちます。

文字列操作は少し難しい場合があり、特に型のジェネリックパラメーターへ簡単にアクセスできない MSVC ではそうです。

(任意)get_value

SBValue.GetValue()SBValue.GetValueAsUnsigned()SBValue.GetValueAsSigned()SBValue.GetValueAsAddress() をオーバーライドします

返される SBValue はプリミティブ型またはポインターであることが想定され、式の中で変数の値として扱われます。

重要: 返される SBValueSyntheticProvider に保存されていなければなりません

2025年11月時点では、

SBValueget_value 内で取得され、どこにも保存されていない場合、 LLDB がその値へアクセスしようとすると Python がセグフォルトするバグがあります。

サマリープロバイダー

サマリープロバイダーは、次の形式の Python 関数です。

def SummaryProvider(valobj: SBValue, _lldb_internal) -> str: ...

ここで、返された文字列はそのままユーザーに渡されます。 返された値が文字列でない場合、それは単純に文字列へ変換されます(例: return None は空文字列ではなく "None" を出力します)。

渡された SBValue が合成プロバイダーを持つ型である場合、valobj.IsSynthetic()True を返し、その合成に対応する関数が使用されます。 これが望ましくない場合は、valobj.GetNonSyntheticValue() によって元の値を取得できます。 これは String のようなケースで役立ちます。この場合、ループ内で GetChildAtIndex を個別に呼び出すよりも、 ヒープポインターにアクセスして、デバッグ対象のメモリからバイト配列全体を直接読み取り、 Python の bytes.decode() を使用する方がはるかに高速です。

インスタンスサマリー

通常の SummaryProvider 関数は不透明な SBValue を受け取ります。 その SBValue は、存在する場合は型の SyntheticProvider を反映しますが、SyntheticProvider インスタンス自体や、 その内部実装の詳細にはアクセスできません。 これは、サマリーを完成させるためにそうした内部詳細の一部が必要な場合に不利です。

2025年11月時点では、合成内で非合成値を合成プロバイダーに通しているだけです

synth = SyntheticProvider(valobj.GetNonSyntheticValue(), _dict))が、これは明らかに最適ではなく、 以下に示す方法を使用する計画があります。

代わりに、Python モジュールの状態を活用してインスタンスサマリーを実現できます。 この手法の先行例は、古い CodeLLDB Rust ビジュアライザースクリプト にあります。

要するに、すべての合成プロバイダーの __init__ 関数は、一意な ID と self への弱参照をグローバル辞書に保存します。 合成プロバイダークラスは get_summary 関数も実装します。 型の SummaryProvider は、この辞書で一意な ID を検索し、 取得したインスタンス上の get_summary を呼び出す関数です。

import weakref

SYNTH_BY_ID = weakref.WeakValueDictionary()

class SyntheticProvider:
    valobj: SBValue

    # slots では __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:
    # InstanceSummaryProvider は `SyntheticProvider` のインスタンスを前提とするため、
    # GetNonSyntheticValue が失敗することは決してありません。非合成型にこのサマリーが
    # 割り当てられることは決してないはずです
    # 合成 vaobj は固有の ID を持つため、GetNonSyntheticValue を使用します
    return SYNTH_BY_ID[valobj.GetNonSyntheticValue().GetID()].get_summary()

たとえば、これを Enum の合成プロバイダーに使用できます。 サマリーは バリアント名にアクセスしたいところですが、synthetic の型名や子の値を介してこれを反映する便利な方法はありません。 インスタンスサマリーを実装することで、 self.variant.GetTypeName() といくつかの文字列操作を介してバリアント名を取得できます。

ビジュアライザースクリプトの作成

重要: GDB や CDB とは異なり、LLDB は DWARF または PDB デバッグ情報のいずれかを持つ実行可能ファイルをデバッグできます。 ビジュアライザーは、可能な限り両方の形式に対応するように記述する必要があります。違いの概要については、次を参照してください: rust-codegen

スクリプトは CLI コマンド command script import <path-to-script>.py によって LLDB に注入されます。 注入されると、 クラスと関数はそれぞれ type synthetic add および type summary add で synthetic/summary プールに追加できます。 サマリーと synthetic は 「カテゴリー」に関連付けることができ、これは通常、プロバイダーが対象とする言語にちなんで名付けられます。 使用するカテゴリーは Rust と呼ばれます。

ヒント: すべての LLDB コマンドには、簡潔な説明、引数の一覧、および例を表示するために help を前置できます(例: help type synthetic add)。

2025 年 11 月現在、

プロバイダーを追加するために、一連の CLI コマンドを ファイル lldb_commands から実行する command source ... を使用しています。 このファイルはやや扱いにくく、まもなく以下で概説する Python API 相当のものに置き換えられる予定です。

__lldb_init_module

これは次の形式の任意の関数です:

def __lldb_init_module(debugger: SBDebugger, _lldb_internal) -> None: ...

この関数は command script import ... の最後、ただし制御が CLI に戻る前に呼び出されます。 これにより、スクリプトは自身の状態を初期化できます。

重要なのは、この関数にはデバッガー自体への参照が渡されることです。 これにより、Rust カテゴリーを作成し、それにプロバイダーを追加できます。 また、スクリプトが検出した LLDB のバージョンに応じて、 使用するプロバイダーを条件付きで変更することもできます。 recognizer 関数の使用を開始すると、recognizer は lldb 19.0 で追加されたため、これは後方互換性に不可欠です。

ビジュアライザーの解決

ビジュアライザーが解決される順序は こちら に記載されています。 簡単に言うと、次のとおりです:

  • 完全一致(正規表現ではない名前、recognizer 関数、またはプロバイダーにすでにマッチ済みの型)がある場合は、 それを使用する
  • オブジェクトがポインター/参照の場合、逆参照された型のフォーマッターを使用しようとする
  • オブジェクトが typedef の場合、基になる型にフォーマッターがあるか確認する
  • 上記のいずれもうまくいかない場合、正規表現の型マッチャーを順に処理する

これらの各ステップ内では、新しいコマンドが古いコマンドを「上書き」できるように、反復は逆順で行われます。 これは、Box<str>Box<T> のようなケースで重要です。前者には特化した synthetic が必要ですが、後者にはより一般化された synthetic が必要だからです。

細かな事項

LLDB の API は非常に強力ですが、いくつかの「落とし穴」や直感的でない挙動があり、その一部を 以下で概説します。 Python 実装は、CLI コマンド lldb -P によって返されるパスの lldb\__init__.py で確認できます。 lldb リポジトリの例 に加えて、参照として使用できる C++ ビジュアライザー もあります (例: Vec<T> に相当する LibCxxVector)。C++ のビジュアライザーは C++ で書かれており、LLDB の内部へアクセスできますが、API と一般的な実践は非常によく似ています。

SBValue

  • ポインター/参照の SBValue は、一部のケースでは実質的に「自動逆参照」され、 指し先オブジェクトの子要素が自身の子要素であるかのように振る舞います。
  • 関数でないフィールドは通常、結局は関数を直接指す property() フィールドです (例: SBValue.type = property(GetType, None))。これらの省略記法経由のアクセスは、 関数を直接呼び出すよりも少し遅いため、避けるべきです。 一部のプロパティは、特殊な性質を持つ特殊なオブジェクトを返します(例: SBValue.member は、 子要素にアクセスするために dict[str, SBValue] のように振る舞うオブジェクトを返します)。 内部的には、これらの特殊なオブジェクトの多くは新しいクラスインスタンスを割り当て、 結局 SBValue 上で関数を呼び出すだけであり、追加の性能低下を招きます (例: SBValue.member は内部的には __getitem__ を実装しているだけで、 これは 1 行の return self.valobj.GetChildMemberWithName(name) です)
  • SBValue.GetID は、デバッグセッションの間、各値に対して一意な int を返します。 合成 SBValue は、その基になる SBValue とは異なる ID を持ちます。 基になる ID は SBValue.GetNonSyntheticValue().GetID() によって取得できます。
  • アドレスを手動で計算する場合、ターゲット固有の挙動 のため、 SBValue.GetValueAsUnsigned よりも SBValue.GetValueAsAddress を優先して使用すべきです。
  • SBValue の文字列表現を取得するのは難しい場合があります。なぜなら GetSummary は サマリープロバイダーを必要とし、GetValue は型がプリミティブで表現可能であることを必要とするからです。 これらの条件のいずれも満たされないほぼすべての場合、その型は StructSummaryProvider に渡せるユーザー定義構造体です。

SBType

  • 「集約型」とは、プリミティブでない構造体/クラス/共用体を意味します
  • 「Template」は「Generic」と同等です
  • 型は SBTarget.FindFirstType(type_name) によって名前で検索できます。 SBTargetSBValue.GetTarget によって取得できます
  • SBType.template_args は、型にジェネリックがない場合、空リストではなく None を返します
  • SBType.GetArrayTypeSBType.GetPointerType のような関数を介して、 型を必要な型に変換することが必要になる場合があります。 これらの関数が失敗することはありません。 これらは基になる LLDB の TypeSystem プラグインに型を要求し、デバッグ情報を完全に迂回します。 その型がデバッグ情報にまったく存在しない場合でも、これらの関数は適切な型を作成できます。
  • SBType.GetCanonicalType は実質的に SBType.GetTypedefedType + SBType.GetUnqualifiedType です。 SBType.GetTypedefedType とは異なり、元の SBType が typedef であるかどうかに関係なく、 常に有効な SBType を返します。
  • SBType.GetStaticFieldWithName は LLDB 18 で追加されました。残念ながら、静的フィールドは それ以外の方法では完全にアクセス不能であるため、後方互換性を常に実現できるとは限りません。

プロバイダーの例: Vec<T>

SyntheticProvider

典型的な前置きから始めます。既知のフィールドがあるため、__slots__ を使用します。 オブジェクト自体に加えて、Vec のヒープポインターは *mut T ではなく *mut u8 であるため、要素の型も格納する必要があります。 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
        # 無効な型は `None` よりも優れたデフォルトです
        self.element_type = SBType()

        # DWARF/PDB の違いを考慮するための特別な処理
        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_argsresolve_msvc_template_arg の実装については、以下を参照してください: lldb_providers.py

次に、update 関数です。 ポインターまたは長さが変わったかどうかを確認します。 子の数は len が変わらない限り同じままなので、容量の確認は省略できます。 容量の変更によって再割り当てが発生した場合、data_ptr のアドレスは異なるものになります。

data_ptrlength が変わっていない場合、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
    ):
        # 子のアドレスオフセットと子の数はまだ有効です
        # そのため、キャッシュされた子を再利用できます
        return True

    self.data_ptr = ptr
    self.len = len

    return False

has_childrennum_children はどちらも単純です:

def has_children(self) -> bool:
    return True

def num_children(self) -> int:
    return self.len

要素にアクセスする際は、インデックス指定を模倣するために [0][1] などの形式の値を想定します。 さらに、デバッグ時に非常に役立つ可能性があるため、ユーザーが長さと容量にも素早くアクセスできるようにしたいです。 これらの値にはそれぞれ u32::MAX - 1u32::MAX - 2 を割り当てます。 これは、それらが要素の値と重複しないことをほぼ確実に保証できるためです。 完全な 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 という名前のユーザー定義型は完全修飾されるため、曖昧さはないはずです。 また、アロケーターのジェネリックも削除できます。これはほとんど役に立つことがないためです。 self.element_type.GetName() ではなく get_template_args を使用する理由は 3 つあります:

  1. 何らかの理由で要素型の解決に失敗した場合でも、self.valobj の型名によって、 ユーザーは要素の実際の型を知ることができます
  2. 型名は DWARF および PDB ノードの制限を受けないため、名前内のテンプレート型には *const/*mut&/&mut のようなものが反映されます。
  3. 2025 年 11 月時点では、

MSVC の型名を正規化していませんが、いったん正規化するようになれば、いずれにせよ型の 文字列名を扱う必要があります。 また、SBType から文字列への変換よりも、 文字列から文字列への変換をキャッシュする方がはるかに簡単です。

def get_type_name(self) -> str:
    return f"Vec<{next(get_template_args(self.valobj))}>"

Vec を表すのに適したプリミティブ値はないため、単に get_value 関数は省略します。

SummaryProvider

summary provider は、synthetic provider のおかげで非常に単純です。 唯一の実質的な問題は、 GetSummary はオブジェクトの型に SummaryProvider がある場合にのみ値を返すことです。 ない場合は空文字列を返しますが、これは理想的ではありません。 完全な visualizer スクリプトのセットでは、 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 f"vec![{", ".join(children)}]"

プロバイダーの有効化

この 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_module を使用する場合:

def __lldb_init_module(debugger: SBDebugger, _dict: LLDBOpaque):
    # カテゴリが存在し、有効化されていることを確認する
    rust_cat = debugger.GetCategory("Rust")

    if not rust_cat.IsValid():
        rust_cat = debugger.CreateCategory("Rust")

    rust_cat.SetEnabled(True)

    # Vec プロバイダーを登録する
    vec_regex = r"^(alloc::([a-z_]+::)+)Vec<.+>$"
    sb_name = lldb.SBTypeNameSpecifier(vec_regex, is_regex=True)

    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) v vec_v
(alloc::vec::Vec<int, 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]
     | ^

プロバイダーあり(v <var_name> はサマリーを出力し、その後にすべての子のリストを出力します):

(lldb) v vec_v
(Vec<int>) 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