ELFの読み込みテスト

この前作ったテスト用のバイナリを読める程度の最低限動くプログラムを書いてみました。manのELF(5)が参考資料です。
メインPC自体は64bitなのですが、作ってるカーネルは32bitなので、テスト用のバイナリも32bitでコンパイルしてます。
これを自作カーネルに持っていきます。
テストプログラムはデータとか全然ないし、静的なバイナリなのでreadelf -Sでセクションを見るとほとんどデータはありません。

There are 6 section headers, starting at offset 0xcc:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        08048074 000074 000011 00  AX  0   0  4
  [ 2] .comment          PROGBITS        00000000 000085 00001c 01  MS  0   0  1
  [ 3] .shstrtab         STRTAB          00000000 0000a1 00002a 00      0   0  1
  [ 4] .symtab           SYMTAB          00000000 0001bc 000080 10      5   4  4
  [ 5] .strtab           STRTAB          00000000 00023c 000026 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

ELFの読み込みは大体以下のような感じでやってます。
ELFヘッダ->プログラムヘッダテーブル->文字列テーブル->セクションヘッダ->シンボルテーブル

最初にELFヘッダから読んでますが、これはファイルの先頭から構造体のサイズ分を一気に読んでるだけです。
次にプログラムヘッダの読み込みですが、
最初にElf32_Ehdrのe_phnumを見てこいつが0じゃなければ、データを読み込みます。
プログラムヘッダのサイズはe_phentsize*e_phnumになっています。
ここから先に出てくる変数fileはELFファイルをmmpaしたデータです。

static int read_program_header(struct elf *data, const unsigned char *file)
{
	unsigned long size = get_program_table_size(data);
	// Is there program header.
	if (!data->e_hdr.e_phnum)
		return 1;
	
	data->p_hdr = malloc(size);
	if (!data->p_hdr)
		return -1;
	
	memcpy(data->p_hdr, file + data->e_hdr.e_phoff, size);
	
	return 0;
}

その次はプログラムヘッダです。最初はさっきと同じようにチェックがあり、e_shnumが0じゃなければプログラムヘッダの読み込みをします。プログラムヘッダのサイズは、Elf32_Ehdrのe_shentsizeとe_shnumを掛け算します。

static int read_section_header(struct elf *data, const unsigned char *file)
{
	unsigned long section_size = get_section_size(data);
	
	// Is there any section header?
	if (!data->e_hdr.e_shnum)
		return 1;
	
	data->s_hdr = malloc(section_size);
	if (!data->s_hdr)
		return -1;
	
	memcpy(data->s_hdr, file + data->e_hdr.e_shoff, section_size);
	
	return 0;
}

次はストリングテーブルです。このテーブルはn個あるセクションヘッダのどこかにいて、そのインデックスはElf32_Ehdrのdata->e_hdr.e_shstrndxで示されてます。
なので、セクションヘッダの配列[Elf32_Ehdr.e_shstrndx]という感じでアクセスします。
sh_offsetはテーブルのアドレス(ファイルの先頭から)で、sh_sizeはテーブルのサイズです。
offsetとsizeはシンボルテーブルなんかでも同様の方法で扱います。

static int read_string_table(struct elf *data, const unsigned char *file)
{
	unsigned long offset = data->s_hdr[data->e_hdr.e_shstrndx].sh_offset;
	unsigned long size = data->s_hdr[data->e_hdr.e_shstrndx].sh_size;

	data->str_table.string_tbl = malloc(size);
	if (!data->str_table.string_tbl)
		return -1;

	memcpy(data->str_table.string_tbl, file + offset, size);

	return 0;
}

次にストリングテーブルですが、こんな感じに書いてみました。
get_section_header()でシンボル名を指定して、欲しいセクションヘッダの構造体を取得します。
あとは、さっきのoffsetとsize方式でデータのアクセスしてます。

static int read_symbol_table(struct elf *data, const unsigned char *file)
{
	const Elf32_Shdr *sym = get_section_header(data, ".symtab");

	if (!sym)
		return -2;
	
	data->sym = malloc(sym->sh_size);
	if (!data->sym)
		return -1;

	memcpy(data->sym, file + sym->sh_offset, sym->sh_size);	
	data->sym_count = sym->sh_size / sizeof(Elf32_Sym);

	return 0;
}

get_section_header()ではget_section_name()を使って、セクション名を取得します。

static const Elf32_Shdr *get_section_header(const struct elf *data, const char *section)
{
	const Elf32_Shdr *sym = NULL;
	unsigned char *str_tbl = data->str_table.string_tbl;
	int i;
	
	for (i = 0; i < data->e_hdr.e_shnum; i++) {
		const Elf32_Shdr *p = data->s_hdr + i;
		char buf[64] = { 0 };
		get_section_name(p, str_tbl, buf);
		if (!strcmp(buf, section)) {
			sym = p;
			break;
		}
	}

	return sym;

}

ELFのストリングテーブルは単に文字列が連続しているだけなんですが、文字列の終端は0x0が入っています。
また、sh_nameというのは名前からして、char型で文字列が入っているイメージなんですが、そういうわけではなくて、
ストリングテーブル先頭からのオフセットが入っています。なので、ストリングテーブルの開始アドレス+sh_nameをstrcpyの2番目の引数にしてあげれば、サイズが分からなくてもデータの取得はできます。

static void get_section_name(const Elf32_Shdr *data, const unsigned char *table, char *buf)
{
	strcpy(buf, (const char *) table + data->sh_name);
}

大体こんな感じでやれば他のデータも読めるようになりました。
作ったプログラムはこれです。エラー処理とかはあまり気にしてません。

#include <elf.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

static const char test_file[] = "/home/masami/experiment/miko/test/test_program/hello";

struct string_tables {
	unsigned char *string_tbl;
	unsigned char *symbol_str_tbl;
};

struct section {
	unsigned char *data;
	size_t size;
};
struct sections {
	struct section *text;
	struct section *bss;
};

struct elf {
	Elf32_Ehdr e_hdr;
	Elf32_Phdr *p_hdr;
	Elf32_Shdr *s_hdr;
	Elf32_Sym  *sym;
	int sym_count;
	Elf32_Rel  *rel;
	Elf32_Rela *rela;
	struct string_tables str_table;
	struct sections section_data;
};

static unsigned long get_file_size(void);
static void *map2memory(unsigned long size);
static inline unsigned long get_program_table_size(const struct elf *data);
static inline unsigned long get_section_size(const struct elf *data);
static int read_section_header(struct elf *data, const unsigned char *file);
static int read_program_header(struct elf *data, const unsigned char *file);
static int read_string_table(struct elf *data, const unsigned char *file);
static int read_symbol_string_table(struct elf *data, const unsigned char *file);
static int read_text_section(struct elf *data, const unsigned char *file);
static int read_bss_section(struct elf *data, const unsigned char *file);
static struct section *read_section(struct elf *data, const unsigned char *file, const char *name);
static int is_elf(const struct elf *data);
static int read_header(struct elf *data, const unsigned char *file);
static int read_symbol_table(struct elf *data, const unsigned char *file);
static void get_section_name(const Elf32_Shdr *data, const unsigned char *table, char *buf);
static void get_symbol_string_name(const Elf32_Sym *data, const unsigned char *table, char *buf);
static const Elf32_Shdr *get_section_header(const struct elf *data, const char *section);
static void print_symbol_table(const struct elf *data, const unsigned char *file);
static void print_section_header(const struct elf *data, const unsigned char *file);
static void print_program_header(const struct elf *data);
static void print_header(const struct elf *data);
static void print_text_section(const struct elf *data);
static void print_bss_section(const struct elf *data);
static void free_elf_data(struct elf *data);
static void free_string_tables(struct string_tables *tables);
static void free_section(struct section *section);
static void free_sections(struct sections *sections);

static unsigned long get_file_size(void)
{
	struct stat st;

	assert(stat(test_file, &st) != -1);

	return st.st_size;
}

static void *map2memory(unsigned long size)
{
	int fd;
	void *ret;

	fd = open(test_file, O_RDONLY);
	assert(fd >= 0);

	ret = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
	assert(ret != MAP_FAILED);

	close(fd);

	return ret;
}

static void free_string_tables(struct string_tables *tables)
{
	if (tables->string_tbl)
		free(tables->string_tbl);
	if (tables->symbol_str_tbl)
		free(tables->symbol_str_tbl);
}

static void free_section(struct section *section)
{
	if (!section)
		return ;
	if (section->data) 
		free(section->data);
	section->size = 0;
	free(section);
}

static void free_sections(struct sections *sections)
{
	free_section(sections->text);
	free_section(sections->bss);
}

static void free_elf_data(struct elf *data)
{
	if (!data)
		return ;

	if (data->p_hdr)
		free(data->p_hdr);
	if (data->s_hdr)
		free(data->s_hdr);
	if (data->sym)
		free(data->sym);
	if (data->rel)
		free(data->rel);
	if (data->rela)
		free(data->rela);
	
	free_string_tables(&data->str_table);
	free_sections(&data->section_data);

	free(data);
}


static const Elf32_Shdr *get_section_header(const struct elf *data, const char *section)
{
	const Elf32_Shdr *sym = NULL;
	unsigned char *str_tbl = data->str_table.string_tbl;
	int i;
	
	for (i = 0; i < data->e_hdr.e_shnum; i++) {
		const Elf32_Shdr *p = data->s_hdr + i;
		char buf[64] = { 0 };
		get_section_name(p, str_tbl, buf);
		if (!strcmp(buf, section)) {
			sym = p;
			break;
		}
	}

	return sym;

}

static inline unsigned long get_program_table_size(const struct elf *data)
{
	return data->e_hdr.e_phentsize * data->e_hdr.e_phnum;
}


static inline unsigned long get_section_size(const struct elf *data)
{
	return data->e_hdr.e_shentsize * data->e_hdr.e_shnum;
}

static int read_section_header(struct elf *data, const unsigned char *file)
{
	unsigned long section_size = get_section_size(data);
	
	// Is there any section header?
	if (!data->e_hdr.e_shnum)
		return 1;
	
	data->s_hdr = malloc(section_size);
	if (!data->s_hdr)
		return -1;
	
	memcpy(data->s_hdr, file + data->e_hdr.e_shoff, section_size);
	
	return 0;
}

static struct section *read_section(struct elf *data, const unsigned char *file, const char *name)
{
	const Elf32_Shdr *sym = get_section_header(data, name);
	struct section *ret;

	if (!sym)
		return NULL;

	ret = malloc(sizeof(*ret));
	ret->size = sym->sh_size;
	if (!sym->sh_size) {
		printf("%s section size is 0\n", name);
		return NULL;
	}

	ret->data = malloc(sym->sh_size);
	if (!ret->data)
		return NULL;

 	memcpy(ret->data, file + sym->sh_offset, sym->sh_size);

	return ret;
}

static int read_text_section(struct elf *data, const unsigned char *file)
{
	data->section_data.text = read_section(data, file, ".text");
	if (!data->section_data.text)
		return -1;

	return 0;
}

static int read_bss_section(struct elf *data, const unsigned char *file)
{
	data->section_data.bss = read_section(data, file, ".bss");
	if (!data->section_data.bss)
		return -1;

	return 0;
}


static int read_program_header(struct elf *data, const unsigned char *file)
{
	unsigned long size = get_program_table_size(data);
	// Is there program header.
	if (!data->e_hdr.e_phnum)
		return 1;
	
	data->p_hdr = malloc(size);
	if (!data->p_hdr)
		return -1;
	
	memcpy(data->p_hdr, file + data->e_hdr.e_phoff, size);
	
	return 0;
}

static int read_string_table(struct elf *data, const unsigned char *file)
{
	unsigned long offset = data->s_hdr[data->e_hdr.e_shstrndx].sh_offset;
	unsigned long size = data->s_hdr[data->e_hdr.e_shstrndx].sh_size;

	data->str_table.string_tbl = malloc(size);
	if (!data->str_table.string_tbl)
		return -1;

	memcpy(data->str_table.string_tbl, file + offset, size);

	return 0;
}

