コグノスケ


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

link もっと前
2024年8月22日 >>> 2024年7月23日
link もっと後

2024年8月13日

Debianのミラーを作ってみる

目次: apt

Debianアーカイブのミラーを試してみた(Debianアーカイブミラーの構築 - Debian)のでメモです。

Debianミラーを作成するにはftpsyncスクリプトを使用します。

ftpsyncを取得する
$ wget https://ftp-master.debian.org/ftpsync.tar.gz

$ tar xf ftpsync.tar.gz
$ ls

distrib

$ tree distrib/

distrib/
|-- README.md
|-- bin
|   |-- ftpsync
|   |-- ftpsync-cron
|   |-- rsync-ssl-tunnel
|   `-- runmirrors
|-- doc
|   |-- ftpsync-cron.1.md
|   |-- ftpsync.1.md
|   |-- ftpsync.conf.5.md
|   |-- rsync-ssl-tunnel.1.md
|   |-- runmirrors.1.md
|   |-- runmirrors.conf.5.md
|   `-- runmirrors.mirror.5.md
`-- etc
    |-- ftpsync.conf.sample
    |-- runmirrors.conf.sample
    `-- runmirrors.mirror.sample

4 directories, 15 files

アーカイブにはdistribディレクトリだけ存在しています。設定する際はdistrib/etcの下にftpsync.confファイルを置きます。distrib/etcディレクトリにあるftpsync.con.sampleが設定ファイルのサンプルです。

ftpsync.confの例

# distrib/etc/ftpsync.conf

TO=/path/to/debian

RSYNC_HOST=ftp.jp.debian.org
RSYNC_PATH="debian"

ARCH_INCLUDE="riscv64"

RISC-V用のDebianアーカイブを日本のサーバーからミラーする設定は上記の通りです。実行はdistrib/bin/ftpsyncを実行するだけです。数時間かかりますが、進捗やステータスの類が何も表示されないので不安になります……。

編集者:すずき(2024/08/17 16:04)

コメント一覧

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



2024年8月11日

Amazonマイリストへの問い合わせの返事がきた

目次: Kindle

先日(2024年8月4日の日記参照)Amazonへ問い合わせた、マイリストの変な挙動についてお返事が来ていました。

Amazonからのお返事
お客様からお問い合わせいただいた件につきまして、担当部署へ報告させていただき、
問題が確認された場合には適切に対応させていただきます。

大変お手数ですが、所有数を手動で変更し、購入済に移動していただきますようお願いいたします。

なおほしい物リストの商品登録数について、1つのアカウントで管理できるデータ量には制限があります。
各リストには、登録できる最大数は2500点となりますが、サーバーに負荷をかけずに利用いただく理想の
商品数はおよそ100件となります。それを超えると、データ容量超過でスムーズに作動しない場合や、
エラーメッセージが表示される等の問題が生じる場合がございますので、それぞれのリストの登録商品が
100件以内に収まるようリストを分けて作成いただきますようお願いいたします。

不具合かどうかわからないまま終わりましたけど、本当に不具合ならAmazonのエンジニアが直すでしょう。たぶん。あとマイリストの理想の商品数なんて全く関係ない豆知識を授けてくれたのはなぜでしょうね。「お前はリストに登録し過ぎだ、変なエラーが起きても不思議じゃない」と揶揄したかった?のでしょうか。

編集者:すずき(2024/09/22 00:14)

コメント一覧

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



2024年8月10日

Linuxを調べる - initrdとカーネル引数

目次: Linux

今どき(?)のinitrdとカーネル引数の渡し方を知らなかったのでメモしておきます。

昔のARM LinuxですとATAGと呼ばれるブートの情報を並べたリストをメモリ領域に置いてカーネル引数やinitrdを渡して(ATAGの仕様はVincentさんのBooting ARM Linuxが詳しいです)いました。ATAGは今も使えます(Booting ARM Linuxの4a. Setup the kernel tagged list)けど主流はデバイスツリーのはずです。たぶん。ARM Linuxの場合レジスタに指定すべき情報はこんな感じだそうです。

  • r0: 0
  • r1: machine type number discovered in (3) above
  • r2: physical address of tagged list in system RAM, or physical address of device tree block (dtb) in system RAM

RISC-V Linuxの場合はカーネルのドキュメント(RISC-V Kernel Boot Requirements and Constraints)によれば、RISC-V Linuxの場合レジスタに指定すべき情報はこんな感じだそうです。

  • $a0 to contain the hartid of the current core.
  • $a1 to contain the address of the devicetree in memory.

ARM Linuxよりさらに減ってhartidとデバイスツリーのアドレスのたった2つです、シンプルですね。

デバイスツリーの例

仕様からデバイスツリーを書いても良いですが、目の前のQEMUでDebianが動作している2024年8月2日の日記参照)のですから実際に動作しているものを調べたほうが早いでしょう。

QEMUが内部で生成しているデバイスツリーのダンプ方法は以前(2024年7月23日の日記参照)紹介した通りです。initrdとappend="root=/dev/nfs"を渡してQEMUを起動し、デバイスツリーをダンプして&dtsに変換します。

QEMUの自動生成したデバイスツリーブロブ(dtb)のダンプ方法
$ 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オプションと対応しています。

  • bootargs: appendオプションに指定したカーネル引数
  • linux,initrd-start: initrdオプションに指定したファイルをロード(QEMUがやってくれる)した領域の「先頭」アドレス
  • linux,initrd-end: initrdオプションに指定したファイルをロード(QEMUがやってくれる)した領域の「終端」アドレス

以上、今どきのLinuxに対してカーネル引数やinitrdを渡す方法がわかりました。Linuxでデバイスツリーに対応したアーキテクチャは多いですから、一度実装しておけば多数のアーキテクチャでカーネルパラメーター指定ができます。便利ですね。

欠点はブートローダーでデバイスツリーのchosenノードを変更する必要がある、すなわちブートローダーにデバイスツリーを加工する処理を実装しなければならない点です。難しくはないでしょうけど、それなりに手間が掛かるでしょう。

ちなみにlinux,initrd-startのようなlinux,で始まるプロパティの仕様はLinuxカーネルのドキュメント(The chosen node - kernel.org)に記載があります。

QEMUのappend引数なしでカーネル引数を渡す実験

先程まで見てきたbootargsやinitrd-なんとかが正しく生成できれば、QEMUのappendやinitrdオプション無しでもカーネル引数やinitrdをカーネルに渡すことができるはずです。やってみましょう。

デバイスツリーのchosenノードにbootargsを追加
/* 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日の日記参照)を使ってカーネルとデバイスツリーをロードします。

デバイスツリーとOpenSBIをロードし、レジスタを設定するGDBスクリプト(再掲)
# 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起動、GDBスクリプトによるカーネルとデバイスツリーのロード
$ 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側もログが出ます。例えばこんな感じです。

Debian RISC-V 64bit版の起動例
$ 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ノードを加工してくれます。ありがたいですね。

編集者:すずき(2024/08/12 16:23)

コメント一覧

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



2024年8月7日

Debian独自の挙動をするQEMUとbinfmt_misc

目次: Linux

前回はbinfmt_miscの使い方や動作を調べました。今回はDebian特有の挙動を見ていきたいと思います。細かいことを言えばQEMUの実装の話でありbinfmt_miscの実装ではありません。しかし二者は密接に関わっているので関連項目として取り上げます。

Debianのbinfmt_miscの設定では、インタープリターに/usr/libexec/qemu-binfmt/以下のファイルを指定します。このディレクトリには独特なファイル名(例: (arch名)-binfmt-P)のシンボリックリンクが置かれています。一例を紹介します。

(arch名)-binfmt-Pの例、RISC-V 64bit向け
$ 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スペシャルなエラーメッセージ

Debianのqemu-user-staticパッケージソースコードを見るとこのエラーメッセージを表示するためのパッチがありました。

DebianのQEMUパッチの一部

// 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のバイナリタイプ設定はこんな感じで、

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つ★

実装のコメントを見る限り、下記の条件を満たすことを期待しています。

  • シンボリックリンクの名前が必ず-binfmt-Pで終わる
  • P flagを指定するので必ず引数が3つ以上ある

この条件から外れる場合はエラーにしたいようです。riscv64-binfmt-Pのリンク先の実体はQEMUですが、間違ってQEMUとして起動することを防いでいる……?そこまでする理由があまり思いつかないですけど、昔に何かあったのかな?

編集者:すずき(2024/08/09 22:52)

コメント一覧

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



2024年8月6日

他アーキテクチャ向けバイナリを実行する仕組みbinfmt_misc

目次: Linux

RISC-V 64bit用の実行ファイルをx86_64 Linuxマシン上のシェルから直接実行できる不思議な仕組み、Linuxのbinfmt_miscについて調べます。

ざっくりbinfmt_miscを説明するなら、バイナリタイプ(binary type)の先頭部分があるパターンに一致したら、設定で指定したインタープリタを起動してバイナリを渡し実行してもらう仕組みです。スクリプト言語で書かれたプログラムは直接実行できず、インタープリタ経由で実行する仕組みと似ています。今回のように他アーキテクチャのバイナリを実行する(インタープリタ: QEMU)以外に、Javaのアーカイブ*.jarを実行する(インタープリタ: Java仮想マシン)用途にも使えます。

今回はbinfmt_miscそのものの詳細な仕組みは調べず(いずれ調べたいとは思います)、binfmt_miscの設定方法、使い方を紹介します。

binfmt_miscの登録方法

Linuxの疑似ファイルシステムの一種としてbinfmt_miscは実装されています。カーネル組み込み、モジュールいずれも可能です。Debian Testingではモジュールとしてビルドされているようです。

binfmt_miscを使用する準備
#### 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かがわかります。書き込むと下記の効果を発揮します。

  • echo -1 > status: 全てのバイナリタイプ設定を消去します
  • echo 0 > status: binfmt_miscを無効にします
  • echo 1 > status: binfmt_miscを有効にします

詳細についてはLinuxカーネルのドキュメント(Kernel Support for miscellaneous Binary Formats (binfmt_misc) - The Linux Kernel documentation)を参照ください。

binfmt_miscの設定

設定はテキスト1行です。例えばRISC-V 64bitバイナリをQEMU上で実行するための設定は下記のとおりです。

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の動作

インタープリタが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と少し違う動きをします。次回はその辺りを調べます。

編集者:すずき(2024/08/09 22:17)

コメント一覧

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



2024年8月5日

debootstrapと他アーキテクチャバイナリとbinfmt_misc

目次: 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」エラーは下記の部分で出力しています。

debootstrapが他アーキテクチャ向けバイナリを実行できるか確認するコード

# /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を調べます。

編集者:すずき(2024/08/11 23:27)

コメント一覧

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



2024年8月4日

Amazonのマイリストの不思議な挙動

目次: Kindle

いつからかわかりませんがAmazonのマイリスト(ほしい物リスト)の挙動が変わっていました。以前は未購入フィルターで絞り込みすればKindleで購入した本は出現しませんでした。


未購入で絞り込みしたリスト

今は仕様が変わったのかバグなのか何だか知りませんが、購入済みの本でもお構いなしに出てきます。例えば上記「未購入」で絞り込みして表示される「宝くじで40億当たった〜 16巻」を見ます。


未購入のリストから選んだのに購入済み

購入済みです!とデカデカと表示されます。Amazonの「未購入」とは一体どういう意味なんでしょう……??

Amazonカスタマーサポート

ちなみにTwitter(現X)でこういうことを呟くとAmazonの公式アカウントがすっ飛んできて「カスタマーサポートに連絡せよ」と言ってきます。

カスタマーサポートは返品対応のような実務は迅速で助かるんですけど、不具合の原因つまり「なぜそうなったのか?」についてはほぼ全く教えてくれません(2021年8月10日の日記参照)から、今回みたいな現象だとあまり当てにならんのではないかと予想してます。一応連絡してみるつもりですけども……。

編集者:すずき(2024/08/05 21:36)

コメント一覧

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



2024年8月2日

Debian on RISC-V

目次: 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)を使用しています。

  • NFS Server: Debian Testing (Trixie)
  • NFS: nfs-common 2.6.4-5
  • QEMU: 9.0.2
  • RISC-V Linux: 6.9.0

今回の実験環境はこんな感じです。

(※)例えばlibcbor0.10はRISC-V版が存在しません。openssh-client -> libfido2-1 -> libcbor0.10の依存を持つ、OpenSSHクライアントがインストールできません。

Debianルートファイルシステムの作成

Debianを動作させるにはDebianのルートファイルシステムが必要です。debootstrapというコマンドで作成できて超便利です。

debootstrapによるRISC-V Debian rootfsの構築
$ 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を指定しているなど、セキュリティ完全無視の設定なので実験が終わったら設定を消してください。

NFSの設定

#### /etc/exports

/path/to/debian_rootfs    *(rw,no_root_squash,no_subtree_check,insecure)

設定を反映するにはexportfsコマンドを使います。何も出力されませんが、エラーがでなければ良いです。

NFSの設定反映
# apt-get install nfs-kernel-server nfs-common

# exportfs -a

手動でマウントする場合はこんな感じです。

NFSマウントの例
# sudo mount -t nfs -o nolock 192.168.1.1:/path/to/debian_rootfs rootfs

オプションnolockを付けている理由は、portmapやnfslockのようなデーモンなしでもマウントできるようにするためです。busyboxからマウントするときは便利です。

NFS rootfsでの動作確認

QEMUの起動引数はたくさんありますがまずはネットワークデバイスの設定をします。netdevでQEMUの外とのネットワーク接続を定義し、deviceでQEMUの中のネットワークデバイスを定義します。カーネル引数はappendに指定します。rootfsをNFSでマウントするためのオプションは、下記の3つです。

  • root=...: rootfsをNFSでマウントする
  • nfsroot=...,v3: NFSでマウントするサーバーとディレクトリの指定、NFS v3を使う
  • ip=...: IPを自動設定

NFS v4でも動きそうに思うんですけど、私の環境はなぜかNFS v3じゃないと動作しませんでした。これに気づくまでかなり時間を浪費しました……。

QEMU上でRISC-V Debian GNU/Linuxが起動する様子
$ 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のログを確認してください。

network backend〜エラー、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と再ビルドをしてください。

編集者:すずき(2024/08/09 02:59)

コメント一覧

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



2024年7月29日

OpenSBIを調べる - デバイスツリーの扱い(実機想定)

目次: Linux

OpenSBIのブート部分を調べます。OpenSBIはいくつか動作モードがあるのですが、payloadを持ったタイプを調べます。対象のファイル名はbuild/platform/generic/firmware/fw_payload.binもしくは.elfです。

今回はデバイスツリーについて調べます。対象はOpenSBI 1.5です。

QEMUの場合は、QEMU起動時にデバイスツリーがロードされ、ブートコードによってレジスタa1にアドレス(-m 128Mの場合0x87e00000、メモリ量オプションによって変わる)が設定されます。実機はこのような動作はしないので、メモリがまっさらな状態からOpenSBIとデバイスツリーをどう配置すれば動くか、その方法についても調べます。

QEMUの動作を復習

QEMU向けの場合はfw_platform_init()がレジスタa1を参照しますので、ブート時にa1の設定が必要です。

OpenSBIのブート部分

//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で既存のデバイスツリーを無視して、手動でロードする実験をしたい場合は、

  • OpenSBIのバイナリをロードする
  • pcをOpenSBIのロードアドレス兼エントリアドレス(0x80000000)に設定する
  • デバイスツリーをロードする
  • a1をデバイスツリーのアドレスに設定する
  • 上記をすべてのhartに対して実行する

このような設定をすると実験できます。コマンドを手動で実行するのは辛いので、スクリプトファイルを用意してsource (スクリプトファイル)コマンドで実行したほうが楽でしょう。

デバイスツリーとOpenSBIをロードし、レジスタを設定するGDBスクリプト
# 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を用意します。

model名だけ変更したデバイスツリー

/* 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側もログが出ます。例えばこんな感じです。

先ほどロードしたOpenSBIのログ

$ ./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/09 12:18)

コメント一覧

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



2024年7月25日

OpenSBIを調べる - デバイスツリーの扱い(別方法)

目次: Linux

OpenSBIのブート部分を調べます。OpenSBIはいくつか動作モードがあるのですが、payloadを持ったタイプを調べます。対象のファイル名はbuild/platform/generic/firmware/fw_payload.binもしくは.elfです。

今回はOpenSBIのデバイスツリー処理について調べます。対象はOpenSBI 1.5 PLATFORM=genericの改造版、実行環境はQEMU RV64 virt machine(qemu-system-riscv64 -machine virt)です。

Linuxにデバイスツリーの場所を伝えるもう一つの方法

以前(2024年7月24日の日記参照)説明したデバイスツリーの先頭アドレスを取得する処理の概要は下記のとおりです。

デバイスツリーの先頭アドレスを取得する処理の概要
  _scratch_init
    //scratch領域を確保する、先頭アドレスはtpレジスタに格納される
    fw_next_arg1()     // firmware/fw_payload.S
      // FW_PAYLOAD_FDT_ADDRが定義済みならFW_PAYLOAD_FDT_ADDRを返す
      // FW_PAYLOAD_FDT_OFFSETが定義済みなら_fw_start + FW_PAYLOAD_FDT_OFFSETを返す
      // いずれも未定義ならばa1レジスタの値を返す    ★改造してこの実装を使ってみる★
      // 結果はscratch->next_arg1に格納

プラットフォームgenericを選択すると選択肢2番目の処理が使われます。が、MakefileからFW_PAYLOAD_FDT_OFFSETの定義を削除すると、3番目の処理を選択できます。

無理やり改造しているので全てが完璧に動作するかわからないですが、少なくともLinuxのブート部分まで到達しますから、OpenSBIの動作を調べるには十分でしょう。Makefileを下記のように改変します。

Makefileの改造箇所

# opensbi/platform/generic/objects.mk

ifeq ($(PLATFORM_RISCV_XLEN), 32)
  # This needs to be 4MB aligned for 32-bit system
  FW_PAYLOAD_OFFSET=0x400000
else
  # This needs to be 2MB aligned for 64-bit system
  FW_PAYLOAD_OFFSET=0x200000
endif
FW_PAYLOAD_FDT_OFFSET=$(FW_JUMP_FDT_OFFSET)    ★この行を削除する★

FW_PAYLOAD_FDT_ADDRもFW_PAYLOAD_FDT_OFFSETも未定義にしたので、3番目の処理が選択されます。

デバイスツリーの先頭アドレスを求めるコード(Makefile改造後)

//opensbi/firmware/fw_payload.S

fw_next_arg1:
#ifdef FW_PAYLOAD_FDT_ADDR
	li	a0, FW_PAYLOAD_FDT_ADDR
#elif defined(FW_PAYLOAD_FDT_OFFSET)
	lla	a0, _fw_start
	li	a1, FW_PAYLOAD_FDT_OFFSET
	add	a0, a0, a1
#else
	add	a0, a1, zero    //★a1レジスタの値を返すだけ★
#endif
	ret

なぜa1レジスタにデバイスツリーの先頭アドレスが格納されるか?については、以前の(2024年7月23日の日記参照)QEMUのデバイスツリーの説明をご参照ください。

レジスタa1の値はすぐに壊れてしまいそうに思えますが、OpenSBIはレジスタa1の値をずっと維持し続けるため実装を工夫しています。

レジスタa1を維持する実装の例

// opensbi/firmware/fw_base.S

	/* Store next address in scratch space */
	MOV_3R	s0, a0, s1, a1, s2, a2    //★a0, a1, a2の値をs0, s1, s2に退避★
	call	fw_next_addr              //★C言語の関数呼び出しでa0, a1, a2レジスタが壊れる★
	REG_S	a0, SBI_SCRATCH_NEXT_ADDR_OFFSET(tp)
	MOV_3R	a0, s0, a1, s1, a2, s2    //★a0, a1, a2の値を復帰★

