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-@は日本語キーボードでも英字キーボードでもそこそこの位置にあるので選択肢としてはあり。
自分は変更していない。

X Emacsでのキーバインドの割当変更

X Emacsではterminal上のEmacsとは異なり、全ての組み合わせのキー割当が変更可能である。
例えばC-1のようにそもそもキーコードがとれなかったものも割当可能になる。
また、C-[などのように他のキーとして動作していたのものも独自に割当可能になる。

全てのキーが割当可能になるため、非常に多くの選択肢ができる。
しかし、terminal上のEmacsキーバインドが異なるため混乱する可能性がある。

TZTesterを更に改良する

nitoyonさんcafelierさんnaoya_tさんのを参考に、TZTesterを更に改良しました。

ベースとしてcafelierさんのものを利用しました。
ここでダウンロードできる最新バージョンでは以下の特徴があります。

  • テストケースを追加しやすい
  • テストケースごとの実行時間がわかる
  • doubleの誤差チェック

これをもとに更に使い勝手がよくなるように以下の機能を追加。

  1. 実行時のメモリ使用量を表示(/proc/statusを使用)
  2. インデントが崩れないようにテストケースに中括弧{}を追加
  3. 実行時の引数で実行するテストケースを指定可能に
  4. テストケースの入力値を任意で表示可能に

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