目次: Linux
今どき(?)のinitrdとカーネル引数の渡し方を知らなかったのでメモしておきます。
昔のARM LinuxですとATAGと呼ばれるブートの情報を並べたリストをメモリ領域に置いてカーネル引数やinitrdを渡して(ATAGの仕様はVincentさんのBooting ARM Linuxが詳しいです)いました。ATAGは今も使えます(Booting ARM Linuxの4a. Setup the kernel tagged list)けど主流はデバイスツリーのはずです。たぶん。ARM Linuxの場合レジスタに指定すべき情報はこんな感じだそうです。
RISC-V Linuxの場合はカーネルのドキュメント(RISC-V Kernel Boot Requirements and Constraints)によれば、RISC-V Linuxの場合レジスタに指定すべき情報はこんな感じだそうです。
ARM Linuxよりさらに減ってhartidとデバイスツリーのアドレスのたった2つです、シンプルですね。
仕様からデバイスツリーを書いても良いですが、目の前のQEMUでDebianが動作している(2024年8月2日の日記参照)のですから実際に動作しているものを調べたほうが早いでしょう。
QEMUが内部で生成しているデバイスツリーのダンプ方法は以前(2024年7月23日の日記参照)紹介した通りです。initrdとappend="root=/dev/nfs"を渡してQEMUを起動し、デバイスツリーをダンプして&dtsに変換します。
$ qemu-system-riscv64 \ -machine virt \ -bios none \ -chardev stdio,id=con,mux=on \ -serial chardev:con \ -smp 4 \ -kernel fw_payload.bin \ -initrd initramfs.cpio \ -append "root=/dev/nfs" (QEMUモニターにて) dumpdtb virt_initrd_cmd.dtb $ dtc -O dts virt_initrd_cmd.dtb > virt_initrd_cmd.dts
/* virt_initrd_cmd.dts */
/dts-v1/;
/ {
...
chosen {
bootargs = "root=/dev/nfs"; /* ★appendで渡したカーネル引数★ */
linux,initrd-end = <0x84227600>; /* ★initrd終端アドレス★ */
linux,initrd-start = <0x84000000>; /* ★initrd先頭アドレス★ */
stdout-path = "/soc/serial@10000000";
rng-seed = <0x2a0a5693 0x41742b2a 0x49faf2f1 0xaf1d27cc 0x9c06cb87 0x277e3472 0xfdf688c7 0x5029079c>;
};
...
ルートノード以下のchosenノードにあるbootargs, linux,initrd-なんとかがappendやinitrdオプションと対応しています。
以上、今どきのLinuxに対してカーネル引数やinitrdを渡す方法がわかりました。Linuxでデバイスツリーに対応したアーキテクチャは多いですから、一度実装しておけば多数のアーキテクチャでカーネルパラメーター指定ができます。便利ですね。
欠点はブートローダーでデバイスツリーのchosenノードを変更する必要がある、すなわちブートローダーにデバイスツリーを加工する処理を実装しなければならない点です。難しくはないでしょうけど、それなりに手間が掛かるでしょう。
ちなみにlinux,initrd-startのようなlinux,で始まるプロパティの仕様はLinuxカーネルのドキュメント(The chosen node - kernel.org)に記載があります。
先程まで見てきたbootargsやinitrd-なんとかが正しく生成できれば、QEMUのappendやinitrdオプション無しでもカーネル引数やinitrdをカーネルに渡すことができるはずです。やってみましょう。
/* virt_mod.dts */ /dts-v1/; / { ... chosen { /* ★この行を追加する★ */ bootargs = "root=/dev/nfs nfsroot=192.168.1.1:/path/to/debian_rootfs,v3 ip=on nfsrootdebug rw"; ...
以前作成したGDBスクリプト(2024年7月29日の日記参照)を使ってカーネルとデバイスツリーをロードします。
# load_dtb_opensbi.gdb restore virt_mod.dtb binary 0x87f00000 thread 1 set $a1=0x87f00000 thread 2 set $a1=0x87f00000 thread 3 set $a1=0x87f00000 thread 4 set $a1=0x87f00000 restore fw_payload.bin binary 0x80000000 thread 1 set $pc=0x80000000 thread 2 set $pc=0x80000000 thread 3 set $pc=0x80000000 thread 4 set $pc=0x80000000
QEMUをkernel, dtb, append引数なし、-Sオプション付き(デバッガーのアタッチ待ち)で起動します。
$ qemu-system-riscv64 \ -machine virt \ -bios none \ -nographic \ -chardev stdio,id=con,mux=on \ -serial chardev:con \ -mon chardev=con,mode=readline \ -netdev user,id=netdev0 \ -device virtio-net-device,netdev=netdev0 \ -m 2g \ -smp 4 \ -s \ -S $ riscv64-unknown-linux-gnu-gdb (gdb) target remote :1234 Remote debugging using :1234 warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000000000001000 in ?? () (gdb) source load_dtb_opensbi.gdb Restoring binary file virt_mod.dtb into memory (0x87f00000 to 0x87f01cb5) [Switching to thread 1 (Thread 1.1)] #0 0x0000000000001000 in ?? () [Switching to thread 2 (Thread 1.2)] #0 0x0000000000001000 in ?? () [Switching to thread 3 (Thread 1.3)] #0 0x0000000000001000 in ?? () [Switching to thread 4 (Thread 1.4)] #0 0x0000000000001000 in ?? () Restoring binary file fw_payload.bin into memory (0x80000000 to 0x81f51608) [Switching to thread 1 (Thread 1.1)] #0 0x0000000000001000 in ?? () [Switching to thread 2 (Thread 1.2)] #0 0x0000000000001000 in ?? () [Switching to thread 3 (Thread 1.3)] #0 0x0000000000001000 in ?? () [Switching to thread 4 (Thread 1.4)] #0 0x0000000000001000 in ?? () (gdb) continue Continuing.
最後のcontinueを実行するとQEMU側もログが出ます。例えばこんな感じです。
$ qemu-system-riscv64 \ -machine virt \ -bios none \ -nographic \ -chardev stdio,id=con,mux=on \ -serial chardev:con \ -mon chardev=con,mode=readline \ -netdev user,id=netdev0 \ -device virtio-net-device,netdev=netdev0 \ -m 2g \ -smp 4 \ -s \ -S OpenSBI v1.5 ____ _____ ____ _____ / __ \ / ____| _ \_ _| | | | |_ __ ___ _ __ | (___ | |_) || | | | | | '_ \ / _ \ '_ \ \___ \| _ < | | | |__| | |_) | __/ | | |____) | |_) || |_ \____/| .__/ \___|_| |_|_____/|____/_____| | | |_| Platform Name : riscv-virtio,qemo Platform Features : medeleg ... Boot HART MIDELEG : 0x0000000000001666 Boot HART MEDELEG : 0x0000000000f0b509 [ 0.000000] Linux version 6.9.0 (katsuhiro@blackbird) (riscv64-unknown-linux-gnu-gcc (GCC) 14.1.0, GNU ld (GNU Binutils) 2.42.50.20240622) #9 SMP Thu Aug 1 00:38:49 JST 2024 [ 0.000000] random: crng init done [ 0.000000] Machine model: riscv-virtio,qemo ... [ 2.652369] VFS: Mounted root (nfs filesystem) on device 0:20. [ 2.661913] devtmpfs: mounted [ 2.831443] Freeing unused kernel image (initmem) memory: 2260K [ 2.833459] Run /sbin/init as init process [ 4.984017] systemd[1]: systemd 256.4-2 running in system mode (+PAM +AUDIT +SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT -GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBCRYPTSETUP_PLUGINS +LIBFDISK +PCRE2 +PWQUALITY +P11KIT +QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD +BPF_FRAMEWORK -XKBCOMMON +UTMP +SYSVINIT +LIBARCHIVE) [ 4.988413] systemd[1]: Detected virtualization qemu. [ 4.989856] systemd[1]: Detected architecture riscv64. Welcome to Debian GNU/Linux trixie/sid! [ 5.048884] systemd[1]: Hostname set to <qemu>. ... [ OK ] Reached target graphical.target - Graphical Interface. Starting systemd-update-utmp-runle…- Record Runlevel Change in UTMP... [ OK ] Finished systemd-update-utmp-runle…e - Record Runlevel Change in UTMP. Debian GNU/Linux trixie/sid qemu ttyS0 qemu login:
Debianのログインプロンプトまで表示されれば成功です。カーネル引数を変えたりinitrdのサイズが変わるたびにdtsを書き換えてdtcでコンパイルする……なんて考えると面倒くさいことこの上ないですが、QEMUや気の利いたブートローダーは自動的にデバイスツリーのchosenノードを加工してくれます。ありがたいですね。
目次: Linux
前回はbinfmt_miscの使い方や動作を調べました。今回はDebian特有の挙動を見ていきたいと思います。細かいことを言えばQEMUの実装の話でありbinfmt_miscの実装ではありません。しかし二者は密接に関わっているので関連項目として取り上げます。
Debianのbinfmt_miscの設定では、インタープリターに/usr/libexec/qemu-binfmt/以下のファイルを指定します。このディレクトリには独特なファイル名(例: (arch名)-binfmt-P)のシンボリックリンクが置かれています。一例を紹介します。
$ ls -la /usr/libexec/qemu-binfmt/riscv64-binfmt-P lrwxrwxrwx 1 root root 29 May 20 22:14 /usr/libexec/qemu-binfmt/riscv64-binfmt-P -> ../../bin/qemu-riscv64-static
この/usr/libexec/qemu-binfmt/riscv64-binfmt-Pはシンボリックリンクで、実体はQEMU(/usr/bin/qemu-riscv64-static)だと解ると思います。引数を何も指定せずに実行すると普通のQEMUとは異なるメッセージを出力します。
$ /usr/libexec/qemu-binfmt/riscv64-binfmt-P qemu: /usr/libexec/qemu-binfmt/riscv64-binfmt-P has to be run using kernel binfmt-misc subsystem
メッセージを出力しているのは見た通りQEMUです、が、オリジナルのQEMUにこのメッセージを出すコードはありません。
Debianのqemu-user-staticパッケージソースコードを見るとこのエラーメッセージを表示するためのパッチがありました。
// qemu/debian/patches/linux-user-binfmt-P.diff
diff --git a/linux-user/main.c b/linux-user/main.c
index e44bdb17b8..587bd02db2 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -562,7 +562,7 @@ static void usage(int exitcode)
exit(exitcode);
}
-static int parse_args(int argc, char **argv)
+static int parse_args(int argc, char **argv, bool *preserve_argv0)
{
const char *r;
int optind;
@@ -579,6 +579,28 @@ static int parse_args(int argc, char **argv)
}
}
+ /* HACK alert.
+ * when run as an interpreter using kernel's binfmt-misc mechanism,
+ * we have to know where are we (our own binary), where's the binary being run,
+ * and what it's argv[0] element.
+ * Only with the P interpreter flag kernel passes all 3 elements as first 3 argv[],
+ * but we can't distinguish if we were run with or without this P flag.
+ * So we register a special name with binfmt-misc system, a name which ends up
+ * in "-binfmt-P", and if our argv[0] ends up with that, we assume we were run
+ * from kernel's binfmt with P flag and our first 3 args are from kernel.
+ */
+ if (strlen(argv[0]) > sizeof("binfmt-P") &&
+ strcmp(argv[0] + strlen(argv[0]) - sizeof("binfmt-P"), "-binfmt-P") == 0) {
+ if (argc < 3) {
+ (void) fprintf(stderr, "qemu: %s has to be run using kernel binfmt-misc subsystem\n", argv[0]); //★★エラーメッセージ★★
+ exit(EXIT_FAILURE);
+ }
+ exec_path = argv[1];
+ handle_arg_argv0(argv[2]);
+ *preserve_argv0 = true;
+ return 2;
+ }
+
optind = 1;
for (;;) {
if (optind >= argc) {
コマンド名の末尾が-binfmt-Pであり、引数が3つより少ない場合にエラーを出す実装です。引数を渡すか、シンボリックリンクをリネームして実行するとエラーメッセージは出現しなくなります。
# ls -la riscv64-binfmt-P_ lrwxrwxrwx 1 root root 29 May 20 22:14 riscv64-binfmt-P_ -> ../../bin/qemu-riscv64-static # ./riscv64-binfmt-P_ qemu: no user program specified # ../../bin/qemu-riscv64-static qemu: no user program specified
QEMUで実行するbinfmt_miscのバイナリタイプ設定はこんな感じで、
# less /usr/lib/binfmt.d/qemu-riscv64.conf :qemu-riscv64:M::\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/libexec/qemu-binfmt/riscv64-binfmt-P:OCPF # ★FlagsはO, C, P, Fの4つ★
実装のコメントを見る限り、下記の条件を満たすことを期待しています。
この条件から外れる場合はエラーにしたいようです。riscv64-binfmt-Pのリンク先の実体はQEMUですが、間違ってQEMUとして起動することを防いでいる……?そこまでする理由があまり思いつかないですけど、昔に何かあったのかな?
目次: Linux
RISC-V 64bit用の実行ファイルをx86_64 Linuxマシン上のシェルから直接実行できる不思議な仕組み、Linuxのbinfmt_miscについて調べます。
ざっくりbinfmt_miscを説明するなら、バイナリタイプ(binary type)の先頭部分があるパターンに一致したら、設定で指定したインタープリタを起動してバイナリを渡し実行してもらう仕組みです。スクリプト言語で書かれたプログラムは直接実行できず、インタープリタ経由で実行する仕組みと似ています。今回のように他アーキテクチャのバイナリを実行する(インタープリタ: QEMU)以外に、Javaのアーカイブ*.jarを実行する(インタープリタ: Java仮想マシン)用途にも使えます。
今回はbinfmt_miscそのものの詳細な仕組みは調べず(いずれ調べたいとは思います)、binfmt_miscの設定方法、使い方を紹介します。
Linuxの疑似ファイルシステムの一種としてbinfmt_miscは実装されています。カーネル組み込み、モジュールいずれも可能です。Debian Testingではモジュールとしてビルドされているようです。
#### binfmt_miscがモジュールかつロードされていない場合 # modprobe binfmt_misc #### binfmt_miscがマウントされていない場合 # mount -t binfmt_misc none /proc/sys/fs/binfmt_misc/
バイナリタイプの登録方法はとても簡単で/proc/sys/fs/binfmt_misc/registerに設定をechoやcatで書き込むだけです。正常に登録されるとbinfmt_misc/ディレクトリの下にファイルが増えます。
#### バイナリタイプの確認、登録方法 # ls /proc/sys/fs/binfmt_misc/ register status # cat /usr/lib/binfmt.d/qemu-riscv64.conf > /proc/sys/fs/binfmt_misc/register # ls /proc/sys/fs/binfmt_misc/ qemu-riscv64 register status # cat /proc/sys/fs/binfmt_misc/qemu-riscv64 enabled interpreter /usr/libexec/qemu-binfmt/riscv64-binfmt-P flags: POCF offset 0 magic 7f454c460201010000000000000000000200f300 mask ffffffffffffff00fffffffffffffffffeffffff
試しにRISC-V 64bit用の設定をregisterに書き込むと、qemu-riscv64ファイルが増えました。ディレクトリ内のファイルを読み出すと登録した設定がわかります。statusファイルは読みだすとbinfmt_miscがenableかdisableかがわかります。書き込むと下記の効果を発揮します。
詳細についてはLinuxカーネルのドキュメント(Kernel Support for miscellaneous Binary Formats (binfmt_misc) - The Linux Kernel documentation)を参照ください。
設定はテキスト1行です。例えばRISC-V 64bitバイナリをQEMU上で実行するための設定は下記のとおりです。
# less /usr/lib/binfmt.d/qemu-riscv64.conf :qemu-riscv64:M::\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/libexec/qemu-binfmt/riscv64-binfmt-P:OCPF
設定の意味はLinuxカーネルのドキュメント(Kernel Support for miscellaneous Binary Formats (binfmt_misc) - The Linux Kernel documentation)に載っています。
私がハマったポイントは最後にある4つのOCPFフラグ指定の1つ、F(fix binary)フラグです。Fフラグが指定されると、新たにバイナリタイプを登録した後にインタープリターのバイナリを削除したり書き換えても動作が変化しないです。
Fフラグを指定したバイナリタイプに対して、インタープリタープログラムを書き換えて動作の変化を見るときはバイナリタイプを登録し直さなければなりません。systemd-binfmt.serviceを再起動(= バイナリタイプを全て登録し直す)すると一番ラクかと思います。
インタープリタがbinfmt_miscから受け取る引数を見るため、/usr/bin/qemu-riscv64-staticを下記のプログラムで置き換えます。
// a.c
#include <stdio.h>
int main(int argc, char *argv[])
{
for (int i = 0; i < argc; i++) {
printf("%d: '%s'\n", i, argv[i]);
}
return 0;
}
プログラムを置き換えた後はsystemd-binfmt.serviceの再起動をお忘れなく。
# sysctl restart systemd-binfmt.service $ export PATH=$PATH:/usr/lib/arch-test $ riscv64 a b c #### Pフラグあり $ riscv64 a b c 0: '/usr/libexec/qemu-binfmt/riscv64-binfmt-P' 1: '/usr/lib/arch-test/riscv64' 2: 'riscv64' 3: 'a' 4: 'b' 5: 'c' #### Pフラグなし $ riscv64 a b c 0: '/usr/libexec/qemu-binfmt/riscv64-binfmt-P' 1: '/usr/lib/arch-test/riscv64' 2: 'a' 3: 'b' 4: 'c'
インタープリタープログラムの引数に、実行するバイナリのフルパス+バイナリ実行用の引数が渡されるシンプルな仕組みです。パスが2つ渡されているように見えるのは、P(preserve-argv[0])フラグの影響です。Pフラグを外せばフルパス1つだけになります。詳しい仕様はドキュメントをご覧ください。
話は少し変わりますが、DebianのQEMUパッケージは特殊なパッチがあたっていて、本家QEMUと少し違う動きをします。次回はその辺りを調べます。
目次: Linux
以前、Debianのrootfsを手軽に作成できるツールdebootstrapを使いました(2024年8月2日の日記参照)が、1つ気になった点があります。アーキテクチャが異なっていても作成可能な(例: x86_64上でRISC-V向けのDebian rootfsを作成できる)点です。
他アーキテクチャ向けバイナリをdebootstrapの内部で実行しているように見えます。qemu-user-staticを使ってエミュレーションするようですが、debootstrapとqemu-user-staticはどうやって連携をとるのでしょう?
まずQEMUの有無、というよりdebootstrapが他アーキテクチャバイナリの実行可否を確認している部分を見ます。コードは/usr/bin/debootstrapと/usr/share/debootstrap/*に分かれて存在しており、qemu-user-staticがインストールされていないときの「Unable to execute target architecture」エラーは下記の部分で出力しています。
# /usr/bin/debootstrap
if [ -x /usr/bin/arch-test ] && am_doing_phase second_stage; then
if doing_variant fakechroot; then
ret=0; arch-test "$ARCH" || ret=$? ★arch-testを使って確認している★
# Avoid failure with old arch-test package
elif arch-test --version > /dev/null 2>&1; then
ret=0; arch-test -c "$TARGET" "$ARCH" || ret=$?
else
ret=3
fi
case $ret in
0) info ARCHEXEC "Target architecture can be executed" ;;
1) error 1 ARCHNOTEXEC "Unable to execute target architecture" ;; ★この行がエラーメッセージ★
*) info ARCHEXECUNKNOWN "Can't verify that target arch works" ;;
esac
fi
判別方法に使うarch-testはシェルスクリプトで、引数にて指定されたアーキテクチャのバイナリが実行できるかどうかを見ています。arch-testが実行するバイナリは/usr/lib/arch-testの下にあり、実行バイナリファイル名はアーキテクチャ名と同じです。
例えばRISC-V 64bit向けを調べるときはarch-test riscv64と実行します。arch-testは/usr/lib/arch-test/riscv64つまりRISC-V 64bitの実行バイナリを実行し、正常に実行できれば成功ステータス、実行できなければ失敗ステータスを返す仕組みです。
シェルから/usr/lib/arch-test/riscv64を直接実行するとokとだけ表示されるはずです。不思議ですよね?なぜx86_64 Linuxマシン上でRISC-V 64bit Linux用のバイナリが実行できるのでしょう?
秘密はLinuxのbinfmt_miscにあります。次回以降はbinfmt_miscを調べます。
目次: Kindle
いつからかわかりませんがAmazonのマイリスト(ほしい物リスト)の挙動が変わっていました。以前は未購入フィルターで絞り込みすればKindleで購入した本は出現しませんでした。
今は仕様が変わったのかバグなのか何だか知りませんが、購入済みの本でもお構いなしに出てきます。例えば上記「未購入」で絞り込みして表示される「宝くじで40億当たった〜 16巻」を見ます。
購入済みです!とデカデカと表示されます。Amazonの「未購入」とは一体どういう意味なんでしょう……??
ちなみにTwitter(現X)でこういうことを呟くとAmazonの公式アカウントがすっ飛んできて「カスタマーサポートに連絡せよ」と言ってきます。
カスタマーサポートは返品対応のような実務は迅速で助かるんですけど、不具合の原因つまり「なぜそうなったのか?」についてはほぼ全く教えてくれません(2021年8月10日の日記参照)から、今回みたいな現象だとあまり当てにならんのではないかと予想してます。一応連絡してみるつもりですけども……。
目次: Linux
OpenSBI + Linuxの環境まで動いたので、次はLinuxのディストリビューションに挑みます。ディストリビューションはDebianにします。以前はUnofficialでしたがDebian 13(Trixie)から正式にRISC-Vをサポートする(riscv64 is now an official architecture - debian-riscv)そうです。良いですね。
DebianのドキュメントにStarFive VisionFive V1用のインストール手順がある(Installing Debian On StarFive VisionFive V1 - Debian Wiki)ので、非常に参考になります。
RISC-Vサポート予定とされているDebian 13(Trixie)は現時点だとTesting版のはずですが、RISC-V向けのパッケージが存在しないものがあって(※)動かなさそうだったので、今回はUnstable版(sid)を使用しています。
今回の実験環境はこんな感じです。
(※)例えばlibcbor0.10はRISC-V版が存在しません。openssh-client -> libfido2-1 -> libcbor0.10の依存を持つ、OpenSSHクライアントがインストールできません。
Debianを動作させるにはDebianのルートファイルシステムが必要です。debootstrapコマンドで作成できて超便利です。
$ apt-get install qemu-user-static binfmt-support debootstrap systemd-container rsync wget $ sudo debootstrap --arch=riscv64 unstable /path/to/riscv-chroot https://deb.debian.org/debian I: Target architecture can be executed I: Retrieving InRelease I: Checking Release signature I: Valid Release signature (key id 4CB50190207B4758A3F73A796ED0E7B82643E131) I: Retrieving Packages I: Validating Packages I: Resolving dependencies of required packages... I: Resolving dependencies of base packages... I: Checking component main on https://deb.debian.org/debian... I: Retrieving adduser 3.137 I: Validating adduser 3.137 I: Retrieving apt 2.9.7 I: Validating apt 2.9.7 ... I: Configuring tasksel-data... I: Configuring libc-bin... I: Configuring ca-certificates... I: Base system installed successfully.
このとき依存パッケージ、特にQEMU(qemu-user-static)をインストールし忘れていると、E: Unable to execute target architectureとメッセージが出て止まります。
作成したDebian rootfsをQEMUからNFSでマウントします。サーバー側の設定例はこんな感じです。insecureを指定しているなど、セキュリティ完全無視の設定なので実験が終わったら設定を消してください。
#### /etc/exports
/path/to/debian_rootfs *(rw,no_root_squash,no_subtree_check,insecure)
設定を反映するにはexportfsコマンドを使います。何も出力されませんが、エラーがでなければ良いです。
# apt-get install nfs-kernel-server nfs-common # exportfs -a
手動でマウントする場合はこんな感じです。
# sudo mount -t nfs -o nolock 192.168.1.1:/path/to/debian_rootfs rootfs
オプションnolockを付けている理由は、portmapやnfslockのようなデーモンなしでもマウントできるようにするためです。busyboxからマウントするときは便利です。
QEMUの起動引数はたくさんありますがまずはネットワークデバイスの設定をします。netdevでQEMUの外とのネットワーク接続を定義し、deviceでQEMUの中のネットワークデバイスを定義します。カーネル引数はappendに指定します。rootfsをNFSでマウントするためのオプションは、下記の3つです。
NFS v4でも動きそうに思うんですけど、私の環境はなぜかNFS v3じゃないと動作しませんでした。これに気づくまでかなり時間を浪費しました……。
$ qemu-system-riscv64 \ -machine virt \ -bios none \ -nographic \ -chardev stdio,id=con,mux=on \ -serial chardev:con \ -mon chardev=con,mode=readline \ -netdev user,id=netdev0 \ -device virtio-net-device,netdev=netdev0 \ -kernel ../../opensbi/build/platform/generic/firmware/fw_payload.bin \ -append "root=/dev/nfs nfsroot=192.168.1.1:/path/to/debian_rootfs,v3 ip=on nfsrootdebug rw" \ -m 2g \ -smp 4 \ -s \ ... [ 2.312617] ALSA device list: [ 2.313060] No soundcards found. [ 2.534757] VFS: Mounted root (nfs filesystem) readonly on device 0:20. [ 2.541726] devtmpfs: mounted [ 2.708101] Freeing unused kernel image (initmem) memory: 2260K [ 2.710000] Run /sbin/init as init process [ 4.808617] systemd[1]: systemd 256.4-2 running in system mode (+PAM +AUDIT +SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT -GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBCRYPTSETUP_PLUGINS +LIBFDISK +PCRE2 +PWQUALITY +P11KIT +QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD +BPF_FRAMEWORK -XKBCOMMON +UTMP +SYSVINIT +LIBARCHIVE) [ 4.811613] systemd[1]: Detected virtualization qemu. [ 4.812465] systemd[1]: Detected architecture riscv64. Welcome to Debian GNU/Linux trixie/sid! [ 4.857724] systemd[1]: Hostname set to <qemu>. ... Debian GNU/Linux trixie/sid qemu ttyS0 qemu login:
うまくいけばログイン画面が拝めるはずです。
ログインユーザーを作成する方法が良くわからなかったため、Debianのrootfsを別のinitramfsからマウントして無理やりuseraddしてユーザーを追加しました。本来どうするものなんでしょうね……?
自分でQEMUをビルドしていると、netdev user,...に対してnetwork backend 'user' is not compiledエラーが出る場合があります。その時はQEMUビルド時のconfigureのログを確認してください。
$ ./qemu-system-riscv64 -netdev user,id=a qemu-system-riscv64: -netdev user,id=a: network backend 'user' is not compiled into this binary #### configureのログ(NGパターン) Network backends AF_XDP support : NO slirp support : NO ## ★slirpが使えない★ vde support : NO netmap support : NO #### configureのログ(OKパターン) Network backends AF_XDP support : NO slirp support : YES 4.8.0 ## ★slirpが使える★ vde support : NO netmap support : NO
QEMUはnetdev user,...の実装にslirpを使っているようで、slirp supportがNOになっているとnetdev user,...が機能しません。libslirp-devをインストール(apt-get install libslirp-dev)してから、QEMUのconfigureと再ビルドをしてください。
目次: Linux
OpenSBIのブート部分を調べます。OpenSBIはいくつか動作モードがあるのですが、payloadを持ったタイプを調べます。対象のファイル名はbuild/platform/generic/firmware/fw_payload.binもしくは.elfです。
今回はデバイスツリーについて調べます。対象はOpenSBI 1.5です。
QEMUの場合は、QEMU起動時にデバイスツリーがロードされ、ブートコードによってレジスタa1にアドレス(-m 128Mの場合0x87e00000、メモリ量オプションによって変わる)が設定されます。実機はこのような動作はしないので、メモリがまっさらな状態からOpenSBIとデバイスツリーをどう配置すれば動くか、その方法についても調べます。
QEMU向けの場合はfw_platform_init()がレジスタa1を参照しますので、ブート時にa1の設定が必要です。
//opensbi/firmware/fw_base.S
_bss_zero:
REG_S zero, (s4)
add s4, s4, __SIZEOF_POINTER__
blt s4, s5, _bss_zero
/* Setup temporary trap handler */
lla s4, _start_hang
csrw CSR_MTVEC, s4
/* Setup temporary stack */
lla s4, _fw_end
li s5, (SBI_SCRATCH_SIZE * 2)
add sp, s4, s5
/* Allow main firmware to save info */
MOV_5R s0, a0, s1, a1, s2, a2, s3, a3, s4, a4
call fw_save_info
MOV_5R a0, s0, a1, s1, a2, s2, a3, s3, a4, s4
#ifdef FW_FDT_PATH
/* Override previous arg1 */
lla a1, fw_fdt_bin
#endif
/*
* Initialize platform
* Note: The a0 to a4 registers passed to the
* firmware are parameters to this function.
*/
MOV_5R s0, a0, s1, a1, s2, a2, s3, a3, s4, a4
call fw_platform_init /* ★この関数がa1を参照する★ */
add t0, a0, zero
MOV_5R a0, s0, a1, s1, a2, s2, a3, s3, a4, s4
add a1, t0, zero
//opensbi/platform/generic/platform.c
/*
* The fw_platform_init() function is called very early on the boot HART
* OpenSBI reference firmwares so that platform specific code get chance
* to update "platform" instance before it is used.
*
* The arguments passed to fw_platform_init() function are boot time state
* of A0 to A4 register. The "arg0" will be boot HART id and "arg1" will
* be address of FDT passed by previous booting stage.
*
* The return value of fw_platform_init() function is the FDT location. If
* FDT is unchanged (or FDT is modified in-place) then fw_platform_init()
* can always return the original FDT location (i.e. 'arg1') unmodified.
*/
unsigned long fw_platform_init(unsigned long arg0, unsigned long arg1,
unsigned long arg2, unsigned long arg3,
unsigned long arg4)
{
const char *model;
void *fdt = (void *)arg1; //★ここでレジスタa1の値を使っている★
u32 hartid, hart_count = 0;
int rc, root_offset, cpus_offset, cpu_offset, len;
...
関数fw_platform_init()は_bss_zeroの後に呼ばれます。fw_next_arg1()を呼び出す前なのでFW_PAYLOAD_FDT_ADDRやFW_PAYLOAD_FDT_OFFSETを定義しても参照されません。
もしQEMUで既存のデバイスツリーを無視して、手動でロードする実験をしたい場合は、
このような設定をすると実験できます。コマンドを手動で実行するのは辛いので、スクリプトファイルを用意してsource (スクリプトファイル)コマンドで実行したほうが楽でしょう。
# load_dtb_opensbi.gdb restore virt.dtb binary 0x87f00000 thread 1 set $a1=0x87f00000 thread 2 set $a1=0x87f00000 thread 3 set $a1=0x87f00000 thread 4 set $a1=0x87f00000 restore build/platform/generic/firmware/fw_payload.bin binary 0x80000000 thread 1 set $pc=0x80000000 thread 2 set $pc=0x80000000 thread 3 set $pc=0x80000000 thread 4 set $pc=0x80000000
デバイスツリーを0x87f00000にロードして実行する例です。restoreコマンドでロードしているデバイスツリーのバイナリをQEMUから得る方法は前回(2024年月日の日記参照)の説明をご参照ください。
QEMUが起動時に用意するデバイスツリーとは異なるデバイスツリーをロードできることを確認するため、QEMUのデバイスツリーのmodelを少し書き換えたデバイスツリーvirt_mod.dtbを用意します。
/* virt.dts */
/dts-v1/;
/ {
#address-cells = <0x02>;
#size-cells = <0x02>;
compatible = "riscv-virtio";
model = "riscv-virtio,qemo"; /* ★★qemu → qemoに変更★★ */
DTSからDTBにするとき(その逆もできます)はデバイスツリーコンパイラdtcを使用します。
$ dtc -O dtb virt.dts > virt.dtb
QEMUを起動した後にデバッガーでQEMUにアタッチし、先ほど作成したGDBスクリプトにてロードとレジスタの設定を行います。
$ riscv64-unknown-linux-gnu-gdb (gdb) target remote :1234 Remote debugging using :1234 warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000000000001000 in ?? () (gdb) source load_dtb_opensbi.gdb Restoring binary file ../qemu/build/virt_mod.dtb into memory (0x87f00000 to 0x87f01c20) [Switching to thread 1 (Thread 1.1)] #0 0x0000000000001000 in ?? () [Switching to thread 2 (Thread 1.2)] #0 0x0000000000001000 in ?? () [Switching to thread 3 (Thread 1.3)] #0 0x0000000000001000 in ?? () [Switching to thread 4 (Thread 1.4)] #0 0x0000000000001000 in ?? () Restoring binary file build/platform/generic/firmware/fw_payload.bin into memory (0x80000000 to 0x81f51608) [Switching to thread 1 (Thread 1.1)] #0 0x0000000000001000 in ?? () [Switching to thread 2 (Thread 1.2)] #0 0x0000000000001000 in ?? () [Switching to thread 3 (Thread 1.3)] #0 0x0000000000001000 in ?? () [Switching to thread 4 (Thread 1.4)] #0 0x0000000000001000 in ?? () (gdb) continue Continuing.
最後のcontinueを実行するとQEMU側もログが出ます。例えばこんな感じです。
$ ./qemu-system-riscv64 -machine virt \ -bios none \ -nographic \ -chardev stdio,id=con,mux=on \ -serial chardev:con \ -mon chardev=con,mode=readline \ -smp 4 \ -s \ -S OpenSBI v1.5 ____ _____ ____ _____ / __ \ / ____| _ \_ _| | | | |_ __ ___ _ __ | (___ | |_) || | | | | | '_ \ / _ \ '_ \ \___ \| _ < | | | |__| | |_) | __/ | | |____) | |_) || |_ \____/| .__/ \___|_| |_|_____/|____/_____| | | |_| Platform Name : riscv-virtio,qemo ★★改造したmodel名になっている★★ Platform Features : medeleg Platform HART Count : 4 ...
OpenSBIがPlatform Nameを出しているログを見れば、先程デバイスツリーを改造したときに付けたmodel名が出力されているはずです。メモリ上にデバイスツリーやOpenSBIがロードされない実機だとしても、デバッガー経由でロードして起動することができそうです。
< | 2024 | > | ||||
<< | < | 08 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | 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 |
合計:
本日: