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 ファイル自体のヘッダの表示) の機能だけを実装してみることにする。
以下に適当なやり方を示す。
- CDT Europa Releases Update Site から適当な CDT をダウンロードする。*2
- ダウンロードしてきた cdt-master-*.zip を展開した後のディレクトリの plugins/org.eclipse.cdt.core_*.jar にクラスパスを通しておく。
- 以下の 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 : :