home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

C言語系/「デーモン君のソース探検」読書メモ/11, file(1)

C言語系/「デーモン君のソース探検」読書メモ/11, file(1)

C言語系 / 「デーモン君のソース探検」読書メモ / 11, file(1)
id: 555 所有者: msakamoto-sf    作成日: 2010-01-14 22:10:23
カテゴリ: BSD C言語 

お題:"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.

今回のテーマに関連してくるのは以下のファイルになる。

  • メインプログラムである 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'をそれぞれ返す。

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で探してみて欲しい。

今回のお題については、ここまで。



プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2010-01-14 22:12:56
md5:25ce15f5c961144b558b09a5446f20ad
sha1:fdc1a922a4a4edcada06a1106708cd1cf4c52e8f
コメント
コメントを投稿するにはログインして下さい。