C言語で実装した関数を呼び出すなど、レジスタa1の値が壊れることがわかっている場合は壊れる前に別レジスタに値を退避し、関数から戻ってきた後に復帰処理を行います。

この改造を行うとデバイスツリーのコピーは行われず、ペイロードにQEMUが生成&配置したデバイスツリーの先頭アドレスが渡されます。メモリマップはこんな感じです。


デバイスツリーに関連するQEMUのメモリマップ(改造版)

動作ログも確認しましょう。

OpenSBI 1.5の起動ログ(抜粋、改造版)
Domain0 Name              : root
Domain0 Boot HART         : 3
Domain0 HARTs             : 0*,1*,2*,3*
Domain0 Region00          : 0x0000000000100000-0x0000000000100fff M: (I,R,W) S/U: (R,W)
Domain0 Region01          : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
Domain0 Region02          : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region03          : 0x0000000080040000-0x000000008005ffff M: (R,W) S/U: ()
Domain0 Region04          : 0x0000000080000000-0x000000008003ffff M: (R,X) S/U: ()
Domain0 Region05          : 0x000000000c400000-0x000000000c5fffff M: (I,R,W) S/U: (R,W)
Domain0 Region06          : 0x000000000c000000-0x000000000c3fffff M: (I,R,W) S/U: (R,W)
Domain0 Region07          : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
Domain0 Next Address      : 0x0000000080200000
Domain0 Next Arg1         : 0x0000000087e00000    ★0x82200000 → 0x87e00000に変化した★
Domain0 Next Mode         : S-mode
Domain0 SysReset          : yes
Domain0 SysSuspend        : yes

