Swift для системного програмування

pic

Середина ночі, developer.apple.com працює повільно і з затримками, і вони не знають чому. Але я все одно спробую Swift.

> swift --version  
Swift version 6.0.3 (swift-6.0.3-RELEASE)  
Target: x86_64-unknown-windows-msvc

Перше враження

В деяких мовах, які вважаються низькорівневими, якщо ви зможете викликати MessageBox, ви зрозумієте Матрицю. У Swift, виявляється, все, що потрібно, це просто import WinSDK:

import WinSDK  

func DllMain(  
 hinstDLL: HINSTANCE,  
 fdwReason: DWORD,  
 lpvReserved: LPVOID  
) -> Bool {  
 return false  
}

Компілірувати це також просто:

swiftc SwiftDll.swift -emit-library -O

Немає необхідності оголошувати нічого вручну, ніяких префіксів, нічого. Однак DllMain в SwiftDll.dll залишається за замовчуванням, викликаючи DisableThreadLibraryCalls і повертаючи 1. Я знайшов таке рішення: зробити його public func DllMain і позначити як @_silgen_name("DllMain"). З якоїсь причини @_cdecl("DllMain") змушує його з'являтися двічі з різними іменами — розфасований $s8SwiftDll0B4Main8hinstDLL9fdwReason11lpvReservedSbSpySo11HINSTANCE_VGs6UInt32VSvtF і очікуваний DllMain. Це працює, але це буквально той самий код, який повторюється двічі.

Новина: У Swift немає статичних змінних, крім статичних членів struct/class. Отже, якщо я хочу зареєструвати обробник виключень, мені потрібно зберігати результат AddVectoredExceptionHandler у глобальній змінній. Якого типу..?

5 | var exceptionHandler: PVOID = NULL  

 | `- error: cannot convert value of type '()'  
to specified type 'PVOID' (aka 'UnsafeMutableRawPointer')

ChatGPT згадує UnsafeMutableRawPointer? і nil. Додавання ? до PVOID та зміна NULL на nil вирішують проблему.

Тест

Через деякий час обробник виключень виглядає ось так:

public func handleException(  
 exceptionInfo: PEXCEPTION_POINTERS?  
) -> LONG {  
 let exceptionCode =  
 exceptionInfo!.pointee.ExceptionRecord!.pointee.ExceptionCode  
 let address =  
 exceptionInfo!.pointee.ExceptionRecord!.pointee.ExceptionInformation.1  
 let context =  
 exceptionInfo!.pointee.ContextRecord  

 if (exceptionCode == EXCEPTION_ACCESS_VIOLATION) {  
 addRecord(address, context!.pointee.Rip)  
 savedAddress = address  

 var oldProtect: DWORD = 0  
 VirtualProtect(  
 LPVOID(bitPattern: UInt(address)),  
 1,  
 DWORD(PAGE_READWRITE),  
 &oldProtect  
 )  

 context!.pointee.EFlags |= trapFlag  
 }  

 if (exceptionCode == EXCEPTION_SINGLE_STEP) {  
 var oldProtect: DWORD = 0  
 VirtualProtect(  
 LPVOID(bitPattern: UInt(savedAddress)),  
 1,  
 DWORD(PAGE_NOACCESS),  
 &oldProtect  
 )  
 }  

 return EXCEPTION_CONTINUE_EXECUTION  
}

При порушенні доступу (код виключення EXCEPTION_ACCESS_VIOLATION), буде виконано наступне:

  1. Логування адреси помилкової інструкції та адреси даних, до яких намагається звернутися інструкція
  2. Зміна захисту сторінки пам'яті, що містить дані, на PAGE_READWRITE, щоб ці дані були доступні для читання та запису
  3. Повторне виконання помилкової інструкції

Після повторного виконання інструкції (код виключення EXCEPTION_SINGLE_STEP), обробник змінює захист сторінки назад на PAGE_NOACCESS.

Тепер ідея — ввести SwiftDll.dll в динамічну гру і встановити захист деякої часто використовуваної сторінки на PAGE_NOACCESS — наприклад, сторінки, що містить здоров'я гравця, його позицію в світі тощо. Cheat Engine підходить для знаходження таких речей. Process Hacker можна використовувати для ін'єкції DLL.

Нехай це буде Counter-Strike 2. Виявляється, його потрібно запустити з прапором -insecure, щоб ін'єкція DLL працювала. Чи не дозволяє Process Hacker змінювати захист конкретної сторінки? Що ж, виклик VirtualProtect безпосередньо в DllMain спрацює.
Нехай адреса буде адресою значення здоров'я.

Отже, після ін'єкції, чи стала гра повільною? Так. Чи повільніша вона зараз, ніж лівий панель на developer.apple.com? Може, так, а може й ні.

У мене також є CppDll.dll, яка є точно таким самим, але реалізована на C/C++. Як вона працює порівняно з SwiftDll.dll? Мені потрібно було б увімкнути лічильник FPS, щоб побачити різницю. Насправді, увімкнення його показує, що в обох випадках це одні й ті ж 5-7 FPS.

Висновок

Сучасний C++ не такий "безкоштовний", якщо тільки ваш компілятор не Clang. Якщо це так, ви можете просто написати новий код на Swift. Проект Embedded Swift також вражає. І останнє, але не менш важливе, компіляція займає багато часу.

SwiftTest

Swift для C++ практиків, Частина 1: Вступ і типи значень

Ліва панель дуже повільна · Проблема #919

Перекладено з: Swift for Systems Programming

Leave a Reply

Your email address will not be published. Required fields are marked *