Skip to main content

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

BG

Пачване на софтуер със SSE4.x за по-стари процесори

Обратно инженерство
Also in:BRENESIT
aAuto-translated from English.read original ↗

Въведение

Повечето съвременни процесори са съвместими, защото споделят една и съща ISA1. Най-разпространената ISA днес е x86_64 — 64-битово разширение на по-старата архитектура x86. Нуждата от по-сложни и по-бързи операции, особено при аритметика с плаваща запетая, води до разширения като SSE2 и AVX3.

Тези разширения имат няколко версии, които позволяват на процесора да интерпретира различни набори от опкодове. SSE достига до версия 4.2, както и до ексклузивната за AMD версия 4a. AVX достига до AVX-512, но по-често срещаният набор от разширения при COTS4 процесорите е AVX-2.

При компилиране на софтуер може да се зададе флаг, който определя дали полученият машинен код ще е съвместим с конкретен набор от разширения. Този избор може да подобри производителността, тъй като разширенията изпълняват някои операции с по-малко процесорни цикли.

По-старите процесори обаче, макар напълно способни да изпълняват съвременен софтуер, може да са били пуснати на пазара преди да станат налични най-новите разширения. Затова софтуер, компилиран с тези по-нови инструкции, изобщо не може да се изпълни на тях.

Откриване на поддръжката на разширения

Информацията за тези разширения се съдържа във флаговете на CPUID. Най-лесният начин да проверите дали процесорът ви поддържа конкретни инструкции е чрез CPU-Z:

CPU-Z, показващ поддържаните набори от инструкции
CPU-Z, показващ поддържаните набори от инструкции

Можете и просто да опитате да стартирате софтуера. Той може да покаже прозорец, че не е съвместим, или просто да се срине без съобщение. Проверете Windows Event Viewer за код на грешка 0xC000001D, който означава, че е направен опит за изпълнение на невалидна инструкция.

Процесът на пачване

Ще опиша процеса на пачване, който използвах, за да заобиколя тези инструкции в играта Days Gone. Това е моят метод и не е непременно оптимален, но работи. Имайте предвид, че дори легитимните притежатели на играта не можеха да я стартират на малко по-стари процесори — чрез пачване на няколко инструкции успях да я направя съвместима.

Стартирането на играта води до незабавен CTD5, като процесът изчезва от Task Manager. Няма предупреждения или индикации за проблема.

Настройване на дебъгера

Заредете играта в x64dbg и задайте breakpoint на EXCEPTION_ILLEGAL_INSTRUCTION. Това изключение се вдига, когато процесорът срещне непознат опкод:

Задаване на breakpoint за изключението
Задаване на breakpoint за изключението

Конфигурация на breakpoint
Конфигурация на breakpoint

Стартирайте играта, докато не бъде достигнат този breakpoint. Може да срещнете други breakpoints — продължавайте, докато не стигнете до този за невалидната инструкция. Имайте предвид, че различните процесори може да имат различни налични разширения и съответно да могат да интерпретират различни опкодове.

Дебъгерът е спрян на невалидна инструкция
Дебъгерът е спрян на невалидна инструкция

Обърнете внимание, че долната лента за състоянието показва, че изпълнението е спряно на breakpoint EXCEPTION_ILLEGAL_INSTRUCTION. Вече сме на неподдържан опкод.

Първи опит: NOP-ване

Обикновено първо опитвам да NOP-на6 инструкцията (десен бутон → Binary → Fill with NOPs). Това може да проработи или не, в зависимост от инструкцията и софтуера. Игрите често използват тези инструкции за декодиране на видеосъдържание, като финални надписи или кътсцени.

Но простото NOP-ване на инструкцията може да счупи софтуера. В такъв случай трябва да заменим тези инструкции с еквивалентни, използващи опкодове, налични на нашия процесор.

Намиране на еквивалентни инструкции

Търсенето на инструкцията pminuw ме отведе до този полезен ресурс, който предлага съвместими със SSE2 заместители:

Реализация на PMINUW, съвместима със SSE2

;   xmm0 = вход|изход
;   xmm1 = вход
;   xmm7 = временен регистър
movdqa xmm7, xmm0    ; Преместване на xmm0 във временния регистър
psubusw xmm7, xmm1   ; Изваждане с насищане без знак
psubw xmm0, xmm7     ; Изваждане (без насищане; тук не би имало препълване)

Това означава, че можем да заменим инструкцията си с тези 3 инструкции, за да постигнем SSE2 съвместимост. Текущата ни инструкция обаче е само 5 байта (66:0F383AD1), а за замяната са ни нужни 3×4 = 12 байта. Как да ги поберем?

Използване на code caves

Можем да скочим на друго място, което побира тези инструкции, след което да скочим обратно, за да продължим изпълнението. Първо да изчислим нужното място:

movdqa  : 4 байта
psubusw : 4 байта
psubw   : 4 байта
jmp     : ≤5 байта

Нуждаем се от приблизително 17 байта свободно място. Потърсете „code cave“ — неизползвана област в изпълнимата секция. Направете търсене по шаблон за 17 последователни байта CC или прегледайте наоколо за празна област:

Търсене за code caves
Търсене за code caves

Отворете първия резултат и напишете заместващите инструкции. В края добавете jmp обратно към инструкцията след заменената. Не забравяйте съответно да коригирате регистрите и да проверите дали временният регистър не се използва другаде:

Записване на заместващ код в cave
Записване на заместващ код в cave

Завършване на пача

Заменете оригиналната инструкция с jmp към нашия code cave:

Замяна на оригиналната инструкция със скок
Замяна на оригиналната инструкция със скок

Това е. Повтаряйте, докато не се задействат повече breakpoints. За Days Gone замених 12× инструкции pminuw и 2× pmaxud. За някои инструкции не успях да намеря лесни заместители, затова просто ги NOP-нах — и проработи.


Обобщение на стъпките

  1. Установете проблема: Проверете CPU-Z за поддържаните набори от инструкции или потърсете кода 0xC000001D в Windows Event Viewer
  2. Настройте дебъгера: Заредете изпълнимия файл в x64dbg и задайте breakpoint на EXCEPTION_ILLEGAL_INSTRUCTION
  3. Намерете неподдържаните инструкции: Стартирайте, докато breakpoint-ът се задейства, за да откриете проблемния опкод
  4. Първо опитайте NOP: Понякога простото запълване на инструкцията с NOP-ове работи
  5. Намерете еквивалентни инструкции: Потърсете съвместими със SSE2 алтернативи за SSE4.x инструкциите
  6. Открийте code caves: Потърсете празно място (байтове CC) в изпълнимия файл
  7. Пачнете code cave: Напишете заместващите инструкции, последвани от скок обратно
  8. Заменете оригиналната инструкция: Променете я така, че да скача към вашия code cave
  9. Повторете: Продължете, докато всички невалидни инструкции бъдат пачнати

Игри, изискващи SSE4.1/4.2 инструкции

Списък, съставен от mafia47 (някои игри изискват и AVX)

Игра Състояние
Sunset Overdrive Все още няма решение
Journey Решение: изключете интерполацията на плата (само за най-новата версия)
Metal Slug XX Все още няма решение
Beyond: Two Souls Може да се играе с Intel SDE7
Detroit: Become Human Налично е решение
Control Ultimate Edition Все още няма решение
Yakuza 3, 4, 5, 6 Все още няма решение
Yakuza: Like a Dragon Все още няма решение
Horizon Zero Dawn Решение само за Steam v1.0.9.3
Days Gone Решение само за v1.0
Little Nightmares Може да се играе с Intel SDE
Unravel Two Може да се играе с Intel SDE
Death Stranding Все още няма решение
Cyberpunk 2077 Решение за GOG v1.22 (изисква тестване)
Red Dead Redemption 2 Решение само за crack-ната версия
Nioh 2 Все още няма решение
God Eater 3 Все още няма решение
Assassin's Creed Origins Решение за v1.51 (не за процесори Phenom)
Assassin's Creed Odyssey Решение за v1.5.3 (не за процесори Phenom)
Assassin's Creed Valhalla Все още няма решение
Immortals Fenyx Rising Все още няма решение
Far Cry 5 Поправено от разработчиците в най-новата версия; няма решение за по-стари версии
Dead Rising 4 Все още няма решение
Crash Bandicoot 4 Все още няма решение
Saints Row The Third Remastered Все още няма решение
DOOM Eternal Решение само за exe без Denuvo (не за процесори Phenom)
Need for Speed Heat Все още няма решение
NBA 2K21 Все още няма решение
Shadow Man Remastered Може да се играе с Intel SDE
Biomutant Все още няма решение
Outriders Все още няма решение
Puyo Puyo Tetris 2 Поправено от разработчиците
Diablo II Resurrected Все още няма решение

Заключителни мисли

Сигурен съм, че има по-добри подходи — например автоматизиране чрез скриптове или използване на специализирани плъгини за дебъгери. Работя и по универсален емулатор, който би могъл да ни помогне да продължим да използваме по-стари, но напълно способни процесори: това би било идеално, защото JIT емулацията не променя оригиналния софтуер и би емулирала само необходимите инструкции, осигурявайки (надявам се) минимален overhead.

Footnotes

  1. Архитектура на набора от инструкции — наборът от инструкции, които процесорът може да разбира и изпълнява.

  2. Streaming SIMD Extensions; SIMD означава Single Instruction, Multiple Data и позволява паралелни операции върху множество точки от данни.

  3. Advanced Vector Extension предоставя по-широки регистри (256-битови и 512-битови) за още повече паралелна обработка.

  4. Commercial Off-The-Shelf означава стандартен потребителски хардуер, за разлика от специализирани или персонализирани процесори.

  5. Crash To Desktop: приложението се прекратява внезапно, без диалогов прозорец за грешка.

  6. NOP (No Operation) е инструкция, която не прави нищо. Запълването на код с NOP-ове на практика премахва функционалността му, като запазва подравняването на байтовете.

  7. Intel Software Development Emulator може да емулира липсващи инструкции, но със значителен спад в производителността.