Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# License: EPL 

2import os 

3import sys 

4import traceback 

5from _pydev_imps._pydev_saved_modules import threading 

6from _pydevd_bundle.pydevd_constants import get_global_debugger, IS_WINDOWS, IS_MACOS, IS_JYTHON, IS_PY36_OR_LESSER, IS_PY38_OR_GREATER, \ 

7 get_current_thread_id 

8from _pydev_bundle import pydev_log 

9 

10try: 

11 xrange 

12except: 

13 xrange = range 

14 

15 

16PYTHON_NAMES = ['python', 'jython', 'pypy'] 

17 

18#=============================================================================== 

19# Things that are dependent on having the pydevd debugger 

20#=============================================================================== 

21def log_debug(msg): 

22 pydev_log.debug(msg) 

23 

24 

25def log_error_once(msg): 

26 pydev_log.error_once(msg) 

27 

28 

29pydev_src_dir = os.path.dirname(os.path.dirname(__file__)) 

30 

31 

32def _get_python_c_args(host, port, indC, args, setup): 

33 host_literal = "'" + host + "'" if host is not None else 'None' 

34 return ("import sys; sys.path.append(r'%s'); import pydevd; " 

35 "pydevd.settrace(host=%s, port=%s, suspend=False, trace_only_current_thread=False, patch_multiprocessing=True); " 

36 "from pydevd import SetupHolder; SetupHolder.setup = %s; %s" 

37 ) % ( 

38 pydev_src_dir, 

39 host_literal, 

40 port, 

41 setup, 

42 args[indC + 1]) 

43 

44 

45def _get_host_port(): 

46 import pydevd 

47 host, port = pydevd.dispatch() 

48 return host, port 

49 

50 

51def _is_managed_arg(arg): 

52 return arg.endswith('pydevd.py') 

53 

54 

55def _is_already_patched(args): 

56 for arg in args: 

57 if 'pydevd' in arg: 

58 return True 

59 return False 

60 

61 

62def _is_py3_and_has_bytes_args(args): 

63 if not isinstance('', type(u'')): 

64 return False 

65 for arg in args: 

66 if isinstance(arg, bytes): 

67 return True 

68 return False 

69 

70 

71def _on_forked_process(): 

72 import pydevd 

73 pydevd.threadingCurrentThread().__pydevd_main_thread = True 

74 pydevd.settrace_forked() 

75 

76 

77def _on_set_trace_for_new_thread(global_debugger): 

78 if global_debugger is not None: 

79 global_debugger.enable_tracing() 

80 

81 

82#=============================================================================== 

83# Things related to monkey-patching 

84#=============================================================================== 

85def is_python_args(args): 

86 return not _is_py3_and_has_bytes_args(args) and len(args) > 0 and is_python(args[0]) 

87 

88 

89def is_executable(path): 

90 return os.access(os.path.abspath(path), os.EX_OK) 

91 

92 

93def starts_with_python_shebang(path): 

94 try: 

95 with open(path) as f: 

96 for line in f: 

97 line = line.strip() 

98 if line: 

99 for name in PYTHON_NAMES: 

100 if line.startswith('#!/usr/bin/env %s' % name): 

101 return True 

102 return False 

103 except UnicodeDecodeError: 

104 return False 

105 except: 

106 traceback.print_exc() 

107 return False 

108 

109 

110def is_python(path): 

111 if path.endswith("'") or path.endswith('"'): 

112 path = path[1:len(path) - 1] 

113 filename = os.path.basename(path).lower() 

114 for name in PYTHON_NAMES: 

115 if filename.find(name) != -1: 

116 return True 

117 return not IS_WINDOWS and is_executable(path) and starts_with_python_shebang(path) 

118 

119 

120def remove_quotes_from_args(args): 

121 if sys.platform == "win32": 

122 new_args = [] 

123 for x in args: 

124 if len(x) > 1 and x.startswith('"') and x.endswith('"'): 

125 x = x[1:-1] 

126 new_args.append(x) 

127 return new_args 

128 else: 

129 return args 

130 

131 

132def quote_args(args): 

133 if sys.platform == "win32": 

134 quoted_args = [] 

