Blog posts by @retrage

mirror of https://retrage.github.io/

OSはどうやってP-coreとE-coreを使い分けているのか

Alder Lake以降のIntel CPUでは、P-coreとE-coreの2種類のコアが搭載されている。 P-coreは性能重視、E-coreは省電力重視という位置づけで、OSがうまくこれらのコアを使い分けることで、消費電力と性能の両立が図られている。 ここまでの話は広く知られているが、実際にどのようにしてOSに対してコアの使い分けをさせているのかの実装レベルでの解説は (少なくとも日本語では) ほぼ存在しないようなので調べてみた。

続きを読む

VisionFive 2をJTAGデバッグ

VisionFive 2 (以下、VF2) はStarFive Technologyが開発したSBCである。名前からわかるように、RISC-VのSoCを搭載している。ここではVF2をJTAGデバッグする方法を簡単にまとめる。

なお、この記事の内容は以下のVF2のフォーラムの投稿を参考に、具体的な手順を整理したものである。

続きを読む

2022年にやったこと

こういう不安を感じたので、心の安寧を取り戻すために今月と今年に何をやったのかをまとめてみる。なお、自分の中で「取り組んでいることを話題にすると完成しなくなる」ジンクスがあるので、いまやっていることはここでは書かない。

GDB Support for Cloud Hypervisor

  • 1月~2月

地味ではあるが、これを作ったおかげでRust Hypervisor Firmwareデバッグが効率的になった。もともとcrosvmのGDB対応を参考に作っていたが、gdbstub 0.6.0でAPIが大きく変わったのでそれに合わせて実装を見直してmulti-core対応などを行なった。他にもkvm-ioctlsにPRを送る必要があったりと、思ったよりいろいろやった。自分が入れたGDB対応はx86_64のみだったが、あとからaarch64対応も他の方が入れてくれた。

Rust Hypervisor Firmware Integration for m1n1

  • 4月~5月

aarch64/LinuxではUEFIを使った起動を採用しているが、Apple Siliconでは従来のIntel MacのようにEFI環境が用意されていない。このギャップを埋めるために、Asahi Linuxではm1n1というブートローダからu-bootを起動し、u-bootの提供するEFI環境を使って(GRUBを経由して)Linux kernelを起動する。 ここで気になったのは、「u-bootでEFI環境を提供する代わりに、Rust Hypervisor Firmwareをm1n1に直接統合することでLinux起動できないか」ということである。幸い、m1n1にはRustで書かれたchainloadがあったため、これを入れ替える形でRHFをm1n1に統合できた。なお、RHFはx86_64向けだったので、これが初めてのaarch64対応でもあった。GRUBのロードと起動まではできたものの、本来であればGRUB CLIを操作して調査するところだが、USBキーボードのドライバがm1n1にはないので動かせないのでデバッグできず、そのまま放置してある。

UEFI Firmware Vulnerability Detector for Ghidra

  • 3月~6月

IDA ProプラグインであるefiXplorerには静的解析による典型的なSMMの脆弱性スキャナが実装されている。Iこの機能がどのように作られているのか知りたかったのでGhidra向けに実装した。Ghidraで使われている中間表現であるpCodeがかなり高レベルな情報まで復元して表現してくれるため、かなり実装がしやすかった。簡単なData Flow Analysisも実装したが、比較的書きやすかった覚えがある。 ついでにこの脆弱性スキャナを簡単に使えるように、GitHub ActionsにHeadless Ghidraを実行するactionsを作成して、検出結果をレポートとして報告するようにしてみた。

まとめ

これぐらいが今年のうちに一区切りついた、もしくは飽きたことだと思う。期間を見るとわかるが、7月以降やったことが書かれていない。これは、自分の中でまだ途中だったりサボっていたりしているためである。 来年は良い年であるように。

Allwinner NezhaにJTAGで接続する

Allwinner NezhaはD1という64-bit RISC-VなSoCが載ったSBCである。Linuxがちゃんと動くRISC-Vマシンとしてはかなり安価なため一部で人気がある。 ここではこれにJTAGで接続してみた話をメモ程度に書いておく。なお、今回は接続してOpenOCDで認識できた程度でその先のGDBでのデバッグはできていない。

JTAGの端子について

NezhaにはUARTの端子が DEBUG として用意されているが、JTAGには専用の端子が用意されていない。その代わりにmicroSDの端子と共通になっている。D1 SoCのデータシート4.3 GPIO Multiplex Functionから対応するピンと役割を読むと、PF0-PF6のI/Oがあり、SDカードのI/Oとしての役割がSDC0-*としてあり、その隣にJTAG関連のI/OがJTAG-*として記載されている。詳しくは回路図などを参照してもらいたいが、まとると以下のようになっている。

PF0: JTAG-MS - SDC0-D1  <--> DAT1
PF1: JTAG-DI - SDC0-D0  <--> DAT0
PF3: JTAG-DO - SDC0-CMD <--> CMD
PF5: JTAG-CK - SDC0-D2  <--> DAT2

これらの端子を引き出すために適当な延長アダプタを利用した。理想的には以下のようなブレークアウトボードを使うのが望ましい。

JTAGアダプタ

こちらの記事ではSipeed RV-Debugger-Plus (BL702C-A0)という別のRISC-VベースのJTAGアダプタを利用している。今回は手元にあったBus PirateをJTAGアダプタとして利用した。

Bus Pirate JTAG connections for OpenOCDにある通り以下のように接続した。

BP   - JTAG
GND  - GND
MOSI - TDI
MISO - TDO
CLK  - TCK
CS   - TMS 

OpenOCDの実行

こちらの設定ファイルをベースに以下のようにBus Pirate向けに書き換えた設定ファイルを作成した。

# Based on: https://github.com/orangecms/RV-Debugger-BL702/blob/main/tools/openocd/openocd-usb-sipeed.cfg

source [find interface/buspirate.cfg]

buspirate_vreg 0
buspirate_mode open-drain
buspirate_pullup 1

buspirate_port /dev/tty.usbserial-AH03FKZ4

transport select jtag
# adapter speed 1000

set _CHIPNAME riscv
#jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x20000001
#jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x0
# wrong?
# jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x00185900
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x08052b43

set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME.0 riscv -chain-position $_TARGETNAME
#$_TARGETNAME.0 configure -work-area-phys 0x80000000 -work-area-size 10000 -work-area-backup 1
#$_TARGETNAME.0 configure -work-area-phys 0x50000000 -work-area-size 32768 -work-area-backup 0

riscv set_prefer_sba on
# riscv set_mem_access progbuf

FEL modeへの切り替えとJTAGの有効化

AllwinnerのSoCにはFEL modeというモードがあり、起動時にこのモードに入ることでJTAGの有効化を含むさまざまな低レベルな操作が可能となっている。 FEL modeではNezhaのOTG USB経由でホストから操作を行う必要がある。今回はxboot/xfelというFELのツールをホストで動かしてJTAGを有効にした。

具体的には次の手順でFEL modeに入ってJTAGを有効化する。

  1. Nezhaにある FEL ボタンを長押しする
  2. 1.のままNezhaのOTG USBとホストを接続する
  3. 2秒待ってボタンを離す
  4. これでFEL modeに入る
  5. ホストで ./xfel sid を実行して値が取れることを確認
  6. ホストで ./xfel jtag を実行してJTAGを有効化
  7. (ホストで ./xfel ddr ddr3を実行してDRAMを初期化)

JTAGが有効になったら上記の設定ファイルでOpenOCDを実行する。

openocd -f openocd-buspirate.cfg

うまくいくと以下のように riscv.cpuJTAG tapが見える。

f:id:retrage01:20220116131141p:plain
Allwinner Nezha JTAG OpenOCD

ただし、現時点では

Error: riscv.cpu: IR capture error; saw 0x1e not 0x01

とあるようにエラーがあり、GDBでの接続ができていない。

参考文献

Apple File Systemの下にはEFI driverが埋まっている

Apple File System (APFS)はAppleが自社製品向けに開発したファイルシステムである.APFSの仕様は公開されており以下で参照できる.

その目次の中で特に興味を引いたのが"EFI Jumpstart"の章である.現代的なデバイスではEFIを含めブートローダファイルシステムを参照してOSを起動する.このとき当然ながらブートローダはそのファイルシステムを扱える必要がある.特にEFIでは仕様上対応していなければならないのはEFI System Partition (ESP)で使われるFATのみでその他のファイルシステムが事前にサポートされていることは期待できない.このため例えばWindowsであればESPに配置されたWindows専用のブートローダNTFSをサポートすることでカーネルの起動を行なっている.

これに対してmacOSではAPFS自身がAPFSのEFI driverをパーティションのブロックに直接埋め込む形で提供するという別のアプローチをとっている.ドキュメントによれば

This design intentionally simplifies the steps needed to boot, which means the code needed to boot a piece of hardware or virtualization software can likewise be simpler.

とのことで起動プロセスをシンプルにすることが目的のようである.

どのようにEFI driverが埋め込まれているのかをみていく. 最初の前提としてディスクのGPTエントリを読み,以下のUUIDのようなAPFSのパーティションがあることがわかったとする.

