Skip to content

Commit d3b0fb0

Browse files
committed
1 parent 215390a commit d3b0fb0

1 file changed

Lines changed: 86 additions & 0 deletions

File tree

Lib/test/test_external_inspection.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import unittest
22
import os
3+
import json
34
import textwrap
45
import contextlib
56
import importlib
@@ -418,6 +419,91 @@ def _frame_to_lineno_tuple(frame):
418419
filename, location, funcname, opcode = frame
419420
return (filename, location.lineno, funcname, opcode)
420421

422+
@contextmanager
423+
def _poisoned_debug_offsets_process(self, poison_code):
424+
script = (
425+
textwrap.dedent(
426+
"""\
427+
import json
428+
import os
429+
import struct
430+
import sys
431+
import threading
432+
import time
433+
434+
cookie = b"xdebugpy"
435+
pid = os.getpid()
436+
437+
def find_debug_offsets():
438+
with open(f"/proc/{pid}/maps") as f:
439+
for line in f:
440+
parts = line.split()
441+
if len(parts) < 2 or "rw" not in parts[1]:
442+
continue
443+
start, end = (int(x, 16) for x in parts[0].split("-"))
444+
if end - start > 10_000_000:
445+
continue
446+
try:
447+
fd = os.open(f"/proc/{pid}/mem", os.O_RDONLY)
448+
os.lseek(fd, start, 0)
449+
data = os.read(fd, end - start)
450+
os.close(fd)
451+
except OSError:
452+
continue
453+
off = data.find(cookie)
454+
if off == -1:
455+
continue
456+
version = struct.unpack_from("<Q", data, off + 8)[0]
457+
if ((version >> 24) & 0xFF) != sys.version_info.major:
458+
continue
459+
return start + off
460+
raise RuntimeError("debug offsets not found")
461+
462+
addr = find_debug_offsets()
463+
"""
464+
)
465+
+ textwrap.dedent(poison_code)
466+
+ "\n"
467+
+ textwrap.dedent(
468+
"""\
469+
print(
470+
json.dumps({"pid": pid, "native_tid": threading.get_native_id()}),
471+
flush=True,
472+
)
473+
time.sleep(60)
474+
"""
475+
)
476+
)
477+
478+
proc = subprocess.Popen(
479+
[sys.executable, "-c", script],
480+
stdout=subprocess.PIPE,
481+
stderr=subprocess.PIPE,
482+
text=True,
483+
)
484+
try:
485+
line = proc.stdout.readline()
486+
if not line:
487+
stderr = proc.stderr.read()
488+
self.fail(
489+
"poisoned child failed to initialize: "
490+
f"{stderr.strip() or 'no stderr output'}"
491+
)
492+
yield proc, json.loads(line)
493+
finally:
494+
try:
495+
proc.terminate()
496+
proc.wait(timeout=SHORT_TIMEOUT)
497+
except subprocess.TimeoutExpired:
498+
proc.kill()
499+
proc.wait(timeout=SHORT_TIMEOUT)
500+
except OSError:
501+
pass
502+
if proc.stdout is not None:
503+
proc.stdout.close()
504+
if proc.stderr is not None:
505+
proc.stderr.close()
506+
421507
def _extract_coroutine_stacks_lineno_only(self, stack_trace):
422508
"""Extract coroutine stacks with line numbers only (no column offsets).
423509

0 commit comments

Comments
 (0)