シンボライザ マークアップ形式

概要

このドキュメントでは、シンボル化フィルタで処理できるログメッセージのテキスト形式を定義します。基本的な考え方は、ログコードが、生ののアドレス値などを含むテキストを出力する一方で、ログコードがそれらの値を人間が読める形式に変換する実際的な作業を行わないということです。代わりに、ログテキストはここで定義されたマークアップ形式を使用して、後で人間が読める形式に変換する必要がある情報を識別します。他のマークアップ形式と同様に、テキストのほとんどがそのまま表示され、マークアップ要素が拡張されたテキストに置き換えられたり、シンボル形式でより詳細な情報を提示するアクティブなUI要素に変換されたりすることが期待されます。

つまり、シンボルテーブル、DWARFデバッグセクション、または同様の情報がランタイムで直接アクセスできる必要はありません。また、C++シンボルデマングルなど、情報を人間が読めるように表示することを目的としたロジックもランタイムでは必要ありません。代わりに、ログには、メモリレイアウトの詳細など、生のデータを理解するために必要なコンテキスト情報を提供するマークアップ要素を含める必要があります。

この形式は、シンプルで特徴的な構文を持つマークアップ要素を識別します。単純なコードでマッチングと解析を行うのに十分なシンプルさです。マークアップ要素の開始または終了のように見える文字シーケンスが、ログテキストに偶然現れることはめったにないほど十分に特徴的です。これは、<&lt;などに置き換えるHTML/XML要件など、プレーンテキストをサニタイズする必要がないように特に意図されています。

llvm-symbolizerには、--filter-markupオプションを介したシンボル化フィルタが含まれています。また、LLVMユーティリティは、LLVM_ENABLE_SYMBOLIZER_MARKUP環境変数が設定されている場合、スタックトレースをマークアップとして出力します。

範囲と前提

シンボル化フィルタの実装は、ログが生成されるターゲットオペレーティングシステムとマシンアーキテクチャ、およびフィルタが実行されるホストオペレーティングシステムとマシンアーキテクチャの両方から独立します。

この形式は、シンボル化フィルタが完全な行全体を処理することを前提としています。ログパイプラインの途中で長い行が分割される可能性がある場合は、シンボル化フィルタに行をフィードする前に、元の改行を復元するために再構成する必要があります。ほとんどのマークアップ要素は、単一行に完全に表示される必要があります(多くの場合、マークアップ要素の前後に他のテキストがあります)。要素の途中で改行がある、複数行にまたがるように指定されたマークアップ要素がいくつかあります。そのような場合でも、フィルタはマークアップ要素内の任意の場所での改行を処理するのではなく、特定のフィールド内でのみ処理することが期待されています。

この形式は、シンボル化フィルタが単一のプロセスアドレス空間コンテキストからのログ行の一貫性のあるストリームを処理することを前提としています。ログストリームに複数のプロセスからのログ行がインターリーブされている場合は、プロセスごとに個別のログストリームに照合し、各ストリームをシンボル化フィルタの個別のインスタンスで処理する必要があります。カーネルとユーザプロセスはほとんどのオペレーティングシステムで分離されたアドレス領域を使用するため、必要に応じて、単一のユーザプロセスアドレス空間とカーネルアドレス空間をシンボル化の目的で単一のアドレス空間として扱うことができます。

ビルドIDへの依存

シンボライザマークアップスキームは、マークアップ要素を役立つシンボル形式に変換できるように、ランタイムメモリのアドレスレイアウトに関するコンテキスト情報に依存しています。これは、各アドレスにどのバイナリがロードされたかを明確に識別できることに依存しています。

ELFビルドIDは、名前が"GNU"でタイプがNT_GNU_BUILD_IDであるELFノートのペイロードであり、特定のバイナリ(実行可能ファイル、共有ライブラリ、ロード可能なモジュール、またはドライバモジュール)を識別する一意のバイトシーケンスです。リンカは、シンボルテーブル全体とデバッグ情報を含むハッシュに基づいてこれを自動的に生成します。たとえこれが後でバイナリから削除されたとしてもです。

この仕様では、ELFビルドIDをバイナリを識別する唯一の手段として使用します。ログに関連する各バイナリは、一意のビルドIDでリンクされている必要があります。シンボル化フィルタには、ビルドIDを元のELFバイナリ(全体のストリップされていないバイナリ、またはストリップされたバイナリとペアになった個別のデバッグファイル)にマッピングする手段が必要です。

