<div dir="ltr">I don't think this belongs in subprocess.  It isn't related to processes creation.<div><br></div><div>A module on PyPI with the Windows code would make more sense.</div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, Mar 20, 2019 at 3:19 PM eryk sun <<a href="mailto:eryksun@gmail.com">eryksun@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">On 3/18/19, Giampaolo Rodola' <<a href="mailto:g.rodola@gmail.com" target="_blank">g.rodola@gmail.com</a>> wrote:<br>
><br>
> I've been having these 2 implemented in psutil for a long time. On<br>
> POSIX these are convenience functions using os.kill() + SIGSTOP /<br>
> SIGCONT (the same as CTRL+Z  / "fg"). On Windows they use<br>
> undocumented NtSuspendProcess and NtResumeProcess Windows<br>
> APIs available since XP.<br>
<br>
Currently, Windows Python only calls documented C runtime-library and<br>
Windows API functions. It doesn't directly call NT runtime-library and<br>
system functions. Maybe it could in the case of documented functions,<br>
but calling undocumented functions in the standard library should be<br>
avoided. Unfortunately, without NtSuspendProcess and NtResumeProcess,<br>
I don't see a way to reliably implement this feature for Windows. I'm<br>
CC'ing Steve Dower. He might say it's okay in this case, or know of<br>
another approach.<br>
<br>
DebugActiveProcess, the other simple approach mentioned in the linked<br>
SO answer [1], is unreliable and has the wrong semantics.  A process<br>
only has a single debug port, so DebugActiveProcess will fail the PID<br>
as an invalid parameter if another debugger is already attached to the<br>
process. (The underlying NT call, DbgUiDebugActiveProcess, fails with<br>
STATUS_PORT_ALREADY_SET.) Additionally, the semantics that I expect<br>
here, at least for Windows, is that each call to suspend() will<br>
require a corresponding call to resume(), since it's incrementing the<br>
suspend count on the threads; however, a debugger can't reattach to<br>
the same process. Also, if the Python process exits while it's<br>
attached as a debugger, the system will terminate the debugee as well,<br>
unless we call DebugSetProcessKillOnExit(0), but that interferes with<br>
the Python process acting as a debugger normally, as does this entire<br>
wonky idea. Also, the debugging system creates a thread in the debugee<br>
that calls NT DbgUiRemoteBreakin, which executes a breakpoint. This<br>
thread is waiting, but it's not suspended, so the process will never<br>
actually appear as suspended in Task Manager or Process Explorer.<br>
<br>
That leaves enumerating threads in a snapshot and calling OpenThread<br>
and SuspendThread on each thread that's associated with the process.<br>
In comparison, let's take an abridged look at the guts of<br>
NtSuspendProcess.<br>
<br>
    nt!NtSuspendProcess:<br>
        ...<br>
        mov     r8,qword ptr [nt!PsProcessType]<br>
        ...<br>
        call    nt!ObpReferenceObjectByHandleWithTag<br>
        ...<br>
        call    nt!PsSuspendProcess<br>
        ...<br>
        mov     ebx,eax<br>
        call    nt!ObfDereferenceObjectWithTag<br>
        mov     eax,ebx<br>
        ...<br>
        ret<br>
<br>
    nt!PsSuspendProcess:<br>
        ...<br>
        call    nt!ExAcquireRundownProtection<br>
        cmp     al,1<br>
        jne     nt!PsSuspendProcess+0x74<br>
        ...<br>
        call    nt!PsGetNextProcessThread<br>
        xor     ebx,ebx<br>
        jmp     nt!PsSuspendProcess+0x62<br>
<br>
    nt!PsSuspendProcess+0x4d:<br>
        ...<br>
        call    nt!PsSuspendThread<br>
        ...<br>
        call    nt!PsGetNextProcessThread<br>
<br>
    nt!PsSuspendProcess+0x62:<br>
        ...<br>
        test    rax,rax<br>
        jne     nt!PsSuspendProcess+0x4d<br>
        ...<br>
        call    nt!ExReleaseRundownProtection<br>
        jmp     nt!PsSuspendProcess+0x79<br>
<br>
    nt!PsSuspendProcess+0x74:<br>
        mov     ebx,0C000010Ah (STATUS_PROCESS_IS_TERMINATING)<br>
<br>
    nt!PsSuspendProcess+0x79:<br>
        ...<br>
        mov     eax,ebx<br>
        ...<br>
        ret<br>
<br>
This code repeatedly calls PsGetNextProcessThread to walk the<br>
non-terminated threads of the process in creation order (based on a<br>
linked list in the process object) and suspends each thread via<br>
PsSuspendThread. In contrast, a Tool-Help thread snapshot is<br>
unreliable since it won't include threads created after the snapshot<br>
is created. The alternative is to use a different undocumented system<br>
call, NtGetNextThread [2], which is implemented via<br>
PsGetNextProcessThread. But that's slightly worse than calling<br>
NtSuspendProcess.<br>
<br>
[1]: <a href="https://stackoverflow.com/a/11010508" rel="noreferrer" target="_blank">https://stackoverflow.com/a/11010508</a><br>
[2]: <a href="https://github.com/processhacker/processhacker/blob/v2.39/phnt/include/ntpsapi.h#L848" rel="noreferrer" target="_blank">https://github.com/processhacker/processhacker/blob/v2.39/phnt/include/ntpsapi.h#L848</a><br>
_______________________________________________<br>
Python-ideas mailing list<br>
<a href="mailto:Python-ideas@python.org" target="_blank">Python-ideas@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/python-ideas" rel="noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/python-ideas</a><br>
Code of Conduct: <a href="http://python.org/psf/codeofconduct/" rel="noreferrer" target="_blank">http://python.org/psf/codeofconduct/</a><br>
</blockquote></div>