UEFIのSMM: デバッグ視点からのSystem Management Mode

SMMは線形のブートフェーズではなく、分離された実行モード。SMIトリガー、SMRAM、ハンドラーのタイミング、SmmReadyToLock、そしてSetVariableがサイレントに失敗するデバッグ方法。

更新 4 分で読めます
Đọc bằng 日本語 Tiếng Việt English
BIOS / UEFI cover

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#を受け取ると:

  1. CPUはすべてのことを止めます——動いているOSも含めて
  2. Save Stateと呼ばれる特殊なメモリ領域にCPU状態全体を保存します
  3. 適切にロックされた後はOSが読み書きできないメモリ領域であるSMRAMにジャンプします
  4. SMMハンドラーを実行します
  5. RSM命令でリターン。CPU状態が復元されOSが継続します
01 OS

OSが動いている

CPUは通常のOSまたはアプリケーションコードを実行している

02 SMI#

SMIが発生

ハードウェアイベントまたはソフトウェアトリガーがSystem Management Interruptを生成する

03 Save State

CPU状態を保存

レジスター状態がSave State領域に書き込まれる

04 SMRAM

SMMに入る

CPUがSMRAM内でSMM Core、Dispatcher、ハンドラーを実行する

05 RSM

SMMからリターン

CPUが状態を復元し、OSが何事もなかったように継続する

SMMのメンタルモデル: SMIがCPUを隔離された実行環境に移し、RSMが以前のコンテキストに戻す

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つのフェーズで起きます。

SMM init関連のPOST code
コード フェーズ 意味
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のライフサイクル:

01 PEI

SMRAMをreserve

SMMRAMが発見されてreserveされるが、まだ完全にはlockされていない

02 DXE

SMM Coreをロード

SMM CoreとSMMドライバーがSMRAMにロードされる

03 登録

ハンドラーを登録

SMMドライバーがlock前にハンドラーを登録する

04 Lock

SmmReadyToLock

SMRAMがclose/lockされ、セキュリティポリシーが適用され始める

05 BDS/OS

ランタイム使用

SMRAMがlock済み。OSは通常のRAMとしてアクセスできない

SMMRAMのライフサイクル: SMRAMがlockされる前にハンドラーを登録しなければならない

SMRAMがlockされた後、新しいSMMモジュールをロードすることはできません。 これがハンドラーをSmmReadyToLock前に登録しなければならない理由です。

非常に多いミス: 開発者が新しいSMMドライバーを追加したが、DEPEXが間違っているかdispatch順が間違っているためにSmmReadyToLockにdispatchされてしまう。結果としてハンドラーは登録されず、サービスはサイレントに何もしない——エラーもなく、ただ沈黙。

SmmReadyToLock: 引き返せないポイント

EDK II/PIスタイルのfirmwareでは、SmmReadyToLockは通常DXEでシグナルされるprotocolまたは通知として表現されます。SMMがまもなくlockされることを示します。このシグナルが発火すると:

  1. SMRAMがcloseおよびlockされる
  2. 新しいSMMモジュールをinstallできなくなる
  3. 多くのセキュリティポリシーが適用される
01 DXE

DXE実行

SMMドライバーはまだdispatchしてハンドラーを登録できる

02 Driver A

ハンドラーを登録

lock前に登録 — OK

03 Driver B

ハンドラーを登録

lock前に登録 — OK

04 Lock

SmmReadyToLock

SMRAMがclose/lock、ポリシーが適用される

05 Driver C

遅くdispatch

ハンドラーを登録するには遅すぎ — サービスがサイレントに何もしない

SmmReadyToLock周辺のタイミングバグ: 遅く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を直接呼びません。

01 DXE

firmwareコンポーネント

DXEドライバーまたはfirmwareコンポーネントがSMRAMの外にCommBufferを作成する

02 Communicate

SmmCommunication

SmmCommunication->Communicate()を呼ぶ

03 SMI

SMIをトリガー

firmwareパスがSMIをトリガーしてSMMハンドラーに入る

04 SMM

ハンドラーが処理

ハンドラーが入力を検証し、リクエストを処理し、結果をCommBufferに書く

05 Return

RSM

SMMがリターンした後にDXEドライバーが結果を読む

ブート時/DXEパス: firmwareコンポーネントがSMM Communication Protocol経由でSMMと通信する
01 OS

OSがRuntime Serviceを呼ぶ

例: efibootmgrまたはOSコンポーネントがSetVariable()を呼ぶ

02 RT

Runtime variable service

ExitBootServices後にRuntime Servicesがリクエストを受け取る

03 SMI

SMIをトリガー

Runtime variableパスがリクエストをSMMに転送する可能性がある

04 SMM

SMM variableハンドラー

ハンドラーがポリシーを適用し、有効であればNVRAM/SPI flashに書く

OS/runtimeパス: OSはSMM Communication Protocolを直接呼ばず、Runtime Services経由で通信する

CommBufferは必ずSMRAMの外に置かなければなりません。 これはすべてのSMMハンドラーで必要な検証です。

// 擬似コード — プロダクションコードではない
if (IsAddressInSmram(CommBuffer, CommBufferSize)) {
    return EFI_SECURITY_VIOLATION;
}

この検証を省略すると、OSレベルの攻撃者がSMRAMを指すCommBufferを作成して機密データを読み書きできるようになります——これはよく知られたクラスのSMM脆弱性です。

SMMでのシリアルログ

SMMでのデバッグはDXEより難しいです。理由:

  1. SMMは隔離された環境で動いている。DXEで通常使えるインフラの一部がここではアクセスできない
  2. 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で忘れてはならないこと

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をデバッグする

参考資料

次に読む

この記事は役に立ちましたか?

ファームウェア、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.

Đọ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.