カラー化

マークアップ形式は、ANSI X3.64 SGR(Select Graphic Rendition)制御シーケンスの制限されたサブセットをサポートしています。これらは他のマークアップ要素とは異なります。

  • これらは、セマンティック情報ではなく、プレゼンテーションの詳細(太字や色)を指定します。色(たとえば、エラーの場合は赤)にセマンティックな意味を関連付けるのは、シンボル化フィルタのUIプレゼンテーションではなく、ログを実行するコードによって選択されます。これは、特定の色の使用を継続しており、代わりにセマンティックマークアップを生成するには大幅な変更が必要になる既存のコード(たとえば、LLVMサニタイザーランタイム)に対する譲歩です。

  • 単一の制御シーケンスは、「状態」を変更するものであり、影響を受けるテキストを囲む階層構造ではありません。

フィルタは、単一行内のANSI SGR制御シーケンスのみを処理します。太字または色状態に入る制御シーケンスが発生した場合、その行の終わりに、デフォルト状態にリセットする制御シーケンスが発生することが期待されます。行の終わりに「ぶら下がった」状態が残っている場合、フィルタは次の行でデフォルト状態にリセットされる可能性があります。

SGR制御シーケンスは、他のマークアップ要素内では解釈されません。ただし、他のマークアップ要素がSGR制御シーケンス間に表示される可能性があり、色/太字の状態は、フィルタの出力でマークアップ要素を置き換えるシンボル出力に適用されることが期待されます。

受け入れられるSGR制御シーケンスはすべて"\033[%um"(ここではC文字列構文を使用)の形式を持ち、%uは次のいずれかです

コード

効果

0

デフォルトの書式設定にリセットします。

1

太字テキスト

色の状態と組み合わせて使用し、色の状態をリセットしません。

30

黒の前景色

31

赤の前景色

32

緑の前景色

33

黄色の前景色

34

青の前景色

35

マゼンタの前景色

36

シアンの前景色

37

白の前景色

共通のマークアップ要素構文

すべてのマークアップ要素は、単純なマッチングおよび解析コードを容易にするために、共通の構文構造を共有しています。各要素は次の形式です

{{{tag:fields}}}

tagは、以下で説明する要素タイプの1つを識別し、常に小文字でなければならない短いアルファベット文字列です。要素の残りの部分は、1つ以上のフィールドで構成されています。フィールドは:で区切られ、:または}文字を含めることはできません。存在する必要があるフィールドの数、または存在する可能性があるフィールドの数、およびそれらに含まれるものは、要素タイプごとに指定されます。

マークアップ要素またはANSI SGR制御シーケンスは、フィールドの内容内では解釈されません。

実装では、予期されるフィールドの後にあるマークアップフィールドを無視する必要があります。これにより、後方互換性を維持しながら新しいフィールドを要素に追加できます。実装では、それらを黙って無視する必要はありませんが、要素は、フィールドが削除されたかのように動作する必要があります。

各要素タイプの説明では、printfスタイルのプレースホルダーがフィールドの内容を示します

%s

:または}を含まない、印刷可能な文字の文字列。

%p

0xに続いて偶数の16進数(AFに小文字または大文字を使用)で表されるアドレス値。数字がすべて0の場合、0xプレフィックスは省略できます。1つの値に16個を超える16進数が表示されることは想定されていません(64ビット)。

%u

非負の10進整数。

%i

非負の整数。数字は、0xがプレフィックスの場合16進数、0がプレフィックスの場合8進数、それ以外の場合は10進数です。

%x

0xプレフィックスなしで、偶数の16進数(AFに小文字または大文字を使用)のシーケンス。これは、ELFビルドIDなどの任意のバイトシーケンスを表します。

プレゼンテーション要素

これらは、人間が読めるシンボル形式で表示される特定のプログラムエンティティを伝える要素です。

{{{symbol:%s}}}

ここで %s は、シンボルまたは型のリンケージ名です。言語の ABI 規則に従ってデマングルする必要がある場合があります。マングルされていない名前の場合でも、このマークアップ要素を使用してシンボル名を識別し、明確に表示できるようにすることをお勧めします。

{{{symbol:_ZN7Mangled4NameEv}}}
{{{symbol:foobar}}}

{{{pc:%p}}}{{{pc:%p:ra}}}{{{pc:%p:pc}}}

ここで %p は、コードの場所のメモリアドレスです。関数名とソースの場所として表示される場合があります。後ろの 2 つの形式は、以下に示す bt 要素の詳細な説明のように、コードの場所の種類を区別します。

{{{pc:0x12345678}}}
{{{pc:0xffffffff9abcdef0}}}

{{{data:%p}}}

ここで %p は、データロケーションのメモリアドレスです。その場所にあるグローバル変数の名前として表示される場合があります。

{{{data:0x12345678}}}
{{{data:0xffffffff9abcdef0}}}

{{{bt:%u:%p}}}{{{bt:%u:%p:ra}}}{{{bt:%u:%p:pc}}}

これは、バックトレース内の 1 つのフレームを表します。通常、(空白のみで囲まれて) 行単位で表示され、フレーム番号が昇順のそのような行が連続しています。そのため、人間が判読できる出力は、bt 要素がそれぞれ単独で1行にあり、各行が均一にインデントされているという前提でフォーマットされる場合があります。ただし、どこにでも表示される可能性があるため、フィルターは要素を囲む空白以外のテキストを削除しないでください。

ここで %u はフレーム番号で、識別されている障害の場所の場合はゼロから始まり、フレーム 0 の呼び出しフレームの呼び出し元の場合は 1、フレーム 1 の呼び出し元の場合は 2、というように増分します。%p はコードの場所のメモリアドレスです。

バックトレース内のコードの場所は、2 つの異なるソースから取得されます。ほとんどのバックトレース フレームは、戻りアドレスのコードの場所、つまり呼び出し命令の直後の命令を示しています。これは、そこで呼び出された関数がまだ戻っていないため、まだ実行されていないコードの場所です。したがって、実際に関心のあるコードの場所は、通常、戻りアドレスではなく、呼び出し元そのもの、つまり 1 つ前の命令です。戻りアドレス フレームのソースの場所を表示する場合、シンボル化フィルターは、呼び出し元のソースの場所に直接変換できるアドレスをログに記録できるように、呼び出し元の実際のアドレスから 1 バイトまたは 1 つの命令長を減算します。その後の見かけ上の戻りサイトではなく、(混乱を招く可能性があります)。インライン関数が関係している場合、呼び出し元と戻りサイトは、わずか 1 行離れているのではなく、完全に無関係なソースの場所にある別の関数にあるように見える可能性があり、戻りサイトではなく呼び出し元を示すという混乱が非常に深刻になる可能性があります。

多くの場合、バックトレースの最初のフレーム(「フレーム 0」)は、戻りアドレスではなく、障害、トラップ、または非同期割り込みの正確なコードの場所を識別します。他の場合、最初のフレームでさえ、実際には戻りアドレスです(たとえば、オブジェクトの割り当て時に収集され、割り当てられたオブジェクトが使用または誤用されたときに後で報告されるバックトレースなど)。システムがスレッド内トラップ処理をサポートしている場合、トラップ ハンドラー関数(たとえば、POSIX システムのシグナル ハンドラー)の「呼び出し元」として表示される、正確な中断されたコードの場所を表す最初のフレームの後のフレームも存在する可能性があります。

戻りアドレスフレームは、:ra サフィックスで識別されます。正確なコードの場所フレームは、:pc サフィックスで識別されます。

従来の方法では、バックトレースを単純なアドレスリストとして収集することが多く、戻りアドレスのコードの場所と正確なコードの場所の違いが失われていました。このようなコードの中には、上記で説明した「1 を減算する」調整をアドレス値に適用してから報告するものもあり、この調整が適用されているかどうかは必ずしも明確または一貫していません。これらのあいまいなケースは、:ra または :pc サフィックスがない bt および pc 形式によってサポートされており、これがどの種類のコードの場所であるかは不明確であることを示します。ただし、すべてのエミッターがサフィックス付き形式を使用し、調整なしでアドレス値を配信することを強くお勧めします。従来の方法があいまいな場合、ほとんどのケースでは、戻りアドレスのコードの場所であるアドレスを、調整せずに印刷したようです。そのため、シンボル化フィルターは通常、曖昧さ回避サフィックスなしで印刷されたアドレスに「1 バイト減算」調整を適用します。呼び出し命令がサポートされているすべてのマシンで 1 バイトより長いと仮定すると、「1 バイト減算」調整を 2 回適用しても、まだ呼び出し命令内のどこかのアドレスになるため、ここでの多少の不注意はほとんどまたはまったく害を及ぼしません。

