elxrリンカにおけるリンカスクリプトの記述
GHSコンパイラのelxrリンカにおけるリンカスクリプト(.ldファイル)の書き方がわからなかったため、ざっくり調べました。
概要
Linker Directiveファイル(通称リンカスクリプト)は、メモリ空間上にプログラムセクションをどのように配置するかをリンカに指示するためのファイルです。マイコンは「このメモリ番地にコードを割り当ててね」と指定されていることが多いため、組み込みソフト開発する上ではリンカスクリプトを書いておく必要があります。(一度書けば変更することはあまりないため、業務上では知識が属人化しやすい気がします)
リンカスクリプトはターゲットとするマイコンに依存するのは当然ですが、使用するリンカによっても記述が微妙に変わります。一般的によく使われるgcc
にはld
というリンカがついていますが、今回はGHSコンパイラに付属するelxrリンカを想定します。ただし記述が共通する部分も多いため、より汎用化して理解するためにld
と比較しながら書いていきます。
elxrリンカ
リンカスクリプトは基本的に、<コマンド>{ <指定内容> }
といった文法で記述します。最低限必要なコマンドはSECTION
のみですが、非常に可読性が悪くなるためMEMORY
コマンドも使われることが多いです。そのほかld
と異なる点として、定数を定義するDEFAULTS
コマンドなどがあります。ここではMEMORY
とSECTION
を取り上げます。
MEMORY
メモリ空間上の特定範囲のメモリ領域に名前をつけて定義します。フォーマットは以下です。
MEMORY{ memory_block: ORIGIN=origin_expression, LENGTH=length_expression }
MEMORY{}
の中に定義したいメモリ領域を追加していきます。ここで定義したメモリ領域はSECTION
コマンド内で参照されます。
それぞれの要素は以下の意味です。
- memory_block: メモリ領域の名前
- origin_expression: メモリ領域の開始アドレス
- length_expression: メモリ領域の長さ(バイト数)
例えば0x00000000
番地〜0x00010000
番地をROM_DATA
と定義するとき、以下のように記述します。
ROM_DATA: ORIGIN=0x00000000, LENGTH=0x10000
LENGTHはK
(1024)やM
(10242)といった数値で指定することもできます。また数値を返す式で記述することも可能です。
// LENGTH=0x10000と等価 ROM_DATA: ORIGIN=0x00000000, LENGTH=64K // 式による指定 ROM_DATA: ORIGIN=0x00000000, LENGTH=0x00010000-0x00000000
SECTION
リンクする各オブジェクトファイル内のプログラムセクション(.text
など)を、メモリ領域に割り当てます。プログラムセクションはCコード内やアセンブリ内で指定することもできますが、コンパイラによってデフォルトで割り付けられるものを使うことが多いです。主なデフォルトセクションは以下。(V850アーキテクチャ向けGHSコンパイラの例です)
セクション名 | 概要 |
---|---|
.text | プログラム・コード自体 |
.data | 明示的に初期化された変数 |
.bss | 明示的に初期化されない変数 |
.rodata | constによる定数 |
test.cというファイルがコンパイルされると、ファイル内の情報が上記のセクションにそれぞれ配置され(便宜的にファイル単位セクションと呼びます)、test.oというオブジェクトファイルにまとめられます。SECTION
ではこれらのオブジェクトファイル群を取り込み、ファイル単位セクションを統合して.text
や.data
といったプログラム全体のセクションに配置する指示を出します。
カスタムセクションを使用しない場合、最低限上記のセクションはメモリ領域のどこかに割り当てる必要があります。どこに割り当てるかはマイコンのデータシートとにらめっこですね。モノや動作設定によってはアクセスのスピードが異なる領域もあるので、要件を見直して決めることもあります。
(ld
では.stack
というセクションの配置を指定する必要がありますが、GHSのランタイム環境は自動でこの辺の割り付けをしてくれるため特に設定する必要はありません。さすが有償ソフト)
フォーマットは以下のように指定します。
SECTION{ secname [start_expression] [attribute] : [{contents}] [>memory_block | >.] }
- secname: 割り当てたいセクション名。
.text
や.data
など。自分で新たに設定することもできます。 - start_expression: セクションの開始アドレス。memory_blockで指定する場合は不要。
- attribute: セクションに付与したい属性。セクションの動作を設定できる。
- contents: セクションの中身。配置したいファイル単位セクションをファイル名とセクション名で指定します。
- memory_block:
MEMORY
で定義したメモリ領域に、secnameで指定したセクションを配置します。ロケーションカウンタを使用する場合は>.
と記述することもできます。
[ ]
はオプション記述です。secnameとstart_expression(またはmemory_block)さえ指定すれば配置できます。ファイル単位セクションの.data
を全てメモリ領域ROM
に配置したいときは
.data : > ROM
のように記述できます。
contents指定
ファイル単位セクションと配置したいセクション名が異なる場合、contentsを指定します。全ファイルの.data
セクションを.mydata
に配置し、メモリ領域ROM
に配置したいときは以下のように書きます。
.mydata : {* (.data) } > ROM
ワイルドカード(*
, ?
)を使って、ファイル名・ファイル単位セクション名を指定することもできます。test_*.o
にマッチするファイル内の.data_*
セクションを.mydata
に配置する場合は以下のようになります。特定の名付けルールがあるセクションを配置する際に便利です。
.mydata : { "test_*.o (.data_*)" }
関数の使用
リンカスクリプトがサポートする関数を使用すれば、アドレス指定などがやりやすくなります。elxr
がサポートする主な関数は以下です。
- ALIGN(n): nバイト境界までアラインされた現在の位置を返す。
- ADDR(secname): セクションsecnameの開始アドレスを返す。
- SIZEOF(secname): セクションsecnameの現在のサイズを返す。
次のセクションを4バイト境界に配置したい時は以下のように書きます。
.text : { . = ALIGN(4); *(.text) } > ROM
属性の使用
attributeを指定することで、セクションの動作を設定できます。おそらくld
にはない機能の一つかと思います。属性はいろいろありますが、もはやマニュアル見た方が早いので割愛します。
(参考)ldリンカ
ld
リンカのマニュアルは以下で確認できます。
また、ld
を使用したリンカスクリプトの記述は以下が参考になります。
MEMORY
やSECTION
の指定方法はほぼ同じです。elxr
では指定する必要がなかったOUTPUT_FORMAT
やOUTPUT_ARCH
を書く必要がありますが、マイコンやアーキテクチャ固有の設定さえ覚えてしまえば、他プロダクトへの流用もできそうです。そこが一番めんどくさかったりするのですが…
まとめ
リンカスクリプトの書き方を調べました。ただ結局アーキテクチャやハード依存になってくる部分も多く、めちゃくちゃ泥臭い「製品固有の知識」になりがちなのが組み込みソフトのつらいところですね…もっとキラキラしたいんだよなぁ…