Mac mini 2020 (M1)を買いました。速くて快適なのですが、一つ問題があります。VirtualBoxが使えないのです。
たまにx86_64のLinux/Windowsアプリをビルドして動作確認をすることがあるので、代替としてQEMUを試してみました。

qemu.orgから最新の6.0.0(2021年5月現在)をダウンロードし、ビルドします。
$ ./configure --target-list=x86_64-softmmu
$ make -j4
しばらくM1 Macを使ってみて、Intelのときに比べて体感は「多少速くなったかな」程度だったのですが、QEMUのビルドはすごい勢いで進み、1分足らずで終わります。
本気を出すとすごく速いです、M1。
QEMUを起動してDebianをインストールし、ログイン。
ここまでは順調だったのですが、ターミナルを開いて操作していると、ウィンドウをクリックしたときに落ちてしまいました。
...

Exception Type:        EXC_BAD_INSTRUCTION (SIGILL)
Exception Codes:       0x0000000000000001, 0x0000000013948294
Exception Note:        EXC_CORPSE_NOTIFY

Termination Signal:    Illegal instruction: 4
Termination Reason:    Namespace SIGNAL, Code 0x4
Terminating Process:   exc handler [3788]

...

Thread 5 Crashed:
0   ???                           	0x00000002b529139c 0 + 11629302684
1   qemu-system-x86_64            	0x0000000105188fc0 cpu_tb_exec + 472 (cpu-exec.c:189)
2   qemu-system-x86_64            	0x0000000105189ae0 cpu_loop_exec_tb + 52 (cpu-exec.c:672) [inlined]
3   qemu-system-x86_64            	0x0000000105189ae0 cpu_exec + 1648 (cpu-exec.c:797)
4   qemu-system-x86_64            	0x0000000105188954 tcg_cpus_exec + 48 (tcg-accel-ops.c:67)
5   qemu-system-x86_64            	0x00000001051faa08 rr_cpu_thread_fn + 464 (tcg-accel-ops-rr.c:216)
6   qemu-system-x86_64            	0x00000001053c5fd4 qemu_thread_start + 128 (qemu-thread-posix.c:521)
7   libsystem_pthread.dylib       	0x00000001861dafd4 _pthread_start + 320
8   libsystem_pthread.dylib       	0x00000001861d5d3c thread_start + 8

...
TCGが生成した正しくない命令を実行しようとして落ちたようです。
同じような症状はないか検索したのですが見つかりません。
まぁ、こんなに簡単に出るバグならそのうち誰かが直してくれるだろうと思っていました。


ある日、QEMUについて検索していたら以下のページを見つけました。
開発ツール(QEMU)への貢献(前半) 〜自作OSのいまと昔 [第3回]
開発ツール(QEMU)への貢献(後半) 〜自作OSのいまと昔 [第4回]
QEMUのバグを追って、パッチを投げるまでが書かれており、すごく面白いです。

これを参考に自分もデバッグしてみようと思い立ちました。
まず、
https://github.com/qemu/qemu.git
から最新版をcloneし、以前と同様にクラッシュするのを確認したので、このソースでデバッグします。

QEMUをデバッグビルドします。
$ ./configure --target-list=x86_64-softmmu --enable-debug
$ make -j4
lldb起動後の入力を省略するため、以下の内容のcmdというファイルを作ります。
process launch -s -- -cpu max -m 1024 /path/to/diskimage.qcow2
process handle -p true -s false -n false SIGUSR2
実行します。
$ lldb -s cmd build/qemu-system-x86_64

...

(lldb) c
Process 12881 resuming
Process 12881 stopped
* thread #6, stop reason = EXC_BAD_INSTRUCTION (code=1, subcode=0x13948294)
    frame #0: 0x000000029500b41c
->  0x29500b41c: .long  0x13948294                ; unknown opcode
    0x29500b420: mov    w20, w20
    0x29500b424: str    x20, [x19]
    0x29500b428: mov    w21, #0x44
Target 0: (qemu-system-x86_64) stopped.
(lldb)  
命令として正しくないビットパターンを実行しようとして落ちています。
この記事を書く段階で気がついたのですが、最初のクラッシュログに0x0000000013948294と載っていますね。

ARMのマニュアルでこのビットパターンを調べました。
Arm A64 Instruction Set Architecture
ここの下矢印ボタンでPDFをダウンロードできます。

PDFの目次のTop-level encodings for A64をクリック
op0==1001なのでData Processing -- Immediateをクリック
op0==111なのでExtractをクリック
sf==0,imms=100000なのでUNALLOCATED

