UEFIのSMM: デバッグ視点からのSystem Management Mode
SMMは線形のブートフェーズではなく、分離された実行モード。SMIトリガー、SMRAM、ハンドラーのタイミング、SmmReadyToLock、そしてSetVariableがサイレントに失敗するデバッグ方法。
firmwareエンジニアがSMMを理解する必要がある理由
POSシステムのBIOSカスタマイズをしていると、あるタイプのバグ報告をかなり頻繁に見ます: 「端末が変な動きをしている——今日は設定が合っているのに翌朝にはデフォルトに戻っている」というものです。自動リセットか何かのマルウェアを疑います。どちらでもありません。
そういったケースのほとんどはSMMにたどり着きます——より正確には、Setup UIからSPI flashまでのパスがSMMを通っていて、明確なエラーメッセージなしにどこかでブロックされています。
BIOSの作業をしていて以下のような状況に遭遇したら:
- BIOS設定を保存してもリブート後に元に戻る
- OSからのBIOSフラッシュが成功したと報告されるが何も変わらない
- OSが起動してからfirmwareサービスの一部が動かない
SetVariable()がEFI_ACCESS_DENIEDまたはEFI_WRITE_PROTECTEDを返す
問題がSMM (System Management Mode)にある可能性が高いです。
SMMは学術的な難解な概念ではありません。多くのプラットフォームがNVRAM/SPI flashへの書き込みを保護し、特定のハードウェアイベントを処理し、セキュリティポリシーを強制するために使うレイヤーです。それが失敗または誤設定されると、症状は曖昧になりがちで——特定の操作だけが動かなくなり、明確なエラーメッセージがありません。
SMM: シンプルなメンタルモデル
最もわかりやすい考え方: SMMは、SMI#と呼ばれる特殊な信号によってアクティブになる、OSから完全に隔離された実行環境です。
CPUがSMI#を受け取ると:
- CPUはすべてのことを止めます——動いているOSも含めて
- Save Stateと呼ばれる特殊なメモリ領域にCPU状態全体を保存します
- 適切にロックされた後はOSが読み書きできないメモリ領域であるSMRAMにジャンプします
- SMMハンドラーを実行します
RSM命令でリターン。CPU状態が復元されOSが継続します
OSが動いている
CPUは通常のOSまたはアプリケーションコードを実行している
SMIが発生
ハードウェアイベントまたはソフトウェアトリガーがSystem Management Interruptを生成する
CPU状態を保存
レジスター状態がSave State領域に書き込まれる
SMMに入る
CPUがSMRAM内でSMM Core、Dispatcher、ハンドラーを実行する
SMMからリターン
CPUが状態を復元し、OSが何事もなかったように継続する
OSはSMMが動いたことを知りません。SMMが遅すぎて顕著なレイテンシーを引き起こさない限り、プロセス全体がOSには見えません。
2つの一般的なSMIタイプ
ハードウェアSMI
電源ボタン、サーマルイベント、GPIOトリガーなどのハードウェアイベントに応じてチップセットが生成します。プラットフォームとボード固有。
ソフトウェアSMI (SW SMI)
APMコントロールポート、通常0xB2に書き込むことで生成します。
mov al, 0x42 ; コマンドID、プラットフォーム固有
out 0xB2, al ; SW SMIをトリガー
out命令の後、チップセットがSMI#をアサートし、CPUがSMMに入り、dispatcherがコマンド0x42に登録されたハンドラーを探します。
// firmware内でSW SMIハンドラーを登録する
SmmSwDispatch2->Register (
SmmSwDispatch2,
MySwSmiHandler,
&Context, // SwSmiInputValue = 0x42 を含む
&DispatchHandle
);
SMM関連のPOST code
AMI Aptio 5.xでは、SMMの初期化は2つのフェーズで起きます。
| コード | フェーズ | 意味 |
|---|---|---|
| 0x36 | PEI | CPUポストメモリ初期化、SMM初期化 |
| 0x6A | DXE | North Bridge DXE SMM初期化 |
| 0x71 | DXE | South Bridge DXE SMM初期化 |
POST codeが0x6Aまたは0x71で停止している場合、問題はDXEにおけるSMM初期化に関係しています——通常はSMRAMが正しく割り当てられないか、SMM Coreのロードに失敗しています。
デバッグビルドのあるボードでは、シリアルポートに次のようなメッセージが出力されることがあります。
SMM Core: SMRAM not found
SMM Core: Failed to allocate SMRAM
SMRAM: 隠されたメモリ領域
SMRAMはfirmwareがある時点でOSとDXEから「隠す」DRAMの一領域です。SMMRAMが適切にcloseおよびlockされた後、SMM mode中のCPUだけがその領域を読み書きできます。
SMMRAMのライフサイクル:
SMRAMをreserve
SMMRAMが発見されてreserveされるが、まだ完全にはlockされていない
SMM Coreをロード
SMM CoreとSMMドライバーがSMRAMにロードされる
ハンドラーを登録
SMMドライバーがlock前にハンドラーを登録する
SmmReadyToLock
SMRAMがclose/lockされ、セキュリティポリシーが適用され始める
ランタイム使用
SMRAMがlock済み。OSは通常のRAMとしてアクセスできない
SMRAMがlockされた後、新しいSMMモジュールをロードすることはできません。 これがハンドラーをSmmReadyToLock前に登録しなければならない理由です。
非常に多いミス: 開発者が新しいSMMドライバーを追加したが、DEPEXが間違っているかdispatch順が間違っているためにSmmReadyToLock後にdispatchされてしまう。結果としてハンドラーは登録されず、サービスはサイレントに何もしない——エラーもなく、ただ沈黙。
SmmReadyToLock: 引き返せないポイント
EDK II/PIスタイルのfirmwareでは、SmmReadyToLockは通常DXEでシグナルされるprotocolまたは通知として表現されます。SMMがまもなくlockされることを示します。このシグナルが発火すると:
- SMRAMがcloseおよびlockされる
- 新しいSMMモジュールをinstallできなくなる
- 多くのセキュリティポリシーが適用される
DXE実行
SMMドライバーはまだdispatchしてハンドラーを登録できる
ハンドラーを登録
lock前に登録 — OK
ハンドラーを登録
lock前に登録 — OK
SmmReadyToLock
SMRAMがclose/lock、ポリシーが適用される
遅くdispatch
ハンドラーを登録するには遅すぎ — サービスがサイレントに何もしない
SmmReadyToLock周辺のタイミングバグはBIOS開発で最も困難なバグの一つです。なぜなら:
- assertや明確なエラーメッセージがない
- ハンドラーは登録されず、サービスはサイレントに何もしない
- 何かドライバーがdispatch順を変えたときにのみ再現する
SMM Communication: SMMと安全に通信するパス
DXEドライバーまたはfirmwareコンポーネントがSMM内のサービスをリクエストする必要があるとき、通常はSMM Communication Protocolを通じます。OSが動いている状態では、SetVariable()のような多くの操作がUEFI Runtime Servicesを通じ、その下のRuntime variable serviceがSMIをトリガーしてリクエストをSMMに転送する可能性があります。OSはDXEドライバーのようにSMM Communication Protocolを直接呼びません。
firmwareコンポーネント
DXEドライバーまたはfirmwareコンポーネントがSMRAMの外にCommBufferを作成する
SmmCommunication
SmmCommunication->Communicate()を呼ぶ
SMIをトリガー
firmwareパスがSMIをトリガーしてSMMハンドラーに入る
ハンドラーが処理
ハンドラーが入力を検証し、リクエストを処理し、結果をCommBufferに書く
RSM
SMMがリターンした後にDXEドライバーが結果を読む
OSがRuntime Serviceを呼ぶ
例: efibootmgrまたはOSコンポーネントがSetVariable()を呼ぶ
Runtime variable service
ExitBootServices後にRuntime Servicesがリクエストを受け取る
SMIをトリガー
Runtime variableパスがリクエストをSMMに転送する可能性がある
SMM variableハンドラー
ハンドラーがポリシーを適用し、有効であればNVRAM/SPI flashに書く
CommBufferは必ずSMRAMの外に置かなければなりません。 これはすべてのSMMハンドラーで必要な検証です。
// 擬似コード — プロダクションコードではない
if (IsAddressInSmram(CommBuffer, CommBufferSize)) {
return EFI_SECURITY_VIOLATION;
}
この検証を省略すると、OSレベルの攻撃者がSMRAMを指すCommBufferを作成して機密データを読み書きできるようになります——これはよく知られたクラスのSMM脆弱性です。
SMMでのシリアルログ
SMMでのデバッグはDXEより難しいです。理由:
- SMMは隔離された環境で動いている。DXEで通常使えるインフラの一部がここではアクセスできない
DEBUG()マクロは使えますが、SMM用に適切に設定されたDebugLibが必要
// SMMドライバー内で、セットアップ後
DEBUG ((DEBUG_INFO, "SMM Handler: received command 0x%x\n", Command));
DEBUG ((DEBUG_INFO, "SMM Handler: CommBuffer at 0x%p, size %d\n",
CommBuffer, CommBufferSize));
シリアルログでSMMをデバッグするときに探すパターン:
Communicateを呼ぶDXEドライバーからのログ:
VariableSmm: Communicate called, command = SetVariable
VariableSmm: Waiting for SMM...
VariableSmm: SMM returned EFI_SUCCESS
SMMハンドラーからのログ (SMMにシリアルログが設定されている場合):
SmmVar: Handler entered, GUID = ...
SmmVar: SetVariable 'BootOrder', size = 8
SmmVar: Writing to NVRAM...
SmmVar: Done
DXE側がCommunicateを呼んでいるのにSMMハンドラーのログが見えない場合、3つのことが疑われます: ハンドラーが登録されていない、SMIトリガーが失敗した、またはログレベルが間違っている。
デバッグ日記: 設定を保存しても効果がない
これは一度以上経験しました。あるPOSシステムで、いくつかのポリシー——ブートタイムアウト、USBポリシー、ネットワークスタック——を変更して新しいfirmwareをフラッシュした後、お客様の技術者がSetupに入って設定を変え、保存して終了します。リブート後、すべてがデフォルトに戻っています。また変えます。また同じ。
最初の直感は攻撃的なデフォルト復元ロジックがどこかにあるのではないかと思いました。しかしシリアルログを有効にすると現れたのは: SetVariable() = EFI_WRITE_PROTECTEDという行、サイレントに、ポップアップもなく、UIにエラーメッセージもなく。
レイヤーごとのアプローチ:
レイヤー1: HII/Setup UIがSetVariableを呼んでいるか?
DXEのvariableサービスをログします。ユーザーが保存した後にSetVariableが呼ばれているのが見えなければ、問題はHIIコールバックまたはSetup Browserにあります。
レイヤー2: SetVariableは呼ばれているがエラーを返しているか?
VariableSmm: SetVariable 'SetupData' = EFI_ACCESS_DENIED
ここでのEFI_ACCESS_DENIEDは通常次を意味します: variableのattributeが認証された書き込みを要求している、またはSMMポリシーがDXEまたはOSからの書き込みをブロックしている。
レイヤー3: SetVariableは成功するがSPI flashが書かれない
VariableSmm: SetVariable success
SpiFlash: WriteBlock failed: write protection enabled
firmwareはRAMのNVRAMキャッシュへの書き込みには成功しますが、フラッシュ書き込み保護が正しくunlockされていないためSPI flashへのフラッシュが失敗します。
レイヤー4: SPI flashに書かれたが破損している
あまり多くはありませんが、variable storeがいっぱいか書き込み中の電源断によって起きる可能性があります。
チェックリスト: 設定が保存されない
SMMハンドラーでできることとできないこと
SMMは高い特権を持つ環境ですが、DXEよりも制約が多い環境でもあります。DXEで普通に見えることがSMM内でクラッシュや未定義の動作を引き起こす可能性があります。
許可されること
通信バッファの検証と処理:
// OK: 使用前に検証する
if (!IsBufferOutsideMmram(CommBuffer, CommBufferSize)) {
return EFI_SECURITY_VIOLATION;
}
CopyMem(&LocalCopy, CommBuffer, sizeof(MY_REQUEST));
MMIOを介したハードウェアレジスターの読み書き:
// OK: SMMでMMIOを介してハードウェアレジスターにアクセスする
UINT32 Val = MmioRead32(ChipsetBase + REG_OFFSET);
MmioWrite32(ChipsetBase + REG_OFFSET, Val | ENABLE_BIT);
SMM安全なメモリサービスの使用、CPU Save Stateの読み取り、シリアルを介したログ (DebugLibがSMM用に設定されている場合)。
許可されないこと、そしてその結果
1. Boot Servicesが無効化された後に呼ぶ
ExitBootServices()後、すべてのBoot Servicesはなくなります。SMMハンドラーがまだgBS->AllocatePool()やBoot Serviceを呼ぼうとすると:
未定義の動作 — 通常CPUの例外またはシステムのハング
gBSポインターはメモリ内にまだ存在しますが、それが指すコードは解放されているか有効でなくなっています。
2. SMMハンドラーからDXE protocolを呼ぶ
SMMハンドラーはDXE protocolを直接locateして使うことができません。SMMは隔離された環境で動いており、DXEのhandle databaseは通常の方法ではここからアクセスできません。
3. SMM mode外からSMRAMにアクセスする
SMRAMがlockされると、OSまたはDXEコードからSMRAM領域への読み書きはハードウェアによって拒否されます。これは意図的なものです。ハードウェアがOSによるSMMのスパイやパッチを防ぎます。
4. SMMハンドラー内からSMIをトリガーする (ネストされたSMI)
ネストされたSMIは非常にプラットフォーム依存です。多くのチップセットはCPUがSMM中のときにSMIをマスクまたは延期します。ネストされたSMIに依存するハンドラーを設計しないでください。
5. 状態の保存/復元なしに浮動小数点またはSIMDを使う
SMMに入るときのCPU Save StateはデフォルトではFPU/SSE/AVXレジスターを保存しません。ハンドラーが保存と復元なしにfloatまたはSIMD演算を使うと:
OS/アプリケーションがFPUレジスターデータを失う
OSレベルでのクラッシュ、firmwareレベルではない
SMMにエラーがないため追跡が非常に困難
6. CPUを長時間ブロックする長い操作
SMMは実行中にマルチコアシステムのすべてのCPUをブロックします。ハンドラーがタイムアウトなしにフラッシュ書き込みを待つなど、数ミリ秒かかることをすると:
OSスケジューラーがストール、ウォッチドッグが発火する可能性
システムハングまたはNMI
ラップトップでは: バッテリー/サーマル管理が中断される
まとめ: SMMで忘れてはならないこと
| 症状 / トピック | 重要なポイント | 確認すること |
|---|---|---|
| ハンドラー登録のタイミング | SmmReadyToLock前でなければならない | lock後はハンドラーを登録できず、サービスはサイレントに何もしない |
| CommBufferの場所 | SMRAM外でなければならない | 検証の省略がSMM脆弱性になり得る |
| POST 0x6A / 0x71でハング | DXEでSMM initが失敗 | SMRAMの割り当てとSMM Coreのロードを確認 |
| POST 0x36でハング | PEIフェーズのSMM initが失敗 | CPU/チップセットのSMM設定に関係 |
| SetVariable = EFI_ACCESS_DENIED | ポリシーまたは保護がブロック | Variableのattribute、認証ポリシー、SMMポリシー、フラッシュ書き込み保護 |
| 設定がリブート後に元に戻る | UIだけの問題ではない | HII → SetVariable → SMM → SPI flashをデバッグする |
参考資料
- AMI Aptio 5.x Status Codes, Public Document — SMM initのPOST codeレンジ
- EDK II PiSmmCore, GitHub — SMM Coreソース、公開リファレンス
- UEFI PI Specification — Volume 4: 公式のSMM spec
- EDK II Debugging, TianoCore Wiki — SMMドライバーのデバッグ設定
- Lauterbach UEFI H2O Awareness Manual — SMMデバッグサポートを持つデバッグツール
次に読む
- SMMとは?
- SMIとは?
- SMRAMとは?
- SW SMIとは?
- SMM Communicationとは?
- SmmReadyToLockとは?
- DXEフェーズ: ドライバー、Protocolと何も動かないときのログの読み方 (関連記事)
この記事は役に立ちましたか?
ファームウェア、BIOS/UEFI、組み込みシステムを学んでいる人に共有できます。
Nội dung liên quan
Một số bài viết, ghi chú hoặc project có liên quan đến nội dung bạn vừa đọc.
SMMとは?
SMMはfirmwareがOS管理外の機密タスクを処理する特殊CPUモード。variable書き込みハング、フラッシュ失敗、セキュリティ問題のデバッグに必要。
SMIとは?
SMIはCPUをSMMに入れてハンドラーを実行させる割り込み。SW、GPIO、タイマー、チップセットなど複数のソースがある。レイテンシースパイクとSMM通信のデバッグに必要。
ACPIとは: firmwareがGPIOをトグルして電源を切ってはいけない理由
なぜfirmwareがGPIOをトグルして電源を落としてはいけないのか。ACPIはfirmwareが書きOSが読む契約: テーブル、AML、電力状態、壊れたスリープ/ウェイクのデバッグ。
Đọc thêm về BIOS/UEFI
Khám phá các bài viết về BIOS/UEFI, embedded firmware, debugging và system-level thinking.