как правильно создать и закончить тред (thread) в WinAPI?
От: ksd Россия  
Дата: 03.02.17 12:27
Оценка:
Казалось бы все просто и понятно, а вот что имеем:

// DLL
HANDLE h[2];

void __cdecl ThreadProc(void*)
{
    SetEvent(h[0]);
}

BOOL APIENTRY DllMain(
    IN HMODULE moduleHandle,
    IN DWORD reasonForCall,
    IN LPVOID pReserved)
{
    switch (reasonForCall)
    {
        case DLL_PROCESS_ATTACH:
        {
            h[0] = CreateEvent(0,0,0,0);
            h[1] = (HANDLE)_beginthread(ThreadProc,0,0);
        }
        break;
        case DLL_PROCESS_DETACH:
        {
            WaitForMultipleObjects(2, h, FALSE, INFINITE); // виснет здесь
        }
    }
    return TRUE;
}


// тест
int _tmain(int argc, _TCHAR* argv[])
{
    HMODULE h = LoadLibrary(_T("dll.dll"));
    FreeLibrary(h);
    return 0;
}


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

Научите, пожалуйста, если не сложно и найдется время, как правильно запускать и останавливать треды! У меня иначе как используя 2 евента и 2 раза принудительно переключаясь не получается
Re: как правильно создать и закончить тред (thread) в WinAPI?
От: Слава  
Дата: 03.02.17 12:35
Оценка: +1
Здравствуйте, ksd, Вы писали:

ksd>Казалось бы все просто и понятно, а вот что имеем:


ksd>Научите, пожалуйста, если не сложно и найдется время, как правильно запускать и останавливать треды! У меня иначе как используя 2 евента и 2 раза принудительно переключаясь не получается


А разве в писании не было сказано — не создавать тредов в DLL main и вообще ничего тяжёлого там не делать?
Re[2]: как правильно создать и закончить тред (thread) в WinAPI?
От: ksd Россия  
Дата: 03.02.17 12:43
Оценка:
С>А разве в писании не было сказано — не создавать тредов в DLL main и вообще ничего тяжёлого там не делать?
а что нещастным еретикам делать?! другой точки входа (экспортируемых функций) у DLL-ки нет.
Re[3]: как правильно создать и закончить тред (thread) в WinAPI?
От: solano  
Дата: 03.02.17 12:47
Оценка:
Здравствуйте, ksd, Вы писали:

ksd>а что нещастным еретикам делать?! другой точки входа (экспортируемых функций) у DLL-ки нет.


А что мешает добавить пару функций типа Init()/Destroy() и обязать пользователя их звать. А в них делать всю подготовительную работу. Но а если пользователь не позвал их, то как-нибудь его наградить.
С наилучшими пожеланиями.
Re[4]: как правильно создать и закончить тред (thread) в WinAPI?
От: ksd Россия  
Дата: 03.02.17 13:28
Оценка:
S>А что мешает добавить пару функций типа Init()/Destroy() и обязать пользователя их звать. А в них делать всю подготовительную работу. Но а если пользователь не позвал их, то как-нибудь его наградить.
мешает отсутствие пользователей.
Re: как правильно создать и закончить тред (thread) в WinAPI?
От: okman Беларусь https://searchinform.ru/
Дата: 04.02.17 07:27
Оценка: 6 (1)
Здравствуйте, ksd, Вы писали:

ksd>Дело в том, что LoadLibrary и FreeLibrary выполняются так, что не успевает зайти в ThreadProc, поэтому на FreeLibrary висим. Более того, понимаю, что хэндл треда ждать в DllMain вообще бесполезно, но пусть будет (это из реальной функции ожидания завершения треда для общего случая).


ksd>Научите, пожалуйста, если не сложно и найдется время, как правильно запускать и останавливать треды! У меня иначе как используя 2 евента и 2 раза принудительно переключаясь не получается


Во-первых, здесь полезно будет заменить _beginthread на _beginthreadex. У них есть маленькое,
но существенное различие, связанное с возвратом хэндла, информация есть на MSDN.

Во-вторых, перед созданием потока можно добавить ссылку на dll, чтобы предотвратить
ее преждевременную выгрузку: LoadLibrary или GetModuleHandleEx. Тогда функция потока по
завершению должна будет вызвать FreeLibraryAndExitThread — освобождение ссылки на dll и
завершение потока за один вызов.