static int read_symbol_string_table(struct elf *data, const unsigned char *file)
{
	const Elf32_Shdr *sym = get_section_header(data, ".strtab");

	if (!sym)
		return -2;

	data->str_table.symbol_str_tbl = malloc(sym->sh_size);
	if (!data->str_table.symbol_str_tbl)
		return -1;

	memcpy(data->str_table.symbol_str_tbl, file + sym->sh_offset, sym->sh_size);
	return 0;
	
}


static int is_elf(const struct elf *data)
{
	if (data->e_hdr.e_ident[0] == 0x7f &&
		data->e_hdr.e_ident[1] == 'E' &&
		data->e_hdr.e_ident[2] == 'L' &&
		data->e_hdr.e_ident[3] == 'F')
		return 1;
		
	return 0;
}

static int read_header(struct elf *data, const unsigned char *file)
{
	memcpy(&data->e_hdr, file, sizeof(data->e_hdr));
	
	return is_elf(data) == 1 ? 0 : -1;
}

static int read_symbol_table(struct elf *data, const unsigned char *file)
{
	const Elf32_Shdr *sym = get_section_header(data, ".symtab");

	if (!sym)
		return -2;
	
	data->sym = malloc(sym->sh_size);
	if (!data->sym)
		return -1;

	memcpy(data->sym, file + sym->sh_offset, sym->sh_size);	
	data->sym_count = sym->sh_size / sizeof(Elf32_Sym);

	return 0;
}

static void get_section_name(const Elf32_Shdr *data, const unsigned char *table, char *buf)
{
	strcpy(buf, (const char *) table + data->sh_name);
}

static void get_symbol_string_name(const Elf32_Sym *data, const unsigned char *table, char *buf)
{
	strcpy(buf, (const char *) table + data->st_name);
}

static void print_text_section(const struct elf *data)
{
	int i;

	if (!data->section_data.text)
		return ;

	printf("[-------Text section size(0x%lx)-------]\n", data->section_data.text->size);

	for (i = 0; i < data->section_data.text->size; i++) {
		printf("0x%02x ", data->section_data.text->data[i]);
		if (i != 0 && (i % 16) == 0)
			printf("\n");
	}
}
static void print_bss_section(const struct elf *data)
{
	int i;

	if (!data->section_data.bss)
		return ;

	printf("[-------BSS section size(0x%lx)-------]\n", data->section_data.bss->size);

	for (i = 0; i < data->section_data.bss->size; i++) {
		printf("0x%02x ", data->section_data.bss->data[i]);
		if (i != 0 && (i % 16) == 0)
			printf("\n");
	}
}

static void print_symbol_table(const struct elf *data, const unsigned char *file)
{
	int i;
	const Elf32_Sym *p;
	unsigned char *sym_str_tbl = data->str_table.symbol_str_tbl;
	
	for (i = 0; i < data->sym_count; i++) {
		p = data->sym + i;
		char buf[128] = { 0 };
		get_symbol_string_name(p, sym_str_tbl, buf);
		printf("[-------Symbol Table %d-------]\nst_name: %s\nst_value: 0x%x\n" 
		       "st_size: 0x%x\nst_info: 0x%x\nst_other: 0x%x\nst_shndx: 0x%x\n",
		       i, buf, p->st_value,
		       p->st_size, p->st_info, p->st_other,
		       p->st_shndx);
	}

}

static void print_section_header(const struct elf *data, const unsigned char *file)
{
	int i;
	const unsigned char *str_tbl = data->str_table.string_tbl;

	for (i = 0; i < data->e_hdr.e_shnum; i++) {
		const Elf32_Shdr *p = data->s_hdr + i;
		char buf[64] = { 0 };
		get_section_name(p, str_tbl, buf);
		printf("[-------Section Header %d-------]\nsh_name: %s\nsh_type: 0x%x\nsh_flags: 0x%x\n"
		       "sh_addr: 0x%x\nsh_offset 0x%x\nsh_size: 0x%x\nsh_link: 0x%x\n"
		       "sh_info: 0x%x\nsh_addralign: 0x%x\nsh_entsize: 0x%x\n",
		       i, buf, p->sh_type, p->sh_flags, 
		p->sh_addr, p->sh_offset, p->sh_size,
		p->sh_link, p->sh_info, p->sh_addralign,
		p->sh_entsize);
	}

}

