src/eric7/DebugClients/Python/MultiProcessDebugExtension.py

branch
eric7-maintenance
changeset 9264
18a7312cfdb3
parent 9221
bf71ee032bb4
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9241:d23e9854aea4 9264:18a7312cfdb3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a function to patch the process creation functions to
8 support multiprocess debugging.
9 """
10
11 import contextlib
12
13 from DebugUtilities import (
14 patchArguments,
15 patchArgumentStringWindows,
16 isPythonProgram,
17 isWindowsPlatform,
18 )
19
20 _debugClient = None
21
22
23 def _shallPatch():
24 """
25 Function to determine, if the multiprocessing patches should be done.
26
27 @return flag indicating patching should be performed
28 @rtype bool
29 """
30 return _debugClient.debugging and _debugClient.multiprocessSupport
31
32
33 def patchModule(module, functionName, createFunction):
34 """
35 Function to replace a function of a module with a modified one.
36
37 @param module reference to the module
38 @type types.ModuleType
39 @param functionName name of the function to be replaced
40 @type str
41 @param createFunction function creating the replacement
42 @type types.FunctionType
43 """
44 if hasattr(module, functionName):
45 originalName = "original_" + functionName
46 if not hasattr(module, originalName):
47 setattr(module, originalName, getattr(module, functionName))
48 setattr(module, functionName, createFunction(originalName))
49
50
51 def createExecl(originalName):
52 """
53 Function to patch the 'execl' process creation functions.
54
55 <ul>
56 <li>os.execl(path, arg0, arg1, ...)</li>
57 <li>os.execle(path, arg0, arg1, ..., env)</li>
58 <li>os.execlp(file, arg0, arg1, ...)</li>
59 <li>os.execlpe(file, arg0, arg1, ..., env)</li>
60 </ul>
61
62 @param originalName original name of the function to be patched
63 @type str
64 @return function replacing the original one
65 @rtype function
66 """
67
68 def newExecl(path, *args):
69 """
70 Function replacing the 'execl' functions of the os module.
71 """
72 import os
73
74 if _shallPatch():
75 args = patchArguments(_debugClient, args)
76 if isPythonProgram(args[0]):
77 path = args[0]
78 return getattr(os, originalName)(path, *args)
79
80 return newExecl
81
82
83 def createExecv(originalName):
84 """
85 Function to patch the 'execv' process creation functions.
86
87 <ul>
88 <li>os.execv(path, args)</li>
89 <li>os.execvp(file, args)</li>
90 </ul>
91
92 @param originalName original name of the function to be patched
93 @type str
94 @return function replacing the original one
95 @rtype function
96 """
97
98 def newExecv(path, args):
99 """
100 Function replacing the 'execv' functions of the os module.
101 """
102 import os
103
104 if _shallPatch():
105 args = patchArguments(_debugClient, args)
106 if isPythonProgram(args[0]):
107 path = args[0]
108 return getattr(os, originalName)(path, args)
109
110 return newExecv
111
112
113 def createExecve(originalName):
114 """
115 Function to patch the 'execve' process creation functions.
116
117 <ul>
118 <li>os.execve(path, args, env)</li>
119 <li>os.execvpe(file, args, env)</li>
120 </ul>
121
122 @param originalName original name of the function to be patched
123 @type str
124 @return function replacing the original one
125 @rtype function
126 """
127
128 def newExecve(path, args, env):
129 """
130 Function replacing the 'execve' functions of the os module.
131 """
132 import os
133
134 if _shallPatch():
135 args = patchArguments(_debugClient, args)
136 if isPythonProgram(args[0]):
137 path = args[0]
138 return getattr(os, originalName)(path, args, env)
139
140 return newExecve
141
142
143 def createSpawnl(originalName):
144 """
145 Function to patch the 'spawnl' process creation functions.
146
147 <ul>
148 <li>os.spawnl(mode, path, arg0, arg1, ...)</li>
149 <li>os.spawnlp(mode, file, arg0, arg1, ...)</li>
150 </ul>
151
152 @param originalName original name of the function to be patched
153 @type str
154 @return function replacing the original one
155 @rtype function
156 """
157
158 def newSpawnl(mode, path, *args):
159 """
160 Function replacing the 'spawnl' functions of the os module.
161 """
162 import os
163
164 args = patchArguments(_debugClient, args)
165 return getattr(os, originalName)(mode, path, *args)
166
167 return newSpawnl
168
169
170 def createSpawnv(originalName):
171 """
172 Function to patch the 'spawnv' process creation functions.
173
174 <ul>
175 <li>os.spawnv(mode, path, args)</li>
176 <li>os.spawnvp(mode, file, args)</li>
177 </ul>
178
179 @param originalName original name of the function to be patched
180 @type str
181 @return function replacing the original one
182 @rtype function
183 """
184
185 def newSpawnv(mode, path, args):
186 """
187 Function replacing the 'spawnv' functions of the os module.
188 """
189 import os
190
191 args = patchArguments(_debugClient, args)
192 return getattr(os, originalName)(mode, path, args)
193
194 return newSpawnv
195
196
197 def createSpawnve(originalName):
198 """
199 Function to patch the 'spawnve' process creation functions.
200
201 <ul>
202 <li>os.spawnve(mode, path, args, env)</li>
203 <li>os.spawnvpe(mode, file, args, env)</li>
204 </ul>
205
206 @param originalName original name of the function to be patched
207 @type str
208 @return function replacing the original one
209 @rtype function
210 """
211
212 def newSpawnve(mode, path, args, env):
213 """
214 Function replacing the 'spawnve' functions of the os module.
215 """
216 import os
217
218 args = patchArguments(_debugClient, args)
219 return getattr(os, originalName)(mode, path, args, env)
220
221 return newSpawnve
222
223
224 def createPosixSpawn(originalName):
225 """
226 Function to patch the 'posix_spawn' process creation functions.
227
228 <ul>
229 <li>os.posix_spawn(path, argv, env, *, file_actions=None, ...
230 (6 more))</li>
231 <li>os.posix_spawnp(path, argv, env, *, file_actions=None, ...
232 (6 more))</li>
233 </ul>
234
235 @param originalName original name of the function to be patched
236 @type str
237 @return function replacing the original one
238 @rtype function
239 """
240
241 def newPosixSpawn(path, argv, env, **kwargs):
242 """
243 Function replacing the 'posix_spawn' functions of the os module.
244 """
245 import os
246
247 argv = patchArguments(_debugClient, argv)
248 return getattr(os, originalName)(path, argv, env, **kwargs)
249
250 return newPosixSpawn
251
252
253 def createForkExec(originalName):
254 """
255 Function to patch the 'fork_exec' process creation functions.
256
257 <ul>
258 <li>_posixsubprocess.fork_exec(args, executable_list, close_fds,
259 ... (13 more))</li>
260 </ul>
261
262 @param originalName original name of the function to be patched
263 @type str
264 @return function replacing the original one
265 @rtype function
266 """
267
268 def newForkExec(args, *other_args):
269 """
270 Function replacing the 'fork_exec' functions of the _posixsubprocess
271 module.
272 """
273 import _posixsubprocess
274
275 if _shallPatch():
276 args = patchArguments(_debugClient, args)
277 return getattr(_posixsubprocess, originalName)(args, *other_args)
278
279 return newForkExec
280
281
282 def createFork(originalName):
283 """
284 Function to patch the 'fork' process creation functions.
285
286 <ul>
287 <li>os.fork()</li>
288 </ul>
289
290 @param originalName original name of the function to be patched
291 @type str
292 @return function replacing the original one
293 @rtype function
294 """
295
296 def newFork():
297 """
298 Function replacing the 'fork' function of the os module.
299 """
300 import os
301 import sys
302
303 # A simple fork will result in a new python process
304 isNewPythonProcess = True
305 frame = sys._getframe()
306
307 multiprocess = _shallPatch()
308
309 isSubprocessFork = False
310 isMultiprocessingPopen = False
311 while frame is not None:
312 if frame.f_code.co_name == "_Popen":
313 # fork() was called from multiprocessing; ignore this here
314 # because it is handled in 'MultiprocessingExtension.py'.
315 isMultiprocessingPopen = True
316 break
317
318 elif (
319 frame.f_code.co_name == "_execute_child"
320 and "subprocess" in frame.f_code.co_filename
321 ):
322 isSubprocessFork = True
323 # If we're actually in subprocess.Popen creating a child, it
324 # may result in something which is not a Python process, (so,
325 # we don't want to connect with it in the forked version).
326 executable = frame.f_locals.get("executable")
327 if executable is not None:
328 isNewPythonProcess = False
329 if isPythonProgram(executable):
330 isNewPythonProcess = True
331 break
332
333 frame = frame.f_back
334 frame = None # Just make sure we don't hold on to it.
335
336 childProcess = getattr(os, originalName)() # fork
337 if not childProcess and not isMultiprocessingPopen and isNewPythonProcess:
338 (
339 wd,
340 host,
341 port,
342 exceptions,
343 tracePython,
344 redirect,
345 noencoding,
346 ) = _debugClient.startOptions
347 _debugClient.startDebugger(
348 filename=sys.argv[0],
349 host=host,
350 port=port,
351 enableTrace=multiprocess and not isSubprocessFork,
352 exceptions=exceptions,
353 tracePython=tracePython,
354 redirect=redirect,
355 passive=False,
356 multiprocessSupport=multiprocess,
357 )
358 return childProcess
359
360 return newFork
361
362
363 def createCreateProcess(originalName):
364 """
365 Function to patch the 'CreateProcess' process creation function of
366 Windows.
367
368 @param originalName original name of the function to be patched
369 @type str
370 @return function replacing the original one
371 @rtype function
372 """
373
374 def newCreateProcess(appName, cmdline, *args):
375 """
376 Function replacing the 'CreateProcess' function of the _subprocess
377 or _winapi module.
378 """
379 try:
380 import _subprocess
381 except ImportError:
382 import _winapi as _subprocess
383 return getattr(_subprocess, originalName)(
384 appName, patchArgumentStringWindows(_debugClient, cmdline), *args
385 )
386
387 return newCreateProcess
388
389
390 def patchNewProcessFunctions(multiprocessEnabled, debugClient):
391 """
392 Function to patch the process creation functions to support multiprocess
393 debugging.
394
395 @param multiprocessEnabled flag indicating multiprocess support
396 @type bool
397 @param debugClient reference to the debug client object
398 @type DebugClient
399 """
400 global _debugClient
401
402 if not multiprocessEnabled:
403 # return without patching
404 return
405
406 import os
407 import sys
408
409 # patch 'os.exec...()' functions
410 # - patchModule(os, "execl", createExecl)
411 # - patchModule(os, "execle", createExecl)
412 # - patchModule(os, "execlp", createExecl)
413 # - patchModule(os, "execlpe", createExecl)
414 # - patchModule(os, "execv", createExecv)
415 # - patchModule(os, "execve", createExecve)
416 # - patchModule(os, "execvp", createExecv)
417 # - patchModule(os, "execvpe", createExecve)
418
419 # patch 'os.spawn...()' functions
420 patchModule(os, "spawnl", createSpawnl)
421 patchModule(os, "spawnle", createSpawnl)
422 patchModule(os, "spawnlp", createSpawnl)
423 patchModule(os, "spawnlpe", createSpawnl)
424 patchModule(os, "spawnv", createSpawnv)
425 patchModule(os, "spawnve", createSpawnve)
426 patchModule(os, "spawnvp", createSpawnv)
427 patchModule(os, "spawnvpe", createSpawnve)
428
429 # patch 'os.posix_spawn...()' functions
430 if sys.version_info >= (3, 8) and not isWindowsPlatform():
431 patchModule(os, "posix_spawn", createPosixSpawn)
432 patchModule(os, "posix_spawnp", createPosixSpawn)
433
434 if isWindowsPlatform():
435 try:
436 import _subprocess
437 except ImportError:
438 import _winapi as _subprocess
439 patchModule(_subprocess, "CreateProcess", createCreateProcess)
440 else:
441 patchModule(os, "fork", createFork)
442 with contextlib.suppress(ImportError):
443 import _posixsubprocess
444
445 patchModule(_posixsubprocess, "fork_exec", createForkExec)
446
447 _debugClient = debugClient

eric ide

mercurial