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
Emacsでキーバインドの割当変更について
Emacsではデフォルトでほとんどのキーにコマンドが割り当てられている。しかし、キーによってはほとんど使わないコマンドが割り当てられていたりする。
キーバインドの割当を変更する上でどのキーにどんなコマンドが割り当てられているかを確認し、そのキーの割当が変更可能かどうかを確認する。
割当変更可能かどうかは、terminal上で起動したEmacsでの場合とする。
X Emacsについては最後に記述。
キーバインド表
割当変更: ○可能△可能だがおすすめしない×不可
キー | コマンド | 説明 | 割当変更 | |
---|---|---|---|---|
C-a | move-beggining-of-line | 行の先頭へ移動 | △ | |
C-b | backward-char | 1文字前に移動 | △ | |
C-c | (prefix-command) | modeによって操作が変わる | △ | |
C-d | delete-char | カーソル位置の文字を消す | △ | |
C-e | move-end-of-line | 行の最後へ移動 | △ | |
C-f | forward-char | 1文字後に移動 | △ | |
C-g | keyboard-quit | 入力のキャンセルなど | △ | |
C-h | delete-backward-char | 1文字前を消す | △ | |
C-i | TAB(タブキー) | インデントなど | × | |
C-j | newline-and-indent | 改行してインデント | ○ | |
C-k | kill-line | 現在の行のカーソル位置より後ろを消す | △ | |
C-l | recenter-top-bottom | カーソル位置が画面の上部、中央、下部くるように移動 | △ | |
C-m | newline(RET,エンターキー) | 改行 | × | |
C-n | next-line | 次の行へ移動 | △ | |
C-o | open-line | カーソル位置に空行を入れる | ○ | |
C-p | previous-line | 前の行へ移動 | △ | |
C-q | quoted-insert | 文字コードで直接入力できる | ○ | |
C-r | isearch-backward | 前検索 | △ | |
C-s | isearch-forward | 後検索 | △ | |
C-t | transpose-chars | 文字を前後入れ替え | ○ | |
C-u | universal-argument | C-u数値commandでcommandを数値回実行 | ○ | |
C-v | scroll-up | 画面を下にスクロール | △ | |
C-w | kill-region | 範囲を消す | △ | |
C-x | (prefix-key) | (大抵)固定の操作 | △ | |
C-y | yank | 貼り付け | △ | |
C-z | suspend-frame | Emacsを一時停止してシェルに戻る | △ | |
C-[ | Esc(エスケープキー) | ESCとして動作 | × | |
C-] | abort-recursive-edit | 再帰編集を抜ける(不明) | ○ | |
C-\ | toggle-input-method | 入力切り替え | △ | |
C-; | (不可) | キーコードがとれないため使えない | × | |
C-' | (不可) | キーコードがとれないため使えない | × | |
C-, | (不可) | キーコードがとれないため使えない | × | |
C-. | (不可) | キーコードがとれないため使えない | × | |
C-/ | undo | 前の変更を取り消す.C-_, C-xuも同様 | ○ | |
C-- | C-_(undo) | C-_として動作 | × | |
C-= | (不可) | キーコードがとれないため使えない | × | |
C-` | (不可) | キーコードがとれないため使えない | × | |
C-@ | set-mark-command | マークセット | ○ | |
C-SPACE | set-mark-command | マークセット | ○ | |
C-1 | (不可) | キーコードがとれないため使えない | × | |
C-2 | C-@ | C-@として動作 | × | |
C-3 | ESC | ESCとして動作 | × | |
C-4 | C-\ | C-\として動作 | × | |
C-5 | C-] | C-]として動作 | × | |
C-6 | C-^(undefined) | C-^として動作 | × | |
C-7 | C-_ | C-_として動作 | × | |
C-8 | DEL(delete-backward-char) | DELとして動作 | × | |
C-9 | (不可) | キーコードがとれないため使えない | × |
割り当てを変えるなら(自分の設定)
C-j => anythingのprefix-key
EnterとTABの操作をあわせただけ、Emacsの設定で改行時の自動インデントもできるし専用キーはもったいない。
C-jは最高の位置にあるキーなので割り当てはよく考えるべき。
自分はanythingのprefixとしてC-jiでimenuとかC-jfでfind-fileとかにしてます。
C-o => completion系
これもC-p C-a RETとかで代用可能。
自分は補完操作を割り当ててます。autocompleteやanythingとか。
C-q
タブがスペースになるときにどうしてもタブを入力したいなど、たまにどうしても必要になります。
ただ、めったにないのでM-x(execute-extended-command)から実行するのもあり。
一回入力したらコピペもできるし、anythingでEmacs Command History使っていれば同じコマンドを連続で使うのも苦じゃないはず。
自分は今のところ変更していない。C-qは結構使いづらいため。
C-t => Undefined(screenのprefix-keyのため)
使う人はよく使うらしい文字入れ替え。大抵消してから打ち直ししてるの で使わない。
自分はC-tのバインドを完全に消してます。screenのprefixをC-tにしてるため誤爆しないように。
C-u
n回連続実行。Vimでは便利だけど、Emacsだと(自分は)あまり使わないかな。M-xから実行で代用可能。複雑なものはキーマクロ使うので。
自分はまだ割当変えていないけど、次何か必要になればこれを変えます。
C-](C-5)
コマンドを使ったことないので変えても平気だろう。ただし位置がかなり遠い。
自分は変更していない。
C-/ C-_
undoは選択肢がいくつかあるのでどれか一つ残せば良いと思う。
いつもC-_(C--)を使っているのでC-/は変えても良いかな。
ただしC-_の場合、X Emacsを使ってるときと操作が変わってしまうので注意(後述)。
自分は変更していない。
C-@ C-SPACE
マークセット系。大抵の人はC-SPACEを使っているはず。C-@は日本語キーボードでも英字キーボードでもそこそこの位置にあるので選択肢としてはあり。
自分は変更していない。
TZTesterを更に改良する
nitoyonさん、cafelierさん、naoya_tさんのを参考に、TZTesterを更に改良しました。
ベースとしてcafelierさんのものを利用しました。
ここでダウンロードできる最新バージョンでは以下の特徴があります。
- テストケースを追加しやすい
- テストケースごとの実行時間がわかる
- doubleの誤差チェック
これをもとに更に使い勝手がよくなるように以下の機能を追加。
- 実行時のメモリ使用量を表示(/proc/statusを使用)
- インデントが崩れないようにテストケースに中括弧{}を追加
- 実行時の引数で実行するテストケースを指定可能に
- テストケースの入力値を任意で表示可能に
1番については適当です、実際の使用量とどれくらい合ってるか確認していないです。
2番は、ベースのTZTesterではEmacsなどでインデントが崩れてしまうため、テストケース生成マクロに関数っぽく括弧をつけました。
3番は、任意のテストケースのみを実行できるように引数で指定できるようにしました。指定しなければ全て実行されます。
自然数を指定でそのテストケースのみ実行できます。 (0で0番実行, 1で1番実行など)
負数を指定であるテストケース以降を全て実行します。(-1なら0以降, -2なら1以降, -3なら2以降など)
4番は、実行時の引数に-vを付けると各テストケースの入力値が表示されます。デフォルトは表示されません。
コンパイル済みのTZTesterとソースコードはここ(github)にあります。
使い方はオリジナルのTZTester.jarを置き換えるだけです。リネームはできません。
それほど多くのパターンでチェックを行なっていないので、まだバグがある可能性も多いです。
もしバグが見つかったり、ほしい機能があればコメントなどで教えてください。
EmacsでJavaScriptを書くときのおすすめメジャーモード
JavaScriptのメジャーモードについて
Emacs23.2からJavaScriptのメジャーモードが変更され(js-mode)、以前とは比べ物にならない程使い勝手がよくなりました。
しかし、js-modeではリアルタイムでのエラーチェックが行われないためもの足りないと感じます。
js2-modeは人気のメジャーモードで、リアルタイムでチェックを行なってくれるのですが、
インデント周りで不具合があるようでespresso.elと組み合わせるのが主流のようです。
基本的にはこちらを参考に設定するだけで良いのですが、設定項目がごちゃごちゃしていてあまりよろしくないです。
そこでid:moozさんがjs2-modeにパッチを当てたものを使用します。
これは特に細かい設定をする必要無く使うことができます。
また、ハイライト機能もこちらの方が良いです。
インストール方法
js2-mode.elをダウンロードしバイトコンパイルします。
gitが使える場合は
$ git clone git://github.com/mooz/js2-mode.git $ cd js2-mode $ emacs --batch -f batch-byte-compile js2-mode.el
.emacsに以下の項目を追加します。
(autoload 'js2-mode "js2-mode" nil t) (add-to-list 'auto-mode-alist '("\\.js$" . js2-mode))
これで拡張子が.jsのファイルを開いたときにjs2-modeで編集することができます。
Ubuntu11.04でEmacs23.3をソースからインストール
2011年5月11日にEmacs23.3がリリースされました。
Ubuntu11.04にインストールしようとしたところ、以下のようなエラーがでてmakeが通らなかったので修正方法をメモしておきます。
In file included from /usr/include/glib-2.0/glib/galloca.h:34:0, from /usr/include/glib-2.0/glib.h:32, from /usr/include/glib-2.0/gobject/gbinding.h:30, from /usr/include/glib-2.0/glib-object.h:25, from /usr/include/glib-2.0/gio/gioenums.h:30, from /usr/include/glib-2.0/gio/giotypes.h:30, from /usr/include/glib-2.0/gio/gio.h:28, from /usr/include/gtk-2.0/gdk/gdkapplaunchcontext.h:30, from /usr/include/gtk-2.0/gdk/gdk.h:32, from /usr/include/gtk-2.0/gtk/gtk.h:32, from xterm.h:44, from frame.c:28: /usr/include/glib-2.0/glib/gtypes.h:34:24: fatal error: glibconfig.h: そのようなファイルやディレクトリはありません compilation terminated.
まずファイルをダウンロードして解凍します。
$ wget http://ftp.gnu.org/pub/gnu/emacs/emacs-23.3.tar.gz $ tar zxf emacs-23.3.tar.gz
解凍したディレクトリ内のconfigure.inを編集します。
$ cd emacs-23.3/ $ vim configure.in
3106行目が次のようになっていると思います。
[cpp_undefs="`echo $srcdir $configuration $canonical unix |
この行のunixの後ろにi386を追加します(スペースを空けて)。
[cpp_undefs="`echo $srcdir $configuration $canonical unix i386 |
編集を保存したらいつも通りにconfigure、make、make installと行います。
$ ./configure
$ make
$ sudo make install
もしmake時に同様のエラーがでた場合はconfigure後に生成されるconfigureファイルを直接編集します。
configure.inを編集時と同様に13527行目にi386を追加します。
cpp_undefs="`echo $srcdir $configuration $canonical unix i386 |
その後はmake、make installとすればインストール完了です。
$ make
$ sudo make install