debuginfod(elfutils debuginfo サーバー)の概要

バグを避けることはできないので、開発者は SystemtapGDB などのデバッグツールへ素早くかつ簡単にアクセスできる必要があります。これらのツールは典型的には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サーバー から必要なファイルを取得できるようになりました。 stapfoo を正常にプローブできます(「 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 の今後

binutilsLLDBなどの他 の ツールに debuginfod クライアント機能を追加する作業を行っています。また、Debian パッケージフォーマットのサポートを行うこと、Fedora の Koji ビルドシステムdebuginfod サーバーを実行すること、そしてDWARF コンテンツクエリーをサポートするよう debuginfodの web API を拡張することも計画しています。

支援またはフィードバックは常に歓迎されます。elfutils-devel@sourceware.org または irc.freenode.net の #elfutils チャンネルに連絡してください。

 

※訳者注:  続きの記事を翻訳しました。以下でごらんいただけます。

 

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。