いけむランド

はてダからやってきました

pure Java な readelf もどきをつくってみる

Eclipse *1 の主要なコンポーネントのひとつに CDT (C/C++ Development Tooling) というのがある。


これが C/C++ での開発を支援するというコンポーネントということを聞いて、まさか C/C++ コンパイラアセンブラJava で書かれているのか?と気になって調べてみたら、そこに関してはなんてことはない cygwin とか MinGW とかのツールを使う仕組みでがっかりしたわけだが、さらに調べてみると、オブジェクトファイルは解釈できることがわかった。

というのも、例えば ELF を表現すると思われる org.eclipse.cdt.utils.elf.Elf をのぞいてみると、明らかにそのフォーマットを解釈しているように見えるためである。

あとは toString() メソッドが ELF ヘッダのいろんな情報を整形してくれるようになってくれていれば簡単に pure Java な readelf が実装できるというちょっとした発見ができたのであったのだが、残念ながらそういう類のメソッドは提供されていないようであるため、とりあえず自前で -h (ELF ファイル自体のヘッダの表示) の機能だけを実装してみることにする。

以下に適当なやり方を示す。

  1. CDT Europa Releases Update Site から適当な CDT をダウンロードする。*2
  2. ダウンロードしてきた cdt-master-*.zip を展開した後のディレクトリの plugins/org.eclipse.cdt.core_*.jar にクラスパスを通しておく。
  3. 以下の Java ソース *3 を用意して、これをコンパイルする。
import java.io.*;
import java.lang.reflect.*;
import org.eclipse.cdt.utils.elf.*;

public class ReadElf
{
  /* cygwin:/usr/include/sys/elf_common.h */
  static public final int EI_OSABI = 7;
  static public final int EI_ABIVERSION = 8;

  static public final int ELFOSABI_SYSV = 0;
  static public final int ELFOSABI_HPUX = 1;
  static public final int ELFOSABI_NETBSD = 2;
  static public final int ELFOSABI_LINUX = 3;
  static public final int ELFOSABI_HURD = 4;
  static public final int ELFOSABI_86OPEN = 5;
  static public final int ELFOSABI_SOLARIS = 6;
  static public final int ELFOSABI_MONTEREY = 7;
  static public final int ELFOSABI_IRIX = 8;
  static public final int ELFOSABI_FREEBSD = 9;
  static public final int ELFOSABI_TRU64 = 10;
  static public final int ELFOSABI_MODESTO = 11;
  static public final int ELFOSABI_OPENBSD = 12;
  static public final int ELFOSABI_ARM = 97;
  static public final int ELFOSABI_STANDALONE = 255;

  static public void main(String[] args) throws IOException
  {
    PrintStream out = System.out;

    Elf elf = new Elf(args[0]);
    Elf.ELFhdr elfhdr = elf.getELFhdr();

    out.println("ELF Header:");

    out.print("  Magic : ");
    for (int i = 0; i < elfhdr.e_ident.length; i++) {
      out.print(toHexString(elfhdr.e_ident[i]) + " ");
    }
    out.println("");

    out.println("  Class:                             " +
                getEnumName(Elf.ELFhdr.class, "ELFCLASS",
                            elfhdr.e_ident[Elf.ELFhdr.EI_CLASS]));
    out.println("  Data:                              " +
                getEnumName(Elf.ELFhdr.class, "ELFDATA",
                            elfhdr.e_ident[Elf.ELFhdr.EI_DATA]));
    out.println("  Version:                           " +
                elfhdr.e_ident[Elf.ELFhdr.EI_VERSION]);
    out.println("  OS/ABI:                            " +
                getEnumName(ReadElf.class, "ELFOSABI",
                            elfhdr.e_ident[EI_OSABI]));
    out.println("  ABI Version:                       " +
                elfhdr.e_ident[EI_ABIVERSION]);
    out.println("  Type:                              " +
                getEnumName(Elf.ELFhdr.class, "ET_",
                            elfhdr.e_type));
    out.println("  Machine:                           " +
                getEnumName(Elf.ELFhdr.class, "EM_",
                            elfhdr.e_machine));
    out.println("  Version:                           " +
                elfhdr.e_version);
    out.println("  Entry:                             0x" +
                elfhdr.e_entry.toString(16));
    out.println("  Program headers:                   " +
                elfhdr.e_phoff);
    out.println("  Section headers:                   " +
                elfhdr.e_shoff);
    out.println("  Flags:                             " +
                elfhdr.e_flags);
    out.println("  Size of this header:               " +
                elfhdr.e_ehsize);
    out.println("  Size of program headers:           " +
                elfhdr.e_phentsize);
    out.println("  Number of program headers:         " +
                elfhdr.e_phnum);
    out.println("  Size of section headers:           " +
                elfhdr.e_shentsize);
    out.println("  Number of section headers:         " +
                elfhdr.e_shnum);
    out.println("  Section header string table index: " +
                elfhdr.e_shstrndx);
  }

