ELF形式を理解する.1
はじめに
リンカが何をやってるのかが、いまいちわからないのでリンカので出力であるELFフォーマットのファイルを調べてみることにした。1stステップで作られるkzload.elfを先頭から解析してみる。kzload.elfはH8上(ビッグエンディアン)で動かすが、解析はintelの64ビットCPU上(リトルエンディアン)で行うので、エンディアンの変換が必要。よって以下の関数を作成
/* swap.c */ short swap_short(unsigned short value); //unsigned-->論理シフト可能に int swap_int(unsigned int value); //unsigned-->論理シフト可能に short swap_short(unsigned short value){ //16bit用 short ret; ret = value << 8; ret |= value >> 8; return ret; } int swap_int(unsigned int value){ //32bit用 int ret; ret = value << 24; ret |= (value&0x0000FF00) << 8; ret |= (value&0x00FF0000) >> 8; ret |= value >> 24; return ret; }
以後、このファイルの関数を使う前提で進める。
Step1: ELFヘッダを読んで見る
Executable and Linkable Format - Wikipediaを参考に構造体(全部で52バイト)を定義し、以下のelf_header_read.cを用意。
/* elf_header_read.c */ #include <stdio.h> short swap_short(unsigned short value); int swap_int(unsigned int value); struct elf_header{ struct{ unsigned char magic[4]; unsigned char class; unsigned char format; unsigned char version; unsigned char abi; unsigned char abi_version; unsigned char reserve[7]; }id; short type; short arch; int version; int entry_point; int program_header_offset; int section_header_offset; int flags; short header_size; short program_header_size; short program_header_num; short section_header_size; short section_header_num; short section_name_index; }; int main(void){ struct elf_header header; FILE *fp; fp = fopen("kzload.elf", "rb"); fread(&header, sizeof(header), 1, fp); printf("%s\n", header.id.magic); printf("%d\n", header.id.class); printf("%d\n", header.id.format); printf("%d\n", header.id.version); printf("%d\n", header.id.abi); printf("%d\n", header.id.abi_version); printf("%s\n", header.id.reserve); printf("%04x\n", swap_short(header.type)); printf("%04x\n", swap_short(header.arch)); printf("%08x\n", swap_int(header.version)); printf("%08x\n", swap_int(header.entry_point)); printf("%08x\n", swap_int(header.program_header_offset)); printf("%08x\n", swap_int(header.section_header_offset)); printf("%08x\n", swap_int(header.flags)); printf("%04x\n", swap_short(header.header_size)); printf("%04x\n", swap_short(header.program_header_size)); printf("%04x\n", swap_short(header.program_header_num)); printf("%04x\n", swap_short(header.section_header_size)); printf("%04x\n", swap_short(header.section_header_num)); printf("%04x\n", swap_short(header.section_name_index)); fclose(fp); return 1; }
実行すると、標準出力は以下の通り。
ELF 1 2 1 0 0 0002 002e 00000001 00000100 00000034 00000520 00810000 0034 0020 0002 0028 0008 0005
Executable and Linkable Format - Wikipediaを参考にそれぞれの意味を調べる。
意味 | kzload.elf | kzload.elfでの意味 |
マジック・ナンバ | ELF | ELF |
32ビット/64ビットの区別 | 1 | 32ビット |
エンディアン | 2 | ビッグエンディアン |
ELF形式のバージョン | 1 | オリジナルバージョン |
OSの種別 | 0 | 意味なし(ターゲットプラットフォームに関係なく0にセットすることが多い) |
OSのバージョン | 0 | 意味なし |
予約 | 意味なし(未使用) | |
ファイルの種類 | 0x0002 | ET_EXEC (実行形式ファイル) |
CPUの種類 | 0x002e | Hitachi H8/300 |
ELF形式のバージョン | 0x00000001 | オリジナルバージョン |
実行開始アドレス | 0x00000100 | ??? |
プログラムヘッダテーブルの先頭位置 | 0x00000034 | kzload.elfの先頭から52バイト目 |
セクションヘッダテーブルの先頭位置 | 0x00000520 | kzload.elfの先頭から1312バイト目 |
各種フラグ | 0x00810000 | ???(ターゲットによって解釈が変わるらしいが詳細不明) |
ELFヘッダのサイズ | 0x0034 | 52バイト |
プログラムヘッダのサイズ | 0x0020 | 32バイト |
プログラムヘッダの個数 | 0x0002 | 2個 |
セクションヘッダのサイズ | 0x0028 | 40バイト |
セクションヘッダの個数 | 0x0008 | 8個 |
セクション名を格納するセクションの番号 | 0x0005 | ??? |
Step2: プログラムヘッダを読んで見る
プログラムヘッダテーブル用の構造体(全部で128×2バイト)を定義する。ELFヘッダから、プログラムヘッダは2個あることがわかったので、2つ用意。
/* program_header_read.c */ #include <stdio.h> short swap_short(unsigned short value); int swap_int(unsigned int value); struct program_header{ int type; int offset; int virtual_addr; int physical_addr; int file_size; int memory_size; int flags; int align; }; int main(void){ struct program_header header[2]; FILE *fp; int i; fp = fopen("kzload.elf", "rb"); fseek(fp, 52, SEEK_SET); //ELFヘッダ分シフト fread(&header, sizeof(header), 1, fp); for(i=0; i<2; i++){ printf("program_header %d\n", i+1); printf("%08x\n", swap_int(header[i].type)); printf("%08x\n", swap_int(header[i].offset)); printf("%08x\n", swap_int(header[i].virtual_addr)); printf("%08x\n", swap_int(header[i].physical_addr)); printf("%08x\n", swap_int(header[i].file_size)); printf("%08x\n", swap_int(header[i].memory_size)); printf("%08x\n", swap_int(header[i].flags)); printf("%08x\n", swap_int(header[i].align)); } fclose(fp); return 1; }
実行すると、標準出力は以下の通り。
program_header 1 00000001 00000074 00000000 00000000 00000236 00000236 00000007 00000001 program_header 2 00000001 000002ac 00000238 00000238 0000000c 0000000c 00000006 00000001
それぞれの意味を調べる。
1つ目のプログラムヘッダは
意味 | kzload.elf | kzload.elfでの意味 |
セグメントの種類 | 0x00000001 | このセグメントはメモリ上にロードされる予定 |
セグメントの先頭位置 | 0x00000074 | kzload.elfの先頭から116バイト目 |
メモリ上でのセグメントの論理アドレス | 0x00000000 | ??? |
メモリ上でのセグメントの物理アドレス | 0x00000000 | ??? |
セグメントのサイズ | 0x00000236 | 566バイト |
メモリ上でのセグメントのサイズ | 0x00000236 | 566バイト |
各種フラグ | 0x00000007 | ??? |
セグメントのアラインメント | 0x00000001 | ??? |
2つ目のプログラムヘッダは
セグメントの種類 | 0x00000001 | このセグメントはメモリ上にロードされる予定 |
セグメントの先頭位置 | 0x000002ac | kzload.elfの先頭から116バイト目 |
メモリ上でのセグメントの論理アドレス | 0x00000238 | ??? |
メモリ上でのセグメントの物理アドレス | 0x00000238 | ??? |
セグメントのサイズ | 0x0000000c | 566バイト |
メモリ上でのセグメントのサイズ | 0x0000000c | 566バイト |
各種フラグ | 0x00000006 | ??? |
セグメントのアラインメント | 0x00000001 | ??? |