コグノスケ


link 未来から過去へ表示  link 過去から未来へ表示(*)

link もっと前
2019年9月13日 >>> 2019年10月10日
link もっと後

2019年9月17日

今まで知らなかったmakeの挙動

シェルからmakeに渡す環境変数とmake変数の関係を知らなくて、かなりハマったのでメモしておきます。

まず、どういう関係か説明します。下記のようなMakefileを2つ用意します。

ディレクトリ構成
$ tree
.
|-- Makefile
`-- sub
    `-- Makefile
親: 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
子: sub/Makefile

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変数の渡し方

外部からmakeに変数を渡すには、下記の2つの方法があります。

  • 環境変数として渡す方法。例: VAR_A=A make
  • makeに渡す方法。例: make VAR_A=A

私は今まで、この2つの渡し方に何も差はないと思っていたのですが、実は全く動きが違いました。

環境変数の場合

下記のようにVAR_A, VAR_Cを環境変数として与えると、子Makefile側の結果がかなり変わります。

VAR_A, VAR_Cを環境変数として渡したときの実行結果
$ 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に渡されます。

この動作は知りませんでした。特に子プロセスへの変数の渡し方が変わる点が衝撃的です。

makeに渡す場合

下記のようにVAR_A, VAR_Cをmakeに渡すと、環境変数として渡す場合とは違う結果になります。

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から、子の ./configureを呼ぶ
  • 親MakefileはCFLAGSやLDFLAGSを固定値に設定している(固有のライブラリをリンクするため)

もう一つ大事な点は、親Makefileが使っているLDFLAGSを、子 ./configureに渡すと「そんなライブラリはないというエラー」になってしまう欠点があることです。

じゃあビルドエラーが起きるのか?というとそうではなく、先ほどご紹介した通り、何も指定せずにmakeを起動すれば、親Makefileの変数は、子 ./configureに渡りませんから、makeだけ実行すればエラーを起こさずビルドできるのです。

debuildと環境変数の罠

一見、正常にビルドできて問題ないように見えますが、このソフトウェアを *.debにパッケージングしようとするとハマります。Debianのパッケージ作成スクリプトdebuildは下記のような動作をするからです。

  • debuildはエラーチェック、セキュリティ強化のためCFLAGS, LDFLAGSにいくつかオプションを指定する
  • CFLAGS, LDFLAGSは「環境変数」でmakeに渡される

そろそろ何が起きるか予想が付くでしょう。そう、こうなるんです。

  • debuildが、親Makefileに環境変数でCFLAGS, LDFLAGSを渡す
  • 親MakefileがCFLAGS, LDFLAGSを書き換える
  • 親Makefileが、子 ./configureに書き換わった後のCFLAGS, LDFLAGSを渡す

この華麗なコンボが決まって、makeとするとビルドが成功し、debuild経由だとビルドが失敗する、謎のビルド環境ができあがります。

感想

こんなバグ、初見で分かる、はずもなし。

Makefileは大抵の人には難しすぎます。Makefileを手で書いているといつか地獄に落ちますよ、CMakeとかautomakeを使いましょう。便利だよ!

編集者:すずき(2019/09/19 02:27)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2019年9月18日

linux-nextが久しぶりに更新された

ここしばらく更新されていなかったlinux-nextが約10日ぶりに更新(9/4の次が9/15)されたので、喜んでgit fetchしてTinker Boardのカーネルを書き換えたところ、ウンともスンとも言わなくなってしまいました。

質の悪いことにエラーメッセージも何も出さずにハングアップするため、起動しなくなった原因がさっぱりわかりません。

仕方ないので頑張ってgit bisectし、起動しなくなった原因のコミットを見つけましたが、実は9/17にLKMLにて既に指摘済みで、直し方まで検討されていました(LKMLへのリンク)。今までの苦労は何だったのか。

結果だけ見ると、最初にLKMLのメールスレッドに気づいていればbisectなんて不要でした。しかし、一切エラーメッセージを出さずにハングするので、検索のヒントがありません。ノーヒントでLKMLの当該メールスレッドを探せたか?と考えてみると、ちょっと難しい気がします。

