/
attach_script.py
245 lines (192 loc) · 9.32 KB
/
attach_script.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
def load_python_helper_lib():
import sys
try:
import ctypes
except ImportError:
ctypes = None
# Note: we cannot use import platform because it may end up importing threading,
# but that should be ok because at this point we can only be in CPython (other
# implementations wouldn't get to this point in the attach process).
# IS_CPYTHON = platform.python_implementation() == 'CPython'
IS_CPYTHON = True
import os
IS_64BIT_PROCESS = sys.maxsize > (2 ** 32)
IS_WINDOWS = sys.platform == 'win32'
IS_LINUX = sys.platform in ('linux', 'linux2')
IS_MAC = sys.platform == 'darwin'
if not IS_CPYTHON or ctypes is None or sys.version_info[:2] > (3, 7):
return None
if IS_WINDOWS:
if IS_64BIT_PROCESS:
suffix = 'amd64'
else:
suffix = 'x86'
filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'pydevd_attach_to_process', 'attach_%s.dll' % (suffix,))
elif IS_LINUX:
if IS_64BIT_PROCESS:
suffix = 'amd64'
else:
suffix = 'x86'
filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'pydevd_attach_to_process', 'attach_linux_%s.so' % (suffix,))
elif IS_MAC:
if IS_64BIT_PROCESS:
suffix = 'x86_64.dylib'
else:
suffix = 'x86.dylib'
filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'pydevd_attach_to_process', 'attach_%s' % (suffix,))
else:
return None
if not os.path.exists(filename):
return None
try:
# Load as pydll so that we don't release the gil.
lib = ctypes.pydll.LoadLibrary(filename)
return lib
except:
return None
def get_main_thread_instance(threading):
if hasattr(threading, 'main_thread'):
return threading.main_thread()
else:
# On Python 2 we don't really have an API to get the main thread,
# so, we just get it from the 'shutdown' bound method.
return threading._shutdown.im_self
def get_main_thread_id(unlikely_thread_id=None):
'''
:param unlikely_thread_id:
Pass to mark some thread id as not likely the main thread.
:return tuple(thread_id, critical_warning)
'''
import sys
import os
current_frames = sys._current_frames()
possible_thread_ids = []
for thread_ident, frame in current_frames.items():
while frame.f_back is not None:
frame = frame.f_back
basename = os.path.basename(frame.f_code.co_filename)
if basename.endswith(('.pyc', '.pyo')):
basename = basename[:-1]
if (frame.f_code.co_name, basename) in [
('_run_module_as_main', 'runpy.py'),
('run_module_as_main', 'runpy.py'),
('run_module', 'runpy.py'),
('run_path', 'runpy.py'),
]:
# This is the case for python -m <module name> (this is an ideal match, so,
# let's return it).
return thread_ident, ''
if frame.f_code.co_name == '<module>':
if frame.f_globals.get('__name__') == '__main__':
possible_thread_ids.insert(0, thread_ident) # Add with higher priority
continue
# Usually the main thread will be started in the <module>, whereas others would
# be started in another place (but when Python is embedded, this may not be
# correct, so, just add to the available possibilities as we'll have to choose
# one if there are multiple).
possible_thread_ids.append(thread_ident)
if len(possible_thread_ids) > 0:
if len(possible_thread_ids) == 1:
return possible_thread_ids[0], '' # Ideal: only one match
while unlikely_thread_id in possible_thread_ids:
possible_thread_ids.remove(unlikely_thread_id)
if len(possible_thread_ids) == 1:
return possible_thread_ids[0], '' # Ideal: only one match
elif len(possible_thread_ids) > 1:
# Bad: we can't really be certain of anything at this point.
return possible_thread_ids[0], \
'Multiple thread ids found (%s). Choosing main thread id randomly (%s).' % (
possible_thread_ids, possible_thread_ids[0])
# If we got here we couldn't discover the main thread id.
return None, 'Unable to discover main thread id.'
def fix_main_thread_id(on_warn=lambda msg:None, on_exception=lambda msg:None, on_critical=lambda msg:None):
# This means that we weren't able to import threading in the main thread (which most
# likely means that the main thread is paused or in some very long operation).
# In this case we'll import threading here and hotfix what may be wrong in the threading
# module (if we're on Windows where we create a thread to do the attach and on Linux
# we are not certain on which thread we're executing this code).
#
# The code below is a workaround for https://bugs.python.org/issue37416
import sys
import threading
try:
with threading._active_limbo_lock:
main_thread_instance = get_main_thread_instance(threading)
if sys.platform == 'win32':
# On windows this code would be called in a secondary thread, so,
# the current thread is unlikely to be the main thread.
if hasattr(threading, '_get_ident'):
unlikely_thread_id = threading._get_ident() # py2
else:
unlikely_thread_id = threading.get_ident() # py3
else:
unlikely_thread_id = None
main_thread_id, critical_warning = get_main_thread_id(unlikely_thread_id)
if main_thread_id is not None:
main_thread_id_attr = '_ident'
if not hasattr(main_thread_instance, main_thread_id_attr):
main_thread_id_attr = '_Thread__ident'
assert hasattr(main_thread_instance, main_thread_id_attr)
if main_thread_id != getattr(main_thread_instance, main_thread_id_attr):
# Note that we also have to reset the '_tstack_lock' for a regular lock.
# This is needed to avoid an error on shutdown because this lock is bound
# to the thread state and will be released when the secondary thread
# that initialized the lock is finished -- making an assert fail during
# process shutdown.
main_thread_instance._tstate_lock = threading._allocate_lock()
main_thread_instance._tstate_lock.acquire()
# Actually patch the thread ident as well as the threading._active dict
# (we should have the _active_limbo_lock to do that).
threading._active.pop(getattr(main_thread_instance, main_thread_id_attr), None)
setattr(main_thread_instance, main_thread_id_attr, main_thread_id)
threading._active[getattr(main_thread_instance, main_thread_id_attr)] = main_thread_instance
# Note: only import from pydevd after the patching is done (we want to do the minimum
# possible when doing that patching).
on_warn('The threading module was not imported by user code in the main thread. The debugger will attempt to work around https://bugs.python.org/issue37416.')
if critical_warning:
on_critical('Issue found when debugger was trying to work around https://bugs.python.org/issue37416:\n%s' % (critical_warning,))
except:
on_exception('Error patching main thread id.')
def attach(port, host, protocol=''):
try:
import sys
fix_main_thread = 'threading' not in sys.modules
if fix_main_thread:
def on_warn(msg):
from _pydev_bundle import pydev_log
pydev_log.warn(msg)
def on_exception(msg):
from _pydev_bundle import pydev_log
pydev_log.exception(msg)
def on_critical(msg):
from _pydev_bundle import pydev_log
pydev_log.critical(msg)
fix_main_thread_id(on_warn=on_warn, on_exception=on_exception, on_critical=on_critical)
else:
from _pydev_bundle import pydev_log # @Reimport
pydev_log.debug('The threading module is already imported by user code.')
if protocol:
from _pydevd_bundle import pydevd_defaults
pydevd_defaults.PydevdCustomization.DEFAULT_PROTOCOL = protocol
import pydevd
# I.e.: disconnect/reset if already connected.
pydevd.SetupHolder.setup = None
py_db = pydevd.get_global_debugger()
if py_db is not None:
py_db.dispose_and_kill_all_pydevd_threads(wait=False)
# pydevd.DebugInfoHolder.DEBUG_RECORD_SOCKET_READS = True
# pydevd.DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS = 3
# pydevd.DebugInfoHolder.DEBUG_TRACE_LEVEL = 3
pydevd.settrace(
port=port,
host=host,
stdoutToServer=True,
stderrToServer=True,
overwrite_prev_trace=True,
suspend=False,
trace_only_current_thread=False,
patch_multiprocessing=False,
)
except:
import traceback
traceback.print_exc()