はじめに

Qiita に書くにはしょぼいネタなので久々にこちらに書きます。

先日、「そうだ ARM64 やろう」と思いついて Raspberry Pi を買いました。以前 QEMU で ARM64 の Linux カーネルを触ったことはあるのですが、実機で試しておきたかったためです。その当時 QEMU 上で ARM64 版 Windows を動かそうとも試みましたが、どうやらドライバーが対応していないらしく起動しませんでした。今回無事カーネル デバッグ環境を作るところまでできたので、そこまでの過程をまとめました。

デバイス選定

まずはハードウェアの選定です。Raspberry Pi を買うことが動機ではなかったので、手始めに既製品のパソコンも探したのですが、どれも高すぎました。$1,000 超えは論外で、学習用途なことを考えると $350 も厳しい。

困っていたところ、ラズベリーパイでいけるらしいという記事を発見。いいじゃん。

ラズパイ4にWindows 10 on ARM64をインストールする - Qiita
https://qiita.com/mkht/items/9d173334dc5b26bfef46

というわけでアマゾンで一番人気のスターター キットを買いました。メモリは当然 Max の 8GB を選びます。お値段 $119.99。これはプチプラ。

Amazon.com: CanaKit Raspberry Pi 4 8GB Starter Kit - 8GB RAM: Computers & Accessories https://www.amazon.com/dp/B08956GVXN

OS インストール/メモリ設定

CanaKit のパッケージ内容がこれ。組み立ては簡単でした。ドライバーすら不要。

とりあえず Windows のインストールを始める前に、キットに入っていた SD カードをそのまま使ったところ、NOOBS とかいうブート画面が難なく起動。

動作確認ができたので、次に Windows をインストールします。大容量の SD カードが手元に他にないので、CanaKit 付属の SD カードの中身を無慈悲に消去して Windows のブート用にします。手順は以下のサイトを参考にしました。大体どこの情報も同じで、WoR (= Windows on Raspberry) というプロジェクトが用意してくれているソフトを使うだけです。

Guide – Windows 10 ARM64 on Pi 4B – Making Pi ServerReady
https://rpi4-uefi.dev/win10-arm64-on-pi-4b/

特に罠はなく、気になるのは OS のインストール イメージを作るステップで、UUP のサイトからダウンロードした怪しげなバッチファイルを実行するのが若干怖いぐらいです。慎重な人はテスト専用の環境で実行してください。

執筆当時の最新バージョンは Windows 10 Insider Preview 10.21390.2025 (co_release) [arm64] となっており 21390.2025.210527-1818.CO_RELEASE_SVC_IM_CLIENTPRO_OEMRET_A64FRE_EN-US.ISO という ISO ファイルができました。話が逸れますが、co_release というブランチ名はコバルトというコードネームですね。

Microsoftが2021年秋を見込むWindowsの一大プロジェクト「Sun Valley」と「Cobalt」:Windowsフロントライン(1/2 ページ) - ITmedia PC USER
https://www.itmedia.co.jp/pcuser/articles/2011/02/news074.html

周期表に従ったコードネームはバナジウムから始まっています。Threshold 2 や Redstone 5 とかいう連番になった過去のコードネームよりは遥かにましですが、二番煎じ感が否めません。

Get Ready for Windows 10 “Vanadium” and “Vibranium”
https://www.howtogeek.com/fyi/get-ready-for-windows-10-vanadium-and-vibranium/

本題に戻って WoR の画面を幾つか貼っておきます。デフォルトの設定のままですんなり起動しました。

作成した SD カードを入れて Raspberry Pi を初回起動するわけですが、とにかく動作が著しく遅いので忍耐力が必要です。OOBE が出てインストールが終わるまでに 30 分ぐらいかかります。

起動した後タスクマネージャーを見ると、常に Disk I/O が 100% で張り付いており、SD カードの I/O が原因だと分かります。そこで I/O を減らすため、以下の設定を行ないます。

  • UEFI の画面で “Limit RAM to 3 GB” を Disable に設定
  • ページファイルサイズを 0 に設定

これで物理メモリを 8GB フルに使うようになったはずで、実際にパフォーマンスは大きく改善しました。ページングの影響がずいぶんと大きかったみたいです。メモリ使用量は 3GB 程度なので、4GB でもページファイルなしでギリギリいけそうです。

とはいっても相変わらず Disk I/O は 100% のままで実用にはほど遠い遅さです。やはり Galaxy Book Go ぐらいはケチらずに買うべきだったか。

Winver と msinfo32 はこのようになりました。基盤はソニーが作っているらしい。

もちろん x64 の Windows から RDP 接続できますし、ファイル共有も問題なく動作し、アーキテクチャの違いは感じません。