QEMUが渡してきたアドレス0x87e00000がそのまま使われていることがわかります。

デバイスツリーのもう1つの扱い方についてわかりました。しかしデバイスツリーを起動時に生成&配置できるのはQEMUのようなエミュレータだけです。実機ではそんなことはできませんから、何か別の方法が採用されているはずです。次回は実機でのデバイスツリーの扱いについて調べたいと思います。

編集者:すずき(2024/07/26 02:08)

コメント一覧

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



2024年7月24日

OpenSBIを調べる - デバイスツリーの扱い(genericプラットフォーム)

目次: Linux

OpenSBIのブート部分を調べます。OpenSBIはいくつか動作モードがあるのですが、payloadを持ったタイプを調べます。対象のファイル名はbuild/platform/generic/firmware/fw_payload.binもしくは.elfです。

今回はOpenSBIのデバイスツリー処理について調べます。対象はOpenSBI 1.5 PLATFORM=generic、実行環境はQEMU RV64 virt machine(qemu-system-riscv64 -machine virt)です。

  • デバイスツリーの先頭アドレスの決め方
  • デバイスツリーを配置する方法

の順で説明したいと思います。

デバイスツリーの先頭アドレス

OpenSBIがLinuxを起動するときは、どんなHWなのか伝えるためデバイスツリーというデータ(正確に言うとFlattened Devicetree Blob、*.dtbというファイル名)のアドレスを渡します。デバイスツリーのアドレスを渡している箇所のコードはこんな感じです。

