LLVMがWindowsのデバッグ情報フォーマットのPDBをサポート
LLVM Project Blog: LLVM on Windows now supports PDB Debug Info
この数年、clangをWindowsでソフトウェア開発するための世界級のツールチェインにするために尽力してきた。このことについては、すでに何度も書いてきたことだ。LLVMは完全なABI互換を実現した(ただしバグ互換ではない)。互換性を実現するのが難しい分野にデバッグ情報があるが、この2年間で、LLVMは飛躍的な発展をとげた。とりあえず結論を先に書くとこうだ。WindowsでClangを使うと、PDBデバッグ情報が出せる。
背景:CodeView VS PDB
CodeViewは1980年台の中頃にMicrosoftによって考案されたデバッグ情報フォーマットだ。様々な理由で、他のデバッガーはDWARFという独立したフォーマットを開発し、これは標準化されて、多くのコンパイラーとプログラミング言語でサポートされている。CodeViewは、DWARFと同じく、ソースコード行とコードアドレスのマッピングと、プログラムが使う型とシンボルのレコード集である。デバッガーはこの情報を使って、関数名でブレイクポイントを設定したり、変数の値を表示したりする。ただし、CodeViewはあまりよくドキュメント化されていない。最新の公式なドキュメントは少なくとも20年前のものだ。レコードの中にはドキュメント化されているものと同じフォーマットのものもあるが、まだドキュメント化されていない後々の追加のレコードもある。
ここで重要なのが、CodeViewは単にレコード集だということだ。もしユーザーが、「Fooの値を表示してくれ」といったときどうなるだろうか。デバッガーはFooについてのレコードを検索する。そして物事は更に複雑になる。どの最適化が有効にされていたのか? コンパイラーのバージョンは?(これはコンパイラーのバージョンによって一部のABIの非互換があったり、極度に最適化されたコードからバックトレースを再現する際のヒントとして使ったり、スタックが破壊されているかどうかの確認に重要だ)。プログラムには大量のシンボルがある。どうやって遅いO(n)にならずにシンボルを検索すればいいのか? どうやってコードを僅かに変更した時にインクリメンタルリンクを実現してデバッグ情報の再生成を回避すればいいのだ? 文字列の重複を省いて空間を節約するにはどうすれば? ここでPDBが登場する。
PDB(Program Database)は、その名前通り、データベースだ。これにはCodeViewが含まれているが、CodeViewレコードを様々な方法でインデックス化するための様々なものが含まれている。これによって型やシンボルを名前やアドレスで検索するのを高速化している。発想としては入力ファイルに対する「テーブル」と同等であり、ユーザーはその存在を気にすることはないものの、Windowsにおけるデバッグを快適にするのに貢献している。しかし問題がある。CodeViewはある程度はドキュメント化されているのに対し、PDBはまったくドキュメント化されていない。しかもその構造は複雑だ。
お手上げだ(ホントか?)
数年前、LLVMは開発の方向性として、CodeViewとPDBを出力する望みを一切捨て去り、以下の2つに注力することにした。
- clang-clはWindowsでDWARFデバッグ情報を出力する
- LLDBをWindowsに移植してWindows ABIに対応させる。これはVisual StudioやWinDbgをDWARFに対応させるより遥かに簡単だ(そもそもそんなことが可能であればの話だが。Visual StudioとWinDbgの拡張機能を使って実装可能なのだろうか)
実際、私はすでにブログ記事でこのことを2年前に書いている。作業の結果、LLDBをWindowsに移植して簡単なデバッグをさせることはできるようになった。
残念ながら、PDBのサポートは必須であることが明らかになった。LLVMの目標はWindowsエコシステムに囚われた開発者にとってできるだけ抵抗が少なくなるようにすることである。Windows Performance AnalyzerやvTuneのようなツールはとても強力でエンジニアの慣れ親しんだものである。企業はPDBファイルを保存、収集してクラッシュダンプを解析するインフラに投資している。PDBによるデバッグはとても高速だ。というのも、インデックスはファイルフォーマットで実現されていて、デバッガーが起動時にシンボルをインデックス化しなくてもいいためだ。それに、WinDbgのようなツールはすでにデバッグ用途に便利で、率直に言って、多くの(おそらくはほとんどの)Windows開発者がVisual Studioを手放すためには、彼らの死体の手からもぎ取る必要があるだろう。
私がとりあえずMicrosoftに協力を求めてみればええんちゃうと提案したときには皆から冷たいまなざしを受けたものだ。しかし、最終的に我々はMicrosoftに協力を求めた、そしてMSは協力した。協力はMicrosoft Githubにコード片を投下するという形で得られた。後はこのコードを解析するだけだ。Microsoftが公開できたコード片はPDBのコードの一部(我々は推測と解析をしなければならないし、そもそもコードは半分ぐらいかけているのでコンパイルすら通らないわけだが)だけであるが、実装をするだけの情報は得られた。
このコードを1年半解析し、試し、更に解析し、更に試しなどした結果、lld(LLVMリンカー)はついに機能するPDBを出力することができるようになった。基本的なことであるコード行や名前でのブレイクポイントの設定や、変数の表示や、シンボルや型の検索は、すべて動くようになった(ただし、もちろん、バグ互換はない)
PDBの詳細を調べたい人のために我々はツールも開発中だ。llvm-pdbutilと呼ばれるツールで、Microsoftのcvdumpユーティリティとよく似たものだ。このツールはPDBの内部情報をダンプして、PDBとyamlの相互変換、2つのPDBのdiffなどを実現している。llvmpdbutilの簡単なドキュメントはここにある。PDBファイルフォーマットの詳細の解説はここにある。この2年間に我々が解析したすべてが書いてある(まだ途中だ。私はドキュメントとPDBの実装の両方に時間を割かなければならないのだ)
バグをもってこい!
さて、ここで読者の協力が必要となる。我々はPDBで簡単なデバッグ状況をテストしたが、まだデバッグ情報の品質としてはアルファ段階だと考えている。是非試して、問題をバグトラッカーで知らせてほしい。始めるためには、まず最新のWindows用Clangのスナップショットをダウンロードしよう。この機能をテストする簡単な2つの方法は以下だ。
clang-clにlldを自動的に実行させる
clang-cl -fuse-ld=lld -Z7 -MTd hello.cpp
clang-clとlldを別々に実行する。
clang-cl -c -Z7 -MTd -o hello.obj hello.cpp
lld-link -debug hello.obj
バグレポートがあふれかえるのをお待ちしております!
Microsoftが我々に協力してgithubレポジトリにコードをアップロードしてくれたことに心から感謝している。Microsoftの協力なしには実現できなかったことだ。
ところで、将来期待される歓喜すべきある事柄について読者の想像に委ねたい。ここに書いたPDBサポートはWindows特有のAPIやdllやライブラリに一切依存していない。100%移植性がある。ところで、君はクロスコンパイルに興味があるかね?
Zach Turner(LLVMのWindowsチームとして)
なかなか興味深い。
ところで、Microsoftが公開したMicrosoft/microsoft-pdbはMSVCのPDBを処理しているコードの一部だ。PDBフォーマットの詳細を開示するよう要請したところ、コード片が開示され、しかも、Microsoft自ら、「ソースコードは究極のドキュメントである」"Source code is the ultimate documentation :-)"、書いているところを見ると、果たしてMicrosoft社内にPDBの詳細なドキュメントはあるのか疑問だ。