Supported termination while the initial CREATE_PROCESS_DEBUG_EVENT remains pending

Rodrigo Lozano Marroquin 0 Reputation points
2026-10-31T07:10:33.9766667+00:00

I am reviewing a hypothetical Windows launcher design. No implementation or debugging experiment is being reported.

Assume the following state:

  • CreateProcessW created a new process using DEBUG_ONLY_THIS_PROCESS.
  • The creating/debugger thread received its initial CREATE_PROCESS_DEBUG_EVENT through WaitForDebugEvent.
  • That event has not been continued.
  • An admission check rejects the process—for example, CREATE_PROCESS_DEBUG_INFO.hFile is null or fails comparison with a retained executable-file handle.
  • The debugger retains a process handle with the required termination and synchronization rights.

Is there a documented, supported Windows API sequence that guarantees termination progress and independently observable completion from this state without permitting normal target user-mode execution?

In particular:

  1. Is TerminateProcess supported while the initial event remains pending, and can termination complete without calling ContinueDebugEvent on that event?
  2. If continuation is necessary for teardown, what supported ordering prevents target execution? Can that requirement include target loader execution, DLL initialization, TLS callbacks and application instructions? I am not assuming that pending-event status alone establishes all these exclusions.
  3. Which events must be received/continued, including EXIT_PROCESS_DEBUG_EVENT, and when does a retained process handle becoming signaled establish completed shutdown?

I want to distinguish a successful termination request from termination progress, completed process shutdown and debug-event reconciliation. The question concerns the initial process; it does not claim descendant containment or stronger image-section identity.

The documentation describes asynchronous external termination and exit-event processing, but I have not established the complete supported sequence from the pending initial event:

Please identify the applicable Microsoft API contracts, ordering/preconditions and Windows-version or architecture restrictions. An explicit clarification that the requested guarantee is unsupported or not provided would also be useful. I am seeking supported semantics rather than a full implementation.

Windows development | Windows API - Win32
0 comments No comments

3 answers

