Какова реальная подпись конструктора C после компиляции

#c #constructor #hook #reverse-engineering #signature

#c #конструктор #крюк #обратное проектирование #подпись

Вопрос:

Какова реальная подпись конструктора в C ?

Предыстория: я пытаюсь подключить внутренние функции dll. У меня есть файлы pdb-Debug, и я смог получить точное местоположение функции, включая ее неотправленное имя. Функция выглядит следующим образом:

 protected: __cdecl ClassName::ClassName(bool * __ptr64) __ptr64
  

Так что это, очевидно, конструктор.
Я попробовал функцию void(void * pThisPointer, bool * pBoolPointer), но поскольку программа завершает работу после простой переадресации вызова (другие функции работают нормально таким образом), я предполагаю, что моя подпись неверна.

Знаете ли вы, какую подпись использовать для конструктора (который, скорее всего, не виртуальный)? Или у вас есть какие-либо другие идеи, что может пойти не так?

РЕДАКТИРОВАТЬ: я использую x64 и компилятор Visual studio 2010, целевой компилятор должен быть чем-то вроде компилятора Visual Studio, поскольку это библиотека DLL Microsoft.

Комментарии:

1. Конструкторы не являются обычными функциями; в них содержится много магии, которая может быть или не быть включена в тело любого метода, на который вы наткнулись.

2. На самом деле это __thiscall . Дополнительная магия включает в себя вызов базового конструктора и замену указателя vtable в конце. Вещи, которые вы не можете легко сделать самостоятельно.

3. Мне не нужно вызывать его полностью самостоятельно. Я просто хочу «обернуть» вокруг него. Итак, когда вы говорите __thiscall , означает ли это, что я должен добавить void * thisPointer в качестве первого аргумента?

Ответ №1:

Вы не можете вызвать конструктор напрямую. Что касается C , конструкторы не имеют имени. Другими словами, вы не можете вызывать конструкторы. Многие компиляторы создают две или даже три разные функции. Какой из них вы должны вызвать?

Ответ — ни один из них. Вы не можете и не должны пытаться вызывать конструктор непосредственно из своего кода.

Комментарии:

1. В моем pdb-файле отображается искаженное имя конструктора. Я вижу asm-дизассемблирование функции. На самом деле, если я только вызываю его, он работает. Программа вылетает только в том случае, если я пытаюсь увеличить свой счетчик отладки после вызова. Итак, я предполагаю, что каким-то образом стек поврежден или что-то в этом роде. Когда я увеличиваю счетчик перед вызовом, все тоже работает нормально. Компилятор должен быть чем-то похожим на компилятор Visual Studio

2. @ArtificialMind: Нет, так не должно быть. Очевидно, что конструктор должен иметь имя (фактически, несколько имен) с точки зрения связывания. С точки зрения самого языка, конструктор не имеет имени. Какие функции и какие функции вызываются при создании экземпляра некоторого класса, это не то, от чего вы когда-либо должны зависеть. Вы не можете и не должны вызывать конструктор из своего кода, даже по его искаженному имени. (Фактически, прямой вызов чего-либо по его искаженному имени является незаконным.)

3. DLL — это Windows dll, поэтому маловероятно, что она изменится в моей версии. Поэтому должно быть «безопасно» зависеть от того, что он не меняется.

Ответ №2:

Самый простой способ — посмотреть на разборку фактического конструктора и посмотреть, к чему он обращается. Обычное соглашение с MSVC (и другими компиляторами) заключается в передаче this в качестве скрытого первого параметра. Обычно это делается с __thiscall помощью соглашения (т. Е. В ecx x86), но в x64 существует только одно соглашение, так __thiscall что это то же __cdecl самое, что и or __stdcall .

Однако другое не очень известное соглашение MSVC заключается в том, что конструкторы должны возвращать this указатель. Я подозреваю, что именно это вызывает ваш сбой.

Вероятно, здесь это не очень актуально, но взгляните на мою статью о внутренних компонентах MSVC C . Он описывает реализацию x86, но многое будет применимо

Ответ №3:

Поскольку вы не можете юридически передать указатель на функцию конструктору, на самом деле у него нет значимой подписи, насколько это касается самого C .

Реализации делают свое дело, но обычно класс может иметь по крайней мере два тела функций конструктора в исходящем коде — одно для обычного использования, а другое для использования в качестве виртуальной базы. Компилятор знает, как использовать каждый из них — вам нужно точно знать, какие части построения объекта выполняются в этом исходящем коде в вашей реализации, что делается вызывающим перед его вызовом, что делается вызывающим после его возврата и что делается вызывающим, если он выдаетисключение, или вы не можете правильно вызвать его, даже учитывая, что вы выяснили его адрес из отладочной информации.

Чтобы перепроектировать конструкцию объекта, вы должны посмотреть на код, выданный в месте, где объект создается с помощью этого конструктора.

Комментарии:

1. Может быть, я выразился недостаточно ясно: я не хочу называть это сам. Я просто хочу подключить его, чтобы я мог регистрировать, когда и с каким параметром вызывается конструктор. Если я просто передам параметр, который у меня есть до сих пор, все идет нормально, я могу даже увеличить глобальную переменную счетчика перед вызовом. Но если я попытаюсь увеличить его впоследствии, программа выйдет из строя. Если я регистрирую указатель this, я получаю около 30 разных указателей для> 1000 вызовов, некоторые использовали больше, некоторые использовали меньше, но я думаю, что это соответствует управлению памятью

2. @ArtificialMind: я все еще думаю, что вам лучше всего посмотреть на код, созданный для его вызова. Если вы правы, и стек поврежден, то, возможно, это связано с тем, что есть некоторые манипуляции со стеком, которые вызывающий должен выполнить после возврата конструктора, но вы этого не делаете до увеличения вашего счетчика. То есть соглашение о вызове для этой внутренней функции конструктора может отличаться от обычных функций, cdecl может вводить в заблуждение. Это даже не обязательно должно быть одним из обычных соглашений о вызовах, хотя оно должно быть задокументировано где-нибудь в Windows C ABI.

3. это звучит многообещающе. Насколько я вижу, функция начинается push rbx; sub rsp, 20h и заканчивается, add rsp,20h; pop rbx; retn что я понимаю как «сохранить rbx, выделить 20 байт одного стека, освободить 20 байт в стеке, восстановить rbx, возвращая», что звучит так, как будто я должен убедиться, что все регистры ожидают rbx (это второй параметр для вызова на основе стекасоглашения?) это то же самое, что и ввод моей функции. Есть ли какая-то встроенная функция для сохранения всех регистров и восстановления их при выходе?