.. _more-tools: 補助ツール ======================================================================= * バイナリからの情報の取り出し * タグジャンプ バイナリからの情報の取り出し ----------------------------------------------------------------------- 概要 ....................................................................... 目的によっては、バイナリファイルを調べることで、ビルドプロセスを読み解く 手間を除くことができます。 例 .. code-block:: console $ cat z.c #if FOO int foo() { return 0; } #else int foo() { return 1; } #endif int main () { return foo(); }; mainの返り値を知るにはFOOがビルドプロセスのどこかで定義されているかど うかを調べる必要がある。 nmの概要 ....................................................................... * バイナリから関数名、変数名(シンボル)を取り出すことができます。 * 型の名前は取り出せません。 .. code-block:: console $ nm [-D] バイナリファイル アドレス クラス シンボル アドレス クラス シンボル ... 例 .. code-block:: console $ nm -D /lib64/libc.so.6 | grep ' fprintf$' 000000366fe4f2e0 T fprintf 主要なシンボルクラス ....................................................................... +-----+--------------------------------+ |CLASS| DESCRIPTION | +=====+================================+ |T,t | Text(Program code) | +-----+--------------------------------+ |D,d | Data with initial value | +-----+--------------------------------+ |B,b | BSS(Data with no initial value)| +-----+--------------------------------+ |U | Undefind | +-----+--------------------------------+ * 大文字はグローバルスコープ (リンクすることで外部から参照可能) * ローカルスコープ (外部から参照できない) オブジェクトファイルのスコープとC言語のスコープ ....................................................................... .. code-block:: c int X = 1; static int S = 2; int main(int argc, char** argv) { int l = 3; static int s = 4; return X + S + argc + l + s; } .. code-block:: console $ gcc -O0 foo.c $ nm a.out ... 0000000000600870 d S 000000000060086c D X 000000000040049c T main 0000000000600874 d s.1712 ... nmの-Dオプション ....................................................................... * -Dオプションで動的ライブラリのリンクにかかわるシンボル (dynamic symbols)を表示する。 * 動的ライブラリのリンクにかかわらないシンボル(normal symbol)は rpmbuildがパッケージ作成時にstripしてしまう。 * debuginfoパッケージがインストールされている場合、 -Dなしでnmを実行するとnormal symbolについても表示できる。 .. code-block:: console $ cat foo.c int main(void) {return 0;} $ gcc foo.c $ nm a.out | grep main nm a.out | grep main U __libc_start_main@@GLIBC_2.2.5 000000000040049c T main $ strip a.out $ nm a.out | grep main nm: a.out: no symbols objdumpによる逆アッセンブル ....................................................................... .. code-block:: console $ objdump [-S] -d バイナリファイル アドレス <関数名>: マシンコード オプコード オペランド ... .... * debuginfoパッケージがインストールされている場合 -S オプションをつけると ソースコードをインラインで表示できる。 逆アッセンブル出力の例 ....................................................................... .. code-block:: c int foo (void) { return 0; } int main(void) { return foo(); } .. code-block:: console $ gcc -O0 foo.c $ objdump -d a.out ... 000000000040049c : 40049c: 55 push %rbp 40049d: 48 89 e5 mov %rsp,%rbp ... 00000000004004a7
: 4004a7: 55 push %rbp 4004a8: 48 89 e5 mov %rsp,%rbp 4004ab: e8 ec ff ff ff callq 40049c ... .. code-block:: console $ objdump [-S] -d バイナリファイル アドレス <関数名>: マシンコード オプコード オペランド ... .... * debuginfoパッケージがインストールされている場合 -S オプションをつけると ソースコードをインラインで表示できる。 大雑把な逆アッセンブル出力の読み方 ....................................................................... * %で始まる名前はレジスタの名前 * callqは関数呼び出し (x86_64) * $0xで始まる文字列は即値 * jで始まるオプコードはたぶんジャンプ (x86_64) * retqはC言語で言うところのreturn * ``*`` はC言語と同じく参照 * # は objdumpによるコメント * <関数名+アドレス> は 関数の定義された箇所からのオフセット 最適化の影響 ....................................................................... * rpmbuildは -O2 オプションを指定してgccを呼び出す。 .. code-block:: console $ rpm --showrc | grep -e -O2 -14: __global_cflags -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 \\ -fexceptions -fstack-protector --param=ssp-buffer-size=4 \\ %{_hardened_cflags} * コンパイラの最適化によって呼び出し関係がソースコードの通りで なくなることに注意する。 * あくまでソースコード読解の補助に使う。 最適化の影響の例 ....................................................................... .. code-block:: c int foo (void) { return 0; } int main(void) { return foo(); } .. code-block:: console $ gcc -O2 foo.c $ objdump -d a.out ... 0000000000400390
: 400390: 31 c0 xor %eax,%eax 400392: c3 retq 400393: 90 nop ... 関数fooの呼び出しがインライン展開されてしまっている。 タグジャンプ ----------------------------------------------------------------------- 概要 ....................................................................... * ソースコードから関数名や変数名などをその定義箇所とともに収集して タグファイルに記録しておく。 * エディタでタグファイルを読み込んでおくと、名前を指定することで その定義をいつでもすぐに見ることができる。 * エディタと組合せて使うのが前提となる。 emacsの場合(1) ....................................................................... etagsを含むemacsパッケージをインストールする .. code-block:: console # yum -y install emacs etagsコマンドにソースコードファイルの名前を与えてTAGSファイルを作る .. code-block:: console $ find . -type f -name '*.[ch]' | etags - emacsにTAGSファイルを読み込ませる:: M-x visit-tags-table Visit tags table (default TAGS): TAGSファイルへのパス emacsの場合(2) ....................................................................... 「名前」の定義へジャンプする:: M-. Find tag (default M-.): 名前 同じ名前の異なる定義へジャンプしたい場合:: C-u M-. ジャンプ前の場所に戻りたい場合(繰り返し実行可能):: M-* viの場合(1) ....................................................................... ctagsコマンドにソースコードファイルの名前を与えてtagsファイルを作る .. code-block:: console $ find . -type f -name '*.[ch]' | ctags -L - viにTAGSファイルを読み込ませる:: :set tags=tagsファイルへのパス viの場合(2) ....................................................................... 「名前」の定義へジャンプする:: :tag 名前 カーソルの直下に「名前」がある場合:: C-] 複数の定義がある場合のジャンプ先の選択 g C-] ジャンプ前の場所に戻りたい場合(繰り返し実行可能):: C-t ジャンプの履歴(スタック)の表示:: :tags ドキュメント ....................................................................... emacs ``M-x info-emacs-manul`` のTagsの項目に詳細な説明があります。 vi ``:help`` のtagsrch.txtの項目に詳細な説明があります。