Страницы

Поиск по вопросам

воскресенье, 9 февраля 2020 г.

Выгрузить dll из кода dll

#delphi #winapi #dll


Есть dll для внедрения в уже работающий процесс, которая что-то делает, а затем самостоятельно
себя выгружает. Всё это происходит на самом этапе внедрения (при DLL_PROCESS_ATTACH).

library Test;

uses Winapi.Windows;

procedure DllMain(Reason: integer);
begin
  if Reason = DLL_PROCESS_ATTACH then
  begin
    // что-то делаем
    FreeLibraryAndExitThread(HInstance, 0); // выгружаемся
  end;
end;

begin
  DllProc := @DllMain;
  DllMain(DLL_PROCESS_ATTACH);
end.


Вероятно, при выгрузке происходит какая-то блокировка, в результате которой иногда
процесс наглухо зависает. Иногда этого не происходит, но корректно закрыть процесс
после этого всё равно не получается.

Пример:
1) Запускам, скажем, калькулятор
2) Внедряем в него эту dll (Process Hacker'ом, например)  

Дальше я смог поймать три типа поведения процесса:
а) Он намертво зависает
б) Перестаёт реагировать на закрытие крестиком
в) При закрытии основного окна остаётся висеть в списке процессов.

Несмотря на то, что dll себя удачно выгружает и даже завершает свой поток — она что-то
портит в процессе.

Если убрать из кода выгрузку, и делать её отдельно — всё работает как надо.

Вопрос: как избавиться от такого поведения с сохранением функциональности по самостоятельной
выгрузке dll?



UPD1: Если заменить FreeLibraryAndExitThread на FreeLibrary всё становиться ещё хуже:
процесс тут же падает с ошибкой BEX64.
    


Ответы

Ответ 1



DLLMain вызывается системой под общей блокировкой, поэтому существуют значитильные ограничения на выполнение чего-либо внутри этой функции. Warning There are significant limits on what you can safely do in a DLL entry point. See General Best Practices for specific Windows APIs that are unsafe to call in DllMain. If you need anything but the simplest initialization then do that in an initialization function for the DLL. You can require applications to call the initialization function after DllMain has run and before they call any other functions in the DLL. т.е. самый простой способ решить проблему - вызывать отдельную функцию инициализации из этой DLL, которую Вы можете экспортировать наравне со всеми другими функциями и структурами. Посмотрите так же перечень задач, которые нельзя выполнять в DllMain: Call LoadLibrary or LoadLibraryEx (either directly or indirectly). This can cause a deadlock or a crash. Call GetStringTypeA, GetStringTypeEx, or GetStringTypeW (either directly or indirectly). This can cause a deadlock or a crash. Synchronize with other threads. This can cause a deadlock. Acquire a synchronization object that is owned by code that is waiting to acquire the loader lock. This can cause a deadlock. Initialize COM threads by using CoInitializeEx. Under certain conditions, this function can call LoadLibraryEx. Call the registry functions. These functions are implemented in Advapi32.dll. If Advapi32.dll is not initialized before your DLL, the DLL can access uninitialized memory and cause the process to crash. Call CreateProcess. Creating a process can load another DLL. Call ExitThread. Exiting a thread during DLL detach can cause the loader lock to be acquired again, causing a deadlock or a crash. Call CreateThread. Creating a thread can work if you do not synchronize with other threads, but it is risky. Create a named pipe or other named object (Windows 2000 only). In Windows 2000, named objects are provided by the Terminal Services DLL. If this DLL is not initialized, calls to the DLL can cause the process to crash. Use the memory management function from the dynamic C Run-Time (CRT). If the CRT DLL is not initialized, calls to these functions can cause the process to crash. Call functions in User32.dll or Gdi32.dll. Some functions load another DLL, which may not be initialized. Use managed code. И перечень задач, которые разрешены в DllMain: Initialize static data structures and members at compile time. Create and initialize synchronization objects. Allocate memory and initialize dynamic data structures (avoiding the functions listed above.) Set up thread local storage (TLS). Open, read from, and write to files. Call functions in Kernel32.dll (except the functions that are listed above). Set global pointers to NULL, putting off the initialization of dynamic members. In Microsoft Windows Vista™, you can use the one-time initialization functions to ensure that a block of code is executed only once in a multithreaded environment. Подробности можно получить из General Best Practices Другой способ вызвать что-либо в DllMain из запрещенного списка выше: создать в DllMain поток, в котором и выполнить все необходимые действия. Поток обойдет глобальную блокировку уже за пределами DllMain, поэтому в нем можно выполнять код без ограничений. Тут тоже есть некоторые нюансы, подробности - в блоге ms: Does creating a thread from DllMain deadlock or doesn’t it?

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

Отправить комментарий