ツール類

何かソフトを入れましょう。まずは Sysinternals Suite の ARM64 版ですかね。

Sysinternals Suite - Windows Sysinternals | Microsoft Docs
https://docs.microsoft.com/en-us/sysinternals/downloads/sysinternals-suite

デバッガーも必須です。デバッガー単体はダウンロードできないので、WDK または SDK の ISO 経由でインストールします。記事を書き溜めている間に Windows 11 が発表されたので、Windows 11 の SDK に入っているデバッガーを入れてみましょう。Windows 11 のメジャー バージョンは 22000 みたいです。

Download the Windows Driver Kit (WDK) - Windows drivers | Microsoft Docs
https://docs.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk

dbgsrv.exe を起動してリモート デバッグを試します。ARM64 のアセンブリ全然ワカランチン。関数の先頭の pacibsp って何やねん、と思って調べたら話題の PAC だった・・・。これは後で勉強しないといけない。

ARM64 の Windows 10 では、x64/x86/arm のプロセスも動きます。残念なことにタスク マネージャーがプロセスのアーキテクチャを表示してくれるのに対して、Process Explorer は bitness しか教えてくれません。タスクマネージャーの方が使えるじゃん。

というか x64 のエミュレーションはできないと思っていたのですが、最近できるようになったんですね。これは素晴らしい仕事。

Introducing x64 emulation in preview for Windows 10 on ARM PCs to the Windows Insider Program | Windows Insider Blog
https://blogs.windows.com/windows-insider/2020/12/10/introducing-x64-emulation-in-preview-for-windows-10-on-arm-pcs-to-the-windows-insider-program/

最後に Firefox を入れてみました。せっかくネイティブ arm64版もあるので。

なお x86 版 Firefox を動かそうとすると、CHPE (=Compiled Hybrid Portable Executable) の動作に対応していないのでちょっと問題が起きます。試していませんが、x64 on ARM64 も同様の問題があるかもしれません。後で直す。

1708587 - Nightly x86 on ARM64 error 0x800000003 on first launch with launcher process
https://bugzilla.mozilla.org/show_bug.cgi?id=1708587

カーネルデバッグ

ユーザーモードのデバッグはできることが分かりましたが、やはりカーネルこそキング、ということでカーネルデバッグもやりましょう。が、しかし、繋ぎ方が問題になります。

Windows 10 であれば、Ethernet 経由で繋げたいところですが、Raspberry Pi のネットワーク アダプターは Broadcom の BCM6E4E で、どうやらデバッグ機能は持っていないようです。

Supported Ethernet NICs for Network Kernel Debugging in Windows 10 - Windows drivers | Microsoft Docs
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/supported-ethernet-nics-for-network-kernel-debugging-in-windows-10

USB の方は 2.0 も 3.0 もデバッグ機能なし。

最後の希望で、シリアル ポートを物理的に付けられないかを調べると、GPIO に TXD/RXD ピンがあってこれを D-Sub 9 ピンに変換することができるようです。これは熱い展開。

SHA - - - Raspberry Pi - Installing a RS232 Serial Port
http://www.savagehomeautomation.com/projects/raspberry-pi-installing-a-rs232-serial-port.html

デバイスマネージャーを見ると、確かに COM1 が存在してドライバーも動作しているようです。

アマゾンで “TTL DB9” で検索し、はんだ付けが不要で、かつユーザー評価が良かったこれをチョイス。たったの $7.49。ジャンパーケーブルも一緒に買ったのですが、この商品にもジャンパーケーブルが必要分の 4 本入ってました。親切!

Amazon.com: DZS Elec RS232 DB9 Male Serial Port to TTL Converter MAX3232 Root Module Connector MCU Programme Mobile Root Vehicle Examine and Repair Converter: Computers & Accessories
https://www.amazon.com/dp/B072KJSS5C

まずは Raspberry Pi 上でカーネル デバッグの設定をします。Raspberry Pi 側のボーレートが不明なので、とりあえずお決まりの 115200 にしておきます。

> bcdedit /debug on
The operation completed successfully.

> bcdedit /dbgsettings SERIAL DEBUGPORT:1 BAUDRATE:115200
The operation completed successfully.

> bcdedit /dbgsettings
debugtype               Serial
debugport               1
baudrate                115200
The operation completed successfully

システムを終了し電源を抜いてから、シリアルポートを接続します。作業は簡単で、4 つのピン (TX, RX, GND, VCC) をそれぞれ GPIO の TXL, RXL, GRD, 3V3 に繋ぐだけです。

ところで、デバッガー側のマシンにシリアルポートあったっけ?という話ですが、前回マザーボードを交換したときに触れたように、シリアルポート カードというロマン溢れるパーツを差しているので準備万端です。まあ正直実際にこれを使う時が来るとは思わなかった。なんという伏線。

デバッガー側のポートにはボーレートを設定できるみたいなので、念のため 115200 にしておきます。

ポート同士をご家庭によくある RS-232C ケーブルで繋ぎます。こんな感じになりました。

以下のコマンドでデバッガーを起動しておいてから Raspberry Pi を起動します。

kd.exe -k com:port=\\.\com1,baud=115200 -b

出力来た!

ブート中、SerPL011 というモジュールが必ずブレークします。

kd> knL
 # Child-SP          RetAddr               Call Site
00 fffff803`da098b30 fffff803`dc959194     nt!DebugService2+0x8
01 fffff803`da098b30 fffff803`dd008d10     nt!DbgLoadImageSymbols+0x44
02 fffff803`da098b70 fffff803`dcfeeb00     nt!KdInitSystem+0xbf0
03 fffff803`da098d10 00000000`00000000     nt!KiSystemStartup+0x170
kd> g
The target has requested that the debugger execute a command: !amli err 0xc 0000000000000001 0000000000000003 0000000000000000 0000000000000000;g">!amli err 0xc 0000000000000001 0000000000000003 0000000000000000 0000000000000000;g
The target has requested that the debugger execute a command: !amli err 0xc 0000000000000001 0000000000000003 0000000000000000 0000000000000000;g">!amli err 0xc 0000000000000001 0000000000000003 0000000000000000 0000000000000000;g
KDTARGET: Refreshing KD connection
KDTARGET: Refreshing KD connection
Break instruction exception - code 80000003 (first chance)
SerPL011!PL011BreakPoint+0x18:
fffff807`8b2d9d20 d43e0000 brk         #0xF000
3: kd> knL
 # Child-SP          RetAddr               Call Site
00 fffffa0f`6c6b3770 fffff807`8b2e04bc     SerPL011!PL011BreakPoint+0x18
01 fffffa0f`6c6b3780 fffff807`8b2dfbc4     SerPL011!PL011pDeviceParseResources+0x1b4
02 fffffa0f`6c6b3800 fffff803`dfb5df00     SerPL011!PL011EvtDevicePrepareHardware+0x34
03 fffffa0f`6c6b3860 fffff803`dfb6bc74     Wdf01000!FxPnpDevicePrepareHardware::InvokeClient+0x30
04 fffffa0f`6c6b3880 fffff803`dfb5c854     Wdf01000!FxPrePostCallback::InvokeStateful+0x144
05 (Inline Function) --------`--------     Wdf01000!FxPnpDevicePrepareHardware::Invoke+0x54
06 fffffa0f`6c6b38f0 fffff803`dfb5b1cc     Wdf01000!FxPkgPnp::PnpPrepareHardware+0x164
07 fffffa0f`6c6b3940 fffff803`dfb5afc4     Wdf01000!FxPkgPnp::PnpEventHardwareAvailable+0xdc
08 fffffa0f`6c6b3990 fffff803`dfb5ad8c     Wdf01000!FxPkgPnp::PnpEnterNewState+0x184
09 fffffa0f`6c6b3a10 fffff803`dfb5aad0     Wdf01000!FxPkgPnp::PnpProcessEventInner+0x27c
0a fffffa0f`6c6b3a90 fffff803`dfb74428     Wdf01000!FxPkgPnp::_PnpProcessEventInner+0x30
0b fffffa0f`6c6b3ab0 fffff803`dfb745b8     Wdf01000!FxEventQueue::EventQueueWorker+0xb0
0c fffffa0f`6c6b3af0 fffff803`dc849cdc     Wdf01000!FxWorkItemEventQueue::_WorkItemCallback+0x28
0d fffffa0f`6c6b3b10 fffff803`dc849394     nt!IopProcessWorkItem+0x8c
0e fffffa0f`6c6b3b70 fffff803`dc97b000     nt!ExpWorkerThread+0x1d4
0f fffffa0f`6c6b3d30 fffff803`dc807de4     nt!PspSystemThreadStartup+0x50
10 fffffa0f`6c6b3d90 00000000`00000000     nt!KiStartSystemThread+0x24

PL011 というのは、ここを見るとシリアル通信の規格で、カーネルデバッグに使っている GPIO の方ではなく、Bluetooth に関係があるようなことが書いてある気がします。当該ドライバーのソースコードは GitHub 上にありました。残念ながら ARM64 アセンブリがチンプンカンプンなのでどの assert がヒットしたのかがすぐに分からないわけですが。もしかしたら contribute チャンスなのかもしれないので、これも TODO リスト入りです。とりあえずは続行したら普通に起動したからヨシ!