{{{bt:0:0x12345678:pc}}}
{{{bt:1:0xffffffff9abcdef0:ra}}}

{{{hexdict:...}}} [1]

この要素は複数行にまたがることができます。ここで ... は、単一の : が各キーをその値から分離し、任意の空白がペアを分離するキーと値のペアのシーケンスです。各ペアの値(右側)は、1 つ以上の 0 桁であるか、0x の後に 16 進数が続きます。各値は、メモリアドレスである場合もあれば、他の整数(メモリ アドレスのように見えるが、実際には無関係な目的を持っている整数を含む)である場合もあります。メモリ レイアウトに関するコンテキスト情報から、特定の値をコードの場所またはグローバル変数のデータ アドレスにすることができると思われる場合、ソースの場所または変数名として表示されたり、そのような解釈をオプションで表示できるようにするアクティブな UI を使用して表示されたりする可能性があります。

意図されている用途は、レジスタ ダンプなどの場合であり、エミッターはどの値がシンボリックな解釈を持つ可能性があるかわからないが、もっともらしいシンボリックな解釈を利用できる表示は、ログを読んでいる人にとって非常に役立つ可能性があります。同時に、フラットなテキスト表示は通常、ダンプの元の内容とフォーマットをあまり妨害しないようにする必要があります。たとえば、コードの場所と思われる値のソースの場所で脚注を使用する可能性があります。アクティブな UI 表示では、ダンプ テキストをそのまま表示するかもしれませんが、シンボリック情報が利用可能な値を強調表示し、値が選択されたときにシンボリック詳細の表示をポップアップ表示する可能性があります。

{{{hexdict:
    CS:                   0 RIP:     0x6ee17076fb80 EFL:            0x10246 CR2:                  0
    RAX:      0xc53d0acbcf0 RBX:     0x1e659ea7e0d0 RCX:                  0 RDX:     0x6ee1708300cc
    RSI:                  0 RDI:     0x6ee170830040 RBP:     0x3b13734898e0 RSP:     0x3b13734898d8
    R8:      0x3b1373489860 R9:          0x2776ff4f R10:     0x2749d3e9a940 R11:              0x246
    R12:     0x1e659ea7e0f0 R13: 0xd7231230fd6ff2e7 R14:     0x1e659ea7e108 R15:      0xc53d0acbcf0
  }}}

トリガー要素

これらの要素は、外部アクションを引き起こし、人間が判読できる形式でユーザーに表示されます。一般に、リンク可能なページにつながる外部アクションを発生させます。リンクまたは外部アクションに関するその他の有益な情報をユーザーに表示できます。

{{{dumpfile:%s:%s}}} [1]

ここで、最初の %s はダンプのタイプを識別するための識別子で、2 番目の %s は公開されたばかりの特定のダンプを識別するための識別子です。ダンプのタイプ、「公開」の正確な意味、および識別子の性質は、マークアップ形式自体とは無関係です。一般に、その名前でファイルを作成することなどに相当する可能性があります。

この要素は、マークアップをシンボル化する以外に、追加の後処理作業をトリガーする可能性があります。これは、何らかの種類のダンプ ファイルが公開されたことを示します。シンボル化フィルターにアタッチされた一部のロジックは、特定のタイプのダンプ ファイルを理解し、この要素に遭遇すると、ダンプ ファイルの追加の後処理(たとえば、視覚化、シンボル化の生成)をトリガーする可能性があります。ログ ストリームのコンテキスト要素(以下で説明)から収集された情報は、ダンプの内容をデコードするために必要になる可能性があると予想されます。したがって、シンボル化フィルターが他の処理をトリガーする場合は、コンテキスト情報の要約された形式をそれらのプロセスに提供する必要がある場合があります。

タイプ識別子の例は、LLVM SanitizerCoverage のダンプの場合の sancov です。

{{{dumpfile:sancov:sancov.8675}}}

コンテキスト要素

これらは、表示要素をシンボリック形式に変換するために必要な情報を提供する要素です。表示要素とは異なり、周囲のテキストに直接関連していません。コンテキスト要素は、他の空白以外のテキストがない行に単独で表示されるため、シンボル化フィルターは、他のログ テキストを非表示にすることなく、行全体を出力から削除する可能性があります。

