glibcは、実行中のプログラムのスタックトレースを取得するためにbacktrace()
とbacktrace_symbols()
を提供します。ただし、これを機能させるには、プログラムをリンカの_-rdynamic
_フラグを使用してビルドする必要があります。
Gccに渡される_-g
_フラグとリンカーの_-rdynamic
_フラグの違いは何ですか?サンプルコードでは、出力を比較するためにreadelfを実行しました。 _-rdynamic
_は_Symbol table '.dynsym'
_の下でより多くの情報を生成するようですが、追加情報が何であるかはよくわかりません。
_-rdynamic
_を使用してビルドされたプログラムバイナリをstrip
しても、backtrace_symbols()
は引き続き機能します。
strip
がバイナリからすべてのシンボルを削除すると、_-rdynamic
_フラグによって追加されたものがすべて残るのはなぜですか?
編集:以下のマットの回答に基づくフォローアップの質問。
あなたが取った同じサンプルコードの場合、これは_-g
_と_-rdynamic
_で見た違いです。
オプションなし。
_ Symbol table '.dynsym' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 218 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
Symbol table '.symtab' contains 70 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400200 0 SECTION LOCAL DEFAULT 1
2: 000000000040021c 0 SECTION LOCAL DEFAULT 2
_
_-g
_を使用すると、セクションが増え、_.symtab
_テーブルにエントリが増えますが、_.dynsym
_は同じままです。
_ [26] .debug_aranges PROGBITS 0000000000000000 0000095c
0000000000000030 0000000000000000 0 0 1
[27] .debug_pubnames PROGBITS 0000000000000000 0000098c
0000000000000023 0000000000000000 0 0 1
[28] .debug_info PROGBITS 0000000000000000 000009af
00000000000000a9 0000000000000000 0 0 1
[29] .debug_abbrev PROGBITS 0000000000000000 00000a58
0000000000000047 0000000000000000 0 0 1
[30] .debug_line PROGBITS 0000000000000000 00000a9f
0000000000000038 0000000000000000 0 0 1
[31] .debug_frame PROGBITS 0000000000000000 00000ad8
0000000000000058 0000000000000000 0 0 8
[32] .debug_loc PROGBITS 0000000000000000 00000b30
0000000000000098 0000000000000000 0 0 1
Symbol table '.dynsym' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 218 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
Symbol table '.symtab' contains 77 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400200 0 SECTION LOCAL DEFAULT 1
_
_-rdynamic
_の場合、追加のデバッグセクションはありません。symtabエントリは70(Vanilla gcc呼び出しと同じ)ですが、_.dynsym
_エントリはさらに多くなります。
_ Symbol table '.dynsym' contains 19 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 218 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
2: 00000000005008e8 0 OBJECT GLOBAL DEFAULT ABS _DYNAMIC
3: 0000000000400750 57 FUNC GLOBAL DEFAULT 12 __libc_csu_fini
4: 00000000004005e0 0 FUNC GLOBAL DEFAULT 10 _init
5: 0000000000400620 0 FUNC GLOBAL DEFAULT 12 _start
6: 00000000004006f0 86 FUNC GLOBAL DEFAULT 12 __libc_csu_init
7: 0000000000500ab8 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
8: 00000000004006de 16 FUNC GLOBAL DEFAULT 12 main
9: 0000000000500aa0 0 NOTYPE WEAK DEFAULT 23 data_start
10: 00000000004007c8 0 FUNC GLOBAL DEFAULT 13 _fini
11: 00000000004006d8 6 FUNC GLOBAL DEFAULT 12 foo
12: 0000000000500ab8 0 NOTYPE GLOBAL DEFAULT ABS _edata
13: 0000000000500a80 0 OBJECT GLOBAL DEFAULT ABS _GLOBAL_OFFSET_TABLE_
14: 0000000000500ac0 0 NOTYPE GLOBAL DEFAULT ABS _end
15: 00000000004007d8 4 OBJECT GLOBAL DEFAULT 14 _IO_stdin_used
16: 0000000000500aa0 0 NOTYPE GLOBAL DEFAULT 23 __data_start
17: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
18: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
Symbol table '.symtab' contains 70 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400200 0 SECTION LOCAL DEFAULT 1
2: 000000000040021c 0 SECTION LOCAL DEFAULT 2
_
今、これらは私が持っている質問です。
Gdbでは、btを実行してbactraceを取得できます。それが_-g
_だけで機能する場合、backtrace_symbolsが機能するために_-rdynamic
_が必要なのはなぜですか?
_.symtab
_への追加と_-g
_の追加、および_.dynsym
_への追加と_-rdynamic
_の追加を比較すると、まったく同じではありません。どちらか一方が他方よりも優れたデバッグ情報を提供しますか? FWIW、生成される出力のサイズは次のようになります。-g>あり-rdynamic>あり、どちらのオプションもありません
.dynsymの使用法は正確には何ですか?このバイナリによってエクスポートされたすべてのシンボルですか?その場合、コードをライブラリとしてコンパイルしていないため、fooが.dynsymに入るのはなぜですか。
すべての静的ライブラリを使用してコードをリンクする場合、backtrace_symbolsが機能するために-rdynamicは必要ありませんか?
ドキュメントによると:
これにより、リンカは、使用されているシンボルだけでなく、すべてのシンボルを動的シンボルテーブルに追加するように指示されます。
これらはデバッグシンボルではなく、ダイナミックリンカーシンボルです。これらは(ほとんどの場合)実行可能ファイルを破壊するため、strip
によって削除されません。実行可能ファイルの最終リンクステージを実行するためにランタイムリンカーによって使用されます。
例:
$ cat t.c
void foo() {}
int main() { foo(); return 0; }
-rdynamic
なしでコンパイルしてリンクします(そして明らかに最適化はありません)
$ gcc -O0 -o t t.c
$ readelf -s t
Symbol table '.dynsym' contains 3 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
Symbol table '.symtab' contains 50 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400270 0 SECTION LOCAL DEFAULT 1
....
27: 0000000000000000 0 FILE LOCAL DEFAULT ABS t.c
28: 0000000000600e14 0 NOTYPE LOCAL DEFAULT 18 __init_array_end
29: 0000000000600e40 0 OBJECT LOCAL DEFAULT 21 _DYNAMIC
したがって、実行可能ファイルにはすべてが.symtab
あります。ただし、.dynsym
はfoo
についてまったく言及していないことに注意してください。そこには必要不可欠なものがすべて含まれています。これは、backtrace_symbols
が機能するのに十分な情報ではありません。コードアドレスと関数名を照合するには、そのセクションにある情報に依存します。
-rdynamic
でコンパイルします。
$ gcc -O0 -o t t.c -rdynamic
$ readelf -s t
Symbol table '.dynsym' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
4: 0000000000601018 0 NOTYPE GLOBAL DEFAULT ABS _edata
5: 0000000000601008 0 NOTYPE GLOBAL DEFAULT 24 __data_start
6: 0000000000400734 6 FUNC GLOBAL DEFAULT 13 foo
7: 0000000000601028 0 NOTYPE GLOBAL DEFAULT ABS _end
8: 0000000000601008 0 NOTYPE WEAK DEFAULT 24 data_start
9: 0000000000400838 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
10: 0000000000400750 136 FUNC GLOBAL DEFAULT 13 __libc_csu_init
11: 0000000000400650 0 FUNC GLOBAL DEFAULT 13 _start
12: 0000000000601018 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
13: 000000000040073a 16 FUNC GLOBAL DEFAULT 13 main
14: 0000000000400618 0 FUNC GLOBAL DEFAULT 11 _init
15: 00000000004007e0 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
16: 0000000000400828 0 FUNC GLOBAL DEFAULT 14 _fini
Symbol table '.symtab' contains 50 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400270 0 SECTION LOCAL DEFAULT 1
....
27: 0000000000000000 0 FILE LOCAL DEFAULT ABS t.c
28: 0000000000600e14 0 NOTYPE LOCAL DEFAULT 18 __init_array_end
29: 0000000000600e40 0 OBJECT LOCAL DEFAULT 21 _DYNAMIC
.symtab
のシンボルについても同じですが、foo
の動的シンボルセクションにシンボルがあります(他のシンボルもそこに表示されます)。これにより、backtrace_symbols
が機能します。コードアドレスを関数名にマップするのに十分な情報(ほとんどの場合)があります。
それを取り除く:
$ strip --strip-all t
$ readelf -s t
Symbol table '.dynsym' contains 17 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
4: 0000000000601018 0 NOTYPE GLOBAL DEFAULT ABS _edata
5: 0000000000601008 0 NOTYPE GLOBAL DEFAULT 24 __data_start
6: 0000000000400734 6 FUNC GLOBAL DEFAULT 13 foo
7: 0000000000601028 0 NOTYPE GLOBAL DEFAULT ABS _end
8: 0000000000601008 0 NOTYPE WEAK DEFAULT 24 data_start
9: 0000000000400838 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
10: 0000000000400750 136 FUNC GLOBAL DEFAULT 13 __libc_csu_init
11: 0000000000400650 0 FUNC GLOBAL DEFAULT 13 _start
12: 0000000000601018 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
13: 000000000040073a 16 FUNC GLOBAL DEFAULT 13 main
14: 0000000000400618 0 FUNC GLOBAL DEFAULT 11 _init
15: 00000000004007e0 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
16: 0000000000400828 0 FUNC GLOBAL DEFAULT 14 _fini
$ ./t
$
これで.symtab
はなくなりましたが、動的シンボルテーブルはまだそこにあり、実行可能ファイルが実行されます。したがって、backtrace_symbols
も引き続き機能します。
動的シンボルテーブルを削除します。
$ strip -R .dynsym t
$ ./t
./t: relocation error: ./t: symbol , version GLIBC_2.2.5 not defined in file libc.so.6 with link time reference
...そしてあなたは壊れた実行可能ファイルを手に入れます。
.symtab
と.dynsym
が何に使用されるかについての興味深い読み物は、ここにあります: ELFシンボルテーブル内 。注意すべき点の1つは、.symtab
は実行時に必要ないため、ローダーによって破棄されることです。そのセクションはプロセスのメモリに残りません。一方、.dynsym
は、実行時にisが必要なため、プロセスイメージに保持されます。したがって、backtrace_symbols
のようなものは、現在のプロセスに関する情報をそれ自体の中から収集することができます。
つまり、要するに:
strip
によって削除されません。これは、実行可能ファイルをロードできなくするためです。backtrace_symbols
は、どのコードがどの関数に属しているかを把握するために動的シンボルを必要としますbacktrace_symbols
はデバッグシンボルを使用しませんしたがって、あなたが気づいた行動。
あなたの特定の質問のために:
gdb
はデバッガーです。実行可能ファイルとライブラリのデバッグ情報を使用して、関連情報を表示します。 backtrace_symbols
よりもはるかに複雑で、ライブプロセスに加えてドライブ上の実際のファイルを検査します。 backtrace_symbols
はそうではなく、完全に処理中です。したがって、実行可能イメージにロードされていないセクションにアクセスすることはできません。デバッグセクションはランタイムイメージにロードされないため、使用できません。.dynsym
はデバッグセクションではありません。ダイナミックリンカが使用するセクションです。 .symbtab
もデバッグセクションではありませんが、実行可能(およびライブラリ)ファイルにアクセスできるデバッガーで使用できます。 -rdynamic
はデバッグセクションを生成せず、その拡張された動的シンボルテーブルのみを生成します。 -rdynamic
からの実行可能ファイルの増加は、その実行可能ファイル内のシンボルの数(および配置/パディングの考慮事項)に完全に依存します。 -g
よりかなり小さいはずです。printf
といくつかのアプリケーション起動手順をCライブラリからリンクするようなものです。これらの外部シンボルは、実行可能ファイルのどこかに指定する必要があります。これが.dynsym
の使用目的であり、.dynsym
を指定しなくてもexeに-rdynamic
があるのはこのためです。これを指定すると、リンカはプロセスが機能するために必要ではないが、backtrace_symbols
などで使用できる他のシンボルを追加します。backtrace_symbols
は、静的にリンクした場合、関数名を解決しません。 -rdynamic
を指定しても、.dynsym
セクションは実行可能ファイルに発行されません。シンボルテーブルは実行可能イメージにロードされないため、backtrace_symbols
はコードアドレスをシンボルにマップできません。