Skip to main content

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

BR

Patcheando software SSE4.x para CPUs mais antigas

Engenharia reversa
Also in:BGENESIT
aAuto-translated from English.read original ↗

Introdução

A maioria das CPUs modernas é compatível entre si porque compartilha a mesma ISA1. A ISA mais comum hoje é a x86_64, uma extensão de 64 bits da antiga arquitetura x86. A necessidade de operações mais complexas e rápidas, especialmente na aritmética de ponto flutuante, levou a extensões como SSE2 e AVX3.

Essas extensões têm várias versões que permitem ao processador interpretar diferentes conjuntos de opcodes. O SSE chega à versão 4.2, além de uma versão 4a exclusiva da AMD. O AVX chega ao AVX-512, mas o conjunto de extensões mais comum em CPUs COTS4 é o AVX-2.

Ao compilar software, é possível definir uma flag que determina se o código de máquina resultante será compatível com um conjunto específico de extensões. Essa escolha pode levar a um desempenho melhor, porque as extensões conseguem realizar determinadas operações usando menos ciclos de CPU.

Porém, CPUs mais antigas, perfeitamente capazes de executar software moderno, podem ter chegado ao mercado antes de as extensões mais recentes estarem disponíveis. Portanto, software compilado com essas instruções mais novas simplesmente não consegue ser executado nelas.

Detectando suporte a extensões

A descrição dessas extensões está incluída nas flags de CPUID. A forma mais fácil de verificar se sua CPU suporta instruções específicas é pelo CPU-Z:

CPU-Z mostrando conjuntos de instruções compatíveis
CPU-Z mostrando conjuntos de instruções compatíveis

Você também pode simplesmente tentar executar o software. Ele pode mostrar um pop-up dizendo que não é compatível, ou apenas travar silenciosamente. Verifique o Visualizador de Eventos do Windows em busca do código de erro 0xC000001D, que indica que uma instrução ilegal foi tentada.

Processo de patching

Vou descrever o processo de patching que usei para contornar essas instruções no jogo Days Gone. Este é o meu método e não é necessariamente o ideal, mas funciona. Vale notar que até compradores legítimos do jogo não conseguiam executá-lo em processadores um pouco mais antigos — ao patchear algumas instruções, consegui torná-lo compatível.

Ao executar o jogo, ocorre um CTD5 imediato e o processo desaparece do Gerenciador de Tarefas. Não há avisos nem indicações do problema.

Configurando o depurador

Carregue o jogo no x64dbg e defina um breakpoint em EXCEPTION_ILLEGAL_INSTRUCTION. Essa exceção é gerada quando o processador encontra um opcode desconhecido:

Definindo o breakpoint de exceção
Definindo o breakpoint de exceção

Configuração do breakpoint
Configuração do breakpoint

Execute o jogo até que esse breakpoint seja atingido. Você pode encontrar outros breakpoints; continue até chegar ao da instrução inválida. Processadores diferentes podem ter conjuntos de extensões disponíveis diferentes e, por isso, interpretar opcodes diferentes.

Depurador parado na instrução ilegal
Depurador parado na instrução ilegal

Observe como a barra de status inferior mostra que a execução parou no breakpoint EXCEPTION_ILLEGAL_INSTRUCTION. Agora estamos em um opcode não suportado.

Primeira tentativa: usar NOP

Geralmente tento primeiro aplicar NOP6 à instrução (botão direito → Binary → Fill with NOPs). Isso pode funcionar ou não, dependendo da instrução e do software. Jogos costumam usar essas instruções para decodificar conteúdo de vídeo, como créditos ou cutscenes.

No entanto, simplesmente aplicar NOP à instrução pode quebrar o software. Nesse caso, precisamos substituir essas instruções por equivalentes que usem opcodes disponíveis na nossa CPU.

Encontrando instruções equivalentes

Uma busca pela instrução pminuw me levou a este recurso útil, que fornece substitutos compatíveis com SSE2:

Implementação de PMINUW compatível com SSE2

;   xmm0 = entrada|saída
;   xmm1 = entrada
;   xmm7 = temporário
movdqa xmm7, xmm0    ; Move xmm0 para o temporário
psubusw xmm7, xmm1   ; Subtração com saturação sem sinal
psubw xmm0, xmm7     ; Subtração (sem saturação; aqui nunca haveria overflow)

Isso significa que podemos substituir nossa instrução por essas três para obter compatibilidade com SSE2. Porém, a instrução atual tem apenas 5 bytes (66:0F383AD1) e precisamos de 3×4 = 12 bytes para a substituição. Como fazer isso caber?

Usando code caves

Podemos saltar para outro local que comporte essas instruções e então saltar de volta para continuar a execução. Primeiro, vamos calcular o espaço necessário:

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

Precisamos de aproximadamente 17 bytes de espaço livre. Procure uma "code cave" — uma área não utilizada na seção executável. Faça uma busca por um padrão de 17 bytes CC consecutivos ou navegue pelo arquivo para encontrar uma área vazia:

Procurando code caves
Procurando code caves

Abra o primeiro resultado e escreva as instruções de substituição. Ao final, adicione um jmp de volta à instrução seguinte à que substituímos. Lembre-se de ajustar os registradores e verificar se o registrador temporário não está sendo usado em outro lugar:

Escrevendo o código substituto na cave
Escrevendo o código substituto na cave

Concluindo o patch

Substitua a instrução original por um jmp para a nossa code cave:

Substituindo a original por um salto
Substituindo a original por um salto

É isso. Repita até que não haja mais breakpoints. Em Days Gone, substituí 12 instruções pminuw e 2 instruções pmaxud. Algumas instruções não tinham substituições fáceis de encontrar, então simplesmente apliquei NOP nelas — e funcionou.


Resumo das etapas

  1. Identifique o problema: verifique o CPU-Z para ver os conjuntos de instruções suportados ou procure o código 0xC000001D no Visualizador de Eventos do Windows
  2. Configure o depurador: carregue o executável no x64dbg e defina um breakpoint em EXCEPTION_ILLEGAL_INSTRUCTION
  3. Encontre instruções não suportadas: execute até o breakpoint ser atingido para identificar o opcode problemático
  4. Tente NOP primeiro: às vezes, simplesmente preencher a instrução com NOPs funciona
  5. Encontre instruções equivalentes: procure alternativas compatíveis com SSE2 para as instruções SSE4.x
  6. Localize code caves: procure espaço vazio (bytes CC) no executável
  7. Patche a code cave: escreva as instruções substitutas seguidas de um salto de volta
  8. Substitua a instrução original: altere-a para saltar à sua code cave
  9. Repita: continue até que todas as instruções ilegais sejam patcheadas

Jogos que exigem instruções SSE4.1/4.2

Lista compilada por mafia47 (alguns jogos também exigem AVX)

Jogo Status
Sunset Overdrive Ainda não há correção
Journey Correção: desative a interpolação de tecido (somente na versão mais recente)
Metal Slug XX Ainda não há correção
Beyond: Two Souls Jogável com Intel SDE7
Detroit: Become Human Correção disponível
Control Ultimate Edition Ainda não há correção
Yakuza 3, 4, 5, 6 Ainda não há correção
Yakuza: Like a Dragon Ainda não há correção
Horizon Zero Dawn Correção apenas para Steam v1.0.9.3
Days Gone Correção apenas para v1.0
Little Nightmares Jogável com Intel SDE
Unravel Two Jogável com Intel SDE
Death Stranding Ainda não há correção
Cyberpunk 2077 Correção para GOG v1.22 (requer testes)
Red Dead Redemption 2 Correção apenas para a versão crackeada
Nioh 2 Ainda não há correção
God Eater 3 Ainda não há correção
Assassin's Creed Origins Correção para v1.51 (não para CPU Phenom)
Assassin's Creed Odyssey Correção para v1.5.3 (não para CPU Phenom)
Assassin's Creed Valhalla Ainda não há correção
Immortals Fenyx Rising Ainda não há correção
Far Cry 5 Corrigido pelos desenvolvedores na versão mais recente; sem correção para versões antigas
Dead Rising 4 Ainda não há correção
Crash Bandicoot 4 Ainda não há correção
Saints Row The Third Remastered Ainda não há correção
DOOM Eternal Correção apenas para exe sem Denuvo (não para CPU Phenom)
Need for Speed Heat Ainda não há correção
NBA 2K21 Ainda não há correção
Shadow Man Remastered Jogável com Intel SDE
Biomutant Ainda não há correção
Outriders Ainda não há correção
Puyo Puyo Tetris 2 Corrigido pelos desenvolvedores
Diablo II Resurrected Ainda não há correção

Considerações finais

Tenho certeza de que existem abordagens melhores, seja automatizando com scripts, seja usando plugins especializados para depuradores. Também estou trabalhando em um emulador genérico que poderia nos ajudar a continuar usando CPUs mais antigas, porém perfeitamente capazes: isso seria ideal porque uma emulação JIT não altera o software original e emularia apenas as instruções necessárias, gerando (esperamos) uma sobrecarga mínima.

Footnotes

  1. Instruction Set Architecture é o conjunto de instruções que um processador consegue entender e executar.

  2. Streaming SIMD Extensions; SIMD significa Single Instruction, Multiple Data e permite operações paralelas sobre vários pontos de dados.

  3. Advanced Vector Extension fornece registradores mais largos (de 256 e 512 bits) para ainda mais capacidade de processamento paralelo.

  4. Commercial Off-The-Shelf é hardware padrão de consumo, em oposição a processadores especializados ou personalizados.

  5. Crash To Desktop: o aplicativo é encerrado abruptamente, sem uma caixa de diálogo de erro.

  6. NOP (No Operation) é uma instrução que não faz nada. Preencher código com NOPs remove sua funcionalidade na prática, preservando o alinhamento dos bytes.

  7. Intel Software Development Emulator pode emular instruções ausentes, mas com sobrecarga significativa de desempenho.