ペイロード(今回はLinux)にデバイスツリーのアドレスを渡す処理のコード

//opensbi/lib/sbi/sbi_hart.c

void __attribute__((noreturn))
sbi_hart_switch_mode(unsigned long arg0, unsigned long arg1,
		     unsigned long next_addr, unsigned long next_mode,
		     bool next_virt)
{
	//...略...

	//★各引数に入っている値は下記の通り★
	//arg0 = hartid
	//arg1 = scratch->next_arg1      -> デバイスツリーのアドレス
	//next_addr = scratch->next_addr -> mretのリターンアドレス
	//next_mode = scratch->next_mode
	//next_virt = false

	register unsigned long a0 asm("a0") = arg0;
	register unsigned long a1 asm("a1") = arg1;
	__asm__ __volatile__("mret" : : "r"(a0), "r"(a1));
	__builtin_unreachable();
}

起動ログからもLinuxに渡されている情報が確認できます。

OpenSBI 1.5の起動ログ(一部抜粋)
(...略...)

Domain0 Name              : root
Domain0 Boot HART         : 0
Domain0 HARTs             : 0*,1*,2*,3*
Domain0 Region00          : 0x0000000000100000-0x0000000000100fff M: (I,R,W) S/U: (R,W)
Domain0 Region01          : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
Domain0 Region02          : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region03          : 0x0000000080040000-0x000000008005ffff M: (R,W) S/U: ()
Domain0 Region04          : 0x0000000080000000-0x000000008003ffff M: (R,X) S/U: ()
Domain0 Region05          : 0x000000000c400000-0x000000000c5fffff M: (I,R,W) S/U: (R,W)
Domain0 Region06          : 0x000000000c000000-0x000000000c3fffff M: (I,R,W) S/U: (R,W)
Domain0 Region07          : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
Domain0 Next Address      : 0x0000000080200000    ★scratch->next_addr: mretのリターンアドレス★
Domain0 Next Arg1         : 0x0000000082200000    ★scratch->next_arg1: デバイスツリーのアドレス★
Domain0 Next Mode         : S-mode    ★scratch->next_mode: Supervisorモードで開始★
Domain0 SysReset          : yes
Domain0 SysSuspend        : yes

次はscratch->next_arg1にデバイスツリーのアドレスを設定する仕組みです。このメンバにはfw_next_arg1()関数が返す値が格納されます。以前(2024年7月19日の日記参照)説明したペイロードの先頭アドレスを取得するfw_next_addr()と仕組みも名前も似ていますね。処理の概要は下記のとおりです。

ペイロード先頭アドレスを取得する処理の概要
  _scratch_init
    //scratch領域を確保する、先頭アドレスはtpレジスタに格納される
    fw_next_arg1()     // firmware/fw_payload.S
      // FW_PAYLOAD_FDT_ADDRが定義済みならFW_PAYLOAD_FDT_ADDRを返す
      // FW_PAYLOAD_FDT_OFFSETが定義済みなら_fw_start + FW_PAYLOAD_FDT_OFFSETを返す    ★この実装が選択される★
      // いずれも未定義ならばa1レジスタの値を返す
      // 結果はscratch->next_arg1に格納

プラットフォームgenericのMakefileを確認するとFW_PAYLOAD_FDT_OFFSETの定義があるので、2番目の実装が選択されます。

FW_PAYLOAD_FDT_OFFSETを定義している箇所

# opensbi/platform/generic/objects.mk

FW_JUMP_FDT_OFFSET=0x2200000
FW_PAYLOAD=y
ifeq ($(PLATFORM_RISCV_XLEN), 32)
  # This needs to be 4MB aligned for 32-bit system
  FW_PAYLOAD_OFFSET=0x400000
else
  # This needs to be 2MB aligned for 64-bit system
  FW_PAYLOAD_OFFSET=0x200000
