eric6/DebugClients/Python/MultiProcessDebugExtension.py

Thu, 10 Dec 2020 20:16:21 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 10 Dec 2020 20:16:21 +0100
branch
multi_processing
changeset 7871
eb65864ca038
parent 7802
eefe954f01e8
child 7872
433dacbfa456
permissions
-rw-r--r--

Added some more process creation function overrides.

# -*- coding: utf-8 -*-

# Copyright (c) 2002 - 2020 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a function to patch the process creation functions to
support multiprocess debugging.
"""


from DebugUtilities import (
    patchArguments, patchArgumentStringWindows, isPythonProgram,
    isWindowsPlatform
)

_debugClient = None


def patchModule(module, functionName, createFunction):
    """
    Function to replace a function of a module with a modified one.
    
    @param module reference to the module
    @type types.ModuleType
    @param functionName name of the function to be replaced
    @type str
    @param createFunction function creating the replacement
    @type types.FunctionType
    """
    if hasattr(module, functionName):
        originalName = 'original_' + functionName
        if not hasattr(module, originalName):
            setattr(module, originalName, getattr(module, functionName))
            setattr(module, functionName, createFunction(originalName))


def createExecl(originalName):
    """
    Function to patch the 'execl' process creation functions.
    
    <ul>
        <li>os.execl(path, arg0, arg1, ...)</li>
        <li>os.execle(path, arg0, arg1, ..., env)</li>
        <li>os.execlp(file, arg0, arg1, ...)</li>
        <li>os.execlpe(file, arg0, arg1, ..., env)</li>
    </ul>
    """
    def newExecl(path, *args):
        """
        Function replacing the 'execl' functions of the os module.
        """
        import os
        if (
            _debugClient.debugging and
            _debugClient.multiprocessSupport
        ):
            args = patchArguments(_debugClient, args)
            if isPythonProgram(args[0]):
                path = args[0]
        return getattr(os, originalName)(path, *args)
    return newExecl


def createExecv(originalName):
    """
    Function to patch the 'execv' process creation functions.
    
    <ul>
        <li>os.execv(path, args)</li>
        <li>os.execvp(file, args)</li>
    </ul>
    """
    def newExecv(path, args):
        """
        Function replacing the 'execv' functions of the os module.
        """
        import os
        if (
            _debugClient.debugging and
            _debugClient.multiprocessSupport
        ):
            args = patchArguments(_debugClient, args)
            if isPythonProgram(args[0]):
                path = args[0]
        return getattr(os, originalName)(path, args)
    return newExecv


def createExecve(originalName):
    """
    Function to patch the 'execve' process creation functions.
    
    <ul>
        <li>os.execve(path, args, env)</li>
        <li>os.execvpe(file, args, env)</li>
    </ul>
    """
    def newExecve(path, args, env):
        """
        Function replacing the 'execve' functions of the os module.
        """
        import os
        if (
            _debugClient.debugging and
            _debugClient.multiprocessSupport
        ):
            args = patchArguments(_debugClient, args)
            if isPythonProgram(args[0]):
                path = args[0]
        return getattr(os, originalName)(path, args, env)
    return newExecve


# TODO: add createSpawn...


def createForkExec(originalName):
    """
    Function to patch the 'fork_exec' process creation functions.
    
    <ul>
        <li>_posixsubprocess.fork_exec(args, executable_list, close_fds,
            ... (13 more))</li>
    </ul>
    """
    def newForkExec(args, *other_args):
        """
        Function replacing the 'fork_exec' functions of the _posixsubprocess
        module.
        """
        import _posixsubprocess
        if (
            _debugClient.debugging and
            _debugClient.multiprocessSupport
        ):
            args = patchArguments(_debugClient, args)
        return getattr(_posixsubprocess, originalName)(args, *other_args)
    return newForkExec


def createFork(original_name):
    """
    Function to patch the 'fork' process creation functions.
    
    <ul>
        <li>os.fork()</li>
    </ul>
    """
    def new_fork():
        """
        Function replacing the 'fork' function of the os module.
        """
        import os
        import sys
        
        # A simple fork will result in a new python process
        isNewPythonProcess = True
        frame = sys._getframe()
        
        multiprocess = (
            _debugClient.debugging and _debugClient.multiprocessSupport
        )
        
        isSubprocessFork = False
        while frame is not None:
            if (
                frame.f_code.co_name == '_execute_child' and
                'subprocess' in frame.f_code.co_filename
            ):
                isSubprocessFork = True
                # If we're actually in subprocess.Popen creating a child, it
                # may result in something which is not a Python process, (so,
                # we don't want to connect with it in the forked version).
                executable = frame.f_locals.get('executable')
                if executable is not None:
                    isNewPythonProcess = False
                    if isPythonProgram(executable):
                        isNewPythonProcess = True
                break
            
            frame = frame.f_back
        frame = None    # Just make sure we don't hold on to it.
        
        childProcess = getattr(os, original_name)()     # fork
        if not childProcess:
            if isNewPythonProcess:
                sys.settrace(None)
                sys.setprofile(None)
                _debugClient.sessionClose(False)
                (wd, host, port, exceptions, tracePython, redirect,
                 noencoding, fork_auto, fork_child) = _debugClient.startOptions
                _debugClient.startDebugger(
                    filename=sys.argv[0],
                    host=host,
                    port=port,
                    enableTrace=multiprocess and not isSubprocessFork,
                    exceptions=exceptions,
                    tracePython=tracePython,
                    redirect=redirect,
                    passive=False,
                    multiprocessSupport=multiprocess)
        return childProcess

    return new_fork


def createCreateProcess(originalName):
    """
    Function to patch the 'CreateProcess' process creation function of
    Windows.
    """
    def newCreateProcess(appName, cmdline, *args):
        """
        Function replacing the 'CreateProcess' function of the _subprocess
        or _winapi module.
        """
        try:
            import _subprocess
        except ImportError:
            import _winapi as _subprocess
        return getattr(_subprocess, originalName)(
            appName, patchArgumentStringWindows(_debugClient, cmdline), *args)
    return newCreateProcess


# TODO: add 'createFork'


def patchNewProcessFunctions(multiprocessEnabled, debugClient):
    """
    Function to patch the process creation functions to support multiprocess
    debugging.
    
    @param multiprocessEnabled flag indicating multiprocess support
    @type bool
    @param debugClient reference to the debug client object
    @type DebugClient
    """
    global _debugClient
    
    if not multiprocessEnabled:
        # return without patching
        return
    
    import os
    
    # patch 'os.exec...()' functions
    patchModule(os, "execl", createExecl)
    patchModule(os, "execle", createExecl)
    patchModule(os, "execlp", createExecl)
    patchModule(os, "execlpe", createExecl)
    patchModule(os, "execv", createExecv)
    patchModule(os, "execve", createExecve)
    patchModule(os, "execvp", createExecv)
    patchModule(os, "execvpe", createExecve)
    
    # TODO: implement patching of the various functions of the os module
    
    if isWindowsPlatform():
        try:
            import _subprocess
        except ImportError:
            import _winapi as _subprocess
        patchModule(_subprocess, 'CreateProcess', createCreateProcess)
    else:
        patchModule(os, "fork", createFork)
        try:
            import _posixsubprocess
            patchModule(_posixsubprocess, "fork_exec", createForkExec)
        except ImportError:
            pass
    
    _debugClient = debugClient

eric ide

mercurial