  static private String getEnumName(Class clazz, String prefix, int value)
  {
    Field[] fields = clazz.getFields();
    for (int i = 0; i < fields.length; i++) {
      try {
        if (fields[i].getName().startsWith(prefix)
            && fields[i].getInt(null) == value) {
          return fields[i].getName();
        }
      } catch (IllegalArgumentException e) {
        return null;
      } catch (IllegalAccessException e) {
        return null;
      }
    }
    return null;
  }

  static private final String HEX_STRING = "0123456789abcdef";

  static private String toHexString(byte value)
  {
    char[] buf = new char[2];
    buf[0] = HEX_STRING.charAt(value >> 4);
    buf[1] = HEX_STRING.charAt(value & 0x0f);
    return new String(buf);
  }
}


きちんと動作するのかどうかを確認するために String.hashCode() の変遷 の時にダウンロードしてきていた ftp://ftp.gwdg.de/pub/languages/java/linux/JDK-1.2.2/i386/FCS/j2sdk-1.2.2-FCS-linux-i386-glibc-2.1.3.tar.bz2 の /jdk1.2.2/bin/i386/native_threads/java を解析させてみた。

% file jdk1.2.2/bin/i386/native_threads/java
jdk1.2.2/bin/i386/native_threads/java: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.0.0, dynamically linked (uses shared libs), not stripped
% readelf -h jdk1.2.2/bin/i386/native_threads/java
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x80489f0
  Start of program headers:          52 (bytes into file)
  Start of section headers:          12964 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         6
  Size of section headers:           40 (bytes)
  Number of section headers:         29
  Section header string table index: 26
% java ReadElf jdk1.2.2/bin/i386/native_threads/java
ELF Header:
  Magic : 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELFCLASS32
  Data:                              ELFDATA2LSB
  Version:                           1
  OS/ABI:                            ELFOSABI_SYSV
  ABI Version:                       0
  Type:                              ET_EXEC
  Machine:                           EM_386
  Version:                           1
  Entry:                             0x80489f0
  Program headers:                   52
  Section headers:                   12964
  Flags:                             0
  Size of this header:               52
  Size of program headers:           32
  Number of program headers:         6
  Size of section headers:           40
  Number of section headers:         29
  Section header string table index: 26


int の値を文字列にマッピングするのが面倒だったため、リフレクションで実装を端折ったフィールドの文字列は異なっているが、ELF のフォーマットを解釈できていることは確認できる。


ちなみに COFF を表現する Coff クラスもある。こちらは main() メソッドおよび toString() メソッドが実装されているようで .exe を渡すと、あっさりダンプすることができる。

% java org.eclipse.cdt.utils.coff.Coff `cygpath -w /usr/bin/ls.exe`
FILE HEADER VALUES
f_magic = 23117
f_nscns = 144
f_timdat = 1970/01/01
f_symptr = 4
f_nsyms = 65535
f_opthdr = 184
f_flags = 0
OPTIONAL HEADER VALUES
magic      = 0
vstamp     = 0
tsize      = 64
dsize      = 0
bsize      = 0
entry      = 0
text_start = 0
data_start = 0
SECTION HEADER VALUES

s_paddr = 1024
s_vaddr = 132370
s_size = 3
s_scnptr = 2097152
s_relptr = 4096
s_lnnoptr = 1048576
s_nreloc = 4096
s_nlnno = 0
s_flags = 0
RELOC VALUES
r_vaddr = 1149831051 r_symndx = 1435173924
RELOC VALUES
r_vaddr = -1962933908 r_symndx = 1972106333
RELOC VALUES
r_vaddr = -2082109099 r_symndx = 2106136812
RELOC VALUES
r_vaddr = 1971975261 r_symndx = -70850312
RELOC VALUES
r_vaddr = -2063597573 r_symndx = 1958906075
:
:

*1:java-ja からきますた とか言いながら、実は Eclipse 等の IDE をほとんど使ったことがない...。

*2:この記事を書いている時の最新は 4.0.3

*3:例外処理とかは完全に手を抜いているため、CDT の API 以外は参考にしないこと。