endif
FW_PAYLOAD_FDT_OFFSET=$(FW_JUMP_FDT_OFFSET)    ★これ★


# opensbi/firmware/objects.mk

ifdef FW_PAYLOAD_FDT_OFFSET
firmware-genflags-$(FW_PAYLOAD) += -DFW_PAYLOAD_FDT_OFFSET=$(FW_PAYLOAD_FDT_OFFSET)
endif

FW_PAYLOAD_FDT_OFFSETの定義は以上のとおりです。デバイスツリーの先頭アドレスを求めるコードはこんな感じです。

デバイスツリーの先頭アドレスを求めるコード

//opensbi/firmware/fw_payload.S

fw_next_arg1:
#ifdef FW_PAYLOAD_FDT_ADDR
	li	a0, FW_PAYLOAD_FDT_ADDR
#elif defined(FW_PAYLOAD_FDT_OFFSET)
	lla	a0, _fw_start
	li	a1, FW_PAYLOAD_FDT_OFFSET
	add	a0, a0, a1    //★_fw_start + FW_PAYLOAD_FDT_OFFSETの値を返すだけ★
#else
	add	a0, a1, zero
#endif
	ret

ちなみに_fw_startは0x80000000、FW_PAYLOAD_FDT_OFFSETは0x2200000ですから、デバイスツリーの先頭アドレスは0x82200000となります。


OpenSBIのgeneric向け、payload付きバイナリのメモリマップ

OpenSBI全体のメモリマップを示すとこんな感じです。

デバイスツリーを配置する方法

結論からいうとOpenSBIがQEMUが配置したデバイスツリーを0x82200000にコピーします。コピーする理由が気になりますが、ざっと見た限り見当たりませんでした。PMPで保護をかけたいのかなあ?

QEMUのデバイスツリーについては前回(2024年7月23日の日記参照)説明したとおりです。QEMUは自動的にデバイスツリーを作成&配置する機能を持っていてアドレス0x87e00000に配置します(-m 128MBの場合、メモリ量に依存してアドレスが変わる)。メモリマップはこんな感じですね。


デバイスツリーに関連するQEMUのメモリマップ

プラットフォームgeneric向けにおいて、デバイスツリーの実体は2つあります。デバイスツリーを配置する主体と方法を時系列順に並べると下記のようになります。

  • QEMU: QEMU起動時に0x87e00000にデバイスツリーを配置、ブートROMコードにてレジスタa1にアドレスセット(コピーしたあとは使用しない)
  • OpenSBI: 0x82200000にデバイスツリーをコピー、scratch->next_arg1にアドレスセット(Linuxに渡すのはこちらのデバイスツリー)

コピーを行うコードはこんな感じです。

デバイスツリーのコピーを行うコード

//opensbi/firmware/fw_base.S

	/*
	 * Relocate Flatened Device Tree (FDT)
	 * source FDT address = previous arg1
	 * destination FDT address = next arg1
	 *
	 * Note: We will preserve a0 and a1 passed by
	 * previous booting stage.
	 */
	beqz	a1, _fdt_reloc_done
	/* Mask values in a4 */
	li	a4, 0xff
	/* t1 = destination FDT start address */
	MOV_3R	s0, a0, s1, a1, s2, a2
	call	fw_next_arg1    //★コピー先アドレスを得る(a0に格納される)★
	add	t1, a0, zero    //★t1: コピー先アドレス★
	MOV_3R	a0, s0, a1, s1, a2, s2
	beqz	t1, _fdt_reloc_done        //★コピー先アドレスが0ならコピー処理をスキップ★
	beq	t1, a1, _fdt_reloc_done    //★アドレスがコピー元 = コピー先ならコピー処理をスキップ★
	/* t0 = source FDT start address */
	add	t0, a1, zero    //★t0: コピー元アドレス★
	/* t2 = source FDT size in big-endian */
#if __riscv_xlen > 32
	lwu	t2, 4(t0)
#else
	lw	t2, 4(t0)
#endif
	/* t3 = bit[15:8] of FDT size */
	add	t3, t2, zero
	srli	t3, t3, 16
	and	t3, t3, a4
	slli	t3, t3, 8
	/* t4 = bit[23:16] of FDT size */
	add	t4, t2, zero
	srli	t4, t4, 8
	and	t4, t4, a4
	slli	t4, t4, 16
	/* t5 = bit[31:24] of FDT size */
	add	t5, t2, zero
	and	t5, t5, a4
	slli	t5, t5, 24
	/* t2 = bit[7:0] of FDT size */
	srli	t2, t2, 24
	and	t2, t2, a4
	/* t2 = FDT size in little-endian */
	or	t2, t2, t3
	or	t2, t2, t4
	or	t2, t2, t5
	/* t2 = destination FDT end address */
	add	t2, t1, t2
	/* FDT copy loop */
	ble	t2, t1, _fdt_reloc_done
_fdt_reloc_again:
	REG_L	t3, 0(t0)    //★デバイスツリーコピーのメインループ★
	REG_S	t3, 0(t1)
	add	t0, t0, __SIZEOF_POINTER__
	add	t1, t1, __SIZEOF_POINTER__
	blt	t1, t2, _fdt_reloc_again
_fdt_reloc_done:

ラベル_fdt_reloc_againの手前にある処理で、レジスタt0がコピー元アドレス(0x87e00000)、レジスタt1がコピー先アドレス(0x82200000)に設定され、_fdt_reloc_againのループにてコピーします。

