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 ???