135 for x in args: 

136 if x.startswith('"') and x.endswith('"'): 

137 quoted_args.append(x) 

138 else: 

139 if ' ' in x: 

140 x = x.replace('"', '\\"') 

141 quoted_args.append('"%s"' % x) 

142 else: 

143 quoted_args.append(x) 

144 return quoted_args 

145 else: 

146 return args 

147 

148 

149def get_c_option_index(args): 

150 """ 

151 Get index of "-c" argument and check if it's interpreter's option 

152 :param args: list of arguments 

153 :return: index of "-c" if it's an interpreter's option and -1 if it doesn't exist or program's option 

154 """ 

155 try: 

156 ind_c = args.index('-c') 

157 except ValueError: 

158 return -1 

159 else: 

160 for i in range(1, ind_c): 

161 if not args[i].startswith('-'): 

162 # there is an arg without "-" before "-c", so it's not an interpreter's option 

163 return -1 

164 return ind_c 

165 

166 

167def patch_args(args): 

168 try: 

169 log_debug("Patching args: %s" % str(args)) 

170 

171 if _is_py3_and_has_bytes_args(args): 

172 warn_bytes_args() 

173 return args 

174 

175 args = remove_quotes_from_args(args) 

176 

177 from pydevd import SetupHolder 

178 new_args = [] 

179 if len(args) == 0: 

180 return args 

181 

182 if is_python(args[0]): 

183 

184 for name in PYTHON_NAMES: 

185 if args[0].find(name) != -1: 

186 break 

187 else: 

188 # Executable file with Python shebang. 

189 args.insert(0, sys.executable) 

190 

191 ind_c = get_c_option_index(args) 

192 

193 if ind_c != -1: 

194 if _is_already_patched(args): 

195 return args 

196 

197 host, port = _get_host_port() 

198 

199 if port is not None: 

200 new_args.extend(args) 

201 new_args[ind_c + 1] = _get_python_c_args(host, port, ind_c, args, SetupHolder.setup) 

202 new_args = quote_args(new_args) 

203 log_debug("Patched args: %s" % str(new_args)) 

204 return new_args 

205 else: 

206 # Check for Python ZIP Applications and don't patch the args for them. 

207 # Assumes the first non `-<flag>` argument is what we need to check. 

208 # There's probably a better way to determine this but it works for most cases. 

209 continue_next = False 

210 for i in range(1, len(args)): 

211 if continue_next: 

212 continue_next = False 

213 continue 

214 

215 arg = args[i] 

216 if arg.startswith('-'): 

217 # Skip the next arg too if this flag expects a value. 

218 continue_next = arg in ['-m', '-W', '-X'] 

219 continue 

220 

221 if arg.rsplit('.')[-1] in ['zip', 'pyz', 'pyzw']: 

222 log_debug('Executing a PyZip, returning') 

223 return args 

224 break 

225 

226 new_args.append(args[0]) 

227 else: 

228 log_debug("Process is not python, returning.") 

229 return args 

230 

231 i = 1 

232 # Original args should be something as: 

233 # ['X:\\pysrc\\pydevd.py', '--multiprocess', '--print-in-debugger-startup', 

234 # '--vm_type', 'python', '--client', '127.0.0.1', '--port', '56352', '--file', 'x:\\snippet1.py'] 

235 from _pydevd_bundle.pydevd_command_line_handling import setup_to_argv 

236 SetupHolder.setup['module'] = False # clean module param from parent process 

237 original = setup_to_argv(SetupHolder.setup) + ['--file'] 

238 while i < len(args): 

239 if args[i] == '-m': 

240 # Always insert at pos == 1 (i.e.: pydevd "--module" --multiprocess ...) 

241 original.insert(1, '--module') 

242 else: 

243 if args[i] == '-': 

244 # this is the marker that input is going to be from stdin for Python 

245 # for now we just disable the debugging here, don't crash but is not supported 

246 return args 

247 elif args[i].startswith('-'): 

248 new_args.append(args[i]) 

249 else: 

250 break 

251 i += 1 

252 

253 # Note: undoing https://github.com/Elizaveta239/PyDev.Debugger/commit/053c9d6b1b455530bca267e7419a9f63bf51cddf 

254 # (i >= len(args) instead of i < len(args)) 

255 # in practice it'd raise an exception here and would return original args, which is not what we want... providing 

256 # a proper fix for https://youtrack.jetbrains.com/issue/PY-9767 elsewhere. 

257 if i >= len(args) or _is_managed_arg(args[i]): # no need to add pydevd twice 

258 log_debug("Patched args: %s" % str(args)) 

259 return args 

260 

261 for x in original: 

262 new_args.append(x) 

263 if x == '--file': 

264 break 

265 

266 while i < len(args): 

267 new_args.append(args[i]) 

268 i += 1 

269 

270 new_args = quote_args(new_args) 

271 log_debug("Patched args: %s" % str(new_args)) 

272 return new_args 

273 except: 

274 traceback.print_exc() 

275 return args 

276 

277 

278def str_to_args_windows(args): 

279 # see http:#msdn.microsoft.com/en-us/library/a1y7w461.aspx 

280 result = [] 

281 

282 DEFAULT = 0 

283 ARG = 1 

284 IN_DOUBLE_QUOTE = 2 

285 

286 state = DEFAULT 

287 backslashes = 0 

288 buf = '' 

289 

290 args_len = len(args) 

291 for i in xrange(args_len): 

292 ch = args[i] 

293 if (ch == '\\'): 

294 backslashes += 1 

295 continue 

296 elif (backslashes != 0): 

297 if ch == '"': 

298 while backslashes >= 2: 

299 backslashes -= 2 

300 buf += '\\' 

301 if (backslashes == 1): 

302 if (state == DEFAULT): 

303 state = ARG 

304 

305 buf += '"' 

306 backslashes = 0 

307 continue 

308 # else fall through to switch 

309 else: 

310 # false alarm, treat passed backslashes literally... 

311 if (state == DEFAULT): 

312 state = ARG 

313 

314 while backslashes > 0: 

315 backslashes -= 1 

316 buf += '\\' 

317 # fall through to switch 

318 if ch in (' ', '\t'): 

319 if (state == DEFAULT): 

320 # skip 

321 continue 

322 elif (state == ARG): 

323 state = DEFAULT 

324 result.append(buf) 

325 buf = '' 

326 continue 

327 

328 if state in (DEFAULT, ARG): 

329 if ch == '"': 

330 state = IN_DOUBLE_QUOTE 

331 else: 

332 state = ARG 

333 buf += ch 

334 

335 elif state == IN_DOUBLE_QUOTE: 

336 if ch == '"': 

337 if (i + 1 < args_len and args[i + 1] == '"'): 

338 # Undocumented feature in Windows: 

339 # Two consecutive double quotes inside a double-quoted argument are interpreted as 

340 # a single double quote. 

341 buf += '"' 

342 i += 1 

343 elif len(buf) == 0: 

344 # empty string on Windows platform. Account for bug in constructor of 

345 # JDK's java.lang.ProcessImpl. 

346 result.append("\"\"") 

347 state = DEFAULT 

348 else: 

349 state = ARG 

350 else: 

351 buf += ch 

352 

353 else: 

354 raise RuntimeError('Illegal condition') 

355 

356 if len(buf) > 0 or state != DEFAULT: 

357 result.append(buf) 

358 

359 return result 

360 

361 

362def patch_arg_str_win(arg_str): 

363 args = str_to_args_windows(arg_str) 

364 # Fix https://youtrack.jetbrains.com/issue/PY-9767 (args may be empty) 

365 if not args or not is_python(args[0]): 

366 return arg_str 

367 arg_str = ' '.join(patch_args(args)) 

368 log_debug("New args: %s" % arg_str) 

369 return arg_str 

370 

371 

372def patch_fork_exec_executable_list(args, other_args): 

373 # When calling a Python executable script with `subprocess.call` the latest uses the first argument as an executable for `fork_exec`. 

374 # This leads to `subprocess.call(["foo.py", "bar", "baz"])` after patching the args will be transformed into something like 

375 # foo.py pydevd.py --port 59043 --client 127.0.0.1 --multiproc --file foo.py bar baz. 

