お題:"file"コマンドがELFファイルを認識する流れを追跡せよ。
※今回はあまり細かい所は見ずに、ざっと関数呼び出しの流れだけを俯瞰するに留めます。「デーモン君のソース探検」の方でも、fileコマンドのソース自体がそれなりに量があるためか細かいコードは紹介されて居らず、ELFファイルの解析までの関数呼び出しフローとELFファイルの構造がメインになっています。
まずfileコマンドと、判定に使うmagicファイル関連のmanページは以下の通り。
man 1 file man 5 magic
NetBSD1.6の場合、magicファイルは "/usr/share/misc/magic" に存在する。他のUNIXの場合は/etc/magicの場合もあるかも。
ソースは
/usr/src/usr.bin/file
にある。複数のヘッダーやソースファイルに分割されている。また、magicファイルはmakeの途中に、magdir/以下のファイル群を集約されて生成されるようになっている。
READMEファイルに簡単に各ファイルの解説があるのでここに載せておく。
apprentice.c - parses /etc/magic to learn magic ascmagic.c - third & last set of tests, based on hardwired assumptions. core - not included in distribution due to mailer limitations. debug.c - includes -c printout routine file.1 - man page for the command magic.4 - man page for the magic file, courtesy Guy Harris. Install as magic.4 on USG and magic.5 on V7 or Berkeley; cf Makefile. file.c - main program file.h - header file fsmagic.c - first set of tests the program runs, based on filesystem info is_tar.c, tar.h - knows about tarchives (courtesy John Gilmore). magdir - directory of /etc/magic pieces magdir/Makefile - ADJUST THIS FOR YOUR CONFIGURATION names.h - header file for ascmagic.c softmagic.c - 2nd set of tests, based on /etc/magic readelf.[ch] - Stand-alone elf parsing code. compress.c - on-the-fly decompression. print.c - print results, errors, warnings.
今回のテーマに関連してくるのは以下のファイルになる。
それでは駆け足でELF判定までの流れを追ってみる。
file.cのmain()の半分以上はオプション解析のコードになっている。その中で、magicファイルの構文解析を行うapprentice()関数が一度だけ呼ばれるようになっている。次のようなコードをmain()中でよく見かける。app変数がフラグとなり、一度だけ行われるようにロックされている。apprentice()関数はapprentice.cの中で定義されている。今回はその中身までは踏み込まない。
if (!app) { ret = apprentice(magicfile, action); if (action) exit(ret); app = 1; }
オプション解析とapprentice()によるmagicファイルの構文解析が完了すれば、コマンドラインで指定された各ファイルについてprocess()関数を実行していく。
for (; optind < argc; optind++) process(argv[optind], wid);
process()関数はfile.cの後半に定義されている。process()関数の詳細は割愛し、ELF判定に関連する部分だけピックアップする。
まずtryit()関数を実行する。
if (nbytes == 0) ckfputs(iflag ? "application/x-empty" : "empty", stdout); else { buf[nbytes++] = '\0'; /* null-terminate it */ match = tryit(inname, buf, nbytes, zflag); }
tryit()関数では zmagic(), softmagic(), ascmagic() の順番に判定関数を実行していき、判定できた時点で'z', 's', 'a'をそれぞれ返す。
int tryit(fn, buf, nb, zfl) const char *fn; /* file name*/ unsigned char *buf; /* buffer */ int nb, zfl; { /* ... */ /* try compression stuff */ if (zfl && zmagic(fn, buf, nb)) return 'z'; /* try tests in /etc/magic (or surrogate magic file) */ if (softmagic(buf, nb)) return 's'; /* try known keywords, check whether it is ASCII */ if (ascmagic(buf, nb)) return 'a'; /* ... */ }
process()関数に戻ると、もしtryit()でsoftmagic()による判定となれば続けてtryelf()によるELFの判定関数を実行している。
if (match == 's' && nbytes > 5) { /* ... */ tryelf(fd, buf, nbytes); }
たとえELFでなくとも、softmagic()で判定となればtryelf()も自動的に呼ばれる流れになっている。
softmagic()はsoftmagic.cで定義されており、magicファイルの構文解析結果を用いて、どれかにマッチすれば1を返すようになっている。
tryelf()に進むと、これはreadelf.cで定義されている。ELFで実行可能なファイルの場合にプログラムヘッダーを解析し、続けてセクションヘッダーを解析するのが次のコードブロック:
if (getu16(swap, elfhdr.e_type) == ET_EXEC) { dophn_exec(class, swap, fd, getu32(swap, elfhdr.e_phoff), getu16(swap, elfhdr.e_phnum), getu16(swap, elfhdr.e_phentsize)); } doshn(class, swap, fd, getu32(swap, elfhdr.e_shoff), getu16(swap, elfhdr.e_shnum), getu16(swap, elfhdr.e_shentsize));
上のコードブロックは32bit用で、本当はこの下に64bit用で同様のコードブロックが続いている。
dophn_exec()ではプログラムヘッダーを一つ一つ見ていき、情報を表示している。
static void dophn_exec(class, swap, fd, off, num, size) /* ... */ { /* ... */ for ( ; num; num--) { if (read(fd, ph_addr, ph_size) == -1) error("read failed (%s).\n", strerror(errno)); switch (ph_type) { case PT_DYNAMIC: linking_style = "dynamically"; break; case PT_INTERP: shared_libraries = " (uses shared libs)"; break; case PT_NOTE: /* NOTE関連はかなり細かく色々解析している */ } } printf(", %s linked%s", linking_style, shared_libraries); }
続いてdoshn()を見てみると、セクションヘッダーを見ていきセクションタイプで"SHT_SYMTAB"のセクションが見つかれば", not stripped"と表示し、一つも"SHT_SYMTAB"セクションが見つからない場合は ", stripped" と表示している。
static void doshn(class, swap, fd, off, num, size) /* ... */ { Elf32_Shdr sh32; Elf64_Shdr sh64; /* ... */ for ( ; num; num--) { if (read(fd, sh_addr, sh_size) == -1) error("read failed (%s).\n", strerror(errno)); if (shs_type == SHT_SYMTAB /* || shs_type == SHT_DYNSYM */) { (void) printf (", not stripped"); return; } } (void) printf (", stripped"); }
なお、ELF関連の構造体や定数だが file/readelf.h を使っている。そのためNetBSD1.6の
/usr/include/elf.h
とは構造体の名前や細かいメンバ名、定数名などが異なっているので注意が必要。
以上、駆け足でELF判定までの流れを見てきた。ELFの構造自体の解説は「デーモン君のソース探検」本文、あるいはバイナリ系で詳しいWebページも増えてきているので、WikipediaやGoogleで探してみて欲しい。
今回のお題については、ここまで。