Kernel-debugging Windows 10 ARM64
はじめに
Qiita に書くにはしょぼいネタなので久々にこちらに書きます。
先日、「そうだ ARM64 やろう」と思いついて Raspberry Pi を買いました。以前 QEMU で ARM64 の Linux カーネルを触ったことはあるのですが、実機で試しておきたかったためです。その当時 QEMU 上で ARM64 版 Windows を動かそうとも試みましたが、どうやらドライバーが対応していないらしく起動しませんでした。今回無事カーネル デバッグ環境を作るところまでできたので、そこまでの過程をまとめました。
デバイス選定
まずはハードウェアの選定です。Raspberry Pi を買うことが動機ではなかったので、手始めに既製品のパソコンも探したのですが、どれも高すぎました。$1,000 超えは論外で、学習用途なことを考えると $350 も厳しい。
- Microsoft Surface Pro X - $999.99
- Lenovo IdeaPad Flex 5G (14”) - Iron Grey - $1,349.99
- HP Elite Folio 13.5 inch 2-in-1 Notebook PC - $1,889.00
- Samsumg Galaxy Book Go - $349
困っていたところ、ラズベリーパイでいけるらしいという記事を発見。いいじゃん。
ラズパイ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 も入れてみましょうかね。