#define APFS_GPT_PARTITION_UUID ”7C3457EF-0000-11AA-AA11-00306543ECAC”

APFSパーティションは一つのAPFS containerオブジェクトとして存在し,その先頭にcontainerの情報を持ったcontainer superblockが配置されている.

container superblockを表すnx_superblock_tにはnx_efi_jumpstartというEFI Jumpstartの情報を持ったnx_efi_jumpstart_tへの物理アドレス(containerの先頭からのAPFSでのブロックサイズ単位でのオフセット)のフィールドが用意されている.

その参照先には以下のようなnx_efi_jumpstart_tがある.

typedef struct {
  obj_phys_t  nej_o;
  uint32_t    nej_magic;
  uint32_t    nej_version;
  uint32_t    nej_efi_file_len;
  uint32_t    nej_num_extents;
  uint64_t    nej_reserved[16];
  prange_t    nej_rec_extents[0];
} nx_efi_jumpstart_t;

このnej_rec_extentsにあるブロックを読むことでAPFSパーティションに直接埋め込まれたEFI driverを読み込むことができる.

ユーザ空間で以上のようなことを行いraw disk imageからEFI driverを抽出する簡単なアプリケーションを実装した.本来は色々と検証を行う必要があるが,ここでは省略した.

抽出したバイナリは以下のように確かにPE32+のEFI driverとして認識されている.

$ file apfs.efi
apfs.efi: PE32+ executable (EFI boot service driver) x86-64 (stripped to external PDB), for MS Windows

せっかくなので抜き出したEFI driverを覗いてみる.バイナリのハッシュは1e780147f2cee614ab7e9a63c4e86525f06c5a18d72a6b8ec572752ffd95dea0である..debugセクションの指す9E9FChをみると"MTOC"の文字列と"apfs.efi.macho"という文字列が見えるのでこのバイナリの名前は"apfs.efi"でMach-OバイナリをApple謹製のmtocでPE32+に変換して生成されたようである.

ちなみにClover EFI bootloaderでは先にたどっていったような手順でapfs.efiをロードするEFI driverがあり,これでAPFS対応を行なっているようである.

https://sourceforge.net/p/cloverefiboot/code/HEAD/tree/FileSystems/ApfsDriverLoader/

話を元に戻してバイナリをみていくと,.textセクションの先頭に"2021/08/30"や"06:36:21"のような日時があるのでこれがこのバイナリがビルドされた日時だと考えられる.対象としたmacOS Bit Sur 11.6は2021年9月13日にリリースされたので少なくとも2週間程度前にはビルドされたようである. 他にもみどころはたくさんありそうだがこれぐらいにしておく.

以上はIntel Mac上での話である.ではARM64ベースのm1 Macの場合はどうだろうか?

$ uname -a
Darwin 7261.local 20.5.0 Darwin Kernel Version 20.5.0: Sat May  8 05:10:31 PDT 2021; root:xnu-7195.121.3~9/RELEASE_ARM64_T8101 arm64
$ ./dumper ~/Desktop/recovery.img
superblock at 280000000
nx_block_size: 0x1000
nx_block_count: 0x13fff5
nx_efi_jumpstart: 0x0
APFS EFI Jumpstart not found

残念ながら答えは否のようである.すでに広く知られているように,m1 MacではEFIではなくiPhoneなどと同じiBootから起動するカスタマイズされたファームウェアであるため,サードパーティでの実行環境を考慮しなければEFI Jumpstartの機能は不要であるためだと考えられる.へその緒のようにあったら面白かったのだが.ということでm1 MacのAPFSではnx_efi_jumpstart_tnx_efi_jumpstartは使われないフィールドとなってしまったようである.

というわけでIntel MacのAPFSの下にはEFI driverが埋まっているのでIntel Macをお持ちの方は酒宴でも開きましょう.

UEFI向け9P File Systemを作ってクラウドからネットワークブートできるようにした

UEFI向け9P File Systemを実装した. これにより9Pサーバからネットワークブートができるようになった. さらにFUSEと組み合わせることで少ない労力で9Pサーバ経由で クラウドからネットワークブートができるようになった.

ソースコードと発表資料と発表の録画は以下で公開している.

続きを読む

EDK2におけるDebugPrintErrorLevel

EDK2のコードにはDebugPrint()が多く埋め込まれている. この関数は第一引数にErrorLevelをとり, ビルド時に与えるPcdDebugPrintErrorLevelを変えることによりデバッグ出力を制御できる. この値は*.dscに以下のような記述をすることで設定できる.

[PcdsFixedAtBuild.common]
  gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000000

このとき,どのような値を設定すればいいのかわからなくなるのでメモとしてまとめた.

続きを読む