5.5. Python Pretty-Printer

GDB コマンド print は、ターゲットアプリケーションのついての総合的なデバッグ情報を出力します。GDB は、可能な限り多くのデバッグデータをユーザーに提供することを目的としています。ただし、非常に複雑なプログラムの場合には、データ量は非常に把握しにくくなる可能性があります。
また、GDB は GDB print 出力の解読に役立つツールを提供しません。GDB は、プログラムデータの解読に役立つツールを簡単に作成する権限をユーザーに与えません。そのため、デバッグデータを読み取り、把握する方法が、とりわけ大規模で複雑なプロジェクトの場合にかなり難しくなります。
ほとんどの開発者にとって、GDB print 出力をカスタマイズし、(これにより意味をもたせる) 唯一の方法は、GDB を変更し、再コンパイルする方法です。ただし、これを実際に行える開発者はほとんどいません。さらにこの方法には拡張性がなく、開発者が異種のプログラムで、同じ様に複雑なデバッグデータを含む複数プログラムもデバッグする必要がある場合には特に対応が難しくなります。
これに対処するため、Red Hat Enterprise Linux 6 バージョンの GDB は Python pretty-printer との互換性を持つようになりました。これにより、イントロスペクション、出力、およびフォーマットロジックを サードパーティー の Python スクリプトに残すことにより、より意味を持つデバッグデータの取得が可能になります。
Python pretty-printer との互換性により、GDB 出力のカスタマイズを目的に適合するものにできます。これにより、GDB 出力を必要に応じて変化させる柔軟性とその容易性が大幅に向上するため、GDB はより幅広いプロジェクトに対するより実行可能なデバッグソリューションになります。また、プロジェクトや特定のプログラミング言語に精通した開発者こそが意味を持つ出力の種類を決定する上で最適であり、その有用性を高めることができます。
Python pretty-printer の実装により、ユーザーは仕様に従ってプログラムデータの検査、フォーマット、および出力を自動的に実行できます。これらの仕様は Python スクリプトによって実装されるルールとして作成されます。これには以下のメリットがあります。
安全性
プログラムデータを登録済みの一連の Python pretty-printer に渡すために、GDB 開発チームは GBD 印刷コードにフックを追加しました。これらのフックは、安全性に注意を払って実装されました。組み込まれた GBD 印刷コードは依然として元のままの状態で、デフォルトのフォールバック印刷ロジックとして機能できます。そのため、専門のプリンターが使用できない場合でも、GDB はこれまでと同様の方法でデバッグデータを出力します。これにより、GDB に後方互換性を持たせ、pretty-printer を必要としないユーザーが GDB を引き続き使用することができます。
高いカスタマイズ性
この新規の「Python スクリプト作成」アプローチにより、ユーザーは必要に応じた量の知識を特定のプリンターに抽出することができます。そのため、プロジェクトには、ユーザー要件に特化した特定の方法でプログラムデータを解析するプリンタースクリプトのライブラリー全体を組み込むことができます。ユーザーが特定プロジェクト用にビルドできるプリンター数には制限がありません。さらに、スクリプトによってデバッグデータのスクリプトがカスタマイズできることで、ユーザーによるプリンタースクリプト — またはそれらのライブラリー全体の再利用と目的の再設定がより容易になります。
習得が容易
このアプローチの最もすぐれている点は、初めてでも実行しやすいことです。Python スクリプト作成は学習が比較的に容易で、オンラインで利用できる無料のドキュメントが多数あります。さらに、大半のプログラマーは Python スクリプト作成や一般的なスクリプト作成における基本から中級レベルの経験をすでに有しています。
以下は、pretty printer の小さな例です。以下の C++ プログラムを見てみましょう。
fruit.cc
enum Fruits {Orange, Apple, Banana};

class Fruit
{
  int fruit;

 public:
  Fruit (int f)
    {
      fruit = f;
    }
};

int main()
{
  Fruit myFruit(Apple);
  return 0;             // line 17                               
}
これは、コマンド g++ -g fruit.cc -o fruit でコンパイルされています。これから、GDB を使ってこのプログラムを検査します。
gdb ./fruit 
[...]
(gdb) break 17
Breakpoint 1 at 0x40056d: file fruit.cc, line 17.
(gdb) run

Breakpoint 1, main () at fruit.cc:17
17	  return 0;             // line 17
(gdb) print myFruit 
$1 = {fruit = 1}
{fruit = 1} の出力は、これがデータ構造 'Fruit' 内の 'fruit' の内部表現であるために正確なものです。ただし、ここでは整数 1 がどの fruit を表すかを判別することが難しいため、人間による読み取りは容易ではありません。
この問題を解決するために、以下の pretty printer を作成します。
fruit.py

class FruitPrinter:
    def __init__(self, val):
        self.val = val

    def to_string (self):
        fruit = self.val['fruit']
        
        if (fruit == 0):
            name = "Orange"
        elif (fruit == 1):
            name = "Apple"
        elif (fruit == 2):
            name = "Banana"
        else:
            name = "unknown"
        return "Our fruit is " + name

def lookup_type (val):
    if str(val.type) == 'Fruit':
        return FruitPrinter(val)
    return None

gdb.pretty_printers.append (lookup_type)
ボトムアップの視点でこのプリンターを検査します。
gdb.pretty_printers.append (lookup_type) 行は、関数 lookup_type を、printer lookup 関数の GDB のリストに追加します。
関数 lookup_type は、出力するオブジェクトのタイプを検査し、適切な pretty printer を返します。オブジェクトは、パラメーター val で GDB によって渡されます。 val.type は pretty printer のタイプを表す属性です。
FruitPrinter は、実際の作業が行われる場所です。さらに具体的には、その場所はそのクラスの to_string 関数になります。この関数では、整数 fruit は、python ディクショナリ構文 self.val['fruit'] を使って取得されます。次に、名前がその値を使って決定されます。この関数によって戻される文字列は、ユーザーに出力される文字列になります。
fruit.py の作成後、これは次のコマンドを使って GDB にロードされる必要があります。
(gdb) python execfile("fruit.py")
GDB および Python Pretty-Printer』 ホワイトペーパーは、この機能についてのより詳しい情報を提供します。このホワイトペーパーには、独自の Python pretty-printer の作成方法や、これを GDB にインポートする方法についての詳細情報といくつかの例も記載されています。詳細は以下のリンクを参照してください。