お題:"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で探してみて欲しい。
今回のお題については、ここまで。