376 # To fix the issue we need to look inside the `fork_exec` executable list and, if necessary, replace an executable script with Python. 

377 i = 0 

378 for arg in args: 

379 i += 1 

380 if arg == '--file': 

381 break 

382 else: 

383 return other_args 

384 executable_list = other_args[0] 

385 if args[i].encode() in executable_list: 

386 return ((sys.executable.encode(),),) + other_args[1:] 

387 return other_args 

388 

389 

390_ORIGINAL_PREFIX = 'original_' 

391 

392 

393def monkey_patch_module(module, funcname, create_func): 

394 if hasattr(module, funcname): 

395 original_name = _ORIGINAL_PREFIX + funcname 

396 if not hasattr(module, original_name): 

397 setattr(module, original_name, getattr(module, funcname)) 

398 setattr(module, funcname, create_func(original_name)) 

399 

400 

401def monkey_patch_os(funcname, create_func): 

402 monkey_patch_module(os, funcname, create_func) 

403 

404 

405def warn_multiproc(): 

406 log_error_once( 

407 "pydev debugger: New process is launching (breakpoints won't work in the new process).\n" 

408 "pydev debugger: To debug that process please enable 'Attach to subprocess automatically while debugging?' option in the debugger settings.\n") 

409 

410 

411def warn_bytes_args(): 

412 log_error_once( 

413 "pydev debugger: bytes arguments were passed to a new process creation function. Breakpoints may not work correctly.\n") 

414 

415 

416def create_warn_multiproc(original_name): 

417 

418 def new_warn_multiproc(*args): 

419 import os 

420 

421 warn_multiproc() 

422 

423 result = getattr(os, original_name)(*args) 

424 

425 if original_name == _ORIGINAL_PREFIX + 'fork': 

426 pid = result 

427 # If automatic attaching to new processes is disabled, it is important to stop tracing in the child process. The reason is the 

428 # "forked" instance of the debugger can potentially hit a breakpoint, which results in the process hanging. 

429 if pid == 0: 

430 debugger = get_global_debugger() 

431 if debugger: 

432 debugger.stoptrace() 

433 return pid 

434 else: 

435 return result 

436 

437 return new_warn_multiproc 

438 

439 

440def create_execl(original_name): 

441 def new_execl(path, *args): 

442 """ 

443 os.execl(path, arg0, arg1, ...) 

444 os.execle(path, arg0, arg1, ..., env) 

445 os.execlp(file, arg0, arg1, ...) 

446 os.execlpe(file, arg0, arg1, ..., env) 

447 """ 

448 import os 

449 args = patch_args(args) 

450 if is_python_args(args): 

451 path = args[0] 

452 send_process_will_be_substituted() 

453 return getattr(os, original_name)(path, *args) 

454 return new_execl 

455 

456 

457def create_execv(original_name): 

458 def new_execv(path, args): 

459 """ 

460 os.execv(path, args) 

461 os.execvp(file, args) 

462 """ 

463 import os 

464 args = patch_args(args) 

465 if is_python_args(args): 

466 path = args[0] 

467 send_process_will_be_substituted() 

468 return getattr(os, original_name)(path, args) 

469 return new_execv 

470 

471 

472def create_execve(original_name): 

473 """ 

474 os.execve(path, args, env) 

475 os.execvpe(file, args, env) 

476 """ 

477 def new_execve(path, args, env): 

478 import os 

479 args = patch_args(args) 

480 if is_python_args(args): 

481 path = args[0] 

482 send_process_will_be_substituted() 

483 return getattr(os, original_name)(path, args, env) 

484 return new_execve 

485 

486 

487def create_spawnl(original_name): 

488 def new_spawnl(mode, path, *args): 

489 """ 

490 os.spawnl(mode, path, arg0, arg1, ...) 

491 os.spawnlp(mode, file, arg0, arg1, ...) 

492 """ 

493 import os 

494 args = patch_args(args) 

495 send_process_created_message() 

496 return getattr(os, original_name)(mode, path, *args) 

497 return new_spawnl 

498 

499 

500def create_spawnv(original_name): 

