読者です 読者をやめる 読者になる 読者になる

elfファイルのdebugセクション分割とgdbの分割されたデバッグ情報のサポート機能めも

linux debug elf

rpmパッケージとかは通常のパッケージとデバッグ用のパッケージを分けて、debugしたいときはデバッグ情報付きのパッケージをインストールしますよね。あれがどんな感じで動いているのか確認したのでメモです。

使っている機能としては、gdbデバッグ情報の分割機能とdebuglink機能だと思います。debuglinkはデバッグ情報のないバイナリファイルにデバッグ情報のあるバイナリファイルの情報を書き込むことでgdbがそのファイルを参照してくれるようになります。

やることとしては、以下の2点というところです。

では、実際に試してみます。まずは適当なc言語で書いたコードを-g付きでビルドします。そして、デバッグ情報付きのバイナリを以下の2ファイルに分けます。

ここでは、以下のようにtestという名称でバイナリを作ります。

masami@saga:~/tmp$ gcc -g test.c -o test

そして、このtestバイナリをobjcopyを使ってデバッグ情報を抜き出したファイルを作ります。

masami@saga:~/tmp$ objcopy --only-keep-debug test test.debug

これで、testに含まれるデバッグ情報をtest.debugファイルに書き出しました。この時点ではtestのほうは変更はありません。 次にtestからデバッグ情報を削除します。

masami@saga:~/tmp$ strip -g test

これでtestからデバッグ情報が消えたのでgdbでちょっと確認してみると、当然デバッグシンボルは無いと言われます。

masami@saga:~/tmp$ gdb ./test
Reading symbols from ./test...(no debugging symbols found)...done.
(gdb) quit

この時点でのtestバイナリには31個のセクションヘッダーがあります。

masami@saga:~/tmp$ readelf -S test
There are 31 section headers, starting at offset 0x1290:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400200  00000200
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             000000000040021c  0000021c
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             000000000040023c  0000023c
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400260  00000260
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           0000000000400280  00000280
       0000000000000060  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           00000000004002e0  000002e0
       000000000000003d  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           000000000040031e  0000031e
       0000000000000008  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400328  00000328
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000400348  00000348
       0000000000000018  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400360  00000360
       0000000000000030  0000000000000018  AI       5    24     8
  [11] .init             PROGBITS         0000000000400390  00000390
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000004003b0  000003b0
       0000000000000030  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         00000000004003e0  000003e0
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         00000000004003f0  000003f0
       00000000000001c2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         00000000004005b4  000005b4
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         00000000004005c0  000005c0
       0000000000000004  0000000000000004  AM       0     0     4
  [17] .eh_frame_hdr     PROGBITS         00000000004005c4  000005c4
       000000000000003c  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000400600  00000600
       0000000000000114  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000600718  00000718
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000600720  00000720
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000600728  00000728
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000600730  00000730
       00000000000001d0  0000000000000010  WA       6     0     8
  [23] .got              PROGBITS         0000000000600900  00000900
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000600908  00000908
       0000000000000028  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000600930  00000930
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000600940  00000940
       0000000000000008  0000000000000000  WA       0     0     1
  [27] .comment          PROGBITS         0000000000000000  00000940
       0000000000000034  0000000000000001  MS       0     0     1
  [28] .shstrtab         STRTAB           0000000000000000  00001184
       000000000000010c  0000000000000000           0     0     1
  [29] .symtab           SYMTAB           0000000000000000  00000978
       0000000000000600  0000000000000018          30    44     8
  [30] .strtab           STRTAB           0000000000000000  00000f78
       000000000000020c  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

次に、objcopyを使って、debuglinkを埋め込みます。

masami@saga:~/tmp$ objcopy --add-gnu-debuglink=test.debug test

この結果、index 28のセクションヘッダに.gnu_debuglinkというのが追加されていて、合計32個のセクションヘッダになります。

