retracesoftware-proxy 0.1.21__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- retracesoftware/__main__.py +266 -0
- retracesoftware/autoenable.py +53 -0
- retracesoftware/config.json +19 -295
- retracesoftware/install/config.py +6 -0
- retracesoftware/install/edgecases.py +23 -1
- retracesoftware/install/patcher.py +93 -649
- retracesoftware/install/patchfindspec.py +117 -0
- retracesoftware/install/phases.py +338 -0
- retracesoftware/install/record.py +97 -37
- retracesoftware/install/replace.py +28 -0
- retracesoftware/install/replay.py +55 -11
- retracesoftware/install/tracer.py +87 -77
- retracesoftware/modules.toml +384 -0
- retracesoftware/proxy/messagestream.py +204 -0
- retracesoftware/proxy/proxysystem.py +283 -64
- retracesoftware/proxy/proxytype.py +34 -10
- retracesoftware/proxy/record.py +97 -64
- retracesoftware/proxy/replay.py +62 -219
- retracesoftware/proxy/serializer.py +28 -0
- retracesoftware/proxy/startthread.py +40 -0
- retracesoftware/proxy/stubfactory.py +42 -19
- retracesoftware/replay.py +104 -0
- retracesoftware/run.py +373 -0
- retracesoftware/stackdifference.py +133 -0
- {retracesoftware_proxy-0.1.21.dist-info → retracesoftware_proxy-0.2.0.dist-info}/METADATA +2 -1
- retracesoftware_proxy-0.2.0.dist-info/RECORD +41 -0
- retracesoftware_proxy-0.1.21.dist-info/RECORD +0 -29
- {retracesoftware_proxy-0.1.21.dist-info → retracesoftware_proxy-0.2.0.dist-info}/WHEEL +0 -0
- {retracesoftware_proxy-0.1.21.dist-info → retracesoftware_proxy-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -16,16 +16,21 @@ class StubRef:
|
|
|
16
16
|
def __init__(self, cls):
|
|
17
17
|
blacklist = ['__class__', '__dict__', '__module__', '__doc__', '__new__']
|
|
18
18
|
|
|
19
|
-
methods = []
|
|
19
|
+
self.methods = []
|
|
20
|
+
self.static_methods = []
|
|
21
|
+
self.class_methods = []
|
|
20
22
|
|
|
21
23
|
for key,value in cls.__dict__.items():
|
|
22
24
|
if key not in blacklist:
|
|
23
|
-
if
|
|
24
|
-
|
|
25
|
+
if isinstance(value, classmethod):
|
|
26
|
+
self.class_methods.append(key)
|
|
27
|
+
elif isinstance(value, staticmethod):
|
|
28
|
+
self.class_methods.append(key)
|
|
29
|
+
elif utils.is_method_descriptor(value):
|
|
30
|
+
self.methods.append(key)
|
|
25
31
|
|
|
26
32
|
self.name = cls.__name__
|
|
27
33
|
self.module = cls.__module__
|
|
28
|
-
self.methods = methods
|
|
29
34
|
|
|
30
35
|
# def __init__(self, module, name, methods, members):
|
|
31
36
|
# self.name = name
|
|
@@ -47,6 +52,11 @@ class StubMethodDescriptor(functional.repeatedly):
|
|
|
47
52
|
super().__init__(next_result)
|
|
48
53
|
self.__name__ = name
|
|
49
54
|
|
|
55
|
+
# @utils.striptraceback
|
|
56
|
+
# def __call__(self, *args, **kwargs):
|
|
57
|
+
# super().__call__(*args, **kwargs)
|
|
58
|
+
|
|
59
|
+
#@utils.striptraceback
|
|
50
60
|
def __str__(self):
|
|
51
61
|
return f"stub - {__name__}"
|
|
52
62
|
|
|
@@ -55,29 +65,25 @@ class StubMemberDescriptor:
|
|
|
55
65
|
self.next_result = next_result
|
|
56
66
|
self.__name__ = name
|
|
57
67
|
|
|
68
|
+
#@utils.striptraceback
|
|
58
69
|
def __get__(self, instance, owner):
|
|
59
70
|
if instance is None:
|
|
60
71
|
return self
|
|
61
72
|
|
|
62
73
|
return self.next_result()
|
|
63
74
|
|
|
75
|
+
#@utils.striptraceback
|
|
64
76
|
def __set__(self, instance, value):
|
|
65
77
|
return self.next_result()
|
|
66
78
|
|
|
79
|
+
#@utils.striptraceback
|
|
67
80
|
def __delete__(self, instance):
|
|
68
81
|
return self.next_result()
|
|
69
82
|
|
|
83
|
+
#@utils.striptraceback
|
|
70
84
|
def __str__(self):
|
|
71
85
|
return f"stub member - {__name__}"
|
|
72
86
|
|
|
73
|
-
class StubFunction(functional.repeatedly):
|
|
74
|
-
def __init__(self, name, next_result):
|
|
75
|
-
super().__init__(next_result)
|
|
76
|
-
self.__name__ = name
|
|
77
|
-
|
|
78
|
-
def __str__(self):
|
|
79
|
-
return f"stub function - {__name__}"
|
|
80
|
-
|
|
81
87
|
class StubFactory:
|
|
82
88
|
|
|
83
89
|
__slots__ = ['next_result', 'thread_state', 'cache']
|
|
@@ -106,9 +112,14 @@ class StubFactory:
|
|
|
106
112
|
if self.thread_state.value == 'disabled' and name == '__repr__':
|
|
107
113
|
return f"stub - {name}"
|
|
108
114
|
else:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
return None
|
|
116
|
+
# print(f'Error trying to call descriptor: {name} {args} {kwargs}, retrace mode: {self.thread_state.value}')
|
|
117
|
+
|
|
118
|
+
# import traceback
|
|
119
|
+
# traceback.print_stack()
|
|
120
|
+
|
|
121
|
+
# utils.sigtrap(None)
|
|
122
|
+
# os._exit(1)
|
|
112
123
|
|
|
113
124
|
next_result = self.thread_state.dispatch(disabled, external = self.next_result)
|
|
114
125
|
|
|
@@ -119,6 +130,8 @@ class StubFactory:
|
|
|
119
130
|
|
|
120
131
|
def create_stubtype(self, spec):
|
|
121
132
|
|
|
133
|
+
assert self.thread_state.value == 'disabled'
|
|
134
|
+
|
|
122
135
|
slots = {
|
|
123
136
|
'__module__': spec.module,
|
|
124
137
|
'__qualname__': spec.name,
|
|
@@ -129,7 +142,14 @@ class StubFactory:
|
|
|
129
142
|
slots[method] = self.create_method(method)
|
|
130
143
|
assert utils.is_method_descriptor(slots[method])
|
|
131
144
|
|
|
145
|
+
def on_disabled(name):
|
|
146
|
+
print(f'Error trying to get/set attribute: {name}, when retrace mode: {self.thread_state.value} was not external')
|
|
147
|
+
utils.sigtrap(None)
|
|
148
|
+
os._exit(1)
|
|
149
|
+
|
|
150
|
+
#@utils.striptraceback
|
|
132
151
|
def getattr(instance, name):
|
|
152
|
+
print('In stub getattr!!!')
|
|
133
153
|
if self.thread_state.value == 'external':
|
|
134
154
|
return self.next_result()
|
|
135
155
|
else:
|
|
@@ -137,6 +157,7 @@ class StubFactory:
|
|
|
137
157
|
utils.sigtrap(None)
|
|
138
158
|
os._exit(1)
|
|
139
159
|
|
|
160
|
+
#@utils.striptraceback
|
|
140
161
|
def setattr(instance, name, value):
|
|
141
162
|
if self.thread_state.value == 'external':
|
|
142
163
|
return self.next_result()
|
|
@@ -145,13 +166,15 @@ class StubFactory:
|
|
|
145
166
|
utils.sigtrap(None)
|
|
146
167
|
os._exit(1)
|
|
147
168
|
|
|
148
|
-
slots['__getattr__'] =
|
|
149
|
-
slots['
|
|
169
|
+
slots['__getattr__'] = self.thread_state.method_dispatch(on_disabled, external = self.next_result)
|
|
170
|
+
# slots['__getattr__'] = getattr
|
|
171
|
+
slots['__setattr__'] = self.thread_state.method_dispatch(on_disabled, external = self.next_result)
|
|
150
172
|
|
|
151
173
|
resolved = resolve(spec.module, spec.name)
|
|
152
174
|
|
|
153
|
-
if isinstance(resolved, type):
|
|
154
|
-
|
|
175
|
+
# if isinstance(resolved, type):
|
|
176
|
+
# slots['__class__'] = property(functional.repeatedly(resolved))
|
|
177
|
+
|
|
155
178
|
# else:
|
|
156
179
|
# utils.sigtrap(f'{spec.module}.{spec.name}')
|
|
157
180
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import retracesoftware.stream as stream
|
|
2
|
+
from retracesoftware.run import install, run_with_retrace, ImmutableTypes, thread_states
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from retracesoftware.proxy.thread import thread_id
|
|
7
|
+
from retracesoftware.proxy.replay import ReplayProxySystem
|
|
8
|
+
import retracesoftware.utils as utils
|
|
9
|
+
import json
|
|
10
|
+
from retracesoftware.stackdifference import on_stack_difference
|
|
11
|
+
|
|
12
|
+
def parse_args():
|
|
13
|
+
parser = argparse.ArgumentParser(
|
|
14
|
+
prog=f"python -m {sys.argv[0]}",
|
|
15
|
+
description="Run a Python module with debugging, logging, etc."
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
'--verbose',
|
|
20
|
+
action='store_true',
|
|
21
|
+
help='Enable verbose output'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
'--timeout', # or '-r'
|
|
26
|
+
type = int, # ensures it's a string (optional, but safe)
|
|
27
|
+
default = 60, # default value if not provided
|
|
28
|
+
help = 'the directory to place the recording files'
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
'--recording', # or '-r'
|
|
33
|
+
type = str, # ensures it's a string (optional, but safe)
|
|
34
|
+
default = '.', # default value if not provided
|
|
35
|
+
help = 'the directory to place the recording files'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return parser.parse_args()
|
|
39
|
+
|
|
40
|
+
def load_json(file):
|
|
41
|
+
with open(file, "r", encoding="utf-8") as f:
|
|
42
|
+
return json.load(f)
|
|
43
|
+
|
|
44
|
+
def main():
|
|
45
|
+
# import debugpy
|
|
46
|
+
|
|
47
|
+
# port = 1977
|
|
48
|
+
# debugpy.listen(("127.0.0.1", port))
|
|
49
|
+
# debugpy.wait_for_client()
|
|
50
|
+
|
|
51
|
+
# self.reader = stream.reader(path,
|
|
52
|
+
# thread = thread_id,
|
|
53
|
+
# timeout_seconds = 60,
|
|
54
|
+
# verbose = verbose,
|
|
55
|
+
# on_stack_difference = thread_state.wrap('disabled', on_stack_difference),
|
|
56
|
+
# magic_markers = magic_markers)
|
|
57
|
+
|
|
58
|
+
# return ReplayProxySystem(thread_state = thread_state,
|
|
59
|
+
# immutable_types = immutable_types,
|
|
60
|
+
# tracing_config = tracing_config,
|
|
61
|
+
# mainscript = mainscript,
|
|
62
|
+
# path = recording_path / 'trace.bin',
|
|
63
|
+
# tracecalls = env_truthy('RETRACE_ALL', False),
|
|
64
|
+
# verbose = verbose,
|
|
65
|
+
# magic_markers = env_truthy('RETRACE_MAGIC_MARKERS', False))
|
|
66
|
+
|
|
67
|
+
args = parse_args()
|
|
68
|
+
path = Path(args.recording)
|
|
69
|
+
|
|
70
|
+
if not path.exists():
|
|
71
|
+
raise Exception(f"Recording path: {path} does not exist")
|
|
72
|
+
|
|
73
|
+
settings = load_json(path / "settings.json")
|
|
74
|
+
|
|
75
|
+
thread_state = utils.ThreadState(*thread_states)
|
|
76
|
+
|
|
77
|
+
with stream.reader(path = path / 'trace.bin',
|
|
78
|
+
thread = thread_id,
|
|
79
|
+
timeout_seconds = args.timeout,
|
|
80
|
+
verbose = args.verbose,
|
|
81
|
+
on_stack_difference = thread_state.wrap('disabled', on_stack_difference),
|
|
82
|
+
magic_markers = settings['magic_markers']) as reader:
|
|
83
|
+
|
|
84
|
+
tracing_config = {}
|
|
85
|
+
|
|
86
|
+
system = ReplayProxySystem(
|
|
87
|
+
reader = reader,
|
|
88
|
+
thread_state = thread_state,
|
|
89
|
+
immutable_types = ImmutableTypes(),
|
|
90
|
+
tracing_config = tracing_config,
|
|
91
|
+
tracecalls = settings['trace_inputs'])
|
|
92
|
+
|
|
93
|
+
install(system)
|
|
94
|
+
|
|
95
|
+
run_with_retrace(system, settings['argv'])
|
|
96
|
+
|
|
97
|
+
# install(system)
|
|
98
|
+
|
|
99
|
+
# run_with_retrace(system, args.rest[1:])
|
|
100
|
+
|
|
101
|
+
# runpy.run_module('foo', run_name="__main__", alter_sys=False)
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
main()
|
retracesoftware/run.py
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import runpy
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
# from retracesoftware.install.phases import *
|
|
6
|
+
import pkgutil
|
|
7
|
+
import tomllib
|
|
8
|
+
import retracesoftware.functional as functional
|
|
9
|
+
import builtins
|
|
10
|
+
import importlib
|
|
11
|
+
import _imp
|
|
12
|
+
import importlib._bootstrap_external as _bootstrap_external
|
|
13
|
+
import atexit
|
|
14
|
+
import threading
|
|
15
|
+
import _signal
|
|
16
|
+
import retracesoftware.utils as utils
|
|
17
|
+
from retracesoftware.install.patcher import patch_module, create_patcher, patch_imported_module
|
|
18
|
+
from retracesoftware.proxy.startthread import patch_thread_start
|
|
19
|
+
from retracesoftware.install.replace import update
|
|
20
|
+
|
|
21
|
+
thread_states = [
|
|
22
|
+
"disabled", # Default state when retrace is disabled for a thread
|
|
23
|
+
"internal", # Default state when retrace is disabled for a thread
|
|
24
|
+
"external", # When target thread is running outside the python system to be recorded
|
|
25
|
+
"retrace", # When target thread is running outside the retrace system
|
|
26
|
+
"importing", # When target thread is running outside the retrace system
|
|
27
|
+
"gc", # When the target thread is running inside the pyton garbage collector
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
class ImmutableTypes(set):
|
|
31
|
+
def __init__(self, *args, **kwargs):
|
|
32
|
+
super().__init__(*args, **kwargs)
|
|
33
|
+
|
|
34
|
+
def __contains__(self, item):
|
|
35
|
+
assert isinstance(item, type)
|
|
36
|
+
|
|
37
|
+
if super().__contains__(item):
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
for elem in self:
|
|
41
|
+
if issubclass(item, elem):
|
|
42
|
+
self.add(item)
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
def load_module_config(filename):
|
|
48
|
+
data = pkgutil.get_data("retracesoftware", filename)
|
|
49
|
+
assert data is not None
|
|
50
|
+
return tomllib.loads(data.decode("utf-8"))
|
|
51
|
+
|
|
52
|
+
def wait_for_non_daemon_threads(timeout=None):
|
|
53
|
+
"""
|
|
54
|
+
Wait for all non-daemon threads to finish, just like Python does on exit.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
timeout (float, optional): Max seconds to wait. None = wait forever.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
bool: True if all threads finished, False if timeout exceeded.
|
|
61
|
+
"""
|
|
62
|
+
import threading
|
|
63
|
+
import time
|
|
64
|
+
|
|
65
|
+
start_time = time.time()
|
|
66
|
+
main_thread = threading.main_thread()
|
|
67
|
+
|
|
68
|
+
while True:
|
|
69
|
+
# Get all active threads
|
|
70
|
+
active = threading.enumerate()
|
|
71
|
+
|
|
72
|
+
# Filter: non-daemon and not the main thread
|
|
73
|
+
non_daemon_threads = [
|
|
74
|
+
t for t in active
|
|
75
|
+
if t is not main_thread and not t.daemon
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
if not non_daemon_threads:
|
|
79
|
+
return True # All done!
|
|
80
|
+
|
|
81
|
+
# Check timeout
|
|
82
|
+
if timeout is not None:
|
|
83
|
+
elapsed = time.time() - start_time
|
|
84
|
+
if elapsed >= timeout:
|
|
85
|
+
print(f"Timeout: {len(non_daemon_threads)} thread(s) still alive")
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
# Sleep briefly to avoid busy-wait
|
|
89
|
+
time.sleep(0.1)
|
|
90
|
+
|
|
91
|
+
def run_python_command(argv):
|
|
92
|
+
"""
|
|
93
|
+
Run a Python app from a command list using runpy.
|
|
94
|
+
|
|
95
|
+
Supports:
|
|
96
|
+
['-m', 'module', 'arg1', ...] → like `python -m module arg1 ...`
|
|
97
|
+
['script.py', 'arg1', ...] → like `python script.py arg1 ...`
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
argv: List of command-line arguments (first item is either '-m' or script path)
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Exit code (0 on success, 1+ on error)
|
|
104
|
+
"""
|
|
105
|
+
if not argv:
|
|
106
|
+
print("Error: No command provided", file=sys.stderr)
|
|
107
|
+
return 1
|
|
108
|
+
|
|
109
|
+
original_argv = sys.argv[:]
|
|
110
|
+
original_cwd = os.getcwd()
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
if argv[0] == '-m':
|
|
114
|
+
if len(argv) < 2:
|
|
115
|
+
print("Error: -m requires a module name", file=sys.stderr)
|
|
116
|
+
return 1
|
|
117
|
+
module_name = argv[1]
|
|
118
|
+
module_args = argv[2:]
|
|
119
|
+
sys.argv = ['-m', module_name] + module_args
|
|
120
|
+
runpy.run_module(module_name, run_name="__main__")
|
|
121
|
+
return 0
|
|
122
|
+
|
|
123
|
+
else:
|
|
124
|
+
script_path = argv[0]
|
|
125
|
+
script_args = argv[1:]
|
|
126
|
+
path = Path(script_path)
|
|
127
|
+
|
|
128
|
+
if not path.exists():
|
|
129
|
+
print(f"Error: Script not found: {script_path}", file=sys.stderr)
|
|
130
|
+
return 1
|
|
131
|
+
|
|
132
|
+
if path.suffix != ".py":
|
|
133
|
+
print(f"Error: Not a Python script: {script_path}", file=sys.stderr)
|
|
134
|
+
return 1
|
|
135
|
+
|
|
136
|
+
full_path = str(path.resolve())
|
|
137
|
+
sys.argv = [full_path] + script_args
|
|
138
|
+
|
|
139
|
+
# Change to script's directory
|
|
140
|
+
os.chdir(path.parent)
|
|
141
|
+
|
|
142
|
+
runpy.run_path(full_path, run_name="__main__")
|
|
143
|
+
return 0
|
|
144
|
+
|
|
145
|
+
except ModuleNotFoundError:
|
|
146
|
+
print(f"Error: No module named '{argv[1]}'", file=sys.stderr)
|
|
147
|
+
return 1
|
|
148
|
+
except Exception as e:
|
|
149
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
150
|
+
return 1
|
|
151
|
+
finally:
|
|
152
|
+
sys.argv = original_argv
|
|
153
|
+
os.chdir(original_cwd)
|
|
154
|
+
|
|
155
|
+
def patch_import(thread_state, patcher, sync, checkpoint):
|
|
156
|
+
# builtins.__import__ = thread_state.dispatch(builtins.__import__, internal = sync(thread_state.wrap('importing', builtins.__import__)))
|
|
157
|
+
# bi = builtins.__import__
|
|
158
|
+
# def foo(*args, **kwargs):
|
|
159
|
+
# print(f'in patched __import__: {thread_state.value} {args[0]}')
|
|
160
|
+
# if thread_state.value == 'internal':
|
|
161
|
+
# with thread_state.select('importing'):
|
|
162
|
+
# return bi(*args, **kwargs)
|
|
163
|
+
# else:
|
|
164
|
+
# return bi(*args, **kwargs)
|
|
165
|
+
|
|
166
|
+
# builtins.__import__ = foo
|
|
167
|
+
|
|
168
|
+
builtins.__import__ = thread_state.dispatch(builtins.__import__, internal = thread_state.wrap('importing', builtins.__import__))
|
|
169
|
+
|
|
170
|
+
def exec(source, globals = None, locals = None):
|
|
171
|
+
checkpoint(f"exec module: {globals.get('__name__', 'unknown')}")
|
|
172
|
+
res = builtins.exec(source, globals, locals)
|
|
173
|
+
patcher(globals, False)
|
|
174
|
+
return res
|
|
175
|
+
|
|
176
|
+
def patch(module):
|
|
177
|
+
patcher(module.__dict__, False)
|
|
178
|
+
return module
|
|
179
|
+
|
|
180
|
+
_imp.exec_dynamic = thread_state.dispatch(_imp.exec_dynamic,
|
|
181
|
+
importing = functional.juxt(
|
|
182
|
+
thread_state.wrap('internal', _imp.exec_dynamic),
|
|
183
|
+
patch))
|
|
184
|
+
# _imp.exec_dynamic = lambda mod: utils.sigtrap(f'{thread_state.value} - {mod}')
|
|
185
|
+
# _imp.exec_builtin = lambda mod: utils.sigtrap(f'{thread_state.value} - {mod}')
|
|
186
|
+
|
|
187
|
+
_imp.exec_builtin = thread_state.dispatch(_imp.exec_builtin,
|
|
188
|
+
importing = functional.juxt(
|
|
189
|
+
thread_state.wrap('internal', _imp.exec_builtin),
|
|
190
|
+
patch))
|
|
191
|
+
|
|
192
|
+
# def runpy_exec(source, globals = None, locals = None):
|
|
193
|
+
# print(f'In runpy exec!!!!!')
|
|
194
|
+
# return builtins.exec(source, globals, locals)
|
|
195
|
+
|
|
196
|
+
# utils.update(runpy, "_run_code",
|
|
197
|
+
# utils.wrap_func_with_overrides,
|
|
198
|
+
# exec = runpy_exec)
|
|
199
|
+
|
|
200
|
+
utils.update(_bootstrap_external._LoaderBasics, "exec_module",
|
|
201
|
+
utils.wrap_func_with_overrides,
|
|
202
|
+
exec = thread_state.dispatch(builtins.exec, importing = thread_state.wrap('internal', sync(exec))))
|
|
203
|
+
|
|
204
|
+
preload = [
|
|
205
|
+
"logging",
|
|
206
|
+
"pathlib",
|
|
207
|
+
"_signal",
|
|
208
|
+
"_posixsubprocess",
|
|
209
|
+
"socket",
|
|
210
|
+
"select",
|
|
211
|
+
"ssl",
|
|
212
|
+
"random",
|
|
213
|
+
"email",
|
|
214
|
+
"email.errors",
|
|
215
|
+
"http.client",
|
|
216
|
+
|
|
217
|
+
"json",
|
|
218
|
+
"typing",
|
|
219
|
+
"queue",
|
|
220
|
+
"mimetypes",
|
|
221
|
+
"tempfile",
|
|
222
|
+
"zipfile",
|
|
223
|
+
"importlib.resources",
|
|
224
|
+
"importlib.metadata",
|
|
225
|
+
"encodings.idna"
|
|
226
|
+
|
|
227
|
+
# "http.client",
|
|
228
|
+
# "queue",
|
|
229
|
+
# "mimetypes",
|
|
230
|
+
# "encodings.idna",
|
|
231
|
+
# "hmac",
|
|
232
|
+
# "ipaddress",
|
|
233
|
+
# "tempfile",
|
|
234
|
+
# "zipfile",
|
|
235
|
+
# "importlib.resources",
|
|
236
|
+
# "importlib.metadata",
|
|
237
|
+
# "atexit",
|
|
238
|
+
# "weakref"
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
# def debugger_is_active():
|
|
242
|
+
# return sys.gettrace() and 'debugpy' in sys.modules
|
|
243
|
+
|
|
244
|
+
def init_weakref():
|
|
245
|
+
import weakref
|
|
246
|
+
def dummy_callback():
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
class DummyTarget:
|
|
250
|
+
pass
|
|
251
|
+
|
|
252
|
+
f = weakref.finalize(DummyTarget(), dummy_callback)
|
|
253
|
+
f.detach()
|
|
254
|
+
|
|
255
|
+
def wrapped_weakref(ref, thread_state, wrap_callback):
|
|
256
|
+
orig_new = ref.__new__
|
|
257
|
+
|
|
258
|
+
def __new__(cls, ob, callback=None, **kwargs):
|
|
259
|
+
return orig_new(cls, ob, wrap_callback(callback) if callback else None)
|
|
260
|
+
|
|
261
|
+
return type('ref', (ref, ), {'__new__': thread_state.dispatch(orig_new, internal = __new__)})
|
|
262
|
+
|
|
263
|
+
def patch_weakref(thread_state, wrap_callback):
|
|
264
|
+
import _weakref
|
|
265
|
+
|
|
266
|
+
update(_weakref.ref, wrapped_weakref(_weakref.ref, thread_state, wrap_callback))
|
|
267
|
+
# _weakref.ref = wrapped_weakref(_weakref.ref)
|
|
268
|
+
|
|
269
|
+
# def patch_signal(thread_state, wrap_callback):
|
|
270
|
+
|
|
271
|
+
# def wrap_handler(handler):
|
|
272
|
+
# return utils.observer(on_call = ..., function = handler)
|
|
273
|
+
|
|
274
|
+
# _signal.signal =
|
|
275
|
+
# update(_signal.signal, thread_state.dispatch(_signal.signal, internal = thread_state.wrap('internal', _signal.signal)))
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def install(system):
|
|
280
|
+
|
|
281
|
+
patch_weakref(thread_state = system.thread_state, wrap_callback = system.wrap_weakref_callback)
|
|
282
|
+
|
|
283
|
+
init_weakref()
|
|
284
|
+
|
|
285
|
+
for name in preload:
|
|
286
|
+
importlib.import_module(name)
|
|
287
|
+
|
|
288
|
+
# if 'pydevd' in sys.modules:
|
|
289
|
+
# utils.update(sys.modules['pydevd'].PyDB, 'enable_tracing', system.disable_for)
|
|
290
|
+
# utils.update(sys.modules['pydevd'].PyDB, 'set_suspend', system.disable_for)
|
|
291
|
+
# utils.update(sys.modules['pydevd'].PyDB, 'do_wait_suspend', system.disable_for)
|
|
292
|
+
|
|
293
|
+
# if '_pydevd_bundle.pydevd_trace_dispatch_regular' in sys.modules:
|
|
294
|
+
# mod = sys.modules['_pydevd_bundle.pydevd_trace_dispatch_regular']
|
|
295
|
+
# utils.update(mod.ThreadTracer, '__call__', system.disable_for)
|
|
296
|
+
|
|
297
|
+
for function in utils.stack_functions():
|
|
298
|
+
system.exclude_from_stacktrace(function)
|
|
299
|
+
|
|
300
|
+
def recursive_disable(func):
|
|
301
|
+
if not callable(func):
|
|
302
|
+
return func
|
|
303
|
+
|
|
304
|
+
def wrapped(*args, **kwargs):
|
|
305
|
+
with system.thread_state.select('disabled'):
|
|
306
|
+
return recursive_disable(func(*args, **kwargs))
|
|
307
|
+
|
|
308
|
+
return wrapped
|
|
309
|
+
|
|
310
|
+
sys.settrace = functional.sequence(recursive_disable, sys.settrace)
|
|
311
|
+
sys.setprofile = functional.sequence(recursive_disable, sys.setprofile)
|
|
312
|
+
threading.settrace = functional.sequence(recursive_disable, threading.settrace)
|
|
313
|
+
|
|
314
|
+
sys.settrace(sys.gettrace())
|
|
315
|
+
sys.setprofile(sys.getprofile())
|
|
316
|
+
|
|
317
|
+
system.checkpoint('About to install retrace system')
|
|
318
|
+
|
|
319
|
+
module_config = load_module_config('modules.toml')
|
|
320
|
+
|
|
321
|
+
patch_loaded = functional.partial(patch_module, create_patcher(system), module_config)
|
|
322
|
+
patch_imported = functional.partial(patch_imported_module, create_patcher(system), system.checkpoint, module_config)
|
|
323
|
+
|
|
324
|
+
system.checkpoint('Started installing system 1')
|
|
325
|
+
|
|
326
|
+
for modname in module_config.keys():
|
|
327
|
+
if modname in sys.modules:
|
|
328
|
+
patch_loaded(sys.modules[modname].__dict__, True)
|
|
329
|
+
|
|
330
|
+
system.checkpoint('About to patch threading')
|
|
331
|
+
|
|
332
|
+
patch_thread_start(system.thread_state)
|
|
333
|
+
threading.current_thread().__retrace__ = system
|
|
334
|
+
|
|
335
|
+
system.checkpoint('About to patch import')
|
|
336
|
+
patch_import(thread_state = system.thread_state,
|
|
337
|
+
patcher = patch_imported,
|
|
338
|
+
sync = system.sync,
|
|
339
|
+
checkpoint = system.checkpoint)
|
|
340
|
+
|
|
341
|
+
# print(f'MODULES: {list(sys.modules.keys())}')
|
|
342
|
+
|
|
343
|
+
importlib.import_module = \
|
|
344
|
+
system.thread_state.dispatch(system.disable_for(importlib.import_module),
|
|
345
|
+
internal = system.thread_state.wrap('importing', importlib.import_module))
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
system.checkpoint('About to patch preload libraries')
|
|
349
|
+
|
|
350
|
+
system.checkpoint('system patched...')
|
|
351
|
+
|
|
352
|
+
def run_with_retrace(system, argv, trace_shutdown = False):
|
|
353
|
+
|
|
354
|
+
def runpy_exec(source, globals = None, locals = None):
|
|
355
|
+
with system.thread_state.select('internal'):
|
|
356
|
+
return builtins.exec(source, globals, locals)
|
|
357
|
+
|
|
358
|
+
utils.update(runpy, "_run_code",
|
|
359
|
+
utils.wrap_func_with_overrides,
|
|
360
|
+
exec = runpy_exec)
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
run_python_command(argv)
|
|
364
|
+
finally:
|
|
365
|
+
wait_for_non_daemon_threads()
|
|
366
|
+
try:
|
|
367
|
+
if trace_shutdown:
|
|
368
|
+
with system.thread_state.select('internal'):
|
|
369
|
+
atexit._run_exitfuncs()
|
|
370
|
+
else:
|
|
371
|
+
atexit._run_exitfuncs()
|
|
372
|
+
except Exception as e:
|
|
373
|
+
print(f"Error in atexit hook: {e}", file=sys.stderr)
|