いわゆる鶏と卵の問題ですね……。

編集者:すずき(2019/09/20 00:01)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2019年10月6日

RISC-Vのバイナリダンプを逆アセンブルする

目次: 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のサイトからダウンロードできます。

Unleashedリセット〜ZSBLを例に解説

U540のマニュアルによると、リセット直後のPCは0x1004で、その後はMSELの値を見て、適切な場所に飛ぶとあります。MSELというのは、Unleashedボード上のDIPスイッチのことです。購入後、変えていなければ状態だと全部ON、つまり0xfになっていると思います。

リセット直後に実行される領域の周辺バイナリをダンプしてみましょう。ダンプには拙作のmemaccess(GitHubへのリンク)を使っています。

Unleashed 0x1000〜0x1040領域のダンプ
# 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のバイナリがスラスラ読めるほど達人ではないので、ファイルにダンプして逆アセンブルします。

Unleashed 0x1000〜0x1040領域の逆アセンブル
$ 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行毎に見ていきます。

0x1004: auipc t0, 0x0
PC + 0をt0にロードします。t0: 0x1004です。
0x1008: lw t1,-4(t0) # 0x1000
レジスタt1にアドレスt0 - 4 = 0x1004 - 4 = 0x1000つまりMSELをロードします。t1: 0xfです。
0x100c: slli t1,t1,0x3
レジスタt1を3ビット左シフト(= 8倍)します。t1: 0xf << 3 = 0x78です。
0x1010: add t0,t0,t1
レジスタt0とt1を足します。t0: です。t0: 0x1004 + 0x78 = 0x107cです。
0x1014: lw t0,252(t0)
レジスタt0にt0 + 252のアドレスからロードします。アドレスは0x107c + 252 = 0x1178です。後述のとおりt0: 0x10000です。
1018: jr t0
レジスタt0のアドレスにジャンプします。すなわち0x10000にジャンプします。

参考として、アドレス0x1178に何が書いてあるか示しておきます。付近の領域0x1100〜0x1180にはMSELの値に応じたジャンプ先のアドレスが書いてあります。MSELが他の値になったらどこにジャンプするか、眺めてみると面白いかと思います。

Unleashed 0x1000〜0x1040領域のダンプ
# 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はソースコードが読めるため、当たりを付けることが比較的容易です。しばらくは素敵なおもちゃになりそうなボードです。

編集者:すずき(2021/06/28 15:26)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2019年9月13日 >>> 2019年10月10日
link もっと後

管理用メニュー

link 記事を新規作成

<2019>
<<<09>>>
1234567
891011121314
15161718192021
22232425262728
2930-----

最近のコメント5件

  • link 24年6月17日
    すずきさん (06/23 00:12)
    「ありがとうございます。バルコニーではない...」
  • link 24年6月17日
    hdkさん (06/22 22:08)
    「GPSの最初の同期を取る時は見晴らしのい...」
  • link 24年5月16日
    すずきさん (05/21 11:41)
    「あー、確かにdpkg-reconfigu...」
  • link 24年5月16日
    hdkさん (05/21 08:55)
    「システム全体のlocale設定はDebi...」
  • link 24年5月17日
    すずきさん (05/20 13:16)
    「そうですねえ、普通はStandardなの...」

最近の記事3件

  • link 24年6月27日
    すずき (06/30 15:39)
    「[何もない組み込み環境でDOOMを動かす - その4 - 自作OSの組み込み環境へ移植] 目次: RISC-V目次: 独自OS...」
  • link 22年12月13日
    すずき (06/30 15:38)
    「[独自OS - まとめリンク] 目次: 独自OS一覧が欲しくなったので作りました。自作OSの紹介その1 - 概要自作OSの紹介...」
  • link 21年6月18日
    すずき (06/29 22:28)
    「[RISC-V - まとめリンク] 目次: RISC-VSiFive社ボードの話、CoreMarkの話のまとめ。RISC-V ...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 06/30 15:39