Skip to main content

/writings / es / patching-sse4x-software-for-older-cpus

ES

Parchear software SSE4.x para CPU antiguas

Ingeniería inversa
Also in:BGBRENIT
aAuto-translated from English.read original ↗

Introducción

La mayoría de las CPU modernas son compatibles porque comparten la misma ISA1. La ISA más común hoy es x86_64, una extensión de 64 bits de la antigua arquitectura x86. La necesidad de operaciones más complejas y rápidas, especialmente en aritmética de coma flotante, ha dado lugar a extensiones como SSE2 y AVX3.

Estas extensiones cuentan con varias versiones que permiten al procesador interpretar distintos conjuntos de opcodes. SSE llega hasta la versión 4.2, con una versión 4a exclusiva de AMD. AVX llega hasta AVX-512, pero el conjunto de extensiones más habitual en CPU COTS4 es AVX-2.

Al compilar software se puede establecer una flag que determina si el código máquina resultante será compatible con un conjunto concreto de extensiones. Esta elección puede mejorar el rendimiento, ya que las extensiones pueden realizar ciertas operaciones con menos ciclos de CPU.

Sin embargo, las CPU antiguas, que son perfectamente capaces de ejecutar software moderno, pueden haber llegado al mercado antes de que estuvieran disponibles las extensiones más recientes. Por ello, el software compilado con esas instrucciones más nuevas sencillamente no puede ejecutarse en ellas.

Detectar la compatibilidad con extensiones

La descripción de estas extensiones está incluida en las flags de CPUID. La forma más sencilla de comprobar si la CPU admite instrucciones específicas es mediante CPU-Z:

CPU-Z mostrando los conjuntos de instrucciones compatibles
CPU-Z mostrando los conjuntos de instrucciones compatibles

También puedes intentar ejecutar el software. Puede mostrar una ventana emergente diciendo que no es compatible, o simplemente fallar en silencio. Consulta el Visor de eventos de Windows para ver el código 0xC000001D, que indica que se intentó ejecutar una instrucción ilegal.

Proceso de parcheo

Describiré el proceso de parcheo que utilicé para evitar estas instrucciones en el juego Days Gone. Es mi método y no es necesariamente óptimo, pero funciona. Incluso compradores legítimos del juego no podían ejecutarlo en procesadores algo más antiguos; al parchear unas cuantas instrucciones pude hacerlo compatible.

Al ejecutar el juego se produce un CTD5 inmediato y el proceso desaparece del Administrador de tareas. No hay advertencias ni indicios del problema.

Configurar el depurador

Carga el juego en x64dbg y establece un breakpoint en EXCEPTION_ILLEGAL_INSTRUCTION. Esta excepción se produce cuando el procesador encuentra un opcode desconocido:

Establecer el breakpoint de excepción
Establecer el breakpoint de excepción

Configuración del breakpoint
Configuración del breakpoint

Ejecuta el juego hasta que se alcance este breakpoint. Es posible que encuentres otros; continúa hasta llegar al de la instrucción no válida. Distintos procesadores pueden tener conjuntos de extensiones disponibles diferentes y, por tanto, interpretar opcodes distintos.

Depurador detenido en la instrucción ilegal
Depurador detenido en la instrucción ilegal

Observa que la barra de estado inferior muestra que se detuvo en el breakpoint EXCEPTION_ILLEGAL_INSTRUCTION. Ahora estamos ante un opcode no compatible.

Primer intento: aplicar NOP

Normalmente primero intento aplicar NOP6 a la instrucción (clic derecho → Binary → Fill with NOPs). Puede funcionar o no, según la instrucción y el software. Los juegos suelen usar estas instrucciones para decodificar contenido de vídeo, como créditos o cinemáticas.

Sin embargo, aplicar NOP a la instrucción puede romper el software. En ese caso, debemos sustituirla por instrucciones equivalentes que utilicen opcodes disponibles en nuestra CPU.

Buscar instrucciones equivalentes

Una búsqueda de la instrucción pminuw me llevó a este recurso útil, que ofrece sustitutos compatibles con SSE2:

Implementación de PMINUW compatible con SSE2

;   xmm0 = entrada|salida
;   xmm1 = entrada
;   xmm7 = temporal
movdqa xmm7, xmm0    ; Mover xmm0 al temporal
psubusw xmm7, xmm1   ; Resta con saturación sin signo
psubw xmm0, xmm7     ; Resta (sin saturación; aquí nunca habría desbordamiento)

Esto significa que podemos sustituir la instrucción por estas tres para conseguir compatibilidad con SSE2. Sin embargo, la instrucción actual solo ocupa 5 bytes (66:0F383AD1) y necesitamos 3×4 = 12 bytes para el reemplazo. ¿Cómo hacemos que quepa?

Usar code caves

Podemos saltar a otra ubicación que tenga espacio para estas instrucciones y luego saltar de vuelta para continuar la ejecución. Primero, calculemos el espacio necesario:

movdqa  : 4 bytes
psubusw : 4 bytes
psubw   : 4 bytes
jmp     : ≤5 bytes

Necesitamos aproximadamente 17 bytes libres. Busca una "code cave": un área sin usar dentro de la sección ejecutable. Busca un patrón de 17 bytes CC consecutivos o desplázate para encontrar un área vacía:

Buscar code caves
Buscar code caves

Abre el primer resultado y escribe las instrucciones de reemplazo. Al final, añade un jmp de vuelta a la instrucción posterior a la que sustituimos. Recuerda ajustar los registros y verificar que el registro temporal no se use en otro lugar:

Escribir el código de reemplazo en la cave
Escribir el código de reemplazo en la cave

Completar el parche

Sustituye la instrucción original por un jmp a nuestra code cave:

Sustituir la instrucción original por un salto
Sustituir la instrucción original por un salto

Eso es todo. Repite hasta que no se alcancen más breakpoints. Para Days Gone, sustituí 12 instrucciones pminuw y 2 pmaxud. Para algunas instrucciones no encontré sustitutos fáciles, así que simplemente les apliqué NOP, y funcionó.


Resumen de pasos

  1. Identifica el problema: revisa CPU-Z para ver los conjuntos de instrucciones admitidos o busca el código 0xC000001D en el Visor de eventos de Windows
  2. Configura el depurador: carga el ejecutable en x64dbg y establece un breakpoint en EXCEPTION_ILLEGAL_INSTRUCTION
  3. Busca instrucciones no compatibles: ejecuta hasta que se alcance el breakpoint para identificar el opcode problemático
  4. Prueba NOP primero: a veces basta con rellenar la instrucción con NOP
  5. Busca instrucciones equivalentes: busca alternativas compatibles con SSE2 para las instrucciones SSE4.x
  6. Localiza code caves: busca espacio vacío (bytes CC) en el ejecutable
  7. Parchea la code cave: escribe las instrucciones de reemplazo seguidas de un salto de vuelta
  8. Sustituye la instrucción original: haz que salte a tu code cave
  9. Repite: continúa hasta parchear todas las instrucciones ilegales

Juegos que requieren instrucciones SSE4.1/4.2

Lista recopilada por mafia47 (algunos juegos también requieren AVX)

Juego Estado
Sunset Overdrive Aún no hay solución
Journey Solución: desactivar la interpolación de tela (solo en la versión más reciente)
Metal Slug XX Aún no hay solución
Beyond: Two Souls Jugable con Intel SDE7
Detroit: Become Human Solución disponible
Control Ultimate Edition Aún no hay solución
Yakuza 3, 4, 5, 6 Aún no hay solución
Yakuza: Like a Dragon Aún no hay solución
Horizon Zero Dawn Solución solo para Steam v1.0.9.3
Days Gone Solución solo para v1.0
Little Nightmares Jugable con Intel SDE
Unravel Two Jugable con Intel SDE
Death Stranding Aún no hay solución
Cyberpunk 2077 Solución para GOG v1.22 (requiere pruebas)
Red Dead Redemption 2 Solución solo para la versión crackeada
Nioh 2 Aún no hay solución
God Eater 3 Aún no hay solución
Assassin's Creed Origins Solución para v1.51 (no para CPU Phenom)
Assassin's Creed Odyssey Solución para v1.5.3 (no para CPU Phenom)
Assassin's Creed Valhalla Aún no hay solución
Immortals Fenyx Rising Aún no hay solución
Far Cry 5 Corregido por los desarrolladores en la versión más reciente; no hay solución para versiones antiguas
Dead Rising 4 Aún no hay solución
Crash Bandicoot 4 Aún no hay solución
Saints Row The Third Remastered Aún no hay solución
DOOM Eternal Solución solo para exe sin Denuvo (no para CPU Phenom)
Need for Speed Heat Aún no hay solución
NBA 2K21 Aún no hay solución
Shadow Man Remastered Jugable con Intel SDE
Biomutant Aún no hay solución
Outriders Aún no hay solución
Puyo Puyo Tetris 2 Corregido por los desarrolladores
Diablo II Resurrected Aún no hay solución

Reflexiones finales

Seguro que existen enfoques mejores: automatizar mediante scripts o utilizar plugins de depurador especializados. También estoy trabajando en un emulador genérico que podría ayudarnos a seguir usando CPU más antiguas pero perfectamente capaces. Sería ideal porque la emulación JIT no altera el software original y emularía únicamente las instrucciones necesarias, con una sobrecarga mínima (esperemos).

Footnotes

  1. Instruction Set Architecture es el conjunto de instrucciones que un procesador puede entender y ejecutar.

  2. Streaming SIMD Extensions; SIMD significa Single Instruction, Multiple Data y permite operaciones paralelas sobre varios datos.

  3. Advanced Vector Extension proporciona registros más anchos (de 256 y 512 bits) para una capacidad de procesamiento paralelo aún mayor.

  4. Commercial Off-The-Shelf es hardware de consumo estándar, en contraposición a procesadores especializados o personalizados.

  5. Crash To Desktop: la aplicación termina bruscamente sin ningún cuadro de diálogo de error.

  6. NOP (No Operation) es una instrucción que no hace nada. Rellenar código con NOP elimina en la práctica su funcionalidad y mantiene la alineación de bytes.

  7. Intel Software Development Emulator puede emular instrucciones ausentes, pero con una sobrecarga significativa de rendimiento.