
문서화되지 않은 하드웨어의 지연 시간 엑스레이
MMIO 타이밍을 탐구하는 도구 — 임의의 물리 주소를 타이밍하고, 레이턴시로부터 하드웨어를 분석한다.

하드웨어 리버스 엔지니어링, 하이퍼바이저 핑거프린팅, 장치 활동의 사이드 채널링, 레지스터 특성 규명, 악성 트리거 보정, 그리고 터무니없이 긴 머신 명령어 제작에 의외로 유용하다.
물리 메모리의 첫 1MB는 구조가 이미 잘 알려져 있더라도 시작하기 쉬운 예제다.

레이턴시 경계를 통해 보고된 레이아웃에 의존하지 않고 주소 공간을 분할할 수 있다.
sudo ./mmiotic --start-address 0x0 --end-address 0x100000 --stride 4 --count 20 --quiet-mmap0x9FFFF까지 DRAM이 약 380 cy, 그 다음 128KB 비디오 홀(video hole)이 0xB0000에서 둘로 나뉜다. 0x20000에서의 분산 변화는 널 페이지에 대한 서로 다른 페이지 워크를 나타낼 수 있다.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, 4KB)을 스캔하여 약 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바이트 읽기 한 번을 약 100,000사이클 스톨로 바꾼다.
sudo ./mmiotic --start-address 0x90e00000 --end-address 0x90e00100 --stride 4 --count 50 — GPU BAR(0x90e00000, 256KB)의 머리를 스윕한다. 0x90e00008은 110,152 cy가 소요되고, 16바이트 떨어진 0x90e00018은 다시 빠르게 1,523이다 — 페이지 단위가 아닌 레지스터 단위다.sudo ./mmiotic --start-address 0x90e00000 --end-address 0x90e40000 --stride 4 --count 1 --binary — --binary는 비트 역순으로 프로브하므로 1,012회 프로브 후 전체 256KB 에퍼처가 균일하게 커버되며, 모든 레지스터가 느린 연속 48KB 블록이 매핑된다.이상적으로는 물리 주소를 읽는 것이 비교적 안전한 작업이어야 하지만, mmiotic은 우연히 예외를 찾아내는 데 특히 뛰어나다:

예를 들어 위 플랫폼에서 1바이트 읽기 0xdc5003b0 — 드라이버가 없는 Zen 4 iGPU 레지스터 BAR(Region 5, 0xdc500000, 512KB)의 오프셋 0x3b0 — 은 시스템을 확실하게 리셋시킨다.
sudo ./mmiotic --address 0xdc5003b0 --size 1 --count 1. 출력 없음; 박스가 재부팅된다.0x3ac와 0x3b4는 약 3,200 cy로 00000000을 읽으며 무해하다.0x3a0–0x3fc를 dword 단위로 스윕하면 17개의 안전한 dword 사이에 7개의 독 dword가 섞여 있다.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은 성능 비최적화(deoptimization)라는 중요한 문제를 위해 광범위하게 사용된다.
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 vs 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 | 벡터화된 gather 타이밍; --size 16/32/64는 XMM/YMM/ZMM(AVX-512)을 선택 |
-w, --write-read | 이전 게시된 쓰기를 관찰해야 하는 읽기의 타이밍으로 쓰기 레이턴시 캡처 — 아래 참조 |
-E, --enter (--enter-2, --enter-3) | 하나의 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 | 2GB 창을 요청 시 매핑; 2GB를 초과하는 영역에 필요 |
--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 |
value | 읽은 값 — 8바이트를 초과하는 크기의 경우 처음 32바이트를 공백으로 구분된 qword로 표시 |
faults | 액세스에 폴트가 발생한 경우에만 추가 |