Sort by: Most helpful
  1. Nguyen Dam (WICLOUD CORPORATION) 1,010 Reputation points Microsoft External Staff Moderator
    2026-11-02T07:40:57.6933333+00:00

    Hello @Rodrigo Lozano Marroquin and thank you for reaching out.

    Regrettably, I could not find a Microsoft-documented guarantee for what you require. The documentation describes the individual APIs, but not the combined sequence or the guarantee that no normal target user-mode code can run. 

    That said, I would still like to share what the API documentation establishes and what I observed in testing. 

    1. Is TerminateProcess supported while the initial event remains pending, and can termination complete without calling ContinueDebugEvent on that event?

    I confirmed that termination can complete while the initial CREATE_PROCESS_DEBUG_EVENT remains pending, without calling ContinueDebugEvent. 

    I created the target with DEBUG_ONLY_THIS_PROCESS, left the initial creation event pending, and called TerminateProcess(..., 123). The retained process handle became signaled, and GetExitCodeProcess returned 123. 

    This also shows the distinction you raised: a successful call to TerminateProcess means that termination was requested. The process handle becoming signaled shows that termination completed. 

    2. If continuation is necessary for teardown, what supported ordering prevents target execution? Can that requirement include target loader execution, DLL initialization, TLS callbacks and application instructions? I am not assuming that pending-event status alone establishes all these exclusions.

    Continuing the initial event was not needed for termination to complete in the first test. 

    I also tested calling TerminateProcess(..., 123) while the creation event was pending and then continuing that event. I later received and continued LOAD_DLL_DEBUG_EVENT and EXIT_PROCESS_DEBUG_EVENT. The exit event reported 123, and the retained process handle then became signaled with the same exit code. 

    To check whether target initialization code ran, I used a target with a TLS callback, an imported DLL with DllMain(DLL_PROCESS_ATTACH), and main. Each created a separate marker file. None of the files was created. 

    This is consistent with those routines not running. However, it does not prove that no target user-mode instruction ran at all. Receiving LOAD_DLL_DEBUG_EVENT also does not prove that the DLL’s DllMain ran. 

    Regarding your clarified concern: I could not find an explicit Microsoft guarantee that, after TerminateProcess succeeds but before termination is confirmed, continuing the initial event cannot allow any normal target user-mode code to run. 

    The individual API contracts suggest calling TerminateProcess before continuing the event, and my test showed no sign that the TLS callback, DllMain, or main ran. However, the documentation does not guarantee that this sequence blocks all loader activity, DLL initialization, TLS callbacks, application code, or other normal target user-mode execution. 

    3. Which events must be received/continued, including EXIT_PROCESS_DEBUG_EVENT, and when does a retained process handle becoming signaled establish completed shutdown?

    Which events must be handled depends on whether the initial event is continued. 

    When I left CREATE_PROCESS_DEBUG_EVENT pending, termination completed without further event processing. When I continued it, I received and continued LOAD_DLL_DEBUG_EVENT and EXIT_PROCESS_DEBUG_EVENT. 

    If EXIT_PROCESS_DEBUG_EVENT is received, it must be continued before the kernel-mode part of process shutdown can complete. After that, WAIT_OBJECT_0 on the retained process handle confirms completed shutdown. A successful call to TerminateProcess only confirms that termination was requested, and receiving the exit event does not by itself confirm final completion. 

    I could not find a supported recovery method that both forces progress if termination or debugging stalls and preserves the requested guarantee against target execution. I also found no guaranteed completion time or stronger Windows-version or architecture-specific guarantee. 

    Regarding the overall scope of this answer: A definitive support statement for this API combination is not something I can independently provide through this forum. My response is limited to the public documentation and the behavior I could test. If your design requires an authoritative clarification of the API contract, I recommend opening a case with Microsoft Support for Business, where the appropriate product team can review the question. If you do not have a business support plan, you can contact Microsoft Support instead.

    If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation. Thank you. 

    Was this answer helpful?

    0 comments No comments

  2. Rodrigo Lozano Marroquin 0 Reputation points
    2026-10-31T07:19:16.9533333+00:00

    Thank you for the detailed explanation.

    I understand the proposed sequence: request termination while the initial creation debug event is pending, continue the event, process subsequent debug events, continue EXIT_PROCESS_DEBUG_EVENT, and confirm completion using an independently retained process handle.

    I would like to clarify one critical distinction.

    Is there an explicit Microsoft-documented guarantee that, after TerminateProcess succeeds but before termination is independently observed, calling ContinueDebugEvent on the initial creation event cannot permit any normal target user-mode execution, including loader activity, DLL initialization, TLS callbacks or application instructions?

    Or is this conclusion an inference from the individual API contracts rather than a guarantee documented for the combined sequence?

    Additionally, if the debugger thread hangs or termination does not progress, is there any supported independent recovery mechanism that preserves this execution-exclusion requirement?

    I understand that bounded termination time is not guaranteed. My main concern is whether the execution-exclusion property itself is supported and guaranteed.

    References to the precise Microsoft documentation establishing these properties would be particularly helpful.

    Thank you.

    Was this answer helpful?

    0 comments No comments

  3. AI answer

    2026-10-31T07:12:37.8+00:00

    No documented Win32 contract guarantees bounded or unconditional termination progress from this state; the supported sequence is to request termination before continuing the initial event, reconcile the debugging events through EXIT_PROCESS_DEBUG_EVENT, continue that exit event, and then observe completion on an independently retained process handle.

    Supported ordering

    1. Retain an independent process handle with:
      • PROCESS_TERMINATE for TerminateProcess.
      • SYNCHRONIZE for waiting.
      Do not rely exclusively on the hProcess supplied in CREATE_PROCESS_DEBUG_INFO, because the debugging subsystem closes its process/thread handles when EXIT_PROCESS_DEBUG_EVENT is continued. Microsoft specifically recommends duplicating that process handle before continuing the exit event when it will be used to observe final shutdown.^1^
    2. Close CREATE_PROCESS_DEBUG_INFO.hFile if non-NULL after completing the admission check. The debugger owns that image-file handle and must close it.^2^
    3. While CREATE_PROCESS_DEBUG_EVENT remains outstanding, call:
         if (!TerminateProcess(retainedProcess, rejectedExitCode)) {
             DWORD error = GetLastError();
             // Termination was not successfully requested; handle as failure.
         }
      
      TerminateProcess requires PROCESS_TERMINATE. For an external process it is asynchronous: success means termination was initiated, not completed. Completion can be delayed while pending I/O is completed or cancelled.^3^
    4. Only after TerminateProcess succeeds, continue the outstanding create event:
         if (!ContinueDebugEvent(ev.dwProcessId,
                                 ev.dwThreadId,
                                 DBG_CONTINUE)) {
             DWORD error = GetLastError();
             // Debug protocol could not be advanced; report/handle failure.
         }
      
      The documentation does not state verbatim that termination cannot progress while this particular create event remains outstanding. Continuing it is the conservative ordering derived from the documented debug-event handshake: a reported event suspends the process, event processing advances through ContinueDebugEvent, and the debugger normally alternates waiting and continuing.^1^^4^
    5. Continue pumping events. For each event delivered by WaitForDebugEvent:
      • Perform required per-event cleanup, such as closing a non-NULL LOAD_DLL_DEBUG_EVENT file handle.
      • Continue the event.
      • Do not assume that only EXIT_PROCESS_DEBUG_EVENT can appear.
      • Do not treat a timeout or API failure as successful shutdown.
    6. On EXIT_PROCESS_DEBUG_EVENT:
      • Record its exit code.
      • Release debugger bookkeeping.
      • Call ContinueDebugEvent for that event.
      • Only then wait on the independently retained process handle.
      Kernel-mode process shutdown cannot complete while EXIT_PROCESS_DEBUG_EVENT remains outstanding. Continuing it releases the debugger-owned process/thread handles and allows final shutdown to proceed.^1^^5^
    7. Observe completion:
         DWORD result = WaitForSingleObject(retainedProcess, timeoutMs);
      
         if (result == WAIT_OBJECT_0) {
             // Completed process shutdown has been observed.
         } else if (result == WAIT_TIMEOUT) {
             // Termination remains incomplete or unobserved.
         } else { // WAIT_FAILED
             DWORD error = GetLastError();
         }
      
      A process object becomes signaled when the process terminates. Waiting requires SYNCHRONIZE; WAIT_OBJECT_0 is the completion observation, while WAIT_TIMEOUT is not.^6^^7^

    Execution exclusion

    The initial CREATE_PROCESS_DEBUG_EVENT is generated before the process begins executing in user mode and before any other debug event for it. While that event is outstanding, all affected threads remain suspended.^1^

    After a successful TerminateProcess, the documented semantics are that all process threads are terminated with no opportunity to execute additional code; termination handlers and DLL detach notifications do not run.^7^ Therefore, terminating first and continuing second is the supported ordering that avoids permitting target user-mode loader activity, DLL initialization, TLS callbacks, or application instructions.

    An extra SuspendThread is neither required by this ordering nor documented as providing a stronger process-wide guarantee. It also operates per thread rather than as an atomic process-wide barrier.

    Contract boundaries

    • TerminateProcess success is a successful request, not proof of progress or completion.
    • Receipt of EXIT_PROCESS_DEBUG_EVENT establishes that the last thread exited and the exit code was updated, but kernel teardown still awaits continuation of that event.
    • WAIT_OBJECT_0 on the retained process handle after continuing the exit event is the independent completion observation.
    • The documentation does not guarantee that EXIT_PROCESS_DEBUG_EVENT or a signaled process handle will be reached within a bounded time.
    • WaitForDebugEvent and ContinueDebugEvent must be called by the thread that created the debugged process.^2^^5^
    • These debugging APIs list desktop-app support beginning with Windows XP and Windows Server 2003. No separate architecture-specific guarantee is documented for this combined sequence.

    References

    1. Debugging Events
    2. WaitForDebugEvent function (debugapi.h)
    3. TerminateProcess function (processthreadsapi.h)
    4. Writing the Debugger's Main Loop
    5. ContinueDebugEvent function (debugapi.h)
    6. WaitForSingleObject function (synchapi.h)
    7. Terminating a Process
    AI-generated content may be incorrect. Read our transparency notes for more information.

    Was this answer helpful?

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.