コンテキスト要素自体は、人間が判読できる出力で表示する必要は必ずしもありません。ただし、それらが伝達する情報は、シンボル化後でさえ、ログ テキストを理解するために不可欠な場合があります。そのため、マークアップを含む元の生のログに何らかの理由で容易にアクセスできなくなった場合、この情報が何らかの形で保持されることをお勧めします。

コンテキスト要素は、必要になる前にログ ストリームに表示する必要があります。つまり、一部のコンテキストが、シンボル化フィルターが後続の表示要素を解釈または表示する方法に影響を与える可能性がある場合、必要なコンテキスト要素は、ログ ストリームの以前のどこかに表示されている必要があります。シンボル化フィルターを、生のログ ストリームを 1 回パスして、コンテキストを蓄積し、テキストをマッサージしながら処理するように実装できる必要があります。

{{{reset}}}

これは、他のコンテキスト要素よりも前に出力される必要があります。このコンテキスト要素が必要な理由は、複数のプロセスからのログを処理する実装をサポートするためです。このような実装では、新しいプロセスがいつ開始または終了するかを把握できない場合があります。一部の識別情報(プロセスIDなど)が古いプロセスと新しいプロセスで同じである可能性があるため、同一の識別情報を持つ2つのプロセスを区別する方法が必要です。この要素は、このような実装に対して、フィルタの状態をリセットして、以前のプロセスのコンテキスト要素からの情報が、同じ識別情報を持つ新しいプロセスに対して引き継がれないようにすることを通知します。

{{{module:%i:%s:%s:...}}}

この要素は、いわゆる「モジュール」を表します。「モジュール」とは、ロードされたELFファイルなど、単一のリンクされたバイナリのことです。通常、各モジュールは連続したメモリ領域を占有します。

ここで、%i はモジュールIDであり、他のコンテキスト要素でこのモジュールを参照するために使用されます。最初の %s は、ELFの DT_SONAME 文字列やファイル名など、人間が読めるモジュールの識別子ですが、空の場合もあります。これは単に参考情報としてのみ利用されます。モジュールIDのみが、他のコンテキスト要素でこのモジュールを参照するために使用され、%s 文字列は使用されません。モジュールIDを定義する module 要素は、そのモジュールIDを参照する他の要素よりも常に前に出力する必要があり、これにより、フィルタが未解決の参照を追跡する必要がないようにします。2番目の %s はモジュールタイプであり、残りのフィールドが何であるかを決定します。以下のモジュールタイプがサポートされています。

  • elf:%x

ここで、%x はELFビルドIDをエンコードします。ビルドIDは、単一のリンクされたバイナリを参照する必要があります。ビルドID文字列は、このモジュールがロードされたバイナリを識別する唯一の方法です。

{{{module:1:libc.so:elf:83238ab56ba10497}}}

{{{mmap:%p:%i:...}}}

このコンテキスト要素は、メモリ内の特定の領域に関する情報を提供するために使用されます。%p は開始アドレスであり、%i はメモリ領域のサイズを16進数で示します。... の部分は、指定されたメモリ領域に関するさまざまな情報を提供するために、さまざまな形式を取ることができます。許可される形式は次のとおりです。

  • load:%i:%s:%p

このサブ要素は、セグメントがモジュールからロードされたことをフィルタに通知します。モジュールは、モジュールID %i によって識別されます。%s は、このメモリセグメントが読み取り可能、書き込み可能、および/または実行可能であることを示すために、文字 'r'、'w'、および 'x'(この順序で、大文字または小文字のいずれか)の1つ以上です。シンボライジングフィルタは、この情報を使用して、指定されたアドレスが、与えられたモジュール内のコードアドレスである可能性が高いか、データアドレスである可能性が高いかを推測できます。残りの %p は、モジュール相対アドレスを提供します。ELFファイルの場合、モジュール相対アドレスは、関連するプログラムヘッダーの p_vaddr になります。たとえば、モジュールの実行可能セグメントに p_vaddr=0x1000p_memsz=0x1234 があり、0x7acba69d5000 にロードされた場合、0x7acba69d5000 から 0x7acba69d6234 の間の任意のアドレスから 0x7acba69d4000 を減算して、モジュール相対アドレスを取得する必要があります。通常、開始アドレスはアクティブなページサイズに切り下げられ、サイズは切り上げられます。

{{{mmap:0x7acba69d5000:0x5a000:load:1:rx:0x1000}}}

脚注