RISC-Vのベアメタル環境向けに実行ファイルを作りたかったが、 どうやってコードの配置を行うかわからなかったので、 既存のリンカスクリプトを参考にしつつリンカスクリプトを調べてみた。
参照するのは riscv-test-env に入っている、 ベアメタル環境向けの以下のスクリプト。
OUTPUT_ARCH( "riscv" ) ENTRY(_start) SECTIONS { . = 0x80000000; .text.init : { *(.text.init) } . = ALIGN(0x1000); .tohost : { *(.tohost) } . = ALIGN(0x1000); .text : { *(.text) } . = ALIGN(0x1000); .data : { *(.data) } .bss : { *(.bss) } _end = .; }
リンカーの役割は、複数の入力オブジェクトファイルに含まれるセクションを、出力オブジェクトファイル(あるいは実行ファイル)のセクションに配置すること。 セクションは実行時に読み込まれる loadable と、実行時に確保されるが読み込むデータがない allocatable がある。 またどちらでもないセクションも存在し、デバッグ情報などを格納している場合もある。 loadable または allocatable なセクションは2つのアドレス、VMA(virtual memory address)とLMA(load memory address)を持つ。 たいていの環境では、どちらも同一だが、場合によってはROMに読み込まれて、実行時にコピーされるような環境も存在する。 またオブジェクトファイルは symbol table も含んでいる。symbol は名前とアドレスとその他情報を持っている。 C などは関数やグローバル変数に symbol を利用する。
まずは、OUTPUT_ARCH
だが、これは出力マシンアーキテクチャを指定する。引数に与えられる名前は BFD ライブラリで使われる名前。
この辺を見ると良い気がする。
ENTRY
はプログラムのエントリーポイントを指定する。引数は symbol 名。
スタートアドレスがあればなくても問題ないと思うが、どうやら ELF ヘッダーは entry point の指定ができるらしく、
そのために使われるらしい。
SECTIONS
が一番重要なセクションの配置に関するコマンドになる。
SECTIONS
のあとにはブレースを使って、複数の sections-command
を並べる。
sections-command
はいくつか種類があるが、今回使っているのは、symbol assignment と output section description である。
symbol assignment は symbol に対して、アドレスを指定する。
.
は特別な symbol で location counter と呼ばれる。
location counter は出力される section のアドレスを示し、代入されると出力先のアドレスが移動する。
またセクションを配置するごとに、自動的に値が変更される。
. = 0x80000000;
はこれよりあとのセクションが 0x80000000
に配置されるように設定するという意味になる。
. = ALIGN(0x1000);
は ALIGN
関数を呼び出した結果を、location counter に設定している。
ALIGN
関数は現在の location counter の値を、引数で指定したバイト数でアラインメントした次のアドレスを返す。
結果として、ここでは次の配置アドレスが 0x1000
単位に整列された値に設定されることになる
output section description はコロンの左辺で指定した出力先セクションに対して、右辺で指定した入力セクションを配置する意味になる。 左辺に指定するセクション名には曖昧性を除くために、スペースが必須になっている。
コロンの右側にはブレースの中に、output-section-command
を書く。
output-section-command
は input section description などがかける。
input section description は入力オブジェクトファイル名とその後にカッコで入力セクションを指定する。 それぞれワイルドカードを指定できる。
.text.init : { *(.text.init) }
は .text.init
出力セクションに対して、任意の入力ファイルの .text.init
セクションを配置するという意味になる。