Правда, второй способ тоже нельзя считать полностью корректным.
Дело в том, что хоть функция создания потока (_beginthreadex, CreateThread, CreateRemoteThread...) в
случае успеха и возвращает его хэндл, она не гарантирует, что поток дойдет до функции thread proc
(потому что жизнь потока начинается не в thread proc, а в ntdll!LdrInitializeThunk — а между
ними еще много всего).

Эту проблему можно было бы разрешить ожиданием (WaitForXxx), но ждать потока в DllMain нельзя.

Другая проблема будет, если dll должна загружаться и выгружаться много раз.
В этом случае мы рискуем нарваться на ситуацию, когда очередной LoadLibrary не загрузит
dll по новой, а просто вернет HMODULE уже загруженной dll и, соответственно, новый поток в
ней запускаться не будет, потому что еще не отработал старый.
Re[2]: еще один вопрос
От: ksd Россия  
Дата: 06.02.17 08:02
Оценка:
O>Эту проблему можно было бы разрешить ожиданием (WaitForXxx), но ждать потока в DllMain нельзя.
частично проблему решает WaitForXxx в DllMain, почему нельзя?
Re[3]: еще один вопрос
От: ononim  
Дата: 06.02.17 08:37
Оценка:
O>>Эту проблему можно было бы разрешить ожиданием (WaitForXxx), но ждать потока в DllMain нельзя.
ksd>частично проблему решает WaitForXxx в DllMain, почему нельзя?
Потому что потоку, чтобы завершиться, требуется захватить PEB loader lock — тот самый лок, который захватывается на время вызова DllMain. В результате — дедлок. Кстати чтобы потоку начать исполняться тоже требуется вначале сходить под этот лок, так что находясь в DllMain нельзя ждять не только завершения потока, но и вообще каких-либо действий с его стороны.
Как много веселых ребят, и все делают велосипед...
Re[4]: еще один вопрос
От: ksd Россия  
Дата: 06.02.17 09:20
Оценка:
Здравствуйте, ononim, Вы писали:

O>Потому что потоку, чтобы завершиться, требуется захватить PEB loader lock — тот самый лок, который захватывается на время вызова DllMain. В результате — дедлок. Кстати чтобы потоку начать исполняться тоже требуется вначале сходить под этот лок, так что находясь в DllMain нельзя ждять не только завершения потока, но и вообще каких-либо действий с его стороны.

ответ: дедлок обходится евентом.
вопрос: что, таки DllMain морозит все потоки dll-ки?
Re[5]: еще один вопрос
От: ononim  
Дата: 06.02.17 09:45
Оценка: 8 (1)
O>>Потому что потоку, чтобы завершиться, требуется захватить PEB loader lock — тот самый лок, который захватывается на время вызова DllMain. В результате — дедлок. Кстати чтобы потоку начать исполняться тоже требуется вначале сходить под этот лок, так что находясь в DllMain нельзя ждять не только завершения потока, но и вообще каких-либо действий с его стороны.
ksd>ответ: дедлок обходится евентом.
ksd>вопрос: что, таки DllMain морозит все потоки dll-ки?
Еще раз. Перед тем как начать исполнять ThreadRoutine и после его завершения поток на короткое время захыватывает PEB loader lock, которым накрыт DllMain. Так что пока какой то поток сидит внутри DllMain никакой другой поток не сможет завершиться окончательно или начать исполнение рутины.
Ивент, если я правильно понял что вы с ним делать хотите, так же не гарантирует от неприятностей т.к. будут следуюшие гонки:
1) Поток может не успеть начать исполнение, то есть не дойти до начала вашей thread routine, если между запуском потока и выгрузкой длл пройдет очень мало времени, соответственно просигналить ваш ивент перед выходом он в таком случае не сможет.
2) Между сигналом ивента и покиданием потока вашего thread routine будут как минимум несколько инструкций. Если поток замешкается на них, а тем временем ваша длл выгрузится — будет крэш.
Вобщем, не ищите костыли, они имеют тенденцию оказываться граблями, а делайте нормально.
Как много веселых ребят, и все делают велосипед...
Отредактировано 06.02.2017 9:52 ononim . Предыдущая версия . Еще …
Отредактировано 06.02.2017 9:48 ononim . Предыдущая версия .
Re[6]: нормально, это как?
От: ksd Россия  
Дата: 06.02.17 12:02
Оценка:
O>Еще раз. Перед тем как начать исполнять ThreadRoutine и после его завершения поток на короткое время захыватывает PEB loader lock, которым накрыт DllMain. Так что пока какой то поток сидит внутри DllMain никакой другой поток не сможет завершиться окончательно или начать исполнение рутины.
O>Ивент, если я правильно понял что вы с ним делать хотите, так же не гарантирует от неприятностей т.к. будут следуюшие гонки:
O>1) Поток может не успеть начать исполнение, то есть не дойти до начала вашей thread routine, если между запуском потока и выгрузкой длл пройдет очень мало времени, соответственно просигналить ваш ивент перед выходом он в таком случае не сможет.
O>2) Между сигналом ивента и покиданием потока вашего thread routine будут как минимум несколько инструкций. Если поток замешкается на них, а тем временем ваша длл выгрузится — будет крэш.
O>Вобщем, не ищите костыли, они имеют тенденцию оказываться граблями, а делайте нормально.
Ну, вот и наблюдаю 1 и 2.
Нормально не совсем понятно, как: длл-ка то инжектируется.
Re[7]: нормально, это как?
От: ononim  
Дата: 06.02.17 12:58
Оценка:
O>>2) Между сигналом ивента и покиданием потока вашего thread routine будут как минимум несколько инструкций. Если поток замешкается на них, а тем временем ваша длл выгрузится — будет крэш.
O>>Вобщем, не ищите костыли, они имеют тенденцию оказываться граблями, а делайте нормально.
ksd>Ну, вот и наблюдаю 1 и 2.
ksd>Нормально не совсем понятно, как: длл-ка то инжектируется.
Ну раз инжектится, значит хукает чтото, раз хукает, значит может похукать весь зоопарк ExitProcess-ов и сделать шатдаун себя оттудова
Как много веселых ребят, и все делают велосипед...
Re[8]: не может
От: ksd Россия  
Дата: 06.02.17 13:11
Оценка:
Здравствуйте, ononim, Вы писали:

