InstrumentsTOSHIBAXilinxホーム製品情報コンピューテックスの製品についてご紹介します。VC組込み製品IoTシリーズ組込みボード組込みモジュール無線LANモジュールショッピングサポート当社製品に関するさまざまなサポート情報をご提供しています。お問い合わせ各種お問い合わせフォームをご紹介します。Cコンパイラを使いこなすために、リンカスクリプトを理解することは必須条件です。飯のタネにならないこの特殊なスクリプトを理解するためには、そこそこのモチベーションが必要とされます。普通は何らかの雛形があって、それをちょっと変更して動かしてみるというケースが多いのではないでしょうか。今までそうやって、その場しのぎで対応してきたリンカスクリプトに、今回は真正面から立ち向かってみたいと思います。Cのldコマンドのマニュアルや、よそのサイトでもやっていますので、このコーナーとしては、より実践的なスクリプトファイルを読み解くことにします。解析するターゲットはRZ/A1LのLチカプログラムで使ったあのリンカスクリプトファイルです。全体で260行ぐらいありますが、これを順番に見ていくことにします。全体の構造を把握するまずは全体の構造を把握しましょう。見出し的な部分だけをピックアップすると以下のような形になっています。全体の構造を見れば楽勝っぽい感じもします。0x00}大きく分けてMEMORYとSECTIONSという項目があります。リンカのマニュアルによるとこれらはコマンドというようです。SECTIONSコマンドの中にはセクションの配置が細かく指定されています。MEMORYコマンドの定義まず最初はMEMORYコマンドが定義されています。0x100000}この定義は、システム全体の物理的なメモリ構成を決めます。ROMとかSYSTEM_RAMという名前は任意に付けることができます。この名前は、SECTIONSコマンドでオブジェクトの出力先を指定するために使用します。ORIGINは配置する物理メモリのアドレスを指定し、LENGTHはそのサイズを指定します。サイズを指定することで、リンク時にオブジェクトのサイズがチェックされ、サイズがオーバーするとリンクエラーになります。LENGTHの指定にはM(メガ)、K(キロ)などの表現が可能ですので、0x100000は1Mと書き換えることができます。ORIGINを「O」、LENGTHを「L」と短縮して書くこともできます。CEVボードでは0x18000000番地より128Mバイトのシリアルフラッシュメモリが搭載されており、0x20000000番地以降は、3Mバイトの内蔵RAM領域です。UNCACHED_RAMとして指定されている0x60139000番地は、0x60000000番地から始まる内蔵RAMのミラーイメージで、0x20000000番地の内容と全く同じものです。2次キャッシュはアドレスの範囲で指定できますので、内蔵RAMの後半部分のミラーイメージを利用して、非キャッシュ領域に割り当てているんですね。次はSECTIONSコマンドの中を見ていきましょう。最初は.textセクションが定義されています。.textセクションはおもにプログラムのコードが配置されるセクションです。普通の組込みシステムではROMに配置されます。ROM」と書かれています。これは.textセクションがMEMORYで指定されたROM領域に出力されることを示しています。0x18000000番地です。先頭に戻りまして、最初の行から見ていきましょう。0x00000000;「.」ドットに0を代入していますが、ドットはロケーションカウンタといって、現在の配置アドレスを示しています。オブジェクトが1バイト出力されれば、ロケーションカウンタも1バイト加算されます。ロケーションカウンタの値を見れば、オブジェクトが何バイト出力されたかを知ることができます。ここではロケーションカウンタを0にしていますが、実際の配置アドレスは、ROMで指定されたアドレスとロケーションカウンタが加算された値となりますので、0x18000000番地となります。」のように値を参照することも可能です。(VECTOR_TABLE)のように書かれています。「*」は任意のオブジェクトファイル名に一致するワイルドカードの指定で、VECTOR_TABLEはソースコード内で定義されたセクション名です。これは、ソースファイルvector_tables.sで、以下のように定義されています。VECTOR_TABLEこのことから、Lチカプログラムの最初に配置されるセクションはVECTOR_TABLEで、オブジェクトコードは0x18000000番地から出力されることになります。次の行はパス付のオブジェクトファイル名を指定しています。.text.*)「*/」で任意のパスに配置されたperipheral_init_basic.oの.textセクションと.text.で始まるセクションを出力します。一致するファイル名がない場合やセクション名がない場合はどうなるんでしょう?実際のリンカの動作を見ると、何事もなかったかのように無視されるようです。ファイルがないとか、セクションがないとかそういったエラーにはなりませんので、間違ったセクション名を指定して、正しくリンクされないということにならないよう注意が必要です。先ほどの定義と似ていますが、この定義はどうでしょうか。.rodata.*).textが.rodataとなっている以外は同じです。.rodataとはconst宣言された変更不可能なデータのセクションです。上に配置されるように指定します。.dataセクションの定義.dataは書き換え可能な、初期値を持つ変数を配置するためのセクションです。次に示す例のように、グローバル変数や、static変数を宣言するときに初期値があると.dataセクションに配置されます。static変数.dataセクションの定義は以下のようになっています。ややこしそうですが、よく見れば3つのブロックで構成されていることが分かります。ROM」となっています。ROM」というキーワードが追加されていますね。これは、実際にプログラムが動作するときのアドレス(SYSTEM_RAM)と、データを保持するためのアドレス(ROM)を指定するやり方です。.dataというのは初期値を持ったデータですので、電源を切っても消えないところにその初期値を保持しておく必要があります。その内容をまるごとRAM領域(SYSTEM_RAM)にコピーします。RAMにコピーするかというと、.dataセクションは書き換え可能な変数領域だからです。20;」のような書き換えができないことになります。「変数のアドレス(&var)はコピー先のRAMを指しているの?」と疑問に思われるかもしれませんが、問題ありません。RAM領域へのコピーはだれがするのでしょう。これは、main関数を実行する前のスタートアップルーチンがやってくれるのが普通です。Lチカのプログラムではinitsect.sというアセンブラソースでコピーしています。突っ込んで、ではROMの領域のどこからどこまでをコピーするのでしょう。ヒントは以下の行にあります。);これらの意味するところですが、まず最初の行でロケーションカウンタを0に設定しています。」でロケーションカウンタの値を変数__vect_loadに代入します。SYSTEM_RAM」で示されるアドレスを足した値、0x20020000が代入されます。・・・」では「LOADADDR」と「ADDR」という関数が使われています。この2つの関数の意味ですが、LOADADDR(.data)は.dataがROM上での配置アドレスを返し、ADDR(.data)は.dataが実際に使われるときのRAM上の配置アドレスを返します。0;のようになります。これは.dataが配置されるROMのアドレスを求めています。・・・」は出力したオブジェクトのROM上の終了アドレスを求めています。yyy;最終的に__vect_startから__vect_endまでが、出力オブジェクトのアドレス範囲を示しています。この変数を使えば、コピー元のROMの領域分かりますので、コピーすることができるわけです。うまいやりかたですね。ちなみに、リンカスクリプトでシンボルに代入を行うと、そのシンボルはC言語やアセンブラから参照できるグローバルな変数となります。C言語から扱う場合は以下のように、外部参照宣言してからvoidポインタでアクセスします。&__vect_end;リンカスクリプトの変数はC言語では型が未定義となります。ここではshortで宣言していますが、void以外なら何でもかまいません。.dataセクションは、__vect_load、__fastcode_load、__data_loadの3つで構成されています。これらを区別することで、システム構成に合わせてコピー先を変えるのだと思われます。__fastcode_loadは速いメモリに配置するとか・・・。.bssセクションの定義.bssセクションも.dataと同じく変数の格納領域となっています。.dataとの違いは変数の宣言時に初期値がないことです。CACHED_RAM.bssは.dataと違って、コピーするデータを持ちませんので、そのままRAMに配置します。C言語の仕様で、初期値の無いグローバル変数やstatic変数は0に初期化すべしとありますので、.bssセクションはスタートアップルーチンで0にクリアします。.stackセクションの定義.stackセクションはスタックの領域ですが、ここではARMのアーキテクチャ特有の書き方になっていますので、少し複雑です。);」とあるのはロケーションカウンタを16バイトに境界調整しています。スタックはキリのいい数字(普通は4バイト境界)を好みますので、こういう調整が必要です。境界調整をせずにスタックが奇数アドレスになった場合は、スタックのアクセス時に即、アボート例外が発生します。その次の行からを理解するには、ARMのアーキテクチャの例外処理を抑えておく必要があります。ARMの例外処理にはリセット、割り込み、アボートなどがあります。ARMは例外が発生すると、レジスタバンクとなっているR13(つまり、スタックポインタ)を切り替えて、例外用のスタックで動作します。この.stackセクションで用意されているのが、まさに例外用のスタックなのです。ここではロケーションカウンタに0x8000や0x2000バイトを加算して、Image$$で始まる変数にアドレスを代入しています。この変数をソースコードで参照して、例外用のスタックポインタを初期化しています。初期化コードはreset_handler.sで以下のように書かれています。=Image$$ARM_LIB_STACK$$ZI$$LimitCPS命令でCPUの動作モードを切り替えて、各々の例外SPを初期化しているんですね。.stackの最後に書かれている以下の行ですが、境界調整が16KBも指定されていて、他と比べてなんか異質です。.;実はこれ、MMUで使用するTTBというアドレス変換テーブルなんです。「LチカプログラムでMMUなんか使っていないでしょ」と思われるかもしれませんが、実際には2次キャッシュを設定するときに使用しています。ARMの2次キャッシュはMMUで設定するのです。その他のセクションあとはザコ的な扱いになってしまいますが、ざっと見ていきましょう。UNCACHED_RAMこのセクションは非キャッシュエリアに配置されますが、「NOLOAD」の指定があるため、オブジェクトの実体として出力されません。このセクションは、Lチカプログラムでは使用していませんでしたが、通信用のバッファとして確保しているようです。非キャッシュになっているものと推察されます。次はキャッシュ関連をまとめているようです。ROMキャッシュ関連の操作は非キャッシュ領域で動作させる必要があるため、最初にROMに配置して、後から非キャッシュ領域にコピーして実行するようになっています。残りのセクションは、今までにどのセクションにも該当しなかったオブジェクトを救い上げるためにあるようです。マップファイルを見ると分かりますが、実際にはこれらの定義は機能していません。0x00以上で、Lチカプログラムのリンカスクリプトを見てきました。リンカスクリプトの仕様としてはまだまだ奥深いものがありますが、それはまた次の機会にしたいと思います。記事に関するご感想やお問い合わせは、下記フォームよりお送りください。問必須ご記入頂きましたら1回だけクリックしてくださいこのサイトは、プライバシー保護のため、TLS/SSL暗号化通信を導入しています。reserved.当社ページではJavaスクリプトを使用しています。情報が正しく表示されない部分があります。