
Radiografía de latencia para hardware no documentado
Una herramienta exploratoria para medir tiempos de MMIO: mide el tiempo de cualquier dirección física y disecciona el hardware a partir de la latencia.

Sorprendentemente útil para ingeniería inversa de hardware, fingerprinting de hipervisores, actividad de dispositivos por canales laterales, caracterización de registros, calibración de activadores maliciosos y creación de instrucciones de máquina absurdamente largas.
El primer megabyte de memoria física es un ejemplo fácil para empezar, incluso si su estructura ya es bien conocida.

Los límites de latencia nos permiten delimitar el espacio de direcciones, sin depender de los diseños reportados.
sudo ./mmiotic --start-address 0x0 --end-address 0x100000 --stride 4 --count 20 --quiet-mmap0x9FFFF a ~380 ciclos, luego el hueco de video de 128 KB, que se divide en dos en 0xB0000. El cambio en la varianza en 0x20000 puede indicar un page walk diferente para la página nula.A0000–AFFFF: lento y con jitter (996 ciclos, desviación estándar 327), enrutado a la Radeon encendida pero inactiva. B0000–BFFFF: rápido y rígido (780 ciclos, desviación estándar 3.2, lee ffffffff), no reclamado por nadie. Datos de aspecto similar, pero una brecha de varianza de 100x.C0000–FFFFF: /proc/iomem dice "System ROM", pero el escaneo muestra latencia de DRAM, no velocidad de flash: la BIOS está sombreada en la DRAM.Escanee el espacio de configuración del root complex (00:00.0, 4 KB) en busca de valores atípicos de latencia frente a un piso plano de ~675 ciclos:

La latencia del buzón es significativamente mayor que la de los datos circundantes, y los picos duplicados y las varianzas equivalentes nos permiten emparejar registros funcionalmente similares.
sudo ./mmiotic --start-address 0xf8000000 --end-address 0xf8001000 --stride 4 --count 2000xe4 (1218 ciclos, valor 80e3110b) y 0xa4 (1199 ciclos, valor deadbeef). e4 es un doorbell documentado; a4 no está documentado.e4 es público.deadbeef se lee como un centinela de error/no inicializado: mismo buzón, a4 no implementado o alimentado con entrada inválida.Ciertas latencias cuidadosamente seleccionadas a veces pueden usarse en escenarios de explotación inusuales (MCHAMMER, smiiiiiiiiiiiiiiii):

Aquí, una Radeon inactiva y sin controlador convierte una lectura alineada de 4 bytes en una parada de ~100,000 ciclos.
sudo ./mmiotic --start-address 0x90e00000 --end-address 0x90e00100 --stride 4 --count 50 — barra la cabecera del BAR de la GPU (0x90e00000, 256 KB). 0x90e00008 cuesta 110,152 ciclos; 0x90e00018, 16 bytes más adelante, vuelve a ser rápido a 1,523 — por registro, no por página.sudo ./mmiotic --start-address 0x90e00000 --end-address 0x90e40000 --stride 4 --count 1 --binary — --binary sondea en orden de inversión de bits, por lo que la apertura completa de 256 KB se cubre uniformemente después de 1,012 sondas, mapeando un bloque contiguo de 48 KB donde todos los registros son lentos.Idealmente, leer una dirección física sería una operación relativamente segura, pero mmiotic es especialmente bueno encontrando las excepciones por accidente:

Por ejemplo, en la plataforma anterior, una lectura de 1 byte en 0xdc5003b0 — offset 0x3b0 de un BAR de registros de iGPU Zen 4 sin controlador (Región 5, 0xdc500000, 512 KB) — reinicia el sistema de forma fiable.
sudo ./mmiotic --address 0xdc5003b0 --size 1 --count 1. Sin salida; la máquina se reinicia.0x3ac y 0x3b4 a cada lado leen 00000000 a ~3,200 ciclos y son inofensivos.0x3a0–0x3fc encuentra 7 dwords venenosos intercalados con 17 seguros.sudo busybox devmem 0xdc5003b0 32 y sudo dd if=/dev/mem bs=1 count=1 skip=$((0xdc5003b0)) tumban la máquina si prefieres saltarte el intermediario.[!WARNING] Algunas plataformas se reiniciarán cuando
mmioticsondee regiones de direcciones específicas. Ten cuidado con dónde lo usas.
La base y el tamaño ECAM se detectan automáticamente desde ACPI MCFG (/sys/firmware/acpi/tables/MCFG). Se puede sobrescribir con -M, --mmio-base / -Z, --mmio-size.
--find-target/--find-longest convierten el escaneo en búsqueda:
| Opción | Efecto |
|---|---|
-F, --find-target <s> | encuentra una dirección cuyo tiempo de acceso alcance s segundos, luego escala el ancho (4b no alineado → 8/16/32/64/512b) para empujarlo más alto |
-G, --find-longest |
b/d/f.o.l: 00:02:00.00.4 address: 00000000f0100000 min: 312 max: 480 stdev: 2.1 dynamic: 0 value: 8086abcd
-T añade el sufijo cy a los conteos de ciclos e inserta un par de tiempos min/max escalados (-N los imprime como nanosegundos crudos):
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
Compilar:
make
Se ejecuta como root para obtener acceso a /dev/mem.
Si ves: mmap ... Operation not permitted, prueba a arrancar con iomem=relaxed para levantar CONFIG_STRICT_DEVMEM:
# /etc/default/grub
GRUB_CMDLINE_LINUX="iomem=relaxed nopat"
Luego sudo update-grub y reinicia.
mmiotic se ha utilizado para avanzar diversas investigaciones internas y externas, a veces de formas inesperadas. Algunos ejemplos de aplicaciones de mmiotic:
MCHAMMER: mmiotic calibra una excepción de machine check retrasada para la entrega de precisión de señales MC# en entornos protegidos.
smiiiiiiiiiiiiiiii: mmiotic resuelve la instrucción de alta latencia para romper el rendezvous de SMM.
The Assembly Hall of Shame: mmiotic se usa ampliamente para el importante problema de la deoptimización de rendimiento.
mmiotic es un esfuerzo de investigación de Christopher Domas (@xoreaxeaxeax).
| Opción | Nombres |
|---|
-b/-d/-f/-r | PCI --bus/--device/--function/--register — decodificado contra la base ECAM |
-o, --offset <n> | offset desde la base MMIO (omite la decodificación B/D/F) |
-A, --address <n> | una dirección física; establece la base de la región implícitamente |
-a/-z | --start-address/--end-address — un rango físico; implica --scan |
| Opción | Efecto |
|---|
-S, --scan | recorre todas las direcciones en la región objetivo |
-t, --stride <n> | paso entre direcciones (por defecto: 4) |
-n, --limit <n> | se detiene después de esta cantidad de direcciones |
--binary | sondea en orden de inversión de bits — después de k sondas el rango tiene cobertura uniforme a granularidad ~range/k (ver la advertencia anterior) |
-x, --limited | rango de registros limitado (0x00–0xff vs 0x000–0xfff) |
-e, --skip | omite funciones que leen 0xffffffff en el offset 0 (dispositivos ausentes) |
-I, --iomem | escanea toda región de nivel superior que no sea System-RAM en /proc/iomem |
-R, --ioregion <name> | escanea regiones de /proc/iomem cuya etiqueta coincida con <name>, a cualquier profundidad |
| misma búsqueda, pero agota todos los candidatos en lugar de detenerse en el primer acierto |
-B, --fallbacks <n> | candidatos llevados a la fase de escalado (por defecto: 10) |
| Opción | Efecto |
|---|
-s, --size <n> | bytes por acceso: 1 / 2 / 4 (por defecto) / 8 / 16 (XMM) / 32 (YMM) / 64 (ZMM) / 512 (fxrstor). Los tamaños > 4 están fuera de especificación pero funcionan en hardware probado; los anchos AVX necesitan AVX / AVX-512F |
-c, --count <n> | muestras por dirección (por defecto: 1); se reporta el mínimo |
-L, --lock | mide un RMW con bloqueo: lock xadd (tamaños 1/2/4/8) o lock cmpxchg16b (tamaño 16). Escribe el valor de vuelta |
-g, --gather | mide un gather vectorizado; --size 16/32/64 elige XMM/YMM/ZMM (AVX-512) |
-w, --write-read | captura la latencia de escritura midiendo una lectura que debe observar una escritura posted previa — ver abajo |
-E, --enter (--enter-2, --enter-3) | impulsa una ráfaga de hasta 30 accesos desde un solo enter $0, $31 — ver abajo |
-k, --continue | ante un fallo de fxrstor, descarta esa muestra y continúa escaneando |
| Opción | Efecto |
|---|
-C, --min-cycles <n> | solo imprime resultados con min_cycles >= n |
-T, --time | muestra nanosegundos junto a los ciclos (detecta la frecuencia TSC) |
-N, --nanosecond | con --time, imprime nanosegundos crudos, sin escalado de unidades |
-P, --progress | progreso de escaneo en vivo en stderr |
-p, --processor <n> | fija a la CPU lógica n (por defecto: 0) — reduce el ruido del temporizador |
-u, --unbound | no fijar a un núcleo |
--lazy-mmap | mapea en ventanas de 2 GB bajo demanda; requerido para regiones > 2 GB |
--quiet-mmap | suprime el ruido de "mmap failed" de regiones inaccesibles |
| Campo | Significado |
|---|
b/d/f.o.l | bus/dispositivo/función, offset de registro, tamaño de acceso |
offset | offset desde la base MMIO — reemplaza a b/d/f.o.l cuando el objetivo provino de -o/-A/-a+-z |
address | dirección física completa de 64 bits |
min/max | acceso más rápido y más lento observado, en ciclos |
stdev | desviación estándar de los conteos de ciclos muestreados |
dynamic | 1 si el valor cambió en algún momento entre muestras |
value | valor leído — para tamaños superiores a 8 bytes, los primeros 32 bytes como qwords separados por espacios |
faults | se agrega solo si un acceso falló |