
MMIO タイミングを探索するためのツール — 任意の物理アドレスを計時し、レイテンシからハードウェアを解析します。

ハードウェアリバースエンジニアリング、ハイパーバイザーのフィンガープリンティング、デバイスアクティビティのサイドチャネル解析、レジスタ特性評価、悪意あるトリガーの校正、そして冗長極まりないマシン命令の作成に、驚くほど役立ちます。
物理メモリの最初の 1 メガバイトは、その構造が既によく知られているにもかかわらず、始めるのに手頃な例です。

レイテンシの境界によって、報告されたレイアウトに頼ることなくアドレス空間を切り分けることができます。
sudo ./mmiotic --start-address 0x0 --end-address 0x100000 --stride 4 --count 20 --quiet-mmap0x9FFFF までは約 380 cy、次に 128 KB のビデオホールがあり、0xB0000 で 2 つに分割されます。0x20000 での分散の変化は、NULL ページのページウォークが異なることを示している可能性があります。A0000–AFFFF: 低速でジッタあり (996 cy、stdev 327)。電源は入っているがアイドル状態の Radeon にルーティングされています。B0000–BFFFF: 高速で安定 (780 cy、stdev 3.2、ffffffff を読み取り)。誰にも要求されていません。似たようなデータですが、分散には 100 倍の差があります。C0000–FFFFF: /proc/iomem は「System ROM」と表示しますが、スキャン結果はフラッシュ速度ではなく DRAM レイテンシを示しています — BIOS は DRAM にシャドウされています。ルートコンプレックスのコンフィグ空間 (00:00.0、4 KB) をスキャンし、約 675 サイクルの平坦なフロアに対するレイテンシの外れ値を探します:

メールボックスのレイテンシは周囲のデータよりも著しく高く、重複したスパイクと同等の分散により、機能的に類似したレジスタを対応付けることができます。
sudo ./mmiotic --start-address 0xf8000000 --end-address 0xf8001000 --stride 4 --count 2000xe4 (1218 cy、値 80e3110b) と 0xa4 (1199 cy、値 deadbeef)。e4 は文書化されたドアベルで、a4 は未文書化です。e4 のみです。deadbeef は未初期化/エラーのセンチネルとして読み取られます — 同じメールボックスで、a4 は未実装か、無効な入力が与えられています。慎重に選択されたレイテンシは、珍しいエクスプロイトシナリオで使用できることがあります (MCHAMMER、smiiiiiiiiiiiiiiii):

ここでは、アイドル状態でドライバのない Radeon が、整列された 4 バイトの読み取り 1 回を約 100,000 サイクルのストールに変えます。
sudo ./mmiotic --start-address 0x90e00000 --end-address 0x90e00100 --stride 4 --count 50 — GPU BAR (0x90e00000、256 KB) の先頭をスイープします。0x90e00008 は 110,152 cy かかります。16 バイト先の 0x90e00018 は再び高速で 1,523 — ページ単位ではなくレジスタ単位です。sudo ./mmiotic --start-address 0x90e00000 --end-address 0x90e40000 --stride 4 --count 1 --binary — --binary はビット反転順でプローブするため、1,012 回のプローブ後、256 KB 全体のアパーチャが均一にカバーされ、すべてのレジスタが低速である連続した 48 KB ブロックがマッピングされます。理想的には、物理アドレスの読み取りは比較的安全な操作であるはずですが、mmiotic は例外を偶然見つけるのが特に得意です:

