バイナリ何もわからない人に送る"ソースコード視点"のELFヘッダ解説
目次
注意
readelf 実装
で検索すると未だに一番上に出てきてしまっているので注意.
これはelfについて全然詳しくない時期に書いたものです.
一応入門的内容についてまとめたものがあるので,
よろしければそちらを.
概要
ELFフォーマットを理解するための試み第二弾。
こちらの記事の続きと言ってもいいです。
上記の記事には間違いが多いので注意してください。
本記事バイナリ何もわからない人に送るELFヘッダ解説
が正確です。
自作readelfの完成度を高める為にも、
単純にELFを勉強する教材として優れているという事からも、
本家readelfのコードリーディングは適切だと考えました。
今回は-h
オプションに限定していきます。
まずはおさらい
まずはELF64ヘッダの構造体を見ながら、
ELFヘッダがどのように形成されているのか復習しましょう。
typedef struct elf64_hdr { unsigned char e_ident[EI_NIDENT]; /* ELFのマジックナンバー*/ Elf64_Half e_type; /* ELFバイナリの種類*/ Elf64_Half e_machine; /*アーキテクチャを識別する番号*/ Elf64_Word e_version; /*現在は基本的に1*/ Elf64_Addr e_entry; /* 仮想アドレスのエントリポイント */ Elf64_Off e_phoff; /* プログラムヘッダテーブルの開始位置 */ Elf64_Off e_shoff; /* セクションヘッダテーブルの開始位置 */ Elf64_Word e_flags; Elf64_Half e_ehsize; /*ELFヘッダのサイズ*/ Elf64_Half e_phentsize; /*プログラムヘッダのサイズ*/ Elf64_Half e_phnum; /*プログラムヘッダの数*/ Elf64_Half e_shentsize; /*セクションヘッダのサイズ*/ Elf64_Half e_shnum; /*セクションヘッダの数*/ Elf64_Half e_shstrndx; /*セクション名を指すインデックス*/ } Elf64_Ehdr;
Elf64_Half
,Elf64_Word
,Elf64_Addr
,Elf64_Off
はそれぞれ、
/* 64-bit ELF base types. */ typedef __u64 Elf64_Addr; //unsigned-int64 typedef __u16 Elf64_Half; //unsigned-int16 typedef __s16 Elf64_SHalf; //signed-int16 typedef __u64 Elf64_Off; //unsigned-int64 typedef __s32 Elf64_Sword; //signed-int32 typedef __u32 Elf64_Word; //unsigned-int32 typedef __u64 Elf64_Xword; //unsigned-int64 typedef __s64 Elf64_Sxword; //signed-int64
と elf.hに記述されています。
C言語が分かりづらいなあという方の為に、
私の作ったreadelfも載せておきます。
(私のブログ見ている人には Go使い が多そうなので。)
type ElfHeader struct { MagicNumber uint64 //マジックナンバー Class uint16 //ELFバイナリの種類 Data uint16 //整数表現の種類 Version uint32 //基本1 OSABI uint16 //ex: UNIX - GNU ABIVersion uint16 //使われてない Padding uint32 //0パディング FileType uint16 //実行ファイルか?共有オブジェクトファイルか? MachineArchitecture uint16 //アーキテクチャの種類を識別 FileVersion uint16 EntryPoint uint64 ProgramHeader uint64 SectionHeader uint64 Flags uint32 //プロセッサ依存のフラグ。使われてない HeaderSize uint16 ProgramHeaderSize uint16 ProgramHeaderNum uint16 SectionHeaderSize uint16 SectionHeaderNum uint16 SectionNumber uint16 }
ELFヘッダの構成については掴めたと思います。
図等でわかりやすく説明している記事に、
http://softwaretechnique.jp/OS_Development/Tips/ELF/elf01.htmlsoftwaretechnique.jp
があるので参考にしてください。
ただソースコードが一番分かりやすいのかな?とは思います。
本題:readelf
重要なのはここまでで、
ここから先はコードを追っていくだけの単純作業です。
ここまでの事がわからなければこの先理解するのは難しいので、
読み直して理解してみてください。
まずは
から、-h
オプションの解析部分を探します。
static struct option options[] = { {"all", no_argument, 0, 'a'}, {"file-header", no_argument, 0, 'h'}, {"program-headers", no_argument, 0, 'l'}, {"headers", no_argument, 0, 'e'}, {"histogram", no_argument, 0, 'I'}, {"segments", no_argument, 0, 'l'}, {"sections", no_argument, 0, 'S'}, {"section-headers", no_argument, 0, 'S'}, {"section-groups", no_argument, 0, 'g'}, {"section-details", no_argument, 0, 't'}, {"full-section-name",no_argument, 0, 'N'}, {"symbols", no_argument, 0, 's'}, {"syms", no_argument, 0, 's'}, {"dyn-syms", no_argument, 0, OPTION_DYN_SYMS}, {"relocs", no_argument, 0, 'r'}, {"notes", no_argument, 0, 'n'}, {"dynamic", no_argument, 0, 'd'}, {"arch-specific", no_argument, 0, 'A'}, {"version-info", no_argument, 0, 'V'}, {"use-dynamic", no_argument, 0, 'D'}, {"unwind", no_argument, 0, 'u'}, {"archive-index", no_argument, 0, 'c'}, {"hex-dump", required_argument, 0, 'x'}, {"relocated-dump", required_argument, 0, 'R'}, {"string-dump", required_argument, 0, 'p'}, {"decompress", no_argument, 0, 'z'}, #ifdef SUPPORT_DISASSEMBLY {"instruction-dump", required_argument, 0, 'i'}, #endif {"debug-dump", optional_argument, 0, OPTION_DEBUG_DUMP}, {"dwarf-depth", required_argument, 0, OPTION_DWARF_DEPTH}, {"dwarf-start", required_argument, 0, OPTION_DWARF_START}, {"dwarf-check", no_argument, 0, OPTION_DWARF_CHECK}, {"version", no_argument, 0, 'v'}, {"wide", no_argument, 0, 'W'}, {"help", no_argument, 0, 'H'}, {0, no_argument, 0, 0} };
オプションの定義部分を見つけました。
このうちfile-header
のフローを読んでいきます。
引数解析部分はとても長いですが、
やっていることはとてもシンプル。
- オプションを解析して、対応するフラグを立てる
- 何も指定されていなければUsageを標準エラー出力。
static void parse_args (Filedata * filedata, int argc, char ** argv) { int c; if (argc < 2) usage (stderr); while ((c = getopt_long (argc, argv, "ADHINR:SVWacdeghi:lnp:rstuvw::x:z", options, NULL)) != EOF) { switch (c) { case 0: /* Long options. */ break; case 'H': usage (stdout); break; case 'a': do_syms = TRUE; do_reloc = TRUE; do_unwind = TRUE; do_dynamic = TRUE; do_header = TRUE; do_sections = TRUE; do_section_groups = TRUE; do_segments = TRUE; do_version = TRUE; do_histogram = TRUE; do_arch = TRUE; do_notes = TRUE; break; case 'g': do_section_groups = TRUE; break; case 't': case 'N': do_sections = TRUE; do_section_details = TRUE; break; case 'e': do_header = TRUE; do_sections = TRUE; do_segments = TRUE; break; case 'A': do_arch = TRUE; break; case 'D': do_using_dynamic = TRUE; break; case 'r': do_reloc = TRUE; break; case 'u': do_unwind = TRUE; break; case 'h': do_header = TRUE; break; case 'l': do_segments = TRUE; break; case 's': do_syms = TRUE; break; case 'S': do_sections = TRUE; break; case 'd': do_dynamic = TRUE; break; case 'I': do_histogram = TRUE; break; case 'n': do_notes = TRUE; break; case 'c': do_archive_index = TRUE; break; case 'x': request_dump (filedata, HEX_DUMP); break; case 'p': request_dump (filedata, STRING_DUMP); break; case 'R': request_dump (filedata, RELOC_DUMP); break; case 'z': decompress_dumps = TRUE; break; case 'w': do_dump = TRUE; if (optarg == 0) { do_debugging = TRUE; dwarf_select_sections_all (); } else { do_debugging = FALSE; dwarf_select_sections_by_letters (optarg); } break; case OPTION_DEBUG_DUMP: do_dump = TRUE; if (optarg == 0) do_debugging = TRUE; else { do_debugging = FALSE; dwarf_select_sections_by_names (optarg); } break; case OPTION_DWARF_DEPTH: { char *cp; dwarf_cutoff_level = strtoul (optarg, & cp, 0); } break; case OPTION_DWARF_START: { char *cp; dwarf_start_die = strtoul (optarg, & cp, 0); } break; case OPTION_DWARF_CHECK: dwarf_check = TRUE; break; case OPTION_DYN_SYMS: do_dyn_syms = TRUE; break; #ifdef SUPPORT_DISASSEMBLY case 'i': request_dump (filedata, DISASS_DUMP); break; #endif case 'v': print_version (program_name); break; case 'V': do_version = TRUE; break; case 'W': do_wide = TRUE; break; default: /* xgettext:c-format */ error (_("Invalid option '-%c'\n"), c); /* Fall through. */ case '?': usage (stderr); } } if (!do_dynamic && !do_syms && !do_reloc && !do_unwind && !do_sections && !do_segments && !do_header && !do_dump && !do_version && !do_histogram && !do_debugging && !do_arch && !do_notes && !do_section_groups && !do_archive_index && !do_dyn_syms) usage (stderr); }
case 'h': do_header = TRUE;
とあるように、
do_header
フラグがヘッダ解析を行うトリガーとなっているようです。
更にgrepを掛けていくと、
process_file_header
でdo_header
の有無によって標準出力の内容を変えている事がわかりました。
その関数を見てみます。
コメントアウトを適宜入れています。
static bfd_boolean process_file_header (Filedata * filedata) { Elf_Internal_Ehdr * header = & filedata->file_header; if ( header->e_ident[EI_MAG0] != ELFMAG0 //マジックナンバー検査 || header->e_ident[EI_MAG1] != ELFMAG1 || header->e_ident[EI_MAG2] != ELFMAG2 || header->e_ident[EI_MAG3] != ELFMAG3) { error (_("Not an ELF file - it has the wrong magic bytes at the start\n")); return FALSE; } init_dwarf_regnames (header->e_machine); if (do_header) { unsigned i; printf (_("ELF Header:\n")); printf (_(" Magic: ")); for (i = 0; i < EI_NIDENT; i++) printf ("%2.2x ", header->e_ident[i]); //16進2桁ずつ出力する printf ("\n"); printf (_(" Class: %s\n"), get_elf_class (header->e_ident[EI_CLASS])); printf (_(" Data: %s\n"), get_data_encoding (header->e_ident[EI_DATA])); printf (_(" Version: %d%s\n"), header->e_ident[EI_VERSION], (header->e_ident[EI_VERSION] == EV_CURRENT ? _(" (current)") : (header->e_ident[EI_VERSION] != EV_NONE ? _(" <unknown>") : ""))); printf (_(" OS/ABI: %s\n"), get_osabi_name (filedata, header->e_ident[EI_OSABI])); printf (_(" ABI Version: %d\n"), header->e_ident[EI_ABIVERSION]); printf (_(" Type: %s\n"), get_file_type (header->e_type)); printf (_(" Machine: %s\n"), get_machine_name (header->e_machine)); printf (_(" Version: 0x%lx\n"), header->e_version); printf (_(" Entry point address: ")); print_vma (header->e_entry, PREFIX_HEX); printf (_("\n Start of program headers: ")); print_vma (header->e_phoff, DEC); printf (_(" (bytes into file)\n Start of section headers: ")); print_vma (header->e_shoff, DEC); printf (_(" (bytes into file)\n")); printf (_(" Flags: 0x%lx%s\n"), header->e_flags, get_machine_flags (filedata, header->e_flags, header->e_machine)); printf (_(" Size of this header: %u (bytes)\n"), header->e_ehsize); printf (_(" Size of program headers: %u (bytes)\n"), header->e_phentsize); printf (_(" Number of program headers: %u"), header->e_phnum); if (filedata->section_headers != NULL && header->e_phnum == PN_XNUM && filedata->section_headers[0].sh_info != 0) { header->e_phnum = filedata->section_headers[0].sh_info; printf (" (%u)", header->e_phnum); } putc ('\n', stdout); printf (_(" Size of section headers: %u (bytes)\n"), header->e_shentsize); printf (_(" Number of section headers: %u"), header->e_shnum); if (filedata->section_headers != NULL && header->e_shnum == SHN_UNDEF) { header->e_shnum = filedata->section_headers[0].sh_size; printf (" (%u)", header->e_shnum); } putc ('\n', stdout); printf (_(" Section header string table index: %u"), header->e_shstrndx); if (filedata->section_headers != NULL && header->e_shstrndx == (SHN_XINDEX & 0xffff)) { header->e_shstrndx = filedata->section_headers[0].sh_link; printf (" (%u)", header->e_shstrndx); } if (header->e_shstrndx != SHN_UNDEF && header->e_shstrndx >= header->e_shnum) { header->e_shstrndx = SHN_UNDEF; printf (_(" <corrupt: out of range>")); } putc ('\n', stdout); } if (filedata->section_headers != NULL) { if (header->e_phnum == PN_XNUM && filedata->section_headers[0].sh_info != 0) header->e_phnum = filedata->section_headers[0].sh_info; if (header->e_shnum == SHN_UNDEF) header->e_shnum = filedata->section_headers[0].sh_size; if (header->e_shstrndx == (SHN_XINDEX & 0xffff)) header->e_shstrndx = filedata->section_headers[0].sh_link; if (header->e_shstrndx >= header->e_shnum) header->e_shstrndx = SHN_UNDEF; free (filedata->section_headers); filedata->section_headers = NULL; } return TRUE; }
ELFのヘッダを定義する構造体のメンバに対して、
いろいろな関数を適用しながら出力しているのがわかります。
readelfの出力にバリエーションをつけたい場合、
これら定義を一つ一つ追って行けば良さそうです。
総評
ELFヘッダの完全理解を目指しました。
バイナリ生成に必要な知識の一つだと考えます。