ELFを自分で生成する方法を調べたところ、 hello worldなELFバイナリを出力するCのプログラム(の一番単純な奴) - memologue が非常に参考になった。 ただ32ビット版なので64ビット版にしたい。
ELF64ビット版への変更
ELF64ビット版にするには、
- 構造体:
32
から64
に(Elf64_Ehdr
など)。 e_machine
:EM_X86_64
- システムコール:
int $0x80
をsyscall
に変更。 - システムコール番号:
write
は1
に、exit
は60
にする。 - システムコールに渡すレジスタ:
rdi
,rsi
,rdx
の順に。 - アセンブラ:
movl
をmov
に。
で一応動いた。
必要なプログラム部分だけを読み込ませたい
生成しているELFを見ると、ロードするプログラムはプログラムヘッダの p_offset = 0x0
なのでファイルの先頭から、
サイズは p_filesz = sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr) + STRING_LEN + code_len
なので出力した内容全体を指すことになる。
でもヘッダ部分は実際の実行には必要ないはずだからメモリに読み込む必要ないんじゃない?
実際のプログラムの部分だけを指定したいよね、と思って試してみた:
- ファイル中のプログラムの開始位置を示す
p_offset
をsizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr)
に、 - サイズを示す
p_filesz
とp_memsz
をSTRING_LEN + code_len
に、 - エントリポイント
e_entry
をLOAD_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_ADDRESS
の 4096
の余りを 64 + 56
(= sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr)
)に合わせる必要がある
(もしくはELFヘッダとプログラムヘッダの後に4096バイトまでパディングを入れる)。
ちなみに p_align
を 0x1000
から 0x10
とかに変えても特に効果はなさそうだった。
なんと不便な、またロード先のアドレスを切りよくしようとするとファイルサイズが、ファイル上で詰めようとすると実行時のメモリが無駄になってしまうのがなんとも…。