Свяжите символ в C++ с адресом, определенным в метаданных.C++

Программы на C++. Форум разработчиков
Ответить
Anonymous
 Свяжите символ в C++ с адресом, определенным в метаданных.

Сообщение Anonymous »

Справочная информация
Я работаю над проектом C++, который внедряется (через нашу собственную программу-загрузчик, которая создает процесс и загружает код и исправления из DLL, в которую скомпилирован проект) в игровой движок с закрытым исходным кодом (который является реверс-инжинирингом; также он предназначен для Windows) с целью исправления ошибок и расширения логики игрового движка. Для взаимодействия с игрой мне, в частности, нужна своего рода библиотека с объявлениями функций игрового движка, глобальных переменных и т. д., что я делаю, переходя к фиксированным/абсолютным адресам (всегда действительным, поскольку DLL находится в том же адресном пространстве, а базовый EXE-файл никогда не перемещается). Типы легко определить, и даже сами переменные (

Код: Выделить всё

reinterpret_cast
на помощь), но для использования функций игры, включая, что наиболее важно, функции-члены, ctors, dtors и виртуальные функции-члены - нам сейчас нужны грязные хаки с написанием некоторых инструкций ассемблера Thunk, которые будут выполняться после пролога, сгенерированного компилятором, и т. д.
Как это выглядит в исходном коде:

Код: Выделить всё

class BeaconClass
{
public:
BeaconClass() JMP_THIS(0x430210)

void Draw(Surface* pSurface, RectangleStruct bounds) JMP_THIS(0x430250)
void SetCoordAndHouse(CoordStruct coord, int houseId) JMP_THIS(0x430590)
//...
};
Расширяется до:

Код: Выделить всё

class BeaconClass
{
public:
BeaconClass()
{
_asm{pop ecx};
_asm{mov esp, ebp};
_asm{pop ebp};
_asm{mov eax, 0x430210};
_asm{jmp eax};
}
// ...

Вот что компилируется — компилятор выдает собственный пролог, для которого требуется исправление:

Код: Выделить всё

.text:100D7290
.text:100D7290                    ; =============== S U B R O U T I N E =======================================
.text:100D7290
.text:100D7290                    ; Attributes: bp-based frame
.text:100D7290
.text:100D7290                    ; BeaconClass *__thiscall BeaconClass::BeaconClass(BeaconClass *this)
.text:100D7290                    ??0BeaconClass@@QAE@XZ proc near        ; CODE XREF: _somehook+1F↓p
.text:100D7290
.text:100D7290                    this            = dword ptr -4
.text:100D7290
.text:100D7290 000 55                             push    ebp
.text:100D7291 004 8B EC                          mov     ebp, esp
.text:100D7293 004 51                             push    ecx
.text:100D7294 008 89 4D FC                       mov     [ebp+this], ecx
.text:100D7297 008 59                             pop     ecx
.text:100D7298 004 8B E5                          mov     esp, ebp
.text:100D729A 004 5D                             pop     ebp
.text:100D729B 000 B8 10 02 43 00                 mov     eax, 430210h
.text:100D72A0 000 FF E0                          jmp     eax
.text:100D72A2                    ; ---------------------------------------------------------------------------
.text:100D72A2 004 8B 45 FC                       mov     eax, [ebp+this]
.text:100D72A5 004 8B E5                          mov     esp, ebp
.text:100D72A7 000 5D                             pop     ebp
.text:100D72A8 -04 C3                             retn
.text:100D72A8                    ??0BeaconClass@@QAE@XZ endp
Но такое поведение зависит от компилятора, и часто вы не можете помешать компилятору генерировать нежелательный код конкретно в конструкторах. Когда я попытался переключиться с MSVC на clang-cl - такие хаки начали давать сбой, и я вылетел из-за поврежденного стека/EIP, а компилятор сгенерировал кучу разных инструкций для более сложного случая ctor:

Код: Выделить всё

class NOVTABLE CCFileClass : public CDFileClass
{
// ...

//Constructor
CCFileClass(const char* pFileName)
: CCFileClass(noinit_t())
{ JMP_THIS(0x4739F0); }

protected:
explicit __forceinline CCFileClass(noinit_t)
: CDFileClass(noinit_t())
{ }

//Properties

public:
MemoryBuffer Buffer;

// ...
};
Дизассемблирование показывает, что компилятор выдает много странных инструкций в прологе и даже вызов MemoryBuffer ctor, чего я так или иначе не делал, что нежелательно.
Изображение

noinit_t() — это бездействующий оператор, поэтому компилятор ничего не делает вместо вызова вектора базового класса, который «связан» с другим адресом. Довольно уродливый хак. И MemoryBuffer даже не должен вызываться, но компилятор вызывает его. И все это не работает.
Очевидно, что это сильно противоречит замыслу и поэтому является довольно нечистым способом...
Вопрос
Можно ли каким-то образом объявить символ с адресом типа extern и позволить компоновщику просто вызывать/использовать этот адрес при обнаружении вызовов/ссылок на него адрес?
Написав что-то вроде

Код: Выделить всё

class NOVTABLE BuildingClass : public TechnoClass
{
//...
[[address(0x43EF90)]]
int GetCurrentFrame();
//...
тогда запись pBuilding->GetCurrentFrame(); приведет к

Код: Выделить всё

mov  ecx, pBuilding
call 0x43EF90
генерируется компилятором/компоновщиком. Возможно, это возможно с помощью добавления какого-либо специального атрибута clang и сценария компоновщика? Но я не нашел, как читать атрибуты из сценария компоновщика.
Предположим, цепочки инструментов Clang/MSVC. В настоящее время проект работает на MSVC, но рассматривается возможность перехода на clang с совместимостью с MSVC (если это вообще возможно).
Рассматриваемые альтернативы
  • Определение символов в ассемблере с использованием искаженного имени — работает, но чрезвычайно громоздко для довольно простой задачи, не может быть записано в режиме реального времени, а также создает функцию преобразования.
  • Использовать как есть - очень непереносимо, происходит сбой при переходе на clang, очень сильно зависит от неясного недокументированного поведения, которое может сломаться.
  • Использование голых функций - невозможно для ctors, dtors.
  • Использование символов, определенных из компоновщик - может работать, но я бы предпочел, чтобы каждое определение символа было в соответствии с его объявлением, такое разделение является громоздким.
  • Использование переменных указателей статических функций - на самом деле не применимо с ctors, dtors, наследованием, виртуальными объектами.


Подробнее здесь: https://stackoverflow.com/questions/797 ... ed-address
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C++»