501 def new_spawnv(mode, path, args): 

502 """ 

503 os.spawnv(mode, path, args) 

504 os.spawnvp(mode, file, args) 

505 """ 

506 import os 

507 args = patch_args(args) 

508 send_process_created_message() 

509 return getattr(os, original_name)(mode, path, args) 

510 return new_spawnv 

511 

512 

513def create_spawnve(original_name): 

514 """ 

515 os.spawnve(mode, path, args, env) 

516 os.spawnvpe(mode, file, args, env) 

517 """ 

518 def new_spawnve(mode, path, args, env): 

519 import os 

520 args = patch_args(args) 

521 send_process_created_message() 

522 return getattr(os, original_name)(mode, path, args, env) 

523 return new_spawnve 

524 

525 

526def create_posix_spawn(original_name): 

527 """ 

528 os.posix_spawn(path, argv, env, *, file_actions=None, ... (6 more)) 

529 os.posix_spawnp(path, argv, env, *, file_actions=None, ... (6 more)) 

530 """ 

531 def new_posix_spawn(path, argv, env, **kwargs): 

532 import os 

533 argv = patch_args(argv) 

534 send_process_created_message() 

535 return getattr(os, original_name)(path, argv, env, **kwargs) 

536 return new_posix_spawn 

537 

538 

539def create_fork_exec(original_name): 

540 """ 

541 _posixsubprocess.fork_exec(args, executable_list, close_fds, ... (13 more)) 

542 """ 

543 def new_fork_exec(args, *other_args): 

544 import _posixsubprocess # @UnresolvedImport 

545 args = patch_args(args) 

546 send_process_created_message() 

547 return getattr(_posixsubprocess, original_name)(args, *patch_fork_exec_executable_list(args, other_args)) 

548 return new_fork_exec 

549 

550 

551def create_warn_fork_exec(original_name): 

552 """ 

553 _posixsubprocess.fork_exec(args, executable_list, close_fds, ... (13 more)) 

554 """ 

555 def new_warn_fork_exec(*args): 

556 try: 

557 import _posixsubprocess 

558 warn_multiproc() 

559 return getattr(_posixsubprocess, original_name)(*args) 

560 except: 

561 pass 

562 return new_warn_fork_exec 

563 

564 

565def create_CreateProcess(original_name): 

566 """ 

567 CreateProcess(*args, **kwargs) 

568 """ 

569 def new_CreateProcess(app_name, cmd_line, *args): 

570 try: 

571 import _subprocess 

572 except ImportError: 

573 import _winapi as _subprocess 

574 send_process_created_message() 

575 return getattr(_subprocess, original_name)(app_name, patch_arg_str_win(cmd_line), *args) 

576 return new_CreateProcess 

577 

578 

579def create_CreateProcessWarnMultiproc(original_name): 

580 """ 

581 CreateProcess(*args, **kwargs) 

582 """ 

583 def new_CreateProcess(*args): 

584 try: 

585 import _subprocess 

586 except ImportError: 

587 import _winapi as _subprocess 

588 warn_multiproc() 

589 return getattr(_subprocess, original_name)(*args) 

590 return new_CreateProcess 

591 

592 

593def apply_foundation_framework_hack(): 

594 # Hack in order to prevent the crash on macOS - load the Foundation framework before any forking in the debugger. 

595 # See: https://bugs.python.org/issue35219. 

596 import ctypes 

597 try: 

598 ctypes.cdll.LoadLibrary('/System/Library/Frameworks/Foundation.framework/Foundation') 

599 except OSError: 

600 log_error_once('Failed to load the Foundation framework shared library. Debugging of code that uses `os.fork()` may not work.\n' 

601 'Consider setting the `OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES` environment variable.') 

602 else: 

603 log_debug('Successfully loaded the Foundation framework shared library.') 

604 

605 

606def create_fork(original_name): 

607 def new_fork(): 

608 import os 

609 

610 # A simple fork will result in a new python process 

611 is_new_python_process = True 

612 frame = sys._getframe() 

613 

614 while frame is not None: 

615 if frame.f_code.co_name == '_execute_child' and 'subprocess' in frame.f_code.co_filename: 

