ELF64を動的に生成してHello World

2019-01-28

ELFを自分で生成する方法を調べたところ、 hello worldなELFバイナリを出力するCのプログラム(の一番単純な奴) - memologue が非常に参考になった。 ただ32ビット版なので64ビット版にしたい。

ELF64ビット版への変更

ELF64ビット版にするには、

  • 構造体: 32 から 64 に(Elf64_Ehdrなど)。
  • e_machineEM_X86_64
  • システムコール: int $0x80syscall に変更。
  • システムコール番号: write1 に、 exit60 にする。
  • システムコールに渡すレジスタ: rdi, rsi, rdx の順に。
  • アセンブラ: movlmov に。

で一応動いた。

必要なプログラム部分だけを読み込ませたい

生成しているELFを見ると、ロードするプログラムはプログラムヘッダの p_offset = 0x0 なのでファイルの先頭から、 サイズは p_filesz = sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr) + STRING_LEN + code_len なので出力した内容全体を指すことになる。 でもヘッダ部分は実際の実行には必要ないはずだからメモリに読み込む必要ないんじゃない? 実際のプログラムの部分だけを指定したいよね、と思って試してみた:

  • ファイル中のプログラムの開始位置を示す p_offsetsizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr) に、
  • サイズを示す p_fileszp_memszSTRING_LEN + code_len に、
  • エントリポイント e_entryLOAD_ADDRESS + STRING_LEN に、

こうすればELFのヘッダを除いたプログラムの部分だけを LOAD_ADDRESS に読み込み動いてくれるはず…と思ったが segmentation fault が起きてしまう。 あれこれ試してなんとなく理解したのは、ファイル中の位置と読み込み先のメモリのアドレスのページ内の位置が合っている必要がある、ということらしい。

Program Header (Linker and Libraries Guide)

p_align: Loadable process segments must have congruent values for p_vaddr and p_offset, modulo the page size. This member gives the value to which the segments are aligned in memory and in the file. Values 0 and 1 mean no alignment is required. Otherwise, p_align should be a positive, integral power of 2, and p_vaddr should equal p_offset, modulo p_align. See “Program Loading (Processor-Specific)”.

ロード可能なプロセスセグメントは、ページサイズを法として、p_vaddrとp_offsetに一致する値を持たなければなりません。 このメンバは、セグメントがメモリ内およびファイル内で整列している値を示します。 値0および1は、配置が不要であることを意味します。 それ以外の場合、p_alignは2の正の整数乗でなければならず、p_vaddrはp_alignを法としたp_offsetと等しくなければなりません。 「プログラムのロード(プロセッサ固有)」を参照してください。

ということで上の変更に加えて、 LOAD_ADDRESS4096 の余りを 64 + 56= sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr))に合わせる必要がある (もしくはELFヘッダとプログラムヘッダの後に4096バイトまでパディングを入れる)。

ちなみに p_align0x1000 から 0x10 とかに変えても特に効果はなさそうだった。

なんと不便な、またロード先のアドレスを切りよくしようとするとファイルサイズが、ファイル上で詰めようとすると実行時のメモリが無駄になってしまうのがなんとも…。

関連記事