#navi_header|C言語系| お題:"file"コマンドがELFファイルを認識する流れを追跡せよ。 ※今回はあまり細かい所は見ずに、ざっと関数呼び出しの流れだけを俯瞰するに留めます。「デーモン君のソース探検」の方でも、fileコマンドのソース自体がそれなりに量があるためか細かいコードは紹介されて居らず、ELFファイルの解析までの関数呼び出しフローとELFファイルの構造がメインになっています。 #more|| まず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ファイルに簡単に各ファイルの解説があるのでここに載せておく。 #pre||> 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. ||< 今回のテーマに関連してくるのは以下のファイルになる。 - メインプログラムである file.c - magicファイルの解析を行う apprentice.c - magicファイルの解析結果を使って判定する softmagic.c - ELFファイルの解析を行う readelf.c, readelf.h それでは駆け足で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'をそれぞれ返す。 #pre||> 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で実行可能なファイルの場合にプログラムヘッダーを解析し、続けてセクションヘッダーを解析するのが次のコードブロック: #pre||> 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()ではプログラムヘッダーを一つ一つ見ていき、情報を表示している。 #pre||> 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" と表示している。 #pre||> 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で探してみて欲しい。 今回のお題については、ここまで。 #navi_footer|C言語系|