目次: RISC-V
相変わらず空き時間にRISC-Vのエミュレータを書いています。RV64IMACくらいが必要なのですが、意外と命令の種類が多くていっぺんにやるのは面倒なので、HiFive Unleashedのファームウェアを動かしてみて、足りない命令から実装していくスタイルで開発しています。
HiFive Unleashedの電源を入れたときに真っ先に起動してくるファームウェアというかブートローダはZSBL(Zeroth Stage Boot Loader), FSBL(First Stage Boot Loader)という名前です。
なんと親切なことにソースコードが公開されています(GitHub - Freedom U540-C000 Bootloader Codeのリンク)。素晴らしいですね……。
なぜかUnleashedから引っこ抜いてきたバイナリと、手元でコンパイルしたZSBL, FSBLのバイナリが一致しません。何か間違っているのかも?ちょっと気になります。しかしながら、ソースコードが公開されている意義は非常に大きいです。
メモリマップも公開されています。SiFive Freedom U540 SoCのサイトからダウンロードできます。
U540のマニュアルによると、リセット直後のPCは0x1004で、その後はMSELの値を見て、適切な場所に飛ぶとあります。MSELというのは、Unleashedボード上のDIPスイッチのことです。購入後、変えていなければ状態だと全部ON、つまり0xfになっていると思います。
リセット直後に実行される領域の周辺バイナリをダンプしてみましょう。ダンプには拙作のmemaccess(GitHubへのリンク)を使っています。
# ma db 0x1000 0x40 00001000 0f 00 00 00 97 02 00 00 03 a3 c2 ff 13 13 33 00 00001010 b3 82 62 00 83 a2 c2 0f 67 80 02 00 00 00 00 00 00001020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00001030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00001040
何かが書いてあるようですが、RISC-Vのバイナリがスラスラ読めるほど達人ではないので、ファイルにダンプして逆アセンブルします。
$ riscv64-unknown-elf-objdump -EL -D -b binary -m riscv:rv64 --adjust-vma=0x1000 reset.bin reset.bin: file format binary Disassembly of section .data: 0000000000001000 <.data>: 1000: 0000000f fence unknown,unknown 1004: 00000297 auipc t0,0x0 1008: ffc2a303 lw t1,-4(t0) # 0x1000 100c: 00331313 slli t1,t1,0x3 1010: 006282b3 add t0,t0,t1 1014: 0fc2a283 lw t0,252(t0) 1018: 00028067 jr t0 ...
先頭が変な命令に見えますが、これは命令ではなくMSELです。値は先程も言ったとおり0xfです。実行されるのは0x1004からです。大した量でもないし、1行毎に見ていきます。
参考として、アドレス0x1178に何が書いてあるか示しておきます。付近の領域0x1100〜0x1180にはMSELの値に応じたジャンプ先のアドレスが書いてあります。MSELが他の値になったらどこにジャンプするか、眺めてみると面白いかと思います。
# ma dd 0x1100 0x80 00001100 00001004 00000000 20000000 00000000 00001110 30000000 00000000 40000000 00000000 00001120 60000000 00000000 00010000 00000000 00001130 00010000 00000000 00010000 00000000 00001140 00010000 00000000 00010000 00000000 00001150 00010000 00000000 00010000 00000000 00001160 00010000 00000000 00010000 00000000 00001170 00010000 00000000 00010000 00000000 ★0x1178には0x10000が書いてある 00001180
マニュアルの言うとおり、MSELが0xfの場合、リセット後ZSBLにジャンプ、アドレスで言うと0x10000にジャンプすることが確認できました。
以降も同様に0x10000付近をダンプし、逆アセンブルしたり、エミュレータに実行させてみたりして、開発を進めています。今はZSBLは通過して、FSBLの先頭の方まで実行できるようになりました。いうなれば、砂山にトンネルを掘っているような気分でしょうか、なかなか面白いです。
通常は逆アセンブルだけだと処理の意図を掴むことが難しいですけども、その点U540はソースコードが読めるため、当たりを付けることが比較的容易です。しばらくは素敵なおもちゃになりそうなボードです。
ここしばらく更新されていなかったlinux-nextが約10日ぶりに更新(9/4の次が9/15)されたので、喜んでgit fetchしてTinker Boardのカーネルを書き換えたところ、ウンともスンとも言わなくなってしまいました。
質の悪いことにエラーメッセージも何も出さずにハングアップするため、起動しなくなった原因がさっぱりわかりません。
仕方ないので頑張ってgit bisectし、起動しなくなった原因のコミットを見つけましたが、実は9/17にLKMLにて既に指摘済みで、直し方まで検討されていました(LKMLへのリンク)。今までの苦労は何だったのか。
結果だけ見ると、最初にLKMLのメールスレッドに気づいていればbisectなんて不要でした。しかし、一切エラーメッセージを出さずにハングするので、検索のヒントがありません。ノーヒントでLKMLの当該メールスレッドを探せたか?と考えてみると、ちょっと難しい気がします。
いわゆる鶏と卵の問題ですね……。
シェルからmakeに渡す環境変数とmake変数の関係を知らなくて、かなりハマったのでメモしておきます。
まず、どういう関係か説明します。下記のようなMakefileを2つ用意します。
$ tree . |-- Makefile `-- sub `-- Makefile
VAR_A = aaa
VAR_B = bbb
all:
@echo "In parent"
@echo "VAR_A: '$(VAR_A)'"
@echo "VAR_B: '$(VAR_B)'"
@echo "VAR_C: '$(VAR_C)'"
make -C sub
all:
@echo "In sub"
@echo "VAR_A: '$(VAR_A)'"
@echo "VAR_B: '$(VAR_B)'"
@echo "VAR_C: '$(VAR_C)'"
親Makefileは変数VAR_A, VAR_Bを書き換え、子sub/Makefileを再帰的に呼び出します。各Makefileでは変数の値を表示しています。
では、トップディレクトリにてmakeを実行してみましょう。
$ make In parent VAR_A: 'aaa' ★VAR_Aは設定した値になっている★ VAR_B: 'bbb' ★VAR_Bは設定した値になっている★ VAR_C: '' ★VAR_Cは特に書き換えていないので空★ make -C sub make[1]: Entering directory '/home/katsuhiro/share/falcon/projects/c/makefile_env_var/sub' In sub VAR_A: '' ★VAR_Aは渡されない★ VAR_B: '' ★VAR_Bは渡されない★ VAR_C: '' ★VAR_Cは渡されない★ make[1]: Leaving directory '/home/katsuhiro/share/falcon/projects/c/makefile_env_var/sub'
ご覧の通り、親のMakefileで値を設定したVAR_AやVAR_Bといった変数は、子のsub/Makefileに「引き継がれません」。
外部からmakeに変数を渡すには、下記の2つの方法があります。
私は今まで、この2つの渡し方に何も差はないと思っていたのですが、実は全く動きが違いました。
下記のようにVAR_A, VAR_Cを環境変数として与えると、子Makefile側の結果がかなり変わります。
$ VAR_A=A VAR_C=C make In parent VAR_A: 'aaa' ★VAR_Aは親が設定した値になる★ VAR_B: 'bbb' VAR_C: 'C' ★VAR_Cは特に書き換えていないので、渡された値のまま★ make -C sub make[1]: Entering directory '/home/katsuhiro/share/falcon/projects/c/makefile_env_var/sub' In sub VAR_A: 'aaa' ★VAR_Aが渡される、Aではなく親が設定した値aaaになっている★ VAR_B: '' VAR_C: 'C' ★VAR_Cが渡される、値は変わらず★ make[1]: Leaving directory '/home/katsuhiro/share/falcon/projects/c/makefile_env_var/sub'
何も渡さなかった場合の実行結果と異なり、子のsub/MakefileにもVAR_A, VAR_Cが渡されます。もし、親Makefileが変数の値を書き換えた場合は、書き換えた値が子Makefileに渡されます。
この動作は知りませんでした。特に子プロセスへの変数の渡し方が変わる点が衝撃的です。
下記のようにVAR_A, VAR_Cをmakeに渡すと、環境変数として渡す場合とは違う結果になります。
$ make VAR_A=A VAR_C=C In parent VAR_A: 'A' ★VAR_Aは親が設定した値にならない★ VAR_B: 'bbb' VAR_C: 'C' make -C sub make[1]: Entering directory '/home/katsuhiro/share/falcon/projects/c/makefile_env_var/sub' In sub VAR_A: 'A' ★VAR_Aが渡される、VAR_Aは親が設定した値にならない★ VAR_B: '' VAR_C: 'C' ★VAR_Cが渡される、値は変わらず★ make[1]: Leaving directory '/home/katsuhiro/share/falcon/projects/c/makefile_env_var/sub'
環境変数で渡した場合と同様に、子Makefileに変数の内容が渡されます。その点は同じです。しかし、親Makefileで行っているaaaという値の代入が無効化され、渡される値が全く違います。
この動作も知りませんでした……。私はMakefileやmakeとは長い付き合いですし、動きも何となくわかっていた気分になっていましたが、勘違いだったようです。makeは難しすぎます。
この動きによって、何が困ったかを紹介しておきます。同じ状況に陥る人は、まずもっていないと思いますけど、ご参考まで。
現象としては「makeでビルドすると成功するが、debuild(Debianのパッケージング作成スクリプト)経由でビルドすると失敗する」です。この現象から原因が予想できた人、あなたは凄い(少なくとも私より凄い)です!この先は読む必要はございません。
下記のような感じの、ちょっと変わったMakefileを使っているソフトウェアをビルドしていました。
もう一つ大事な点は、親Makefileが使っているLDFLAGSを、子 ./configureに渡すと「そんなライブラリはないというエラー」になってしまう欠点があることです。
じゃあビルドエラーが起きるのか?というとそうではなく、先ほどご紹介した通り、何も指定せずにmakeを起動すれば、親Makefileの変数は、子 ./configureに渡りませんから、makeだけ実行すればエラーを起こさずビルドできるのです。
一見、正常にビルドできて問題ないように見えますが、このソフトウェアを *.debにパッケージングしようとするとハマります。Debianのパッケージ作成スクリプトdebuildは下記のような動作をするからです。
そろそろ何が起きるか予想が付くでしょう。そう、こうなるんです。
この華麗なコンボが決まって、makeとするとビルドが成功し、debuild経由だとビルドが失敗する、謎のビルド環境ができあがります。
こんなバグ、初見で分かる、はずもなし。
Makefileは大抵の人には難しすぎます。Makefileを手で書いているといつか地獄に落ちますよ、CMakeとかautomakeを使いましょう。便利だよ!
< | 2019 | > | ||||
<< | < | 10 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | - | - |
合計:
本日: