どうもうまくいかないので、お手本のサイトに合わせて 32bit 版の実行ファイルを作りたいと思います。
動いた
とりあえずコードです。
bits 32
; org 0x08048000
org 0x114514000
; e_ident[]
elf_header:
db 0x7F,"ELF" ; magic numbers
db 1, 1, 1 ; 32bit, little endian, current ELF version
db 0, 0 ; No ABI, ABI version 0
db 0, 0, 0, 0, 0, 0, 0 ; zero-padding
dw 2, 3 ; executable file, x86 machine
dd 1 ; current object version
dd _start
dd program_header - $$ ; program header offset
dd 0 ; section header offset
dd 0 ; flags(zero)
dw elf_header_size
dw program_header_size
dw 1 ; progra header number
dw 0, 0, 0 ; section header(none)
elf_header_size equ $ - elf_header
program_header:
dd 1 ; loadable segment
dd 0 ; segment offset
dd $$ ; first segment virtual address
dd 0 ; reserved
dd filesize, filesize
dd 5 ; readable & executable segment
dd 0x4 ; align(what!?)
program_header_size equ $ - program_header
align 4
_start:
mov eax, 1
mov ebx, 69
int 0x80
filesize equ $ - $$
これを実行すると、OS への返り値が 69 になります。この数字にした理由は深く考えなくていいです。
知見
とりあえず動いたので、得た知見をここに記します。備忘録も兼ねて。
アドレスについて
OS が絡んでくると 物理アドレス と 仮想アドレス という1対の概念が生まれます。これまでのマイコン等、OS 無し環境ですと常に物理アドレスの考え方でいいのですが(絶対アドレス)、今回のような場合は 仮想アドレスが重要になってきます。
仮想アドレスとは
プログラムから見たメモリアドレスのことです。そもそもなぜこのような概念が必要なのかというと、OS 上ではマルチタスクで色々なプログラムが動いていて、各プログラムで並列で処理する必要があるからだと思います(推測)。
実行するプログラムのバイナリは当然ながら実行時には全てその中身が決まっています。例えばプログラムAは「0x00番地からスタート」、プログラムBも「0x00から」と書いてあるとしましょう。当然プログラムは作者が各自で作成するものですから、固有のエントリポイントからプログラムを走らせる、なんてことは事実上不可能なわけです。ですから万が一、OS さんが何も考えずに今のプログラムAとプログラムBを並列で実行しようもんなら物理アドレスがぶつかって落ちます。
そこで仮想アドレスと言う概念を導入するとこのかちあいを回避することが出来るということです。多分。例えばAとBが実行したがっている時にOSさんは「はいはい、Aさんの0x00番地は実際は0x800番地ね」「Bさんの0x00番地は実際は0x900番地ね」というように物理アドレス空間で割当をします。こうすることで、例えばプログラムAさんが「0x10番地にジャンプしたい」とCPUに語りかけようとすると、CPUさんはプロセスごとに「勝手にアドレス当てちゃうもんね、0x810番地」と仮想アドレスを吐き出します。これによって衝突が防げるんですね。そして実際の物理アドレスアクセスは、MMUという仮想アドレスと物理アドレスの対応を司るユニットが「ほ〜い、じゃあプログラムAさん、0x810番地、、、ではなく0x010番地にジャンプしてあげて」と変換をしてくれると言った感じで、よしなに都合よくしてくれます。
注: MMU の機能に誤りがありました。上のパラグラフでは既に修正済みです。
ELF のエントリポイント
まだ分からない点がありますがとりあえず。ELF のエントリポイント(記述の開始アドレス)は 0x08048000
です。ここから ELF ヘッダ等の記述を始めればいいということですねぇ。これは ELF の仕様書に書いてある値です。実際に見たい方は elf specification 等で検索をすればいいと思います。
でもよく分からないです。上のコードのように 0x114514000
としても動くし(32bit なので最上位の 1 は nasm
によって無視される)、なんなら 0x0000
としても動きます。なんでも良いのでは...あ、でも下位 12bit 分は全て0じゃないとセグフォで落ちます。ここらへんは MMU の制約臭いですけど、どうなのでしょうか。
解析ツールについて
xxd
と hexdump
どちらもバイナリの中身を表示してくれるものなのですが、hexdump
はオプションを指定しないと勝手に16byteごとに上位8バイトと下位8バイトをひっくり返すので印象が悪いです。xxd
ならそのまま表示してくれるのでこちらを使いましょう。
ということで、ぼちぼちやっていきます。次は 64bit 版、セクションヘッダ等ですかねぇ