Написание голых функций с собственным прологом и эпилог-кодом в Visual Studio - PullRequest
4 голосов
/ 20 мая 2009

Я пишу некоторый код плагина в dll, который вызывается хостом, который я не могу контролировать.

Хост предполагает, что плагины экспортируются как функции __stdcall. Хосту сообщается имя функции и подробности ожидаемых аргументов, и он динамически обрабатывает вызов к нему через LoadLibrary, GetProcAddress и вручную помещает аргументы в стек.

Обычно dll плагина предоставляет постоянный интерфейс. Мой плагин предоставляет интерфейс, который настраивается во время загрузки DLL. Чтобы достичь этого, мой плагин предоставляет набор стандартных точек входа, которые определяются во время компиляции dll, и распределяет их по мере необходимости для раскрываемой внутренней функциональности.

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

Хост может успешно вызывать функции в моем плагине с правильными аргументами, и все работает хорошо ... Однако я знаю, что а) мои функции не очищают стек, как они должны, поскольку они Определяются как функции __stdcall, которые принимают 4-байтовый указатель, и поэтому они всегда делают 'ret 4' в конце, даже если вызывающий объект выдвинул больше аргументов в стек. и б) я не могу иметь дело с функциями, которые не принимают аргументов, так как ret 4 вытащит 4 байта слишком много из стека при моем возвращении.

Проследив из моего плагина в вызывающий код хоста, я вижу, что на самом деле а) не так уж и много; хост теряет некоторое место в стеке, пока не вернется из диспетчерского вызова, и в этот момент он очищает свой кадр стека, который очищает мой мусор; однако ...

Я могу решить б), переключившись на __cdecl и вообще не убирая. Я предполагаю, что могу решить а), переключившись на обнаженные функции и написав свой собственный общий код очистки аргументов.

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

extern "C" __declspec(naked) __declspec(dllexport) void  * __stdcall EntryPoint(void *pArg1)
{                                                                                                        
   size_t argumentSpaceUsed;
   {
      void *pX = RealEntryPoint(
         reinterpret_cast<ULONG_PTR>(&pArg1), 
         argumentSpaceUsed);

      __asm
      {
         mov eax, dword ptr pX
      }
   }
   __asm
   {
      ret argumentSpaceUsed
   }
}

Но это не работает, так как ret нуждается в постоянной времени компиляции ... Есть предложения?

ОБНОВЛЕНИЕ:

Благодаря предложениям Роба Кеннеди я дошел до этого, который, кажется, работает ...

extern "C" __declspec(naked) __declspec(dllexport) void  * __stdcall EntryPoint(void *pArg1)
{      
   __asm {                                                                                                        
      push ebp          // Set up our stack frame            
      mov ebp, esp  
      mov eax, 0x0      // Space for called func to return arg space used, init to 0            
      push eax          // Set up stack for call to real Entry point
      push esp
      lea eax, pArg1                
      push eax                      
      call RealEntryPoint   // result is left in eax, we leave it there for our caller....         
      pop ecx 
      mov esp,ebp       // remove our stack frame
      pop ebp  
      pop edx           // return address off
      add esp, ecx      // remove 'x' bytes of caller args
      push edx          // return address back on                   
      ret                        
   }
}

Это выглядит правильно?

1 Ответ

4 голосов
/ 30 июня 2009

Поскольку для ret требуется постоянный аргумент, вам необходимо настроить постоянное число параметров для вашей функции, но такая ситуация требуется только в тот момент, когда вы готовы вернуться из функции. Итак, незадолго до конца функции сделайте следующее:

  1. Вытащите адрес возврата с вершины стека и сохраните его во временном хранилище; ECX хорошее место.
  2. Удалите переменное количество аргументов из стека, либо отсекая каждый из них по отдельности, либо напрямую настраивая ESP.
  3. Вставьте обратный адрес обратно в стек.
  4. Используйте ret с постоянным аргументом.

Между прочим, проблема, которую вы называете (а), действительно является проблемой, в общем случае. Вам просто повезло, что вызывающая сторона, кажется, всегда ссылается на свои собственные локальные переменные, используя указатель кадра вместо указателя стека. Для этого функции не требуются, и нет никакой гарантии, что будущая версия хост-программы продолжит работать таким же образом. Компилятор также обязан сохранять некоторые значения регистров в стеке только на время вызова, а затем ожидать, что сможет потом их снова вытолкнуть. Ваш код сломает это.

...