ろぐれこーど

限界組み込みエンジニアの学習記録とちょっぴりポエム

elxrリンカにおけるリンカスクリプトの記述

GHSコンパイラのelxrリンカにおけるリンカスクリプト(.ldファイル)の書き方がわからなかったため、ざっくり調べました。

概要

Linker Directiveファイル(通称リンカスクリプト)は、メモリ空間上にプログラムセクションをどのように配置するかをリンカに指示するためのファイルです。マイコンは「このメモリ番地にコードを割り当ててね」と指定されていることが多いため、組み込みソフト開発する上ではリンカスクリプトを書いておく必要があります。(一度書けば変更することはあまりないため、業務上では知識が属人化しやすい気がします)

リンカスクリプトはターゲットとするマイコンに依存するのは当然ですが、使用するリンカによっても記述が微妙に変わります。一般的によく使われるgccにはldというリンカがついていますが、今回はGHSコンパイラに付属するelxrリンカを想定します。ただし記述が共通する部分も多いため、より汎用化して理解するためにldと比較しながら書いていきます。

elxrリンカ

リンカスクリプトは基本的に、<コマンド>{ <指定内容> }といった文法で記述します。最低限必要なコマンドはSECTIONのみですが、非常に可読性が悪くなるためMEMORYコマンドも使われることが多いです。そのほかldと異なる点として、定数を定義するDEFAULTSコマンドなどがあります。ここではMEMORYSECTIONを取り上げます。

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リンカのマニュアルは以下で確認できます。

Top (LD)

また、ldを使用したリンカスクリプトの記述は以下が参考になります。

blueeyes.sakura.ne.jp

MEMORYSECTIONの指定方法はほぼ同じです。elxrでは指定する必要がなかったOUTPUT_FORMATOUTPUT_ARCHを書く必要がありますが、マイコンアーキテクチャ固有の設定さえ覚えてしまえば、他プロダクトへの流用もできそうです。そこが一番めんどくさかったりするのですが…

まとめ

リンカスクリプトの書き方を調べました。ただ結局アーキテクチャやハード依存になってくる部分も多く、めちゃくちゃ泥臭い「製品固有の知識」になりがちなのが組み込みソフトのつらいところですね…もっとキラキラしたいんだよなぁ…