static void print_program_header(const struct elf *data)
{
	int i;
	
	for (i = 0; i < data->e_hdr.e_phnum; i++) {
		const Elf32_Phdr *p = data->p_hdr + i;
		printf("[-------Program Header %d-------]\np_type: 0x%x\np_offset: 0x%x\n"
		       "p_vaddr: 0x%x\np_paddr: 0x%x\np_memsz: 0x%x\np_flags: 0x%x\np_align: 0x%x\n", 
			i, p->p_type, p->p_offset, p->p_vaddr,
			p->p_paddr, p->p_memsz, p->p_flags, p->p_align);
	}
}

static void print_header(const struct elf *data)
{
	printf("[-------ELF Header-------]\n");
	printf("EI_CLASS: 0x%x\n", data->e_hdr.e_ident[4]);
	printf("EI_DATA: 0x%x\n", data->e_hdr.e_ident[5]);
	printf("EI_VERSION: 0x%x\n", data->e_hdr.e_ident[6]);
	printf("EI_OSABI: 0x%x\n", data->e_hdr.e_ident[7]);
	
	printf("e_type: 0x%x\n", data->e_hdr.e_type);
	printf("e_machine: 0x%x\n", data->e_hdr.e_machine);
	printf("e_version: 0x%x\n", data->e_hdr.e_version);
	printf("e_entry: 0x%x\n", data->e_hdr.e_entry);
	printf("e_phoff: 0x%x\n", data->e_hdr.e_phoff);
	printf("e_shoff: 0x%x\n", data->e_hdr.e_shoff);
	printf("e_ehsize: 0x%x\n", data->e_hdr.e_ehsize);
	printf("e_phentsize: 0x%x\n", data->e_hdr.e_phentsize);
	printf("e_phnum: 0x%x\n", data->e_hdr.e_phnum);
	printf("e_shentsize: 0x%x\n", data->e_hdr.e_shentsize);
	printf("e_shnum: 0x%x\n", data->e_hdr.e_shnum);
	printf("e_shstrndx: 0x%x\n", data->e_hdr.e_shstrndx);
}

struct elf *init_elf_data(void)
{
	struct elf *data;

	data = malloc(sizeof(*data));
	if (!data) {
		printf("couldn't allocate for data\n");
		return NULL;
	}

	memset(data, 0, sizeof(*data));

	return data;
}

int main(int argc, char **argv)
{
	unsigned long file_size = get_file_size();
	unsigned char *file = NULL;
	struct elf *data;

	data = init_elf_data();
	if (!data)
		return 0;

	printf("file %s size is %ld\n", test_file, file_size);
	
	file = map2memory(file_size);
	
	printf("file %s has been mmapped\n", test_file);
	
	if (read_header(data, file) < 0) {
		munmap(file, file_size);
		printf("this is not ELF file\n");
		return -1;
	}

	print_header(data);
	
	read_program_header(data, file);
	print_program_header(data);

	read_section_header(data, file);
	read_string_table(data, file);
		
	print_section_header(data, file);

	read_symbol_table(data, file);
	read_symbol_string_table(data, file);

	print_symbol_table(data, file);

	read_text_section(data, file);
	print_text_section(data);

	read_bss_section(data, file);
	print_bss_section(data);

	free_elf_data(data);
	munmap(file, file_size);
	
	return 0;
}

これを実行すると、下のようになります。