616 # If we're actually in subprocess.Popen creating a child, it may 

617 # result in something which is not a Python process, (so, we 

618 # don't want to connect with it in the forked version). 

619 executable = frame.f_locals.get('executable') 

620 if executable is not None: 

621 is_new_python_process = False 

622 if is_python(executable): 

623 is_new_python_process = True 

624 break 

625 

626 frame = frame.f_back 

627 frame = None # Just make sure we don't hold on to it. 

628 

629 if IS_MACOS and IS_PY36_OR_LESSER: 

630 is_fork_safety_disabled = os.environ.get('OBJC_DISABLE_INITIALIZE_FORK_SAFETY') == 'YES' 

631 if not is_fork_safety_disabled: 

632 apply_foundation_framework_hack() 

633 

634 child_process = getattr(os, original_name)() # fork 

635 if not child_process: 

636 if is_new_python_process: 

637 log_debug("A new child process with PID %d has been forked" % os.getpid()) 

638 _on_forked_process() 

639 else: 

640 if is_new_python_process: 

641 send_process_created_message() 

642 return child_process 

643 return new_fork 

644 

645 

646def send_process_created_message(): 

647 debugger = get_global_debugger() 

648 if debugger is not None: 

649 debugger.send_process_created_message() 

650 

651 

652def send_process_will_be_substituted(): 

653 """Sends a message that a new process is going to be created. 

654 When `PyDB` works in server mode this method also waits for the 

655 response from IDE to be sure that IDE received this message. 

656 """ 

657 from _pydevd_bundle.pydevd_comm import get_global_debugger 

658 debugger = get_global_debugger() 

659 if debugger is not None: 

660 debugger.send_process_will_be_substituted() 

661 

662 

663def patch_new_process_functions(): 

664 # os.execl(path, arg0, arg1, ...) 

665 # os.execle(path, arg0, arg1, ..., env) 

666 # os.execlp(file, arg0, arg1, ...) 

667 # os.execlpe(file, arg0, arg1, ..., env) 

668 # os.execv(path, args) 

669 # os.execve(path, args, env) 

670 # os.execvp(file, args) 

671 # os.execvpe(file, args, env) 

672 monkey_patch_os('execl', create_execl) 

673 monkey_patch_os('execle', create_execl) 

674 monkey_patch_os('execlp', create_execl) 

675 monkey_patch_os('execlpe', create_execl) 

676 monkey_patch_os('execv', create_execv) 

677 monkey_patch_os('execve', create_execve) 

678 monkey_patch_os('execvp', create_execv) 

679 monkey_patch_os('execvpe', create_execve) 

680 

681 # os.spawnl(mode, path, ...) 

682 # os.spawnle(mode, path, ..., env) 

683 # os.spawnlp(mode, file, ...) 

684 # os.spawnlpe(mode, file, ..., env) 

685 # os.spawnv(mode, path, args) 

686 # os.spawnve(mode, path, args, env) 

687 # os.spawnvp(mode, file, args) 

688 # os.spawnvpe(mode, file, args, env) 

689 

690 monkey_patch_os('spawnl', create_spawnl) 

691 monkey_patch_os('spawnle', create_spawnl) 

692 monkey_patch_os('spawnlp', create_spawnl) 

693 monkey_patch_os('spawnlpe', create_spawnl) 

694 monkey_patch_os('spawnv', create_spawnv) 

695 monkey_patch_os('spawnve', create_spawnve) 

696 monkey_patch_os('spawnvp', create_spawnv) 

697 monkey_patch_os('spawnvpe', create_spawnve) 

698 

699 if IS_PY38_OR_GREATER and not IS_WINDOWS: 

700 monkey_patch_os('posix_spawn', create_posix_spawn) 

701 monkey_patch_os('posix_spawnp', create_posix_spawn) 

702 

703 if not IS_JYTHON: 

704 if not IS_WINDOWS: 

705 monkey_patch_os('fork', create_fork) 

706 try: 

707 import _posixsubprocess 

708 monkey_patch_module(_posixsubprocess, 'fork_exec', create_fork_exec) 

709 except ImportError: 