デバイスツリーのアドレスを使う箇所

OpenSBIのプラットフォームgenericがデバイスツリーのアドレス(レジスタa0)を参照する方法は少なくとも2つあって、レジスタa1の参照と、scratch->next_arg1を参照する方法です。時系列順に並べると、

  • PLATFORM=genericのfw_platform_init()関数: レジスタa1を直接参照する
  • デバイスツリーのコピー処理: レジスタa1を直接参照する
  • fw_next_arg1()関数: プラットフォーム依存、genericの場合は固定値(後述)
  • C言語実装部分: scratch->next_arg1(fw_next_arg1()関数が返す結果のコピー)

プラットフォームによっても処理が違います。読んでいてややこしい部分の1つですね……。

デバイスツリーがどこからきてどこへ行くのか?がわかりました。次はデバイスツリーのアドレスを決めるもう1つの実装について調べます。

編集者:すずき(2024/07/26 02:02)

コメント一覧

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



2024年7月23日

OpenSBIを調べる - QEMUのデバイスツリー

目次: Linux

OpenSBIのブート部分を調べます。OpenSBIはいくつか動作モードがあるのですが、payloadを持ったタイプを調べます。対象のファイル名はbuild/platform/generic/firmware/fw_payload.binもしくは.elfです。

今回はOpenSBIを見る前にQEMUの動作、デバイスツリーの扱いについて調べます。実行環境はQEMU RV64 virt machine(qemu-system-riscv64 -machine virt)です。なぜ突然QEMUの話をするかというと、OpenSBIはQEMUからデバイスツリーを受け取るからです。OpenSBIを調べる前にQEMUがわかっていた方が理解しやすいです。

QEMUは-dtbオプションにてデバイスツリーファイルを明示的に指定できます。もし-dtbオプションを省略するとHW構成に従ってデバイスツリーを自動生成します。便利ですね。明示的に指定した場合、自動生成した場合いずれであってもQEMUのメモリの何処かにデバイスツリーのデータを展開します。

OpenSBIにデバイスツリーのアドレスを渡す方法

OpenSBIを起動するときはレジスタa0にhartid、レジスタa1にデバイスツリーのアドレスを渡す決まりがあります。

OpenSBIのドキュメント(適当訳)
####opensbi/docs/firmware/fw.md####

OpenSBI Platform Firmwares
==========================

OpenSBI provides firmware builds for specific platforms. Different types of
firmwares are supported to deal with the differences between different platforms
early boot stage. All firmwares will execute the same initialization procedure
of the platform hardware according to the platform specific code as well as
OpenSBI generic library code. The supported firmwares type will differ in how
the arguments passed by the platform early boot stage are handled, as well as
how the boot stage following the firmware will be handled and executed.

The previous booting stage will pass information via the following registers
of RISC-V CPU:

* hartid via *a0* register
* device tree blob address in memory via *a1* register. The address must
  be aligned to 8 bytes.

(適当訳)
OpenSBIは特定のプラットフォーム用のファームウェアビルドを提供します。初期ブートローダーでの
異なるプラットフォーム間の違いに対処するため、いろいろな種類のファームウェアがサポートされています。
全てのファームウェアはプラットフォーム固有のコードとOpenSBIの汎用ライブラリのコードに従って、
プラットフォームHWの同じ初期化処理を実行します(訳注: 何を言いたいかよくわからん……)。
サポートされているファームウェアの種類は、プラットフォームの初期ブートローダーから渡された引数が
どのように処理されるかと、ファームウェアに続く起動段階がどのように処理され実行されるかによって異なります。

直前のブートローダーは、次のRISC-V CPUレジスタ経由で情報を渡します:

* レジスタa0経由でhartid
* レジスタa1経由でメモリ上のデバイスツリーブロブ(訳注: *.dtbのこと)のアドレス、アドレスは8バイトアラインが必要

QEMUの場合はブートROMコード(qemu-system-riscv64 -machine virtの場合はアドレス0x1000付近)がレジスタa1にアドレスを設定します。コードの逆アセンブルはこんな感じです。

QEMUのブートROMの逆アセンブルとダンプ
   0x1000:      auipc   t0,0x0      //t0 <- 0x1000
   0x1004:      addi    a2,t0,40    //a2 <- 0x1028
   0x1008:      csrr    a0,mhartid  //a0 <- hartid
   0x100c:      ld      a1,32(t0)   //a1 <- 0x87e00000(アドレス0x1020の値)
   0x1010:      ld      t0,24(t0)   //t0 <- 0x80000000(アドレス0x1018の値)
   0x1014:      jr      t0          //0x80000000にジャンプ(OpenSBIの先頭)

0x1000: 0x00000297      0x02828613      0xf1402573      0x0202b583
0x1010: 0x0182b283      0x00028067      0x80000000      0x00000000
0x1020: 0x87e00000      0x00000000      0x4942534f      0x00000000
0x1030: 0x00000002      0x00000000      0x80000000      0x00000000

QEMUはリセットすると必ずブートROMコードから実行開始しますから、常にレジスタa1に0x87e00000(-m 128MBの場合、メモリ量に依存してアドレスが変わる)を設定してからOpenSBIにジャンプすることがわかります。アドレス0x87e00000付近の内容をダンプすると、