例えば、上記のプラットフォームでは、0xdc5003b0 の 1 バイト読み取り — ドライバのない Zen 4 iGPU レジスタ BAR (Region 5、0xdc500000、512 KB) のオフセット 0x3b0 — は、確実にシステムをリセットします。
sudo ./mmiotic --address 0xdc5003b0 --size 1 --count 1。出力なし。マシンは再起動します。0x3ac と 0x3b4 は約 3,200 cy で 00000000 を読み取り、無害です。0x3a0–0x3fc の 1 ダブルワードごとのスイープでは、17 個の安全なダブルワードに 7 個の poison ダブルワードがインターリーブされています。sudo busybox devmem 0xdc5003b0 32 と sudo dd if=/dev/mem bs=1 count=1 skip=$((0xdc5003b0)) は、仲介を省略したい場合にマシンを停止させます。[!WARNING] 一部のプラットフォームでは、
mmioticによって特定のアドレス領域がプローブされるとリセットが発生します。使用場所には注意してください。
ECAM ベースとサイズは ACPI MCFG (/sys/firmware/acpi/tables/MCFG) から自動検出されます。-M, --mmio-base / -Z, --mmio-size で上書きします。
--find-target/--find-longest はスキャンを検索に変えます:
| オプション | 効果 |
|---|---|
-F, --find-target <s> | アクセス時間が s 秒に達するアドレスを見つけ、幅を拡大 (非整列 4b → 8/16/32/64/512b) してさらに引き上げます |
-G, --find-longest | 同じ検索ですが、最初のヒットで諦めずにすべての候補を尽くします |
-B, --fallbacks <n> | 拡大フェーズに持ち越される候補数 (デフォルト: 10) |
b/d/f.o.l: 00:02:00.00.4 address: 00000000f0100000 min: 312 max: 480 stdev: 2.1 dynamic: 0 value: 8086abcd
-T はサイクル数に cy を付け、スケーリングされた最小/最大時間ペアを挿入します (-N はそれらを生のナノ秒で表示):
b/d/f.o.l: 00:02:00.00.4 address: 00000000f0100000 min: 312 cy max: 480 cy ( 104 ns/ 160 ns) stdev: 2.1 dynamic: 0 value: 8086abcd
ビルド:
make
/dev/mem へのアクセスを得るため root で実行します。
mmap ... Operation not permitted が表示される場合は、iomem=relaxed でブートして CONFIG_STRICT_DEVMEM を解除してみてください:
# /etc/default/grub
GRUB_CMDLINE_LINUX="iomem=relaxed nopat"
その後 sudo update-grub を実行して再起動します。
mmiotic は、内部および外部のさまざまな研究を、時に予期しない形で前進させるために使用されてきました。mmiotic の応用例をいくつか示します:
MCHAMMER: mmiotic は、保護された環境への MC# 信号の精密な配信のために、遅延マシンチェック例外を校正します。
smiiiiiiiiiiiiiiii: mmiotic は、SMM ランデブーを破るための高レイテンシ命令を特定します。
The Assembly Hall of Shame: mmiotic は、パフォーマンスの非最適化という重要な問題のために広範囲に使用されています。
mmiotic は Christopher Domas (@xoreaxeaxeax) による研究プロジェクトです。
| オプション | 名前 |
|---|
-b/-d/-f/-r | PCI --bus/--device/--function/--register — ECAM ベースに対してデコードされます |
-o, --offset <n> | MMIO ベースからのオフセット (B/D/F デコードをスキップ) |
-A, --address <n> | 単一の物理アドレス。リージョンベースを暗黙的に設定します |
-a/-z | --start-address/--end-address — 物理範囲。--scan を暗黙的に指定します |
| オプション | 効果 |
|---|
-S, --scan | ターゲットリージョン内のすべてのアドレスを走査します |
-t, --stride <n> | アドレス間のステップ (デフォルト: 4) |
-n, --limit <n> | このアドレス数に達したら停止します |
--binary | ビット反転順でプローブ — k 回のプローブ後、範囲は約 range/k の粒度で均一なカバレッジになります (上記の警告を参照) |
-x, --limited | レジスタ範囲を限定 (0x00–0xff と 0x000–0xfff) |
-e, --skip | オフセット 0 で 0xffffffff を読み取るファンクション (存在しないデバイス) をスキップします |
-I, --iomem | /proc/iomem 内の非 System-RAM トップレベルリージョンをすべてスキャンします |
-R, --ioregion <name> | ラベルが <name> に一致する /proc/iomem リージョンを任意の深さでスキャンします |
| オプション | 効果 |
|---|
-s, --size <n> | アクセスあたりのバイト数: 1 / 2 / 4 (デフォルト) / 8 / 16 (XMM) / 32 (YMM) / 64 (ZMM) / 512 (fxrstor)。4 より大きいサイズは仕様外ですが、テスト済みハードウェアで動作します。AVX 幅には AVX / AVX-512F が必要です |
-c, --count <n> | アドレスあたりのサンプル数 (デフォルト: 1)。最小値が報告されます |
-L, --lock | ロック付き RMW を計時: lock xadd (サイズ 1/2/4/8) または lock cmpxchg16b (サイズ 16)。値を書き戻します |
-g, --gather | ベクトルギャザーを計時。--size 16/32/64 で XMM/YMM/ZMM (AVX-512) を選択 |
-w, --write-read | 先行するポストされた書き込みを観測しなければならない読み取りを計時することで、書き込みレイテンシを取得します — 下記参照 |
-E, --enter (--enter-2, --enter-3) | 1 つの enter $0, $31 から最大 30 回のアクセスのバーストを駆動します — 下記参照 |
-k, --continue | fxrstor フォールト時にそのサンプルを破棄してスキャンを続行します |
| オプション | 効果 |
|---|
-C, --min-cycles <n> | min_cycles >= n の結果のみ表示します |
-T, --time | サイクルと併せてナノ秒を表示します (TSC 周波数を検出) |
-N, --nanosecond | --time と併用し、生のナノ秒を単位スケーリングなしで表示します |
-P, --progress | スキャンの進行状況を stderr にライブ表示します |
-p, --processor <n> | 論理 CPU n にピン留めします (デフォルト: 0) — タイマーノイズを低減します |
-u, --unbound | コアにピン留めしません |
--lazy-mmap | 2 GB ウィンドウをオンデマンドでマップします。2 GB を超えるリージョンで必要です |
--quiet-mmap | アクセス不能なリージョンからの「mmap failed」ノイズを抑制します |
| フィールド | 意味 |
|---|
b/d/f.o.l | バス/デバイス/ファンクション、レジスタオフセット、アクセスサイズ |
offset | MMIO ベースからのオフセット — ターゲットが -o/-A/-a+-z 由来の場合に b/d/f.o.l の代わりになります |
address | 完全な 64 ビット物理アドレス |
min/max | 観測された最速および最遅のアクセス (サイクル単位) |
stdev | サンプリングされたサイクル数の標準偏差 |
dynamic | サンプル間で値が 1 度でも変化した場合は 1 |
value | 読み取られた値 — 8 バイトを超えるサイズの場合、スペース区切りの qword として最初の 32 バイト |
faults | アクセスがフォールトした場合にのみ追加されます |