c++ - 从 exe 入口点返回不会终止 Windows 10 上的进程

标签 c++ windows threadpool program-entry-point exit

我的尝试

我通过指定 /GS- 编译器标志和 /NoDefaultLib 链接器标志,使用 Microsoft Visual Studio 创建了一个最小的、无 CRT 的、依赖性耗尽的可执行文件,并且将主函数命名为 mainCRTStartup。应用程序不会创建额外的线程并在 < 5 秒后从 mainCRTStartup 返回,但进程终止总共需要 30 秒。

问题描述

根据我的经验,如果在 Windows 10 上执行的应用程序仅依赖于默认加载到每个 Windows 进程中的动态库,命名为 ntdll.dllKernelBase.dllkernel32.dll,当主线程从mainCRTStartup函数返回时,进程正常退出。

如果静态或动态加载其他库(例如通过调用 LoadLibraryW ),从主函数返回将使进程保持事件状态:正常运行时为 30 秒,在调试器下运行时为无限期。

上下文

在创建进程时,Windows 10 进程加载器会创建额外的线程来更快地加载动态库,请参阅:

Cylance 在 Windows 10 Parallel Loading Breakdown 中提到:

The worker thread idle timeout is set to 30 seconds. Programs which execute in less than 30 seconds will appear to hang due to ntdll!TppWorkerThreadwaiting for the idle timeout before the process terminates.

Microsoft 在 Terminating a Process: How Processes are Terminated 中提到:

Note that some implementation of the C run-time library (CRT) call ExitProcess if the primary thread of the process returns.

另一方面,Microsoft 在 ExitProcess 中提到:

Note that returning from the main function of an application results in a call to ExitProcess.

测试代码

这是我使用的最小测试代码,我使用了 kernel32!CloseHandleuser32!CloseWindow 作为示例,对它们的调用实际上没有做任何事情:

#include <cstdint>

namespace windows {
    typedef const intptr_t Handle;
    typedef const void *   Module;

    constexpr Handle InvalidHandleValue = -1;

    namespace kernel32 {
        extern "C" uint32_t __stdcall CloseHandle(Handle);
        extern "C" uint32_t __stdcall FreeLibrary(Module);
        extern "C" Module   __stdcall LoadLibraryW(const wchar_t *);
    }

    namespace user32 {
        extern "C" uint32_t __stdcall CloseWindow(Handle);
    }
}

int mainCRTStartup() {
    // 0 seconds
    // windows::kernel32::CloseHandle(windows::InvalidHandleValue);

    // 30 seconds
    // windows::user32::CloseWindow(windows::InvalidHandleValue);

    // 0 seconds
    // windows::kernel32::FreeLibrary(windows::kernel32::LoadLibraryW(L"kernel32.dll"));

    // 30 seconds
    // windows::kernel32::FreeLibrary(windows::kernel32::LoadLibraryW(L"user32.dll"));

    // 0 seconds
    // windows::kernel32::FreeLibrary(windows::kernel32::LoadLibraryW(L""));

    return 0;
}

调试

mainCRTStartup 函数中注释 WinAPI 用法会导致上述相应 WinAPI 调用的执行时间。

这是在a debugger中追踪到的程序的执行流程在伪 C++ 中:

ntdll.RtlUserThreadStart() {
    kernel32.BaseThreadInitThunk() {
        const auto return_code = test.mainCRTStartup();

        ntdll.RtlExitUserThread(return_code) {
            if (ntdll.NtQueryInformationThread(CURRENT_THREAD, ThreadAmILastThread) != STATUS_SUCCESS || !AmILastThread) {
                // Bad path - for `30 seconds`.

                ntdll.LdrShutdownThread();
                ntdll.TpCheckTerminateWorker(0);
                ntdll.NtTerminateThread(0, return_code);

                // The thread execution does not return from `NtTerminateThread`, but the process still runs.
            } else {
                // Good path - for `0 seconds`.

                ntdll.RtlExitUserProcess(return_code) {
                    ntdll.EtwpShutdownPrivateLoggers();
                    ntdll.LdrpDrainWorkQueue(0);
                    ntdll.LdrpAcquireLoaderLock();
                    ntdll.RtlEnterCriticalSection(ntdll.FastPebLock);
                    ntdll.RtlLockHeap(peb.ProcessHeap);
                    ntdll.NtTerminateProcess(0, return_code);
                    ntdll.RtlUnlockProcessHeapOnProcessTerminate();
                    ntdll.RtlLeaveCriticalSection(ntdll.FastPebLock);
                    ntdll.RtlReportSilentProcessExit(CURRENT_PROCESS, return_code);
                    ntdll.LdrShutdownProcess();
                    ntdll.NtTerminateProcess(CURRENT_PROCESS, return_code);

                    // The thread execution does not return from `NtTerminateProcess` and the process is terminated.
                }
            }
        }
    }
}

预期结果

如果进程没有创建额外的线程并从主函数返回,我预计进程会终止。

调用 ExitProcess在 main 函数结束时终止进程,即使调用 WinAPI 导致之前 30 秒执行。使用此 API 并不总是可行的,因为有问题的应用程序可能不是我的,而是第三方应用程序(来自 Microsoft),如下所示:Why would a process hang within RtlExitUserProcess/LdrpDrainWorkQueue?

在我看来,Windows 10 进程加载器已损坏,即使 Microsoft 进程的行为不正确也是如此。

  1. 这个问题有一个干净的解决方案吗?
  2. 如果最后一个用户创建的线程退出,那么需要那些加载程序线程做什么?据我所知,此时无法加载任何其他库。

最佳答案

I expected the process to terminate if it does not create additional threads and returns from the main function.

进程可以隐式创建额外的线程。例如装载机。需要理解什么意思

returns from the main function

这里是指从标准 CRT mainCRTStartup 函数调用的函数。在 mainCRTStartup 调用 ExitProcess 之后。所以不是任何 exe 入口真正的入口点函数,而是一些从入口点调用的子函数。但入口点调用 ExitProcess 比。

如果我们不使用 CRT - 我们需要自己调用 ExitProcess。如果我们只是从入口点返回 - 将是 RtlExitUserThread,它不会调用 ExitProcess,除非这是进程中的最后一个线程 (AmILastThread)(这里如果 2 个或更多线程并行调用 ExitThread)

也可以是竞争

关于c++ - 从 exe 入口点返回不会终止 Windows 10 上的进程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58561951/

相关文章:

windows - 是否可以通过编程方式禁用 Windows 上的 Caps Lock 键?

java - Tomcat的Threadpool到底是如何工作的

java - 线程池如何处理 Web 服务器中的静态和动态请求?

c++ - 我得到这个错误是 Visual Studio C++::"error LNK2019: unresolved external symbol"

c++ - 带有 gso 的 UDP 没有收到整个消息

windows - 如何使用路径作为包含空格的参数运行 javac?

c++ - 如何获取当前进程的线程 ID 列表

c# - C# 和 C++ 之间的指针 - p/invoke

c++ - 如何将 -fix 选项添加到 .clang-tidy 文件?

python - 打开文件(例如双击文件)的代码