0

I create a new thread in Dllmain() by the CreateThread() API, which does not involve thread synchronization, it is only a separate thread.

Dllmain() invokes WaitForSingleObject(funcThread, INFINITE); to force the main thread to wait for funcThread to finish.

Then I dynamic link this Dllmain(), but the result shows funcThread() finishes early. I know adding a System Pause will work, but do you have some ways to make it work with only changing the Dllmain() file?

What is weird is that it has different results when changing the order of printf() and OutputDebugStringW().

targetFuction.exe

#include <Windows.h>
#include <iostream>

int main() {
    HINSTANCE hModule = LoadLibrary(L"testDllmain.dll");
    return 0;
}

testDllmain.dll

#include <Windows.h>
#include <iostream>

DWORD WINAPI function(LPVOID lpParam) {
    for (int i = 0; i < 100; i++) {
        printf("%d \n", i);
    }

    return 0;
}
HANDLE funcThread;

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:

        funcThread = CreateThread(
            NULL,                   // default security attributes
            0,                      // use default stack size  
            function,       // thread function name
            NULL,          // argument to thread function 
            0,                      // use default creation flags 
            NULL);
        OutputDebugStringW(L"attach process!\n");

        printf("attach process!\n");

        break;
    case DLL_PROCESS_DETACH:

        WaitForSingleObject(funcThread, INFINITE);
        OutputDebugStringW(L"detach process!\n");
        printf("detach process!\n");
        // printf("detach process!\n");
        // OutputDebugStringW(L"detach process!\n");
        break;
    case DLL_THREAD_ATTACH:
        printf("attach thread!\n");
        OutputDebugStringW(L"attach thread!\n");

        break;

    case DLL_THREAD_DETACH:
        OutputDebugStringW(L"detach thread!\n");

        printf("detach thread!\n");
        break;
    }

    return TRUE;
}

Output:

image

OutputDebugStringW() before printf():

image

printf() before OutputDebugStringW()

image

