GCC(binutils)のツールの紹介: nm&c++filt
前回の続きでBinutilsのツール群の説明をします。
今回はnmとc++filtを説明します。
これらのツールはどれも前回説明したobjdumpの一部の機能みたいなものです。
ソースコード
#include <cstdio> const static int CONST_VALUE = 100; int uninitialized_value; class SuperClass { public: virtual void print(const char *str) = 0; }; class Sub1 : public SuperClass{ public: void print(const char *str){ printf("Sub1: %s\n", str); } }; class Sub2 : public SuperClass{ public: void print(const char *str){ printf("Sub2: %s\n", str); } }; int main(void){ SuperClass *hoge = new Sub1(); printf("CONST_VALUE: %p\n", &CONST_VALUE); printf("uninitialized_value: %p\n", &uninitialized_value); return 0; }
コンパイルしておきます。今回はリンクまでします。
$ g++ -g test.cpp $ ls a.out test.cpp $ ./a.out CONST_VALUE: 0x8048774 uninitialized_value: 0x8049a34
nm
nmコマンドはオブジェクトの中のシンボルを一覧表示します。
$ nm a.out # 一部削ってます
08049898 d _DYNAMIC
08049984 d _GLOBAL_OFFSET_TABLE_
0804873c R _IO_stdin_used
w _Jv_RegisterClasses
08048774 r _ZL11CONST_VALUE
08048654 W _ZN10SuperClassC2Ev
08048638 W _ZN4Sub15printEPKc
08048662 W _ZN4Sub1C1Ev
080487b8 V _ZTI10SuperClass
0804879c V _ZTI4Sub1
080487a8 V _ZTS10SuperClass
08048794 V _ZTS4Sub1
08048788 V _ZTV10SuperClass
08048778 V _ZTV4Sub1
080499c0 V _ZTVN10__cxxabiv117__class_type_infoE@@CXXABI_1.3
08049a00 V _ZTVN10__cxxabiv120__si_class_type_infoE@@CXXABI_1.3
U _Znwj@@GLIBCXX_3.4
080485d4 T main
U printf@@GLIBC_2.0
08049a34 B uninitialized_value
2列目に表示されるシンボルの意味は以下の通りです。
大文字は外部公開されるシンボルで、小文字はされないシンボルとなります。
- A 絶対値。リンク後も値が変わらない。
- B,b 実初期化データ(BSS)
- D,d 初期化済みデータ(Dataセクション)
- R,r リードオンリー
- T,t コード(Textセクション)
- U 未定義シンボル
- V,v ウィークオブジェクト。
- W,w ウィークシンボル。
ソースコードの実行結果と比較してみると、uninitialized_valueとCONST_VALUEのアドレスが一致していることがわかります。
c++filt
c++ではシンボル名が一意になるように名前マングルされるため、ぱっと見では何の関数かよくわかりません。
そこでデマングルするためのツールがこのc++filtです。
単純な使い方は引数にデマングルしたいシンボル名を渡すだけですが、標準入力から渡すこともできます。
$ nm a.out | c++filt 08049898 d _DYNAMIC 08049984 d _GLOBAL_OFFSET_TABLE_ 0804873c R _IO_stdin_used w _Jv_RegisterClasses 08048774 r CONST_VALUE 08048654 W SuperClass::SuperClass() 08048638 W Sub1::print(char const*) 08048662 W Sub1::Sub1() 080487b8 V typeinfo for SuperClass 0804879c V typeinfo for Sub1 080487a8 V typeinfo name for SuperClass 08048794 V typeinfo name for Sub1 08048788 V vtable for SuperClass 08048778 V vtable for Sub1 080499c0 V vtable for __cxxabiv1::__class_type_info@@CXXABI_1.3 08049a00 V vtable for __cxxabiv1::__si_class_type_info@@CXXABI_1.3 U operator new(unsigned int)@@GLIBCXX_3.4 080485d4 T main U printf@@GLIBC_2.0 08049a34 B uninitialized_value
この例では、nm自体にデマングルの機能があるためわざわざc++filtを使う必要はないです
$ nm -C a.out
シンボル表をどういったケースで使うかをぱっと思いつかなかったので今回は割愛します。
実際にはデバッグなどで関数のアドレスを知りたいときとかにobjdumpではなくシンボル一覧から探したりすることは結構あります。
GCC(binutils)のツールの紹介: objdump
GCCには様々なツールが付属しています。実際にはGCCではなく、一緒にコンパイルされるBinutilsに付属しているものですが。
Binutilsを使うことでプログラムに関する様々な情報を容易に取得できたり、アセンブラレベルでのデバッグや最適化にも有効です。
組み込み用途には必須のツールと言えます。
今回紹介するのはobjdumpだけですが、今後残りも紹介していきます。
サンプルに使うソースコード
内容は何でも良いのですが、ファイルシステムのソースから一部を抜粋してきました。
#include <stdio.h> #include <stdlib.h> #include <string.h> #define SECTOR_SIZE 512 // Read a sector from filesystem. void readsector(char *mmc, char *dest, int sec) { if (!dest) return; memcpy(dest, &mmc[sec * SECTOR_SIZE], SECTOR_SIZE); } // Write a sector to filesystem. void writesector(char *mmc, char *src, int sec) { int i; char nullchar = '\0'; if (src) { memcpy(&mmc[sec * SECTOR_SIZE], src, SECTOR_SIZE); } else { for (i = 0; i < SECTOR_SIZE; i++) { memcpy(&mmc[sec * SECTOR_SIZE + i], &nullchar, sizeof(char)); } } }
これをコンパイルして.oを作っておきます。また、debugオプションを付けてコンパイルしたファイルも生成しておきます。
$ gcc -c test.c $ gcc -g -c test.c -o test-debug.o $ ls test-debug.o test.c test.o
objdump
objdumpはbinutilsの中でも(asとldを除いて)最も使う頻度の高いツールです。
いわゆるディスアセンブラのようなもので、機械語となったプログラムをアセンブリ言語に戻してくれます。
また、それ以外にもプログラムの情報を表示する機能もあります。
まずは、一番基本の-dオプション(disassemble)を実行してみます。
$ objdump -d test.o test.o: file format elf32-i386 Disassembly of section .text: 00000000: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 18 sub $0x18,%esp 6: 83 7d 0c 00 cmpl $0x0,0xc(%ebp) a: 74 22 je 2e c: 8b 45 10 mov 0x10(%ebp),%eax f: c1 e0 09 shl $0x9,%eax 12: 03 45 08 add 0x8(%ebp),%eax 15: c7 44 24 08 00 02 00 movl $0x200,0x8(%esp) 1c: 00 1d: 89 44 24 04 mov %eax,0x4(%esp) 21: 8b 45 0c mov 0xc(%ebp),%eax 24: 89 04 24 mov %eax,(%esp) 27: e8 fc ff ff ff call 28 2c: eb 01 jmp 2f 2e: 90 nop 2f: c9 leave 30: c3 ret 00000031 : ...
以上ののように関数毎に、
が表示されることがわかります。
また、2段目にsectionが.textと表示されていますが、これはこれらの関数が.textセクションにあるということを意味しています。
セクションには様々なものがあり、自分で任意のセクションを作ることができますが、通常は.textセクションにプログラムがあります。
-Sオプションを指定すると-dオプションと同様にディスアセンブリするだけでなく、対応するソースコードも表示してくれます。
ただし、ソースコードが表示されるのはdebugオプションを付けてコンパイルした場合のみとなります。
$ objdump -S test-debug.o
-xオプションでヘッダ情報を全て表示することもできます。
$ objdump -x test.o test.o: file format elf32-i386 test.o architecture: i386, flags 0x00000011: HAS_RELOC, HAS_SYMS start address 0x00000000 Sections: Idx Name Size VMA LMA File off Algn 0 .text 0000009e 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 1 .data 00000000 00000000 00000000 000000d4 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 00000000 00000000 000000d4 2**2 ALLOC 3 .comment 0000001d 00000000 00000000 000000d4 2**0 CONTENTS, READONLY 4 .note.GNU-stack 00000000 00000000 00000000 000000f1 2**0 CONTENTS, READONLY SYMBOL TABLE: 00000000 l df *ABS* 00000000 test.c 00000000 l d .text 00000000 .text 00000000 l d .data 00000000 .data 00000000 l d .bss 00000000 .bss 00000000 l d .note.GNU-stack 00000000 .note.GNU-stack 00000000 l d .comment 00000000 .comment 00000000 g F .text 00000031 readsector 00000000 *UND* 00000000 memcpy 00000031 g F .text 0000006d writesector RELOCATION RECORDS FOR [.text]: OFFSET TYPE VALUE 00000028 R_386_PC32 memcpy 0000005d R_386_PC32 memcpy 0000008b R_386_PC32 memcpy
先ほど出てきた.textセクション以外にいくつかのセクションが存在していることがわかります。
簡単に説明すると、.dataはstaticなデータが置いてある場所で、.bssはstaticなデータだけど値が0のものがおいてあります。残り2つについてはよく知りません。
.commentに関してはファイル内に何かデータがある(sizeが1d)ようなので、バイナリエディタで.commentセクションの中身を見てみましょう。
ヘッダの情報によるとファイル先頭から0xd4バイト目から0x1dバイト分とのことです。
$ bvi -b 0xd4 -e $[0xd4+0x1d] test.o 000000D4 00 47 ..... 00 00 .GCC: (Debian 4.4.5-8) 4.4.5..
このように、GCCのバージョン情報が埋め込まれているということがわかりります。
同様にして.textをみると-dオプションでみた機械語が並んでいることもわかります。
今回はobjdumpのみの紹介でしたが、objdumpによってわかる情報はまだまだたくさんあります。
更に詳しい情報は
$ man objdump $ man elf
などをどうぞ。
GCCの警告を部分的にオフにする方法
本来ならGCCの警告を出ないようにするのは良いことではないですが、どうしても警告を消すことができない場合はあると思います。
自分の例では、同じコードをgccとg++の両方でコンパイルすることがあるため、C++スタイルのキャストを使うことができずに警告が大量に出たりします。
そこで、ファイル単位や関数単位など、自分の好きな範囲で任意の警告をオフにする(またはオンにする)方法を記しておきます。
参考にしたページはこちらです。
オフにする場合には任意の場所で次のプラグマを追加します。
#pragma GCC diagnostic ignored "-Wcast-qual"
オンにする場合も同様に次のプラグマを追加します。
#pragma GCC diagnostic warning "-Wcast-qual"
ちなみに今書いてるC++のプログラムではオプションで以下のような警告を利用しています。
ただ、クラスのメンバ変数の初期化を全てメンバイニシャライザで行わないといけないのが結構面倒です。
-Wall -Wextra -Wformat=2 -Wcast-qual -Wwrite-strings -Wconversion -Wfloat-equal -Wpointer-arith -Winit-self -Weffc++ -Wredundant-decls