710 pass 

711 else: 

712 # Windows 

713 try: 

714 import _subprocess 

715 except ImportError: 

716 import _winapi as _subprocess 

717 monkey_patch_module(_subprocess, 'CreateProcess', create_CreateProcess) 

718 

719 

720def patch_new_process_functions_with_warning(): 

721 monkey_patch_os('execl', create_warn_multiproc) 

722 monkey_patch_os('execle', create_warn_multiproc) 

723 monkey_patch_os('execlp', create_warn_multiproc) 

724 monkey_patch_os('execlpe', create_warn_multiproc) 

725 monkey_patch_os('execv', create_warn_multiproc) 

726 monkey_patch_os('execve', create_warn_multiproc) 

727 monkey_patch_os('execvp', create_warn_multiproc) 

728 monkey_patch_os('execvpe', create_warn_multiproc) 

729 monkey_patch_os('spawnl', create_warn_multiproc) 

730 monkey_patch_os('spawnle', create_warn_multiproc) 

731 monkey_patch_os('spawnlp', create_warn_multiproc) 

732 monkey_patch_os('spawnlpe', create_warn_multiproc) 

733 monkey_patch_os('spawnv', create_warn_multiproc) 

734 monkey_patch_os('spawnve', create_warn_multiproc) 

735 monkey_patch_os('spawnvp', create_warn_multiproc) 

736 monkey_patch_os('spawnvpe', create_warn_multiproc) 

737 

738 if IS_PY38_OR_GREATER and not IS_WINDOWS: 

739 monkey_patch_os('posix_spawn', create_warn_multiproc) 

740 monkey_patch_os('posix_spawnp', create_warn_multiproc) 

741 

742 if not IS_JYTHON: 

743 if not IS_WINDOWS: 

744 monkey_patch_os('fork', create_warn_multiproc) 

745 try: 

746 import _posixsubprocess 

747 monkey_patch_module(_posixsubprocess, 'fork_exec', create_warn_fork_exec) 

748 except ImportError: 

749 pass 

750 else: 

751 # Windows 

752 try: 

753 import _subprocess 

754 except ImportError: 

755 import _winapi as _subprocess 

756 monkey_patch_module(_subprocess, 'CreateProcess', create_CreateProcessWarnMultiproc) 

757 

758 

759class _NewThreadStartupWithTrace: 

760 

761 def __init__(self, original_func, args, kwargs): 

762 self.original_func = original_func 

763 self.args = args 

764 self.kwargs = kwargs 

765 

766 def __call__(self): 

767 # We monkey-patch the thread creation so that this function is called in the new thread. At this point 

768 # we notify of its creation and start tracing it. 

769 global_debugger = get_global_debugger() 

770 

771 thread_id = None 

772 if global_debugger is not None: 

773 # Note: if this is a thread from threading.py, we're too early in the boostrap process (because we mocked 

774 # the start_new_thread internal machinery and thread._bootstrap has not finished), so, the code below needs 

775 # to make sure that we use the current thread bound to the original function and not use 

776 # threading.currentThread() unless we're sure it's a dummy thread. 

777 t = getattr(self.original_func, '__self__', getattr(self.original_func, 'im_self', None)) 

778 if not isinstance(t, threading.Thread): 

779 # This is not a threading.Thread but a Dummy thread (so, get it as a dummy thread using 

780 # currentThread). 

781 t = threading.currentThread() 

782 

783 if not getattr(t, 'is_pydev_daemon_thread', False): 

784 thread_id = get_current_thread_id(t) 

785 global_debugger.notify_thread_created(thread_id, t) 

786 _on_set_trace_for_new_thread(global_debugger) 

787 

788 if getattr(global_debugger, 'thread_analyser', None) is not None: 

789 try: 

790 from pydevd_concurrency_analyser.pydevd_concurrency_logger import log_new_thread 

791 log_new_thread(global_debugger, t) 

792 except: 

793 sys.stderr.write("Failed to detect new thread for visualization") 

794 try: 

795 ret = self.original_func(*self.args, **self.kwargs) 

796 finally: 

797 if thread_id is not None: 

798 global_debugger.notify_thread_not_alive(thread_id) 