asami@saga:~/tmp$ readelf -S test
There are 32 section headers, starting at offset 0x12c8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400200  00000200
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             000000000040021c  0000021c
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             000000000040023c  0000023c
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400260  00000260
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           0000000000400280  00000280
       0000000000000060  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           00000000004002e0  000002e0
       000000000000003d  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           000000000040031e  0000031e
       0000000000000008  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400328  00000328
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000400348  00000348
       0000000000000018  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400360  00000360
       0000000000000030  0000000000000018  AI       5    24     8
  [11] .init             PROGBITS         0000000000400390  00000390
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000004003b0  000003b0
       0000000000000030  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         00000000004003e0  000003e0
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         00000000004003f0  000003f0
       00000000000001c2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         00000000004005b4  000005b4
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         00000000004005c0  000005c0
       0000000000000004  0000000000000004  AM       0     0     4
  [17] .eh_frame_hdr     PROGBITS         00000000004005c4  000005c4
       000000000000003c  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000400600  00000600
       0000000000000114  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000600718  00000718
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000600720  00000720
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000600728  00000728
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000600730  00000730
       00000000000001d0  0000000000000010  WA       6     0     8
  [23] .got              PROGBITS         0000000000600900  00000900
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000600908  00000908
       0000000000000028  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000600930  00000930
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000600940  00000940
       0000000000000008  0000000000000000  WA       0     0     1
  [27] .comment          PROGBITS         0000000000000000  00000940
       0000000000000034  0000000000000001  MS       0     0     1
  [28] .gnu_debuglink    PROGBITS         0000000000000000  00000974
       0000000000000010  0000000000000000           0     0     1
  [29] .shstrtab         STRTAB           0000000000000000  000011ac
       000000000000011b  0000000000000000           0     0     1
  [30] .symtab           SYMTAB           0000000000000000  00000988
       0000000000000618  0000000000000018          31    45     8
  [31] .strtab           STRTAB           0000000000000000  00000fa0
       000000000000020c  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

readelfコマンドでどんな内容か確認すると、test.debugという文字列が入っています。

masami@saga:~/tmp$ readelf -p .gnu_debuglink test

String dump of section '.gnu_debuglink':
  [     0]  test.debug
  [     c]  c

で、ここでgdbを再度実行すると、自動的にtest.debugを読み込んで、デバッグ情報が使えるようになります。

masami@saga:~/tmp$ gdb ./test
Reading symbols from ./test...Reading symbols from /home/masami/tmp/test.debug...done.
done.
(gdb) list
1       #include <stdio.h>
2
3       static void print_args(char **argv)
4       {
5               while (*argv) {
6                       printf("%s\n", *argv++);
7               }
8       }
9
10      int main(int argc, char **argv)
(gdb)

今はtestとtest.debugが同じ場所にあるので問題なかったですが、例えば、test.debugをdebug/においた場合、gdbデバッグシンボルを見つけられません。このような場合は、set debug-file-directoryでデバッグ情報のあるディレクトリを指定します。setでディレクトリを指定して、fileコマンドで読み込んだりすれば大丈夫です。

masami@saga:~/tmp$ gdb ./test
Reading symbols from ./test...(no debugging symbols found)...done.
(gdb) set debug-file-directory ./debug
(gdb) list
No symbol table is loaded.  Use the "file" command.
(gdb) show debug-file-directory
The directory where separate debug symbols are searched for is "./debug".
(gdb) file ./debug/test.debug
Reading symbols from ./debug/test.debug...done.
(gdb) list
1       #include <stdio.h>
2
3       static void print_args(char **argv)
4       {
5               while (*argv) {
6                       printf("%s\n", *argv++);
7               }
8       }
9
10      int main(int argc, char **argv)
(gdb)

あと、Build IDも重要なはずですね。これもreadelfコマンドで確認できます。どちらのファイルもBuild IDは同じなことを確認できます。

masami@saga:~/tmp$ readelf -n test

Displaying notes found at file offset 0x0000021c with length 0x00000020:
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 2.6.32

Displaying notes found at file offset 0x0000023c with length 0x00000024:
  Owner                 Data size       Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: d97bf9a494dbde8a03785c102de536b2d8f82ac7
masami@saga:~/tmp$ readelf -n test.debug

Displaying notes found at file offset 0x0000021c with length 0x00000020:
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 2.6.32

Displaying notes found at file offset 0x0000023c with length 0x00000024:
  Owner                 Data size       Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: d97bf9a494dbde8a03785c102de536b2d8f82ac7

( ´ー`)フゥー...

参考

Separate Debug Files - Debugging with GDB

Learning Linux Binary Analysis

Learning Linux Binary Analysis