crunchgenとは
*BSDには、複数のプログラム(コンポーネント)を結合して、一つの実行ファイルにするcrunchgenというプログラムがあります。
それぞれのプログラムを別々の実行ファイルにするよりも、トータルのサイズを小さくする事ができます。
要は、Linuxでのbusyboxのような物です。
crunchgenのキモは、各コンポーネントに含まれるシンボルが重ならないように、シンボルの書き換えを行っている点です。
結合されたプログラムの動作概要
例として、catとcpとchmodという3つのコンポーネントを一つの実行ファイルallprogにまとめたとします。 (allprogという名前は、任意ですので、別の名前にする事もできます)。
ファイルシステム上では、cat、cp、chmodから、allprogへリンク(シンボリック、ハードどちらでも可)を張っておきます。 こうすると、cat, cp, chmodのどれを実行しても、allprogが実行される事になります。
allprogのmain()は、
- argv[0]がcatならcatのmainを呼ぶ
- argv[0]がcpならcpのmainを呼ぶ
- argv[0]がchmodならchmodのmainを呼ぶ
という動作をします。
crunchgenが作るファイルの説明
crunchgenは、allprog.confファイルを読み込んで、allprog.mkファイルとallprog.cファイルを作成します。
allprog.confには、
- どのコンポーネントをallprogに含めるか
- ソースファイルのパス
- リンクするライブラリ
などの設定を書いておきます。
allprog.mk: 各コンポーネントのビルドをし、その後一つの実行ファイルを作るためのmakefile
allprog.c: main()。ここからargv[0]を見て、各のコンポーネントのmainを呼ぶ。
使い方:
$ crunchgen allprog.conf (allprog.mkとallprog.cの作成)
$ make -f allprog.mk
allprogのmain()の説明
crunchgenは、allprog.cを自動生成します。 allprog.cの中身は、以下のようになってします(一部省略しています)。
struct stub {
char *name;
int (*f)();
};
extern struct stub entry_points[];
int main(int argc, char **argv, char **envp)
{
char *slash, *basename;
struct stub *ep;
if(argv[0] == NULL || *argv[0] == '\0')
crunched_usage();
for(ep=entry_points; ep->name != NULL; ep++)
if(!strcmp(argv[0], ep->name)) break;
if(ep->name)
return ep->f(argc, argv, envp);
else {
fprintf(stderr, "%s: %s not compiled in\n", EXECNAME, basename);
crunched_usage();
}
}
extern int _crunched_cat_stub();
extern int _crunched_chmod_stub();
extern int _crunched_cp_stub();
extern int _crunched_dd_stub();
extern int _crunched_df_stub();
extern int _crunched_ed_stub();
struct stub entry_points[] = {
{ "cat", _crunched_cat_stub },
{ "chmod", _crunched_chmod_stub },
{ "cp", _crunched_cp_stub },
{ "dd", _crunched_dd_stub },
{ "df", _crunched_df_stub },
{ "ed", _crunched_ed_stub },
{ NULL, NULL }
};
各コンポーネントのmain()は、_crunched_xxx_stub()という名称に変わっています。 これは、mainというシンボル名が重ならないようにするためです。
allprog.mkの中身
- 各コンポーネントをコンパイルして、xxx.roを作る。
- xxx.roのシンボルを書き換えて、xxx.croを作る。
- xxx.croとallprog.oをリンクして、allprogを作る。
という動作をします。
xxx.croは、各コンポーネントのシンボルが重ならないように、シンボル書き換え済みのオブジェクトファイルです。
xxx.croの作り方
catを例にとります。
まず、catを作るのに必要なファイルをコンパイルして、cat.roを作ります。 cat.roから、cat.croを作るのは、allprog.mkの以下の部分です。
cat.cro: cat .WAIT ${cat_OBJPATHS}
${NM} -ng cat.ro | awk '/^ *U / { next };\
/^[0-9a-fA-F]+ C/ { next };\
/ main$$/ { print "main _crunched_cat_stub"; next };\
{ print $$3 " " $$3 "$$$$from$$$$cat" }' > cat.cro.syms
${OBJCOPY} --redefine-syms cat.cro.syms cat.ro cat.cro
まず、nm -ngで、cat.roの外部シンボルのみを取り出します。
bash-3.00$ nm -ng cat.ro
U __fstat30
U __sF
U __setlocale_mb_len_max_32
U __srget
U __swbuf
U _ctype_
U close
U err
U exit
U fclose
U fcntl
U fopen
U fprintf
U fputs
U getopt
U malloc
U open
U optind
U read
U setbuf
U setprogname
U strcmp
U warn
U warnx
U write
00000000 T raw_cat
000000f8 T raw_args
0000023c T cook_buf
00000408 B vflag
0000040c B tflag
00000410 B fflag
00000414 B rval
00000418 B filename
0000041c B eflag
00000420 B bflag
00000424 B lflag
00000428 B nflag
0000042c B sflag
0000048c T cook_args
00000558 T main
次にawkで、以下の処理をします。
- U(undefined)なシンボルは、無視します。
- C(common)なシンボルは、無視します。
- main()があったら、"main crunchgedcat_stub"を出力します。
- その他のシンボルは、"xxx xxx$$from$$cat"を出力します。
この出力をcat.cro.symsに書き込み、cat.cro.symsが作られます。 このファイルは、シンボルの書き換え前の名前と書き換え後の名前のテーブルです。
bash-3.00$ cat cat.cro.syms
raw_cat raw_cat$$from$$cat
raw_args raw_args$$from$$cat
cook_buf cook_buf$$from$$cat
vflag vflag$$from$$cat
tflag tflag$$from$$cat
fflag fflag$$from$$cat
rval rval$$from$$cat
filename filename$$from$$cat
eflag eflag$$from$$cat
bflag bflag$$from$$cat
lflag lflag$$from$$cat
nflag nflag$$from$$cat
sflag sflag$$from$$cat
cook_args cook_args$$from$$cat
main _crunched_cat_stub
シンボルの書き換えは、objcopyの--redefine-syms機能を使って行います。
cat.cro.symsをシンボルの書き換えテーブルとして指定する事で、cat.roのシンボルを書き換えたcat.croを作ります。
$ objcopy --redefine-syms cat.cro.syms cat.ro cat.cro
なぜ$$をシンボル名に入れるか
- C言語では、$をシンボル名に使えないので、$入りのシンボル名にする事で、衝突を確実に回避できる
という事だと思います。
ただ、main()はallprog.cから呼ぶので、mainだけは$入りのシンボル名に変換する事はできません。
Last Modified: 2008-05-15 Thursday