AT32UC3B0256のデフォルト・リンカ・スクリプト
AT32UC3B0256のスタンドアローン版アプリケーションで暗黙的に使用されるリンカ・スクリプトを読んでみる。リンカ・スクリプトの格納位置についてはこちらを。
今回読むリンカ・スクリプトはavr32elf_uc3b0256.xだ。
参考URL GNU リンカ LD の使い方 − 3. リンカスクリプト
2: OUTPUT_FORMAT("elf32-avr32", "elf32-avr32", 3: "elf32-avr32") 4: OUTPUT_ARCH(avr32:uc)
OUTPUT_FORMATは出力フォーマットを指定し、エンディアンを指定するリンカ・オプションの「-EB」や「-EL」により第1〜3引数のいずれかが有効になる。指定がなければ第1引数、-EBなら第2引数、-ELなら第3引数である。どれを指定しても"elf32-avr32"である。ELFフォーマットはエンディアン種別を格納できるので、これで問題ないのだろう。
OUTPUT_ARCHは出力機種アーキテクチャを指定する。指定可能なアーキテクチャはWindowsなら以下のコマンドで判る。
"%AVR32_HOME%\bin\avr32-objdump" --help
末尾に「supported architectures:」と書いてある部分がそれだ。「avr32:uc」はucr1やucr2の共通のようだ。ucr3に相当するものが無いが...
5: ENTRY(_start)
エントリポイントを指定する。オプションで指定されていれば、オプションが優先する。余談だが、エントリポイントの決定は以下の優先度がある。
- コマンド・オプション「-e」
- リンカスクリプトのENTRY(symbol)コマンド
- 定義されていれば、シンボル start の値
- 存在するなら、.textセクションの先頭のバイトのアドレス
- アドレス 0
6: SEARCH_DIR("/home/mingwbuild/mingwavr32/avr32/lib");
ライブラリ・ファイル検索パスに"/home/mingwbuild/mingwavr32/avr32/lib"を追加する。しかし、そのようなパスはインストール・ディレクトリ以下にも存在しない。
7: /* Do we need any of these for elf? 8: __DYNAMIC = 0; */
コメントでそんなこと書かれても...たぶんダイナミック・ライブラリ(gcc的にはシェアード・ライブラリか)のとき必要かも、って他人のことは言えませんな。
9: MEMORY 10: { 11: FLASH (rxai!w) : ORIGIN = 0x80000000, LENGTH = 256K 12: CPUSRAM (wxa!ri) : ORIGIN = 0x00000000, LENGTH = 32K 13: USERPAGE : ORIGIN = 0x80800000, LENGTH = 512 14: FACTORYPAGE : ORIGIN = 0x80800200, LENGTH = 512 15: }
MEMORYコマンドはターゲットのメモリブロック=リージョンの位置と大きさを記述する。ブロック内の一般形は以下の通り。
name [(attr)] : ORIGIN = origin, LENGTH = len
nameはリージョン名。属性attrは省略可能。originが開始位置、lenが大きさである。attrを構成する文字の意味は以下の通り。
R | 読み出し専用セクション |
W | 読み書き両用セクション |
X | 実行可能セクション |
A | 割り当て可能セクション |
I | 初期化済みセクション |
L | Iと同じ |
! | 後に続く属性の意味を否定 |
RとWの意味がread/writeでないのが要注意。チップ内蔵SRAMのリージョンCPUSRAMが「!R」となるのが象徴的。リージョンUSERPAGEとFACTORYPAGEの役割は最後まで読んでもよく判らない。該当するアドレスになにがあるのか製品マニュアルやアーキテクチャ・マニュアル見ても載っていない。
16: SECTIONS 17: { 18: /* Read-only sections, merged into text segment: */ 19: PROVIDE (__executable_start = 0x80000000); . = 0x80000000;
SECTIONSコマンドは、オブジェクトファイル等の入力セクションから出力セクションへの対応方法、それに出力セクションのメモリ配置方法について指示する。
SECTIONSコマンドのブレイス記号({}のこと)内にセクション・コマンドを複数行にわたり記述できる。セクション・コマンドには以下のものがある。
- ENTRYコマンド
- シンボル代入
- 出力セクション記述
- オーバーレイ記述
詳細については参考URLを参照して欲しい。
19行目の「PROVIDE」は、シンボル__executable_startが入力オブジェクトで参照されていて、かつ未定義のとき、__executable_startを値0x80000000で定義する(一種のシンボル代入)。入力に定義があればそちらを使用する。同じ行の後半(1つ目のセミコロン以降)にはもう1つシンボル代入があり、位置(ロケーション)カウンタに0x80000000を代入している。セミコロンは代入文の数式の直後に必須だ。他の記述ではセミコロンを付けないので注意が必要だ。
20: .interp : { *(.interp) } >FLASH AT>FLASH
これは出力セクション記述である。先頭の「.interp」が出力セクション名、アドレスとタイプが省略されていて、「*(.interp)」が出力セクション・コマンドの入力セクション記述である。「>FLASH」がリージョンで、「AT>FLASH」がLMAリージョンである。
セクション名の前後に空白が必須、またコロンとブレイスも必須である。省略されたアドレスには、リージョンが存在すればその値が、存在しなければ位置カウンタの値が使用される。
出力セクション・コマンドには、以下の4つが記述できる。
- シンボル代入
- 入力セクション記述
- 直接取り込むデータ値
- 特別な出力セクション・キーワード
入力セクション記述の一部のみ説明する。「*(.interp)」のケースで説明すると、「*」はすべての入力オブジェクトファイル名(つまりワイルドカード)を示し、「.interp」がそのファイル内のセクションを示す。特定の入力ファイルを記述すれば、同一セクションでも別々に入力できる。また空白で区切り複数のセクション名を記述可能だ。このケースでは、全入力ファイルのセクション.interpが入力セクションとなる。
30: .rel.text : { *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*) } >FLASH AT>FLASH
これが複数入力セクションの例だ。
出力セクションは仮想アドレス(VMA)とロードアドレス(LMA)を持つ。先の記述に出てきたリージョンとLMAリージョンが、それぞれVMAとLMAに対応する。「AT>」の記述が無ければ、VMAとLMAは同じに設定されるが、「AT>」によりLMAを異なるLMAリージョンに変更できる。これは(C言語の初期値付きstatic変数のような)ROMイメージを作成するときに使用する。
95: .init_array : { KEEP (*(.init_array)) } >CPUSRAM AT>FLASH
これなどはROMイメージの例だ。FLASHのLMAで出力され、その後CPUSRAMのVMAへ転送される(アドレスが設定されるだけで転送はプログラムの仕事)。
VMAとLMAが一致していれば「AT>」は不要だが、このリンカ・スクリプトでは省略されていないケースがほとんどだ。
54: .init : 55: { 56: KEEP (*(.init)) 57: } >FLASH AT>FLASH =0xd703d703
この出力セクション記述は、KEEP()と=0xd703d703という記述方法が加わっている。
このリンカ・スクリプトには多くの出力セクション・コマンドがあるが、実際に出力されているセクションはもっと少ない。これはリンク・オプション--gc-sectionsが指定されているとき、入力のないセクションを出力から削除するためである。入力が無くても出力したければ、入力セクション記述で「KEEP(*(.init))」のようにすればよい。従って、この.initセクションは必ず出力される。
「=0xd703d703」は出力セクション埋め潰し属性で、セクションに属し、かつ指定のない部分を指定データを繰り返しながら埋め潰す。ちなみに0xd703はAVR32のnop命令であるが、個人の好みとしては後続が何なのか不安なため、「rjmp 0」の0xc008の方が良いと思う。
59: .text : 60: { 61: *(.text .stub .text.* .gnu.linkonce.t.*) 62: KEEP (*(.text.*personality*)) 63: /* .gnu.warning sections are handled specially by elf32.em. */ 64: *(.gnu.warning) 65: } >FLASH AT>FLASH =0xd703d703
これは今まで説明した内容を越えないが、複雑な記述だ。
出力セクション.textには、ワイルドカードを含む複数のセクションを入力し、そのうち*(.text.*personality*)に該当するものは内容が無くても残し、リージョンFLASHに出力、隙間は0xd703d703で埋める。
70: PROVIDE (__etext = .); 71: PROVIDE (_etext = .); 72: PROVIDE (etext = .);
セクション.textの末尾を示すためによく使われるシンボル__etext、_etext、etextを自動的に生成している。
76: .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) } >FLASH AT>FLASH
ONLY_IF_ROは「もし入力セクションがread onlyなら」入力するということらしい(読んでそのままだが)。これとは別にONLY_IF_RWもある。
78: .dalign : { . = ALIGN(8); PROVIDE(_data_lma = .); } >FLASH AT>FLASH 79: PROVIDE (_data = ORIGIN(CPUSRAM)); 80: . = ORIGIN(CPUSRAM);
78行目は、位置カウンタを8でアライメントし、_data_lmaが未定義のときそのアドレスをシンボル_data_lmaに定義する。
79行目は、シンボル_dataが未定義のとき、リージョンCPUSRAMの先頭アドレスを_dataに定義する。つまりC言語ソースでグローバル変数dataを定義すると恐ろしいことが起きる...かもしれない。
80行目は、位置カウンタにリージョンCPUSRAMの先頭を設定する。これ以降がSRAMに配置(出力)するセクションになる。もちろん初期値が必要なセクションは「AT>」でROM化しておくことになる。
86: .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } >CPUSRAM
.tbssはTLS(スレッド・ローカル・ストレージ)の初期化無しデータ領域なので、「>CPUSRAM」のみが記述されている(「AT>」が付かない)。
131: .data : 132: { 133: *(.data .data.* .gnu.linkonce.d.*) 134: KEEP (*(.gnu.linkonce.d.*personality*)) 135: SORT(CONSTRUCTORS) 136: } >CPUSRAM AT>FLASH
「SORT(CONSTRUCTORS)」について。まずSORT()は出力する前に、ファイル名やセクション名について昇順ソートしてから出力する(デフォルトは入力順)。シンボルを探すとき高速になるからと思われる。
CONSTRUCTORSは出力セクションキーワードの1つであり、C++のコンストラクタとデストラクタに対応するためのものである。たぶんC++はオーバーロードのため、これらやメソッドのシンボルを特殊な方法で命名している。そのために必要な記述なのだろう。
142: .bss : 143: { 144: *(.dynbss) 145: *(.bss .bss.* .gnu.linkonce.b.*) 146: *(COMMON) 150: . = ALIGN(8); 151: } >CPUSRAM
COMMONはコモンシンボル用の入力セクションである。コモンシンボルは現在あまり使われていないようなので無視する。
155: __heap_start__ = ALIGN(8); 156: . = ORIGIN(CPUSRAM) + LENGTH(CPUSRAM) - _stack_size; 157: __heap_end__ = .;
__heap_start__と__heap_end__はヒープ領域の先頭と末尾を示すシンボルである。
ここまでSRAMに割り付けたセクション・サイズ分だけ位置カウンタが移動しているので、現在の位置カウンタからSRAM末尾までが未使用領域である。この未使用領域からスタックを除いた部分をヒープ領域とする、
155行目で、位置カウンタをアライメントしてヒープ先頭とする。
156行目で、位置カウンタをリージョンCPUSRAMの末尾−スタック・サイズとする、
157行目で、位置カウンタをヒープ末尾+1とする。ここでアライメントを取らないのは「スタック・サイズは常識で8や4の倍数だろ」ということらしい。
ところでSRAM出力セクションとスタックだけでSRAMサイズをオーバーしても、この記述だとエラーが出ない(後ろの.stackでエラーが出る)。また、__heap_start__と__heap_end__はセクションに属さない。
この後に続くのはデバッグ関係セクションである。この辺は知識が乏しいのでパス。
ヒープを計算してしまったので、SRAMに出力する出力セクション記述はスタックのみだ。
191: .stack ORIGIN(CPUSRAM) + LENGTH(CPUSRAM) - _stack_size : 192: { 193: _stack = .; 194: *(.stack) 195: . = _stack_size; 196: _estack = .; 197: } >CPUSRAM
デバッグ関係の出力セクションで位置カウンタが変更されているので、アドレスを計算し直している(ヒープの直後に記述しないのはなぜ?)。_stack_sizeが4の倍数でないと、スタック関連のシンボルがアライメントされず、たぶん例外が発生するだろう。
_stackはスタック先頭アドレスを示すシンボル、_estackは末尾+1を示すシンボルである。位置カウンタにスタック・サイズ_stack_sizeを設定すると、なぜスタック末尾+1になるんだろう?
195: . = . + _stack_size;
なら解るけど。
試しにリンカ・スクリプトを修正してみた。
修正箇所 | _stack, _estack | 備考 |
---|---|---|
195: /*. = _stack_size;*/ | 0x00007000, 0x00007000 | セクション.stack消滅 |
195: . = . + _stack_size; | 0x00007000, 0x00008000 | |
195: . += _stack_size; | 0x00007000, 0x00008000 | |
195: . = ORIGIN(CPUSRAM) + LENGTH(CPUSRAM); | - | リンク・エラー |
195: . = 0x8000; | - | リンク・エラー (0x8000はSRAM末尾+1) |
195: . = 0x7000; | - | リンク・エラー (0x7000はスタック先頭) |
195: . = 0x1000; | 0x00007000, 0x00008000 | |
195: . = 0x1001; | - | リンク・エラー |
195: . = 0x100; | 0x00007000, 0x00007100 |
「.」への代入が入力セクション記述内では相対移動になるようだ。
156行目のように入力セクション記述ではないときと異なるようだ。
198: .userpage : { *(.userpage .userpage.*) } >USERPAGE AT>USERPAGE 199: .factorypage : { *(.factorypage .factorypage.*) } >FACTORYPAGE AT>FACTORYPAGE
ずっと上の方で「役割が判らない」と書いたUSERPAGEとFACTORYPAGEへ出力するセクション.userpageと.factorypage。
200: /DISCARD/ : { *(.note.GNU-stack) }
/DISCARD/は出力セクション削除で、入力セクションは捨てられる。
とりあえずスタンドアローンな組み込みプログラムなら、この程度リンカ・スクリプトが読めれば問題ないだろう。
しかし、「スタックの指定はどこにあるのだろう?」という当初の問題は解決しなかった。リンカ・スクリプトで「_stack_size」を参照しているのだから、リンカ・オプションか、リンカに組み込まれているかのいずれかだと思う。しかし、Console出力にあるリンカ・オプションには出てこないし、リンカに埋め込まれていたら変更できないし、どうなっているんだろう。
194: *(.stack) 195: . = _stack_size; 196: _estack = .;
194行目のセクション.stack入力があると、入力セクション・サイズ+_stack_sizeのスタックができる。_stack_sizeを変更せずに、こういう方法でスタックを拡張しろということか?
余分(_stack_size)なスタックまで確保してしまい無駄だなあ。