It should also print 23 to 99 and detach process messages, but all of them are missing.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 2
    After your app calls LoadLibrary it just returns from main and stops. All threads will be destroyed. So you just see it – KoVadim Jun 30 '22 at 07:14
  • @KoVadim No this is just not a valid scenario in DllMain, see below – Pepijn Kramer Jun 30 '22 at 07:22
  • I know. But it is not prohibited to call Createthread. And your answer does not answer the question. – KoVadim Jun 30 '22 at 07:23
  • I haven't seen 'WaitForSingleObject' - it changes a little bit everything – KoVadim Jun 30 '22 at 07:33
  • @KoVadim And on CreateThread : Risky in documentation is enough for me to never use it in production code. Not knowing what the use case is I can't come up with a solution. – Pepijn Kramer Jun 30 '22 at 07:35
  • whole c++ is risky, but we love it – KoVadim Jun 30 '22 at 07:36
  • @KoVadim I beg to differ :) If you play by the rules (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) it is very predictable and very safe. – Pepijn Kramer Jun 30 '22 at 07:38
  • @PepijnKramer thanks for your reply! the deadlock seems to appear when I attempt to communicate with other threads or processes. but I only create a separate thread that does not communicate with other threads or processes. maybe it is safe. the core problem is how to force the main thread to wait for funcThread. do you have some solutions? – GuangJun Liu Jun 30 '22 at 07:48
  • @KoVadim I invoke `WaitForSingleObject` in `case DLL_PROCESS_DETACH:`. but it seems not to work, it seems not inside this condition. – GuangJun Liu Jun 30 '22 at 07:50
  • try to call WaitForSingleObject in your main() before return. Also you need to pass the thread handle. – KoVadim Jun 30 '22 at 07:52
  • @GuangJunLiu Yes the host process that loads the dll should after loading the dll call an initialize function (then using threads is safe) and call an unitialize function before unloading the dll, this function can then do the syncrhonization safely. Plug-in solutions often do this. – Pepijn Kramer Jun 30 '22 at 07:53
  • @KoVadim do you have some ways only change Dllmain.dll, not change main.exe? – GuangJun Liu Jun 30 '22 at 07:56
  • what exact task do you want to solve? You shown the solution but not the "original task" – KoVadim Jun 30 '22 at 07:57
  • @KoVadim the purpose of our project is to inject `.dll` file to a running procedure(maybe it is calc.exe, notepad.exe, etc) and log this procedure's callstack. So I can't pass thread handle or add System Pause to the main thread. and for log callstack correctly, I need to create a separate thread to process the output of `CaptureStackBackTrace` api. it meets the same problem as the above code. That is how to force the main thread to wait for funcThread. But noticed, Not change main file, only change `.dll` file. if I can solve this problem, the solution will work for my project. – GuangJun Liu Jun 30 '22 at 08:11
  • Maybe you do not need to create a thread? just put your code in DLL_PROCESS_ATTACH – KoVadim Jun 30 '22 at 08:16
  • @KoVadim that is true, but I need to log messages, to improve CPU effectiveness, I want to create a separate thread to write callstack into the specified log file. – GuangJun Liu Jun 30 '22 at 08:36
  • In your console window I do not see "detach process!", nor "detach thread!" which means that very weird things are happening. Could you please use `OutputDebugStringW` (from debugapi.h) instead of `printf()` and try again? Then, besides the contents of the console window, please also show us the contents of the Visual Studio debug output window. – Mike Nakis Jun 30 '22 at 08:53
  • Also, another very important question is: exactly how do you build your main app and your dll? Specifically, exactly which C++ runtime are you linking? (Are you using the statically linked runtime or the debug-multithreaded-DLL runtime?) You probably see where I am getting at. – Mike Nakis Jun 30 '22 at 08:59
  • thanks for your help. I have updated our description and retested our code using OutputDebugStringW. it is weird the result is different when adopting different order of `printf()` and `OutputDebugStringW`. I also check my configuration, I use debug-multithreaded-DLL runtime. – GuangJun Liu Jun 30 '22 at 09:43
  • 2
    This is a CRT implementation detail. The main thread calls main(), then when it returns does necessary cleanup and calls ExitProcess() to return control to the OS. This kills all running threads first, the DLL_PROCESS_DETACH notifications happen after that. Or in other words, a running thread cannot prevent a program from terminating. There is no simple way to alter this behavior, you do have to explicitly synchronize with the thread to get ahead. – Hans Passant Jun 30 '22 at 09:44
  • you not need wait for thread. – RbMm Jun 30 '22 at 14:31
  • According to [LoadLibrary](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw#remarks), *The system unloads a module when its reference count reaches zero or when the process terminates (regardless of the reference count).* So Have you tried [FreeLibrary](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-freelibrary)? – YangXiaoPo-MSFT Jul 01 '22 at 08:06
  • If you are going to do things like this, please let us know the name of your DLL so we can blacklist it and ensure it is never loaded into our processes. DllMain should not create threads. DllMain should definitely not wait on other threads. That is going to deadlock the entire process. And the only reason to even attempt any of this is if it's a DLL for someone else's process, as otherwise you could do it in the main exe code. This is a bad idea. – Leo Davidson Jul 03 '22 at 11:36
  • So, any news on this? – Mike Nakis Jul 04 '22 at 13:48
  • @MikeNakis I agree with the descriptions of Hans. when the targetFuction.exe terminate, all threads will be killed. DLL_PROCESS_DETACH notifications happen after that. So it doesn't work when `WaitForSingleObject` is put in DLL_PROCESS_DETACH(the thread is killed already). – GuangJun Liu Jul 05 '22 at 01:35
  • So the only way is to continue the process of this thread in DLL_PROCESS_DETACH. For this small test, my solution is to set a global variance in this thread to record which number has been printed and then print the other number in DLL_PROCESS_DETACH. but this solution has risky, for example, the number 22 has been printed, but the global variance doesn't change since the thread is over. – GuangJun Liu Jul 05 '22 at 01:36

2 Answers2

3

What you are trying to do is explicitly NOT supported from DllMain().

From Dynamic-Link Library Best Practices:

You should never perform the following tasks from within DllMain:

  • ...
  • Synchronize with other threads. This can cause a deadlock.
  • ...
  • 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.

And more.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • 2
    There's nothing risky about calling `CreateThread` from `DllMain`. See [Does creating a thread from DllMain deadlock or doesn't it?](https://devblogs.microsoft.com/oldnewthing/20070904-00/?p=25283). Some parts of the documentation seem to stay incorrect for decades. You'll have to learn which ones those are (no, they will not get fixed now that the documentation is hosted on GitHub). – IInspectable Jun 30 '22 at 12:47
  • 1
    Simply creating a thread in `Dllmain()` is fairly safe. What is not safe is synchronizing with that thread in `Dllmain()`, which includes waiting on the thread, exactly what the OP's code is trying to do. – Remy Lebeau Jun 30 '22 at 20:05
1

According to LoadLibrary, The system unloads a module when its reference count reaches zero or when the process terminates (regardless of the reference count). Then calling FreeLibrary should make main thread execute through DLL_PROCESS_DETACH.
I have tried and then the problem could be the main process is always waiting for the thread but the thread never execute. Is this the risky?

Edit: It should be no problem if the thread function is not in the freed dynamic library.

YangXiaoPo-MSFT
  • 1,589
  • 1
  • 4
  • 22