Динамический вызов метода-члена C++

Я знаю, что это обсуждалось несколько раз, но моя ситуация немного отличается.

У меня есть сторонний dll, экспортирующий некоторые классы. К сожалению, файл заголовка недоступен. Все еще возможно вызывать экспортированные функции. Но я не могу обойти, передав правильный указатель "this" (который передается в регистре RCX).

Сначала я использую dumpbin /exports для извлечения имен функций (имена изменяются, поскольку сторонние библиотеки и имена функций являются конфиденциальными).

       4873 1308 0018B380 ?GetId@ThirdPartyClass@ThirdPartyNamespace@@QEBAJXZ = ??GetId@ThirdPartyClass@ThirdPartyNamespace@@QEBAJXZ (public: long __cdecl ThirdPartyNamespace::ThirdPartyClass::GetId(void)const )

Теперь API позволяет мне зарегистрировать мой обратный вызов, который получает указатель на ThirdPartyNamespace::ThirdPartyClass (есть только предварительное объявление ThirdPartyClass).

Вот как я пытаюсь вызвать ThirdPartyNamespace::ThirdPartyClass::GetId():

long (ThirdPartyNamespace::ThirdPartyClass::*_pFnGetId)() const;
HMODULE hModule = GetModuleHandle("ThirdPartyDLL.dll");
*(FARPROC*)&_pFnGetId= GetProcAddress(hModule, "?GetId@ThirdPartyClass@ThirdPartyNamespace@@QEBAJXZ");

long id = (ptr->*_pFnGetId)();

Все выглядит хорошо (то есть, если я вступаю - я действительно получаю внутри метод ThirdPartyClass:: GetId. Но этот указатель не является хорошим. Хотя ptr хорош, и если в отладчике я вручную изменяю rcx на ptr - он работает нормально. Но компилятор не передает ptr по некоторым причинам. Вот разборка:

long id = (ptr->*_pFnGetId)();
000000005C882362  movsxd      rax,dword ptr [rdi+30h]  
000000005C882366  test        eax,eax  
000000005C882368  jne         MyClass::MyCallback+223h (05C882373h)
000000005C88236A  movsxd      rcx,dword ptr [rdi+28h]  
000000005C88236E  add         rcx,rsi  
000000005C882371  jmp         MyClass::MyCallback+240h (05C882390h)  
000000005C882373  movsxd      r8,dword ptr [rdi+2Ch]  
000000005C882377  mov         rcx,rax  
000000005C88237A  mov         rax,qword ptr [r8+rsi]  
000000005C88237E  movsxd      rdx,dword ptr [rax+rcx]  
000000005C882382  movsxd      rcx,dword ptr [rdi+28h]  
000000005C882386  lea         rax,[r8+rdx]  
000000005C88238A  add         rcx,rax  
000000005C88238D  add         rcx,rsi  
000000005C882390  call        qword ptr [rdi+20h]  
000000005C882393  mov         ebp,eax  

Перед выполнением этих команд rsi содержит указатель на объект ThirdPartyClass (то есть ptr), но вместо передачи его непосредственно в rcx выполняется некоторая арифметика, и в результате этот указатель становится полностью неверным.

некоторые следы, которые я не понимаю, почему компилятор делает это, так как в итоге вызывает не виртуальную функцию ThirdPartyClass:: GetId ():

000000005C88237A  mov         rax,qword ptr [r8+rsi] 
    R8  0000000000000000    
    RSI 000000004C691AA0    // good pointer to ThirdPartyClass object
    RAX 0000000008E87728    // this gets pointer to virtual functions table of ThirdPartyClass
000000005C88237E  movsxd      rdx,dword ptr [rax+rcx] 
    RAX 0000000008E87728    
    RCX FFFFFFFFFFFFFFFF    
    RDX FFFFFFFFC0F3C600
000000005C882382  movsxd      rcx,dword ptr [rdi+28h]  
    RCX 0000000000000000    
    RDI 000000005C9BE690    
000000005C882386  lea         rax,[r8+rdx]
    RAX FFFFFFFFC0F3C600    
    RDX FFFFFFFFC0F3C600    
    R8  0000000000000000    
000000005C88238A  add         rcx,rax 
    RAX FFFFFFFFC0F3C600    
    RCX FFFFFFFFC0F3C600    
000000005C88238D  add         rcx,rsi  
    RCX 000000000D5CE0A0    
    RSI 000000004C691AA0    
000000005C882390  call        qword ptr [rdi+20h]   

На мой взгляд, это должно быть так просто, как

long id = (ptr->*_pFnGetId)();
mov         rcx,rsi  
call        qword ptr [rdi+20h] 
mov         ebp,eax

И если я перед вызовом qword ptr [rdi+20h] установлю rcx равным rsi, он вернет мне ожидаемое значение.

Я делаю что-то совершенно не так? Заранее спасибо.

1 ответ

Решение

Хорошо, я нашел решение по случайности (поскольку я уже использовал похожий подход, и он работал в несколько иной ситуации.

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

Возможно, это не имеет значения, но я знаю, что ThirdPartyNamespace::ThirdPartyClass имеет виртуальные функции, поэтому я объявляю также поддельный класс с виртуальной функцией.

class FakeCall
{
private:
    FakeCall(){}
    virtual ~FakeCall(){}
};

Остальное, как в исходном коде, за исключением одного мелкого момента, вместо вызова ptr->*_pFnGetId (где ptr - указатель на неизвестное, форвард объявил класс ThirdPartyNamespace::ThirdPartyClass), я притворяюсь, что вызываю метод-член в своем классе FakeCall:

    FakeCall * fake = (FakeCall*)ptr;
    long sico = (fake->*_pFnGetId)();

Разборка выглядит именно так, как и ожидалось:

long sico = (fake->*_pFnGetSico)();
000000005A612096  mov         rcx,rax  
000000005A612099  call        qword ptr [r12+20h]  
000000005A61209E  mov         esi,eax 

И это работает отлично!

Некоторые наблюдения:

  1. Указатель на метод-член, как я думал изначально, не более чем обычный указатель на функцию.
  2. Компилятор Microsoft (по крайней мере VS2008) сходит с ума, если вызывает метод-член для не определенного класса (т. Е. Только прямое объявление имени).
Другие вопросы по тегам