確かに未定義命令でした。
便利ですね、このマニュアル。トップレベルからクリックしていくと命令にたどり着ける。
imms=0xxxxxならEXTR命令になるので、これを生成しようとしてミスっていると予想しました。
EXTR命令の説明を見てみると、Rn:Rmをバレルシフタに入力し、シフト数をimmsとして下位をRdにセットする命令のようです。
RnとRmに同じものを指定するとローテート命令になる。なるほど。

次に、これを生成した場所を突き止めます。
TCGの命令生成について調べると、以下のページを見つけました。
QEMUのTCG(Tiny Code Generator)を読み解く(3. TCGContextによりホスト命令がターゲット命令に変換される流れ)

ターゲットのバイナリはtcg_out32()で出力されていくようです。
ソースを調べると、tcg/tcg.cで定義されていました。

tcg_out32()で、その命令を出力しようとしたところでブレークします。
(lldb) br s -f tcg.c -l 247 -c v==0x13948294
Breakpoint 1: where = qemu-system-x86_64`tcg_out32 + 12 at tcg.c:248:26, address = 0x0000000100419764
(lldb) c
しばらく待ってもブートしません。ブレークに条件をつけると極端に遅くなることがわかりました。
ハードウェアで検出できるのはアドレスの一致だけで、-cの部分は条件判定ルーチンが呼び出されるのでしょう。
そこで、関数の先頭に以下のコードを挿入し、c++のところにブレークを仕掛けることにしました。
    if (v == 0x13948294) {
        static int c;
        c++;
    }
実行結果です。横に長い行はあとから改行を入れています。
(lldb) br s -f tcg.c -l 249
Breakpoint 1: where = qemu-system-x86_64`tcg_out32 + 40 at tcg.c:249:4, address = 0x00000001004974f8
(lldb) c
Process 12849 resuming
Process 12849 stopped
* thread #6, stop reason = breakpoint 1.1
    frame #0: 0x00000001004974f8 qemu-system-x86_64`tcg_out32(s=0x0000000108358000, v=328499860) at tcg.c:249:4
   246 	{
   247 		if (v == 0x13948294) {
   248 			static int c;
-> 249 			c++;
   250 		}
   251 		if (TCG_TARGET_INSN_UNIT_SIZE == 4) {
   252 	        *s->code_ptr++ = v;
Target 0: (qemu-system-x86_64) stopped.
(lldb) bt
* thread #6, stop reason = breakpoint 1.1
  * frame #0: 0x00000001004974f8 qemu-system-x86_64`tcg_out32(s=0x0000000108358000,
              v=328499860) at tcg.c:249:4
    frame #1: 0x00000001004a0c1c qemu-system-x86_64`tcg_out_insn_3403(s=0x0000000108358000,
              insn=I3403_EXTR, ext=TCG_TYPE_I32, rd=TCG_REG_X20, rn=TCG_REG_X20, rm=TCG_REG_X20, imms=32) at tcg-target.c.inc:694:5
    frame #2: 0x000000010049fce4 qemu-system-x86_64`tcg_out_extr(s=0x0000000108358000,
              ext=TCG_TYPE_I32, rd=TCG_REG_X20, rn=TCG_REG_X20, rm=TCG_REG_X20, a=32) at tcg-target.c.inc:1259:5
    frame #3: 0x000000010049f318 qemu-system-x86_64`tcg_out_rotl(s=0x0000000108358000,
              ext=TCG_TYPE_I32, rd=TCG_REG_X20, rn=TCG_REG_X20, m=0) at tcg-target.c.inc:1296:5
    frame #4: 0x000000010049de4c qemu-system-x86_64`tcg_out_op(s=0x0000000108358000,
              opc=INDEX_op_rotl_i32, args=0x00000001700b6a88, const_args=0x00000001700b6a48) at tcg-target.c.inc:2140:13
    frame #5: 0x0000000100496338 qemu-system-x86_64`tcg_reg_alloc_op(s=0x0000000108358000, op=0x0000000118ef0b38) at tcg.c:4217:9
    frame #6: 0x00000001004920a8 qemu-system-x86_64`tcg_gen_code(s=0x0000000108358000, tb=0x0000000134f63800) at tcg.c:4755:13
    frame #7: 0x000000010050ea94 qemu-system-x86_64`tb_gen_code(cpu=0x0000000108338000,
              pc=139793480488555, cs_base=0, flags=12632755, cflags=-16777216) at translate-all.c:1940:21
    frame #8: 0x0000000100467c1c qemu-system-x86_64`tb_find(cpu=0x0000000108338000,
              last_tb=0x0000000134f63600, tb_exit=0, cflags=4278190080) at cpu-exec.c:428:14
    frame #9: 0x00000001004672d8 qemu-system-x86_64`cpu_exec(cpu=0x0000000108338000) at cpu-exec.c:796:18
    frame #10: 0x00000001004c9428 qemu-system-x86_64`tcg_cpus_exec(cpu=0x0000000108338000) at tcg-accel-ops.c:67:11
    frame #11: 0x0000000100469750 qemu-system-x86_64`rr_cpu_thread_fn(arg=0x0000000108338000) at tcg-accel-ops-rr.c:216:21
    frame #12: 0x00000001007a3d30 qemu-system-x86_64`qemu_thread_start(args=".PingFangTC-Semibold") at qemu-thread-posix.c:521:9
    frame #13: 0x00000001861dafd4 libsystem_pthread.dylib`_pthread_start + 320