file /home/masami/experiment/miko/test/test_program/hello size is 610
file /home/masami/experiment/miko/test/test_program/hello has been mmapped
[-------ELF Header-------]
EI_CLASS: 0x1
EI_DATA: 0x1
EI_VERSION: 0x1
EI_OSABI: 0x0
e_type: 0x2
e_machine: 0x3
e_version: 0x1
e_entry: 0x8048074
e_phoff: 0x34
e_shoff: 0xcc
e_ehsize: 0x34
e_phentsize: 0x20
e_phnum: 0x2
e_shentsize: 0x28
e_shnum: 0x6
e_shstrndx: 0x3
[-------Program Header 0-------]
p_type: 0x1
p_offset: 0x0
p_vaddr: 0x8048000
p_paddr: 0x8048000
p_memsz: 0x85
p_flags: 0x5
p_align: 0x1000
[-------Program Header 1-------]
p_type: 0x6474e551
p_offset: 0x0
p_vaddr: 0x0
p_paddr: 0x0
p_memsz: 0x0
p_flags: 0x6
p_align: 0x4
[-------Section Header 0-------]
sh_name: 
sh_type: 0x0
sh_flags: 0x0
sh_addr: 0x0
sh_offset 0x0
sh_size: 0x0
sh_link: 0x0
sh_info: 0x0
sh_addralign: 0x0
sh_entsize: 0x0
[-------Section Header 1-------]
sh_name: .text
sh_type: 0x1
sh_flags: 0x6
sh_addr: 0x8048074
sh_offset 0x74
sh_size: 0x11
sh_link: 0x0
sh_info: 0x0
sh_addralign: 0x4
sh_entsize: 0x0
[-------Section Header 2-------]
sh_name: .comment
sh_type: 0x1
sh_flags: 0x30
sh_addr: 0x0
sh_offset 0x85
sh_size: 0x1c
sh_link: 0x0
sh_info: 0x0
sh_addralign: 0x1
sh_entsize: 0x1
[-------Section Header 3-------]
sh_name: .shstrtab
sh_type: 0x3
sh_flags: 0x0
sh_addr: 0x0
sh_offset 0xa1
sh_size: 0x2a
sh_link: 0x0
sh_info: 0x0
sh_addralign: 0x1
sh_entsize: 0x0
[-------Section Header 4-------]
sh_name: .symtab
sh_type: 0x2
sh_flags: 0x0
sh_addr: 0x0
sh_offset 0x1bc
sh_size: 0x80
sh_link: 0x5
sh_info: 0x4
sh_addralign: 0x4
sh_entsize: 0x10
[-------Section Header 5-------]
sh_name: .strtab
sh_type: 0x3
sh_flags: 0x0
sh_addr: 0x0
sh_offset 0x23c
sh_size: 0x26
sh_link: 0x0
sh_info: 0x0
sh_addralign: 0x1
sh_entsize: 0x0
[-------Symbol Table 0-------]
st_name: 
st_value: 0x0
st_size: 0x0
st_info: 0x0
st_other: 0x0
st_shndx: 0x0
[-------Symbol Table 1-------]
st_name: 
st_value: 0x8048074
st_size: 0x0
st_info: 0x3
st_other: 0x0
st_shndx: 0x1
[-------Symbol Table 2-------]
st_name: 
st_value: 0x0
st_size: 0x0
st_info: 0x3
st_other: 0x0
st_shndx: 0x2
[-------Symbol Table 3-------]
st_name: hello.c
st_value: 0x0
st_size: 0x0
st_info: 0x4
st_other: 0x0
st_shndx: 0xfff1
[-------Symbol Table 4-------]
st_name: __bss_start
st_value: 0x8049085
st_size: 0x0
st_info: 0x10
st_other: 0x0
st_shndx: 0xfff1
[-------Symbol Table 5-------]
st_name: main
st_value: 0x8048074
st_size: 0x11
st_info: 0x12
st_other: 0x0
st_shndx: 0x1
[-------Symbol Table 6-------]
st_name: _edata
st_value: 0x8049085
st_size: 0x0
st_info: 0x10
st_other: 0x0
st_shndx: 0xfff1
[-------Symbol Table 7-------]
st_name: _end
st_value: 0x8049088
st_size: 0x0
st_info: 0x10
st_other: 0x0
st_shndx: 0xfff1
[-------Text section size(0x11)-------]
0x55 0x89 0xe5 0xb8 0x01 0x00 0x00 0x00 0xcd 0x80 0xb8 0x00 0x00 0x00 0x00 0x5d 0xc3