GNU linker scriptでhello world

はじめに

みなさんはGNU linker(以下ld)やlinker scriptという言葉をご存知でしょうか。組み込み系のかたやOS屋さん以外のかたにとっては、直接使うものではないので、馴染みのないものだと思います。ldの役割は、皆さんが書いたソースファイルからコンパイラが生成したオブジェクトファイル(実行ファイルの断片)をリンクして実行ファイルを作ることです。linker scriptの役割は、ldが各種オブジェクトファイルをどのようにリンクさせるのかを制御することです。

linker scriptの応用

linker scriptを応用すれば、コードを含むオブジェクトファイルが無くてもスクリプトだけからプログラムを生成できます。本書では例としてld-helloというプログラムを紹介します。linux on x86_64環境で動作します。このプログラムはいわゆるhello worldプログラムです。動かし方は簡単で、単にソースディレクトリに移動してから以下のコマンドを発行するだけです

$ make && ./hello
Hello, world!

何をしているのか

helloを生成する際には以下のコマンドを実行します(実際にはmakeコマンドから間接的に実行されます)。

ld -o hello -T hello.ld empty.o

このコマンドの意味は、linker scriptとしてデフォルトのものではなくhello.ldを使用して、empty.oを元に実行ファイルhelloを作成するというものです。

empty.oはas -o empty.o empty.sによって生成されますが、実はempty.sの中身は空です。

$ cat empty.s

つまり、これを使ってできたempty.oはコードもデータも何も入っていない、実質無意味なオブジェクトファイルです。では何故このようなオブジェクトファイルからhello worldプログラムを作れるのでしょうか。それは、linker scriptであるhello.ldが直接コードやデータを生成しているからです。

hello.ldの中身の簡単な解説

ENTRY(_start)とは、プログラムの実行をstartというシンボルの位置から開始するという意味です。startは.text : { _start = . ; ...によって、機械語命令を格納する.textセクションの先頭に設定されます。

_startの位置からしばらく続くBYTE()やLONG()の列によって、所定の機械語命令を直接.textセクションに書き込んでいます。この命令をC風に書くと、だいたい次のようになります。

int main(void)
{
  puts("Hello, world!¥n");
  exit(0);
}

LONG(message);内のmessageというシンボルは、"Hello, world!¥n"という文字列データを指しています。このデータは、その後に続く、読み取り専用データを保持する.rodataセクションにあります。.rodata : {...}の中のBYTE()の列がこの文字列データです。

あとの細かいところはソース内のコメント、および本記事の末尾に紹介している参考文献をごらんください。

なんだかバイナリエディタで書くのとあんまり変わらなくて無理矢理臭いですが、一応これで、linker scriptでもプログラムが書けるということがわかりました。その気になればコンパイラでもカーネルでもなんでも書けます。わたしは絶対にやりませんが。

おわりに

linker scriptにはシンボルへの値の代入、四則演算、および三項演算などの機能があるので一見色々遊べそうなのですが、致命的なことに繰り返し構文ないしジャンプ命令がありません。linker sciptでもっと楽しく遊ぶために、将来のldにこれら機能が実装されることを切に願います。

参考文献