Написание голых функций с пользовательским кодом prolog и epilog в Visual Studio

StackOverflow https://stackoverflow.com/questions/886836

Вопрос

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

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

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

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

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

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

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

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

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                        
   }
}

Правильно ли это выглядит?

Это было полезно?

Решение

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top