2006年4月24日
checkstack.pl で関数のスタック消費量を調べる
Linux カーネルのソースコードに付属する checkstack.pl を使うと、C/C++ のプログラムの関数のスタック消費量を調べることができます。checkstack.pl は objdump -d のディスアセンブルの出力からスタックポインタの操作をパターンマッチしてスタックの消費量を計算しています。
入手方法
checkstack.pl は Linux カーネルのソースコードに付属しています。Debian GNU/Linux sarege なら次のようにコマンドラインから実行して取得できます。ソースツリーに含まれる scripts/checkstack.pl が目的のものです。
% apt-get source kernel-source-2.6.8
使い方
checkstack.pl の使い方は簡単です。スタックサイズを調べたいバイナリに対して次のように実行します。
% objdump -d foo.o | perl checkstack.pl
foo.o の部分は ELFバイナリなら実行ファイルでも共有ライブラリでも大丈夫です。ただし、関数名を表示するにはバイナリにシンボル情報が入っている必要があります。strip したバイナリに対しては関数のアドレスのみが表示されるため、あまりうれしくありません。
実験
それでは次のプログラムに対して checkstack.pl を適用してみます。グローバル変数 void *p を使って妙なことをしているのはコンパイラの最適化によって func() が空になってしまうのを防ぐためです。
#include <signal.h> void *p; void func() { struct sigaction x; p = &x; }
% gcc -c test.c % objdump -d test.o| checkstack.pl 0x0003 func: 144
結果は、144バイトになりました。sizeof(struct sigaction) の結果は 140バイトですが、x86_32 用のコードでは gcc はデフォルトでスタックポインタを 16バイトでアライメントして操作しています。サイズによる最適化オプション -Os をつけると 140 になります。
% gcc -c -Os test.c % objdump -d test.o| checkstack.pl 0x0003 func: 140
#include <signal.h> void *p, *q; void func() { struct sigaction x; struct sigaction y; p = &x; q = &y; }
プログラムを次のように変更して struct sigaction の変数を 2つに増やすと、それぞれ -Os なしは 288バイト、 -Os つきは 280 バイトになります。
#include <signal.h> void *p, *q; void func() { struct sigaction x; struct sigaction y; p = &x; q = &y; }
ところで、手元の checkstack.pl はスタックの消費量が 100 バイト以下の関数は無視するようにコードが書かれていました。100バイト以下の関数も表示する場合は以下のラインをコメントアウトします。
next if ($size < 100);
仕組み
checkstack.pl の仕組みは簡単です。単純に objdump -d の出力に対し次のようなパターンを探して、esp に対して引き算 (sub)、足し算 (add) する値を表示しているだけです。パターンはいくつかの CPU のアーキテクチャ用に用意されています。
/^.*[as][du][db] \$(0x$x{1,8}),\%esp$/
まとめ
checkstack.pl を使って関数のスタック消費量を調べる方法を紹介しました。通常、スタックの消費量を気にすることはそれほどありませんが、大量のスレッドを生成するプログラムを書く上では、スタックの消費量は重要な要素になります。特に x86_32 ではアドレス空間が限られているため、大量のスレッドを生成するには個々のスレッドのスタックサイズを小さく抑える必要があります。組み込み用途などでメモリの使用量をできるだけ減らしたいときにも checkstack.pl は役に立つのではないかと思います。