バグを避けることはできないので、開発者は Systemtap や GDB などのデバッグツールへ素早くかつ簡単にアクセスできる必要があります。これらのツールは典型的にはDWARF(Debugging With Attributed Record Formats)を含むdebuginfoやソースファイルに依存します。これらのリソースへのアクセスは、独自のローカルビルドツリーをデバッグする場合には問題とはなりませんが、ほとんどの場合すぐには利用できません。
たとえば、ディストリビューションではデバッグする実行可能ファイルとは別に debuginfo およびソースファイルをパッケージ 化していて、あなたにはこれらのパッケージをインストールする権限がないケースが考えられます。または、これらのリソースを含んで構築されていないコンテナー内でデバッグする場合や、マシン上でこれらのファイルを置くために領域を使いたくない場合があります。
Debuginfo ファイルは、大量の領域を占め、対応する実行可能ファイルの5倍から15倍といったサイズになることもあります。debuginfod
は、この問題を解決する ことを目指しています。
debuginfod とは何ですか?どのように動作しますか?
debuginfod
は、デバッガーのようなツールに対してデバッグリソースを提供する HTTP ファイルサーバーです。このサーバーはディレクトリーツリーと RPM アーカイブを定期的にスキャンし、実行可能ファイルや debuginfo ファイルのビルド ID を展開します。これには、ビルド ID をファイル名または(パッケージ、コンテンツ)のタプルに対応づける SQLite データベースが含まれます。
ビルド ID は、ELF ノートとしてオブジェクトファイルに埋め込まれた一意なハッシュ値です。
$ eu-readelf -n foo | grep Build.ID Build ID: be1743a97c6afb4a066a93c57499e9fba41cbcd5
この ID は、この10 年間にわたりGCCでデフォルトで有効化されており、LLVMでもサポートされています。詳細は Fedora wikiを参照してください。
またdebuginfod
は、特定のビルド ID に関連するソースファイルも提供します。ソースファイルにはビルド ID が含まれないため、debuginfod
は、ソースファイルの場所を決定するために、対応する debuginfo または実行ファイルに含まれるビルド ID およびソースパス情報に依存します。
debuginfod はどのように使用しますか?
以下に例を示します。
$ debuginfod -vv -R /var/tmp/rpmbuild -F /usr/lib/debug [...] Opened database /home/amerey/.debuginfod.sqlite [...] Searching for ELF/DWARF files under /usr/lib/debug [...] Searching for RPMs under /var/tmp/rpmbuild [...] fts/rpm traversed /var/tmp/rpmbuild [...] debuginfo=386, executable=0, sourcerefs=34119, sourcedefs=3398 [...] fts traversed /usr/lib/debug [...] debuginfo=8071, executables=2, source=1958339 [...] Started http server on IPv4 IPv6 port=8002
サーバーへの問いあわせには、debuginfod
パッケージに含まれるdebuginfod-find
コマンドラインツールを使用できます。debuginfod-find
コマンドは、特定の debuginfo、実行可能ファイル、またはソースファイルについて $DEBUGINFOD_URLS
環境変数に含まれる URL へ問いあわせます。ファイルが正常に取得されると、このコマンドはローカルキャッシュに保存し、ファイルのパスを出力します。
$ export DEBUGINFOD_URLS="http://buildhost:8002/" $ debuginfod-find source BUILD-ID /path/to/foo.c /home/amerey/.debuginfod_client_cache/source#path#to#foo#c $ debuginfod-find debuginfo BUILD-ID /home/amerey/.debuginfod_client_cache/debuginfo
debuginfod
には共有ライブラリー libdebuginfod
も含まれます。このライブラリーを使用すると、ツールはdebuginfod
サーバーにビルド ID(ソースファイルの取得を試みる場合はパスも)を使用して、debuginfo、実行可能ファイル、またはソースファイルの問いあわせを行えます。debuginfod-find
と同様、このファイルはサーバーからローカルキャッシュにダウンロードされ、特別なパーミッションなしにツールで利用できるようになります。
debuginfod
クライアントサポートをツール に 追加する方法として、ツールがファイルを見つけることができない場合に、サーバーにクエリーするフォールバックコードを追加する方法があります。この実装により、ツールの通常の動作を変更しなくても debuginfod
機能を追加できます(当然ながら、libdebuginfod
は 開発者が妥当だと考える任意の方法で統合できます)。
elfutils および GDB 用のプロトタイプ debuginfod
クライアント があります(「 debuginfod を取得する方法」を参照)。以下のコードは、GDB の debuginfo 検索ルーチンに debuginfod
機能 を追加するパッチに基づいています。このプロトタイプで、debuginfod
クライアント機能で必要なコードは小さく済むことがわかります。
#if HAVE_LIBDEBUGINFOD if ([separate debuginfo should exist but was not found]) { const struct bfd_build_id *build_id; char *debugfile_path; build_id = build_id_bfd_get (objfile->obfd); int fd = debuginfod_find_debuginfo (build_id->data, build_id->size, &debugfile_path); if (fd >= 0) { /* debuginfo successfully retrieved from server. */ gdb_bfd_ref_ptr debug_bfd (symfile_bfd_open (debugfile_path)); symbol_file_add_separate (debug_bfd.get (), debugfile_path, symfile_flags, objfile); close (fd); free (debugfile_path); } } #endif /* LIBDEBUGINFOD */
ターゲットの debuginfo ファイルのビルド ID を debuginfod_find_debuginfo()
に渡します。この関数は、debuginfod
サーバー にファイルを問いあわせし、正常に取得できれば、ファイルのローカルコピーのファイル記述子とパスが GDB から利用できるので Binary File Descriptor (BFD) ライブラリーを使用してファイルを開き、デバッグ対象の対応するオブジェクトファイルと関連付けます。
また、Systemtap などの elfutils ベースのツールは、elfutils debuginfod
クライアント から debuginfod
機能 を自動的に継承します。stap
コマンドと gdb
コマンドを実行可能ファイル foo
で実行しようとして、debuginfo やソースコードをローカルに持っていない場合を考えます。glibc debuginfo もソースファイルもありません。
$ stap -e 'probe process("/path/to/foo").function("*") { [...] }' semantic error: while resolving probe point: identifier 'process' at t.stp:1:7 source: probe process("/path/to/foo").function("*") { semantic error: no match $ gdb /path/to/foo [...] Reading symbols from /path/to/foo... (No debugging symbols found in /path/to/foo)
foo
のビルドツリーと glibc debuginfo RPM を使ってリモートマシン上で debuginfod
サーバーを起動し、ローカルマシンの stap
インスタンスおよび gdb
インスタンスが利用できるようにします。
$ debuginfod -p PORT -F foo_build/ -R debug_rpms/ [...] [...] Started http server on IPv6 IPv6 port=PORT
$ export DEBUGINFOD_URLS="http://foobuildhost:PORT/" $ stap -v -e probe process("/path/to/foo").function("*") { [...] }' [...] Pass 5: starting run ^C $ gdb /path/to/foo [...] Reading symbols from /home/amerey/.debuginfod_client_cache/debuginfo... (gdb) break printf [...] (gdb) run [...] Breakpoint 1, __printf (format=0x40201e, "main\n") at printf.c:28 28 { (gdb) list [...] 26 int 27 __printf (const char *format, ...) 28 { 29 va_list arg; 30 int done; [...]
これで、debuginfod
サーバー から必要なファイルを取得できるようになりました。 stap
で foo
を正常にプローブできます(「 Pass 5: starting run」でわかります)。 gdb
では、C ライブラリー関数への呼び出しに加えて、foo
のソースコードをデバッグして表示できます。サーバーがターゲットファイルを見つけることができない場合には、debuginfod
は 要求を他の debuginfod
サーバー へ委譲するよう簡単に設定できます。$DEBUGINFOD_URL
環境変数に他の debuginfod
サーバーの URL を追加するだけです。
debuginfod を入手する方法
現時点では、debuginfod
サーバー 、 コマンドラインインターフェース、共有ライブラリーのプロトタイプ、それらのドキュメントは elfutils Git リポジトリーから利用できます。debuginfod
は 、今後の elfutilsリリースに組み込むことが計画されています(訳注: elfutils 0.178でマージされました)。プロトタイプGDB クライアントは、GDB Git リポジトリーの 実験的ブランチ で利用できます。また、 debuginfod
バイナリーは Fedora COPR でも利用でき、コマンドラインから以下のようにダウンロードできます。
yum copr enable fche/elfutils yum -y install elfutils-debuginfod
debuginfod の今後
binutils や LLDBなどの他 の ツールに debuginfod
クライアント機能を追加する作業を行っています。また、Debian パッケージフォーマットのサポートを行うこと、Fedora の Koji ビルドシステムで debuginfod
サーバーを実行すること、そしてDWARF コンテンツクエリーをサポートするよう debuginfod
の web API を拡張することも計画しています。
支援またはフィードバックは常に歓迎されます。elfutils-devel@sourceware.org または irc.freenode.net の #elfutils
チャンネルに連絡してください。
※訳者注: 続きの記事を翻訳しました。以下でごらんいただけます。