799 

800 return ret 

801 

802 

803class _NewThreadStartupWithoutTrace: 

804 

805 def __init__(self, original_func, args, kwargs): 

806 self.original_func = original_func 

807 self.args = args 

808 self.kwargs = kwargs 

809 

810 def __call__(self): 

811 return self.original_func(*self.args, **self.kwargs) 

812 

813_UseNewThreadStartup = _NewThreadStartupWithTrace 

814 

815 

816def _get_threading_modules_to_patch(): 

817 threading_modules_to_patch = [] 

818 

819 try: 

820 import thread as _thread 

821 except: 

822 import _thread 

823 threading_modules_to_patch.append(_thread) 

824 threading_modules_to_patch.append(threading) 

825 

826 return threading_modules_to_patch 

827 

828threading_modules_to_patch = _get_threading_modules_to_patch() 

829 

830 

831def patch_thread_module(thread_module): 

832 

833 if getattr(thread_module, '_original_start_new_thread', None) is None: 

834 if thread_module is threading: 

835 if not hasattr(thread_module, '_start_new_thread'): 

836 return # Jython doesn't have it. 

837 _original_start_new_thread = thread_module._original_start_new_thread = thread_module._start_new_thread 

838 else: 

839 _original_start_new_thread = thread_module._original_start_new_thread = thread_module.start_new_thread 

840 else: 

841 _original_start_new_thread = thread_module._original_start_new_thread 

842 

843 class ClassWithPydevStartNewThread: 

844 

845 def pydev_start_new_thread(self, function, args=(), kwargs={}): 

846 ''' 

847 We need to replace the original thread_module.start_new_thread with this function so that threads started 

848 through it and not through the threading module are properly traced. 

849 ''' 

850 return _original_start_new_thread(_UseNewThreadStartup(function, args, kwargs), ()) 

851 

852 # This is a hack for the situation where the thread_module.start_new_thread is declared inside a class, such as the one below 

853 # class F(object): 

854 # start_new_thread = thread_module.start_new_thread 

855 # 

856 # def start_it(self): 

857 # self.start_new_thread(self.function, args, kwargs) 

858 # So, if it's an already bound method, calling self.start_new_thread won't really receive a different 'self' -- it 

859 # does work in the default case because in builtins self isn't passed either. 

860 pydev_start_new_thread = ClassWithPydevStartNewThread().pydev_start_new_thread 

861 

862 try: 

863 # We need to replace the original thread_module.start_new_thread with this function so that threads started through 

864 # it and not through the threading module are properly traced. 

865 if thread_module is threading: 

866 thread_module._start_new_thread = pydev_start_new_thread 

867 else: 

868 thread_module.start_new_thread = pydev_start_new_thread 

869 thread_module.start_new = pydev_start_new_thread 

870 except: 

871 pass 

872 

873 

874def patch_thread_modules(): 

875 for t in threading_modules_to_patch: 

876 patch_thread_module(t) 

877 

878 

879def undo_patch_thread_modules(): 

880 for t in threading_modules_to_patch: 

881 try: 

882 t.start_new_thread = t._original_start_new_thread 

883 except: 

884 pass 

885 

886 try: 

887 t.start_new = t._original_start_new_thread 

888 except: 

889 pass 

890 

891 try: 

892 t._start_new_thread = t._original_start_new_thread 

893 except: 

894 pass 

895 

896 

897def disable_trace_thread_modules(): 

898 ''' 

899 Can be used to temporarily stop tracing threads created with thread.start_new_thread. 

900 ''' 

901 global _UseNewThreadStartup 

902 _UseNewThreadStartup = _NewThreadStartupWithoutTrace 

903 

904 

905def enable_trace_thread_modules(): 

906 ''' 

907 Can be used to start tracing threads created with thread.start_new_thread again. 

908 ''' 

909 global _UseNewThreadStartup 

910 _UseNewThreadStartup = _NewThreadStartupWithTrace 

911 

912 

913def get_original_start_new_thread(threading_module): 

914 try: 

915 return threading_module._original_start_new_thread 

916 except: 

917 return threading_module.start_new_thread