読者です 読者をやめる 読者になる 読者になる

わらばんし仄聞記

南の国で引きこもってるWeb屋さん

ELFファイルを作る part1

ELF

池袋バイナリ勉強会(7)に行ってきました。前回にも触れたように、先にJITで動作させていたバイナリ値をELFの中に組み込めればbfコンパイラが出来上がる。
というわけで、次のステップとしてELFファイルの作成へ。

目標

64bit用ELFの挙動を知るため、必要最小限のELFと何か命令を作成し、実行する。
実行する命令が長いとELFについて集中出来ないので、まずは出来る限り短いバイナリから成る命令文を作成してみる。

導入

さて、実際の所、ELFと言ったところで名前を知っている程度で内容についてはサッパリ。
適当に簡単なコードを書いてみてa.outを出力し、objdumpしてみたりもするけど、いきなりやるには容量が大きすぎてどう手を付ければいいのか・・・。
そこでアドバイスをいただき、こちらを参考に最小限のELFファイルを作成してみることにした。ちなみにこのサイトで注意する点は、基本的に32bitでの内容であることと、途中から基本的なELFを学ぶだけにはそぐわないトリッキーな内容に突入していくこと。

sample 1

tiny1.cの方は単純に42を返して終了するだけの、極めて単純なCの記述。これを実行して結果を取得してみる。

$ gcc -Wall tiny1.c
$ ./a.out ; echo $?
42

参考にしたサイトと全く同様ですね。
echo $?は終了ステータスの値を取得します。ここでは42が返されていたので、その値が。

さて、続いて上記のtiny1.sのコード。こっちもほぼ同じだけど、最初のBITSで指定される値を32から64に参考元のコードを変更。実行してみる。

$ nasm -f elf64 tiny1.s
$ gcc -Wall -s tiny1.o
$ ./a.out
$ echo $?
42

参考元のコマンドのまま、nasm -f elf tiny1.sとすると、32bitで解釈されてしまう。
自分の環境下では32bitの開発環境を入れてなかったのでエラーが発生した。ともあれこれも無事に動作してくれた。

sample 2

さて、これもほぼ参考元と同じ。
mainインターフェースを使う箇所を省き、自分で_startルーチンを記述することにより、出来上がるELFファイルを単純にしている。勿論これに伴って、ビルドするコマンドもtiny1.sの時とは変わってくる。
ここで参考元のコードとtiny2.sが異なる点としては、最後から二行目のmov命令を実行しているところ。32bitの場合はコールする関数へ引数を渡す場合はpush命令でスタックしていくが、64bitの場合は特定のレジスタへ値をmovして格納しておく事で指定する。
rdiではなくediを使っているのは、単にビルドして出来上がるバイナリの長さを無駄に長くしないため。

これを実行してみる。

$ nasm -f elf64 tiny2.s
$ gcc -Wall -s -nostartfiles tiny2.o
$ ./a.out
$ echo $?
42

見込み通りに42を得られた。
_startルーチンを独自に実装してることになるので、gccの実行時に-nostartfilesオプションが加えられている。

sample 3

exitは結局の所ライブラリ関数なので、このコールはlibcと関連付く事になってしまう。その為、sample2までは出来上がるバイナリにリンクのための、今回欲しくは無い箇所が含まれてしまった。
ということで、ここでシステムコールを直接呼び出す事に。

参照元ではexitシステムコール番号が1となっているが、手元の環境では60となっていた。システムコール一覧については /usr/include/asm/unistd_64.h を参照。

#define __NR_exit               60                                                                                                               
__SYSCALL(__NR_exit, sys_exit)

との記述がある。

また、システムコール呼び出しを実行する為に32bitではint 0x80という命令を実行させているが、64bit環境ではsyscallという命令になる。

そして、引数を渡すレジスタも32bitではebxとなっているが、64bitではsample2と同様にedi(又はrdi)。この引数を渡すレジスタ指定についてはこちらに詳しい。

さて、実行してみよう。

$ nasm -f elf64 tiny3.s
$ gcc -Wall -s -nostdlib tiny3.o
$ ./a.out
$ echo $?
42

参考元にあるように、この時点でgccで無ければならない事はなくなっているので、直接リンカを呼び出してもOK

$ nasm -f elf64 tiny3.s
$ ld -s tiny3.o
$ ./a.out
$ echo $?
42

同様の結果を得られる。

conclusion

ここまでで外部呼び出しや無駄なリンクを除いた、非常にシンプルな命令文が得られた。
これをELFに従って組み込んでやれば、非常に単純なELFファイルが出来上がる。