QEMUのデバイスツリー配置領域のダンプ
0x87e00000:     0xedfe0dd0      0x201c0000      0x38000000      0x341a0000
0x87e00010:     0x28000000      0x11000000      0x10000000      0x00000000
0x87e00020:     0xec010000      0xfc190000      0x00000000      0x00000000
0x87e00030:     0x00000000      0x00000000      0x01000000      0x00000000
0x87e00040:     0x03000000      0x04000000      0x1d000000      0x02000000
0x87e00050:     0x03000000      0x04000000      0x11000000      0x02000000
0x87e00060:     0x03000000      0x0d000000      0x06000000      0x63736972
0x87e00070:     0x69762d76      0x6f697472      0x6d657100      0x03000000

なるほど、このデータはデバイスツリーですね……と思える人は相当デバイスツリー通です。私はわかりませんので、データが何者か調べるため先頭にある謎の値0xedfe0dd0に注目します。

デバイスツリーのドキュメント(5. Flattened Devicetree (DTB) Format)を見るとヘッダは下記のようになっています。先頭のmagicメンバーはマジックナンバーであり、ビッグエンディアンの0xd00dfeedでなければならないと記述されています。

Flattened Devicetreeのヘッダ構造体の定義

struct fdt_header {
    uint32_t magic;
    uint32_t totalsize;
    uint32_t off_dt_struct;
    uint32_t off_dt_strings;
    uint32_t off_mem_rsvmap;
    uint32_t version;
    uint32_t last_comp_version;
    uint32_t boot_cpuid_phys;
    uint32_t size_dt_strings;
    uint32_t size_dt_struct;
};

メモリ上にあった謎の値0xedfe0dd0をビッグエンディアンで解釈すると0xd00dfeedとなり、struct fdt_headerのマジックナンバーと一致しました。このデータはきっとデバイスツリーでしょう。

まとめるとQEMU(qemu-system-riscv64 -machine virt)のブートROMコードは、

  • 起動オプションに応じてデバイスツリーを自動生成して0x87e00000に配置(アドレスはメモリ量依存で変わる)
  • もしくは-dtbオプションに指定した*.dtbファイルを0x87e00000に配置
  • ROMブートコードでデバイスツリー先頭のアドレスをレジスタa1に格納

このような動作をします。

QEMUが自動生成するデバイスツリーをダンプ

QEMUが自動生成したデバイスツリーをファイルにダンプする方法をメモしておきます。簡単に言えばQEMU Monitorでdumpdtb (file名)コマンドを実行すればダンプできます。

QEMU Monitorを使うにはいくつか手があります。グラフィック環境が使えるなら-nographicと-monオプションを外し、ウインドウから入力するのが一番早いでしょう。

QEMU Monitorの使用方法(GUI)
# グラフィック環境が使えるとき

./qemu-system-riscv64 \
  -machine virt \
  -bios none \
  -chardev stdio,id=con,mux=on \
  -serial chardev:con \
  -smp 4

こんなウインドウが表示されるはずです。


QEMU Monitor(GUI)

グラフィック環境がない場合はシリアル出力を一時的に無効化して、-monを標準入出力に振り向けると使えるようです。

QEMU Monitorの使用方法(CUI)
# グラフィック環境が使えないとき

./qemu-system-riscv64 \
  -machine virt \
  -bios none \
  -nographic \
  -chardev stdio,id=con,mux=on \
  -chardev null,id=none,mux=on \
  -serial chardev:none \
  -mon chardev=con,mode=readline \
  -smp 4

QEMU 9.0.2 monitor - type 'help' for more information
(qemu) dumpdtb virt.dtb
info: dtb dumped to virt.dtb
(qemu) 

もっとスマートなやり方がありそうですけど……使えればよしとしましょう。

QEMUは起動時にデバイスツリーを自動生成もしくはファイルからロードしてくれることがわかりました。次回はOpenSBIでデバイスツリーをどう扱っているか調べたいと思います。

編集者:すずき(2024/07/26 00:10)

コメント一覧

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



link もっと前
2024年8月22日 >>> 2024年7月23日
link もっと後

管理用メニュー

link 記事を新規作成

<2024>
<<<08>>>
----123
45678910
11121314151617
18192021222324
25262728293031

最近のコメント5件

  • link 24年10月1日
    すずきさん (10/06 03:41)
    「xrdpで十分動作しているので、Wayl...」
  • link 24年10月1日
    hdkさん (10/03 19:05)
    「GNOMEをお使いでしたら今はWayla...」
  • link 24年10月1日
    すずきさん (10/03 10:12)
    「私は逆にVNCサーバーに繋ぐ使い方をした...」
  • link 24年10月1日
    hdkさん (10/03 08:30)
    「おー、面白いですね。xrdpはすでに立ち...」
  • link 14年6月13日
    2048player...さん (09/26 01:04)
    「最後に、この式を出すのに紙4枚(A4)も...」

最近の記事3件

  • link 24年10月3日
    すずき (10/06 01:33)
    「[Ubuntu 20.04 LTSでxrdpのエラーを再現できるか挑戦] 目次: Linux先日(2024年10月1日の日記参...」
  • link 23年4月10日
    すずき (10/05 15:09)
    「[Linux - まとめリンク] 目次: Linux関係の深いまとめリンク。目次: RISC-V目次: ROCK64/ROCK...」
  • link 24年10月2日
    すずき (10/05 15:08)
    「[VirtualBoxでxrdpのエラーを再現できるか挑戦] 目次: Linux昨日(2024年10月1日の日記参照)のUbu...」
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

最終更新: 10/06 03:41