O>Ну раз инжектится, значит хукает чтото, раз хукает, значит может похукать весь зоопарк ExitProcess-ов и сделать шатдаун себя оттудова

Это ни к чему. Так все ок почему то. Этовот проявляется, когда внешняя прога ее инжектит/режектит быстра бытсра.
Re[9]: не может
От: ononim  
Дата: 06.02.17 13:26
Оценка:
ksd>Здравствуйте, ononim, Вы писали:
O>>Ну раз инжектится, значит хукает чтото, раз хукает, значит может похукать весь зоопарк ExitProcess-ов и сделать шатдаун себя оттудова
ksd>Это ни к чему. Так все ок почему то. Этовот проявляется, когда внешняя прога ее инжектит/режектит быстра бытсра.
Ну так тут вообще никаких проблем. Просто инжект сделать не тупым CreateRemoteThread(..LoadLibrary...), а с генерацией в АП процесса машкода, который сделает
dll = LoadLibrary("inject.dll")
pfn = GetProcAddress(dll, "inject_init");
if (pfn) pfn();
return dll;


pfn = GetProcAddress(dll, "inject_deinit");
if (pfn) pfn();
FreeLibrary(dll);


..да, сложно, ну а кому ща легко.
PS Если знать кое какие секреты и при определенных ограничениях этот прием можно значительно упростить. Но это тот случай когда до этого лучше дойти самому.
Как много веселых ребят, и все делают велосипед...
Отредактировано 06.02.2017 13:27 ononim . Предыдущая версия .
Re[10]: не может
От: ksd Россия  
Дата: 07.02.17 08:34
Оценка:
Здравствуйте, ononim, Вы писали:

O>Ну так тут вообще никаких проблем. Просто инжект сделать не тупым CreateRemoteThread(..LoadLibrary...), а с генерацией в АП процесса машкода, который сделает

O>
O>dll = LoadLibrary("inject.dll")
O>pfn = GetProcAddress(dll, "inject_init");
O>if (pfn) pfn();
O>return dll;
O>


O>
O>pfn = GetProcAddress(dll, "inject_deinit");
O>if (pfn) pfn();
O>FreeLibrary(dll);
O>


O>..да, сложно, ну а кому ща легко.

O>PS Если знать кое какие секреты и при определенных ограничениях этот прием можно значительно упростить. Но это тот случай когда до этого лучше дойти самому.
спасибо. очень хорошее решение. ну, не дошел сам... но отчего совсем не жалко.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.