順に見ていきます。
frame #1:
static void tcg_out_insn_3403(TCGContext *s, AArch64Insn insn, TCGType ext,
                              TCGReg rd, TCGReg rn, TCGReg rm, int imms)
{
    tcg_out32(s, insn | ext << 31 | ext << 22 | rm << 16 | imms << 10
              | rn << 5 | rd);
}
命令を組み立てていますね。バックトレースではimms==32になっているので確かに間違っています。
frame #2:
static inline void tcg_out_extr(TCGContext *s, TCGType ext, TCGReg rd,
                                TCGReg rn, TCGReg rm, unsigned int a)
{
    tcg_out_insn(s, 3403, EXTR, ext, rd, rn, rm, a);
}
ここは問題ありません。
frame #3:
static inline void tcg_out_rotl(TCGContext *s, TCGType ext,
                                TCGReg rd, TCGReg rn, unsigned int m)
{
    int bits = ext ? 64 : 32;
    int max = bits - 1;
    tcg_out_extr(s, ext, rd, rn, rn, bits - (m & max));
}
ん?これはおかしい。m==0でimms==32になっている。
mをimmsに変換するということはmはローテートするビット数なので、本来はimms==0が正しいでしょう。
ということで、以下のように変更します。
static inline void tcg_out_rotl(TCGContext *s, TCGType ext,
                                TCGReg rd, TCGReg rn, unsigned int m)
{
    int max = ext ? 63 : 31;
    tcg_out_extr(s, ext, rd, rn, rn, -m & max);
}
ビルドして走らせると、ウィンドウをクリックしても落ちなくなりました。
しばらく色々と操作しても正常でした。

パッチの作成から提出まで

GitHubでプルリクエストを作成してっていうのは慣れているのですが、メーリングリストに投げる方式はやったことがありません。
QEMUのメーリングリストを調べると、qemu-develの他に、qemu-trivialというのがありました。
簡単なパッチはこちらでいいのかな?と思い、アーカイブをのぞいてみると、大丈夫そうです。

では、パッチメールを作っていきます。以下のページにやり方が書いてあります。
https://wiki.qemu.org/Contribute/SubmitAPatch

なんか面倒くさいです。でもせっかくなので一通りざっと読んでみました。
まずgit format-patchでパッチを作るため、qemuをforkして変更点を反映し、commitします。
そして
$ git format-patch HEAD^
これでパッチファイル(0001-*.patch)ができるので、他の投稿を真似してメールのタイトルと本文を切り出します。
本文にはSigned-off-by:で始まる行を入れるのが重要です。
あと、
$ scripts/get_maintainer.pl -f 修正したファイルのpath
でメンテナを調べ、Cc:に入れます。

メールができたので送信しました。
qemu-trivialをsubscribeして、しばらく経っても反映されないので気が付きました。
「あ、subscribeしてから送信しないとダメなんじゃ」
件名にスペルミスがあったのでついでに直して再送信しました。
さらに数時間後、2件続けて投稿されていました。恥ずかしい...
[PATCH] tcg/aarch64/tcg-target.c.inc: correction of rotate bit number

(日本時間の)翌日返信がありました。
Re: [PATCH] tcg/aarch64/tcg-target.c.inc: correction of rotate bit numbe

qemu-develに送ったほうがよかったみたいです。
また、メールはGmailから送ったのですが、余計な整形を避けるためgit send-emailか、webベースのsourcehutで送るのがいいようです。

TCG patch queueに入りました。
[PULL 00/31] tcg patch queue
[PULL 10/31] tcg/aarch64: Fix tcg_out_rotl

メンテナのRichardさんのリポジトリ
https://gitlab.com/rth7680/qemu/-/commit/26b1248f66

GitHubに反映されました。
https://github.com/qemu/qemu/commit/26b1248f66

(2021/07/18更新)
UTMに反映されました。

https://github.com/utmapp/UTM/issues/2630

https://github.com/utmapp/UTM/commit/0dc4405

https://github.com/utmapp/UTM/releases/tag/v2.1.2

(2021/08/29更新)
QEMU
バージョン6.1.0からこのパッチが含まれています。