その後適当にコマンドを打つなど。やはりシリアル経由での .reload は遅い。1394 が復活して欲しい。

0: kd> g
Breakpoint 0 hit
win32kfull!xxxCreateWindowEx:
fffff70c`55f1f3c0 d503237f pacibsp

2: kd> !process -1 0
PROCESS ffffd38b8e928080
    SessionId: 1  Cid: 1394    Peb: f842a99000  ParentCid: 0dc8
    DirBase: 14563e000  ObjectTable: ffffa90cd35535c0  HandleCount:  45.
    Image: mn_arm64.exe

2: kd> .reload
Connected to Windows 10 21390 ARM 64-bit (AArch64) target at (Sat Jun 26 09:58:58.727 2021 (UTC - 7:00)), ptr64 TRUE
Loading Kernel Symbols
..................

Press ctrl-c (cdb, kd, ntsd) or ctrl-break (windbg) to abort symbol loads that take too long.
Run !sym noisy before .reload to track down problems loading symbols.

.............................................
................................................................
.................................
Loading User Symbols
...............

Press ctrl-c (cdb, kd, ntsd) or ctrl-break (windbg) to abort symbol loads that take too long.
Run !sym noisy before .reload to track down problems loading symbols.

.
Loading unloaded module list
.........
2: kd> knL
 # Child-SP          RetAddr               Call Site
00 fffffa0f`6eee0800 fffff70c`55f0e998     win32kfull!xxxCreateWindowEx
01 fffffa0f`6eee0800 fffff70c`55cdd074     win32kfull!NtUserCreateWindowEx+0x578
02 fffffa0f`6eee0990 fffff803`dc808360     win32k!NtUserCreateWindowEx+0x64
03 fffffa0f`6eee09f0 fffff803`dc807fd8     nt!KiSystemServiceCopyEnd+0x38
04 fffffa0f`6eee0a50 00007ff8`403476f4     nt!KiSystemServiceExit
05 000000f8`42cff290 00007ff8`40bcc96c     win32u!NtUserCreateWindowEx+0x4
06 000000f8`42cff290 00000000`00000000     USER32!CreateWindowExW+0x80c
2: kd> r
 x0=0000000000000000   x1=fffffa0f6eee0860   x2=fffffa0f6eee08c0   x3=fffffa0f6eee08d0
 x4=0000000000cf0000   x5=0000000080000000   x6=0000000000000000   x7=00000000000001e6
 x8=000000000000012c   x9=fffffa0f6eee0878  x10=fffff803dc61e930  x11=00007ff7aa970f20
x12=00007ff7aa970f42  x13=000000000000007f  x14=0000000000000020  x15=0000000000000080
x16=0000000000000000  x17=fffffa0f6eee0878  x18=ffffd38b8e928700  x19=00007ff7aa971560
x20=0000000000000000  x21=fffff74881723390  x22=0000000000cf0000  x23=0000000000000000
x24=fffff748806cb010  x25=0000000000000000  x26=0000000000000000  x27=0000000000000000
x28=0000000000000000   fp=fffffa0f6eee0850   lr=fffff70c55f0e998   sp=fffffa0f6eee0800
 pc=fffff70c55f1f3c0  psr=80000144 N--- EL1
win32kfull!xxxCreateWindowEx:
fffff70c`55f1f3c0 d503237f pacibsp
2: kd> u .
win32kfull!xxxCreateWindowEx:
fffff70c`55f1f3c0 d503237f pacibsp
fffff70c`55f1f3c4 a9ba7bfd stp         fp,lr,[sp,#-0x60]!
fffff70c`55f1f3c8 a90153f3 stp         x19,x20,[sp,#0x10]
fffff70c`55f1f3cc a9025bf5 stp         x21,x22,[sp,#0x20]
fffff70c`55f1f3d0 a90363f7 stp         x23,x24,[sp,#0x30]
fffff70c`55f1f3d4 a9046bf9 stp         x25,x26,[sp,#0x40]
fffff70c`55f1f3d8 f9002bfb str         x27,[sp,#0x50]
fffff70c`55f1f3dc 910003fd mov         fp,sp
2: kd> !cpuinfo
CP Model Revision   Manufacturer     Features         Speed
 0  D08   r00p03    A                0000000000000000  1500 Mhz
 1  D08   r00p03    A                0000000000000000  1500 Mhz
 2  D08   r00p03    A                0000000000000000  1500 Mhz
 3  D08   r00p03    A                0000000000000000  1500 Mhz

以上、どこまで実用に値するのかは分かりませんが、カーネルデバッグ環境を作ることができました。せっかくなので SD カードをもう一枚買って Linux も入れてみましょうかね。