./configure --target-list=arm-softmmu,arm-linux-user
という風にコンフィギュレーションしたわけですが、arm-linux-userを含めることでビルドされるのが、qemu-armで、ユーザモードのプログラムを動かすツールです。一方のarm-softmmu、これがシステムモードのプログラムを動かすためのツールをビルドするらしいです。そのツールがqemu-system-armです。ユーザモードのプログラムでは、mainのようなエントリポイントからプログラムが始まるわけですが、システムモードではリセットベクタからプログラムの実行がスタートします。この前はlinuxカーネルを動かすのに使いましたが、今回はリセットベクタからスタートする小さなコードを書いてみます。
Hello World!を動かしたときにはarm-none-linux-gnueabi-というツールを使いましたが、今回はベアメタル用のプログラムをコンパイルするため、くarm-none-eabi-を使います。まずはそのインストール。
次にQEMUのCPU種を調べます。ソースのアーカイブを展開したディレクトリの下のhwに移動して、エミュレーションターゲットとして用いるVersatilePBのCPUを特定してみます。
cpu_model = "arm926"だそうです、ARM9っすね。
続いて、ツールチェインの対応を確認。
(`・ω・´) おk。
んで、とりあえずHello World!を出力してみようと思いますが、システムモードで動かすので、printfのようなライブラリ関数を使わずにUARTに直接キャラクタを叩き込むなんてこともできます。
UARTのアドレスは
となっているので、とりあえず0x101f1000に送り込んでみます。
まずはCソースinit.cの作成。
UARTのアドレスにvolatileを指定することで、コンパイラによる最適化を防ぎます。普通、コンパイラは冗長なアクセスは省略するのですが、volatile指定されたアドレスにはそれが適用されません。UARTの場合、キャラクタ数だけ書き込みを行う必要がありますが、普通のメモリアクセスを仮定して最適化するとすれば、最後の一回の書き込み以外は、結局上書きされてしまうものなので、コンパイラは不要なものと判断します。組み込みの世界では、こういうことを気にしなければならないことがしばしばありますね。
で、ユーザプログラムなら何も考えずにmain()を実装すれば自動的にそれをエントリポイントとして実行してくれるわけですが、 リセットベクタから始まるプログラムの場合、最初にどの関数に飛ぶかを指定しなければなりません。より正確に言えば、所望の関数へジャンプするコードを書く必要があるということです。それが、次のアセンブラコード、startup.sです。
私は昔、MIPSのアセンブラを触ったことがあるのですが、MIPSにはJALという命令があって、シンボルへのジャンプに使ったのを思い出しました。Jump And Linkという意味で、現在の実行箇所をリターン先としてスタックに積んだ上で、指定のシンボル(関数)へジャンプするというものです。上記のARMアセンブラコードのBLはおそらくそれに相当するものだと思います。Bというのは分岐の意味のBranchでしょうか。その前のLDRが、sp_topというシンボルで指定するスタックポインタをSPレジスタにロード、後のBはその場でループといったところでしょう。
最後に、これらをまとめるリンカスクリプト、
ENTRY(_Start)でリセットベクタのエントリポイントを指定。startup.sの_Startシンボルを指定しています。0x10000から初めて、.test、.data、.bssセクションを置いて、0x500だけ離して、スタックポインタを指定しています。この0x500は、下に伸びるスタックポインタのためのマージンといえますね。今回は関数を一つジャンプするだけなので、大きなスタックは必要ありません。さて、ビルドします。普段はMakefileで自動化されてて見えませんが、一つ一つコマンドで、コンパイル、リンク、バイナリの変換を行っていきます。
ELFフォーマットで作られた実行バイナリを、ベアメタルで実行できるようにRAWバイナリに変換しています。
では実行。
参考情報
Using QEMU for Embedded Systems Development, Part 2 - LINUX For YouHello world for bare metal ARM using QEMU - Balau
0 件のコメント:
コメントを投稿