コグノスケ


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

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

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 この記事にコメントする



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

管理用メニュー

link 記事を新規作成

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

最近のコメント5件

  • link 14年6月13日
    2048playerさん (09/08 17:30)
    「私も2048の最高スコアを求めたのですが...」
  • link 14年6月13日
    2048さん (09/08 17:16)
    「私も2048の最高スコアを求めたのですが...」
  • link 14年6月13日
    2048playerさん (09/08 16:10)
    「私も2048の最高スコアを求めたのですが...」
  • link 02年8月4日
    lxbfYeaaさん (07/12 10:11)
    「555」
  • link 24年6月17日
    すずきさん (06/23 00:12)
    「ありがとうございます。バルコニーではない...」

最近の記事3件

  • link 24年8月31日
    すずき (09/01 15:01)
    「[Microsoftマウスが壊れた] 3年前くらいに購入した(2021年3月6日の日記参照)Microsoft Basic O...」
  • link 23年5月15日
    すずき (09/01 15:00)
    「[車 - まとめリンク] 目次: 車三菱FTOの話。群馬県へのドライブ将来車を買い替えるとしたら?FTOのオイル交換とオイル漏...」
  • link 24年8月25日
    すずき (09/01 14:59)
    「[レガシィの7回目の車検完了] 目次: 車ディーラーまで車検の車を取りに行きました。外は非常に暑くて辛いです…&...」
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

最終更新: 09/08 17:30