76
76
import sys
77
77
import time
78
78
from contextlib import contextmanager
79
+ import platform
80
+ import traceback
79
81
80
82
try :
81
83
TimeoutError = TimeoutError # @ReservedAssignment
@@ -122,13 +124,139 @@ def wait_for_event_set(self, timeout=None):
122
124
CloseHandle (event )
123
125
124
126
127
+ IS_WINDOWS = sys .platform == 'win32'
128
+ IS_LINUX = sys .platform in ('linux' , 'linux2' )
129
+ IS_MAC = sys .platform == 'darwin'
130
+
131
+
125
132
def is_python_64bit ():
126
133
return (struct .calcsize ('P' ) == 8 )
127
134
128
135
129
- def is_mac ():
130
- import platform
131
- return platform .system () == 'Darwin'
136
+ def get_target_filename (is_target_process_64 = None , prefix = None , extension = None ):
137
+ # Note: we have an independent (and similar -- but not equal) version of this method in
138
+ # `pydevd_tracing.py` which should be kept synchronized with this one (we do a copy
139
+ # because the `pydevd_attach_to_process` is mostly independent and shouldn't be imported in the
140
+ # debugger -- the only situation where it's imported is if the user actually does an attach to
141
+ # process, through `attach_pydevd.py`, but this should usually be called from the IDE directly
142
+ # and not from the debugger).
143
+ libdir = os .path .dirname (__file__ )
144
+
145
+ if is_target_process_64 is None :
146
+ if IS_WINDOWS :
147
+ # i.e.: On windows the target process could have a different bitness (32bit is emulated on 64bit).
148
+ raise AssertionError ("On windows it's expected that the target bitness is specified." )
149
+
150
+ # For other platforms, just use the the same bitness of the process we're running in.
151
+ is_target_process_64 = is_python_64bit ()
152
+
153
+ arch = ''
154
+ if IS_WINDOWS :
155
+ # prefer not using platform.machine() when possible (it's a bit heavyweight as it may
156
+ # spawn a subprocess).
157
+ arch = os .environ .get ("PROCESSOR_ARCHITEW6432" , os .environ .get ('PROCESSOR_ARCHITECTURE' , '' ))
158
+
159
+ if not arch :
160
+ arch = platform .machine ()
161
+ if not arch :
162
+ print ('platform.machine() did not return valid value.' ) # This shouldn't happen...
163
+ return None
164
+
165
+ if IS_WINDOWS :
166
+ if not extension :
167
+ extension = '.dll'
168
+ suffix_64 = 'amd64'
169
+ suffix_32 = 'x86'
170
+
171
+ elif IS_LINUX :
172
+ if not extension :
173
+ extension = '.so'
174
+ suffix_64 = 'amd64'
175
+ suffix_32 = 'x86'
176
+
177
+ elif IS_MAC :
178
+ if not extension :
179
+ extension = '.dylib'
180
+ suffix_64 = 'x86_64'
181
+ suffix_32 = 'x86'
182
+
183
+ else :
184
+ print ('Unable to attach to process in platform: %s' , sys .platform )
185
+ return None
186
+
187
+ if arch .lower () not in ('amd64' , 'x86' , 'x86_64' , 'i386' , 'x86' ):
188
+ # We don't support this processor by default. Still, let's support the case where the
189
+ # user manually compiled it himself with some heuristics.
190
+ #
191
+ # Ideally the user would provide a library in the format: "attach_<arch>.<extension>"
192
+ # based on the way it's currently compiled -- see:
193
+ # - windows/compile_windows.bat
194
+ # - linux_and_mac/compile_linux.sh
195
+ # - linux_and_mac/compile_mac.sh
196
+
197
+ try :
198
+ found = [name for name in os .listdir (libdir ) if name .startswith ('attach_' ) and name .endswith (extension )]
199
+ except :
200
+ print ('Error listing dir: %s' % (libdir ,))
201
+ traceback .print_exc ()
202
+ return None
203
+
204
+ if prefix :
205
+ expected_name = prefix + arch + extension
206
+ expected_name_linux = prefix + 'linux_' + arch + extension
207
+ else :
208
+ # Default is looking for the attach_ / attach_linux
209
+ expected_name = 'attach_' + arch + extension
210
+ expected_name_linux = 'attach_linux_' + arch + extension
211
+
212
+ filename = None
213
+ if expected_name in found : # Heuristic: user compiled with "attach_<arch>.<extension>"
214
+ filename = os .path .join (libdir , expected_name )
215
+
216
+ elif IS_LINUX and expected_name_linux in found : # Heuristic: user compiled with "attach_linux_<arch>.<extension>"
217
+ filename = os .path .join (libdir , expected_name_linux )
218
+
219
+ elif len (found ) == 1 : # Heuristic: user removed all libraries and just left his own lib.
220
+ filename = os .path .join (libdir , found [0 ])
221
+
222
+ else : # Heuristic: there's one additional library which doesn't seem to be our own. Find the odd one.
223
+ filtered = [name for name in found if not name .endswith ((suffix_64 + extension , suffix_32 + extension ))]
224
+ if len (filtered ) == 1 : # If more than one is available we can't be sure...
225
+ filename = os .path .join (libdir , found [0 ])
226
+
227
+ if filename is None :
228
+ print (
229
+ 'Unable to attach to process in arch: %s (did not find %s in %s).' % (
230
+ arch , expected_name , libdir
231
+ )
232
+ )
233
+ return None
234
+
235
+ print ('Using %s in arch: %s.' % (filename , arch ))
236
+
237
+ else :
238
+ if is_target_process_64 :
239
+ suffix = suffix_64
240
+ else :
241
+ suffix = suffix_32
242
+
243
+ if not prefix :
244
+ # Default is looking for the attach_ / attach_linux
245
+ if IS_WINDOWS or IS_MAC : # just the extension changes
246
+ prefix = 'attach_'
247
+ elif IS_LINUX :
248
+ prefix = 'attach_linux_' # historically it has a different name
249
+ else :
250
+ print ('Unable to attach to process in platform: %s' % (sys .platform ,))
251
+ return None
252
+
253
+ filename = os .path .join (libdir , '%s%s%s' % (prefix , suffix , extension ))
254
+
255
+ if not os .path .exists (filename ):
256
+ print ('Expected: %s to exist.' % (filename ,))
257
+ return None
258
+
259
+ return filename
132
260
133
261
134
262
def run_python_code_windows (pid , python_code , connect_debugger_tracing = False , show_debug_info = 0 ):
@@ -139,49 +267,41 @@ def run_python_code_windows(pid, python_code, connect_debugger_tracing=False, sh
139
267
140
268
process = Process (pid )
141
269
bits = process .get_bits ()
142
- is_64 = bits == 64
270
+ is_target_process_64 = bits == 64
143
271
144
272
# Note: this restriction no longer applies (we create a process with the proper bitness from
145
273
# this process so that the attach works).
146
- # if is_64 != is_python_64bit():
274
+ # if is_target_process_64 != is_python_64bit():
147
275
# raise RuntimeError("The architecture of the Python used to connect doesn't match the architecture of the target.\n"
148
276
# "Target 64 bits: %s\n"
149
- # "Current Python 64 bits: %s" % (is_64 , is_python_64bit()))
277
+ # "Current Python 64 bits: %s" % (is_target_process_64 , is_python_64bit()))
150
278
151
279
with _acquire_mutex ('_pydevd_pid_attach_mutex_%s' % (pid ,), 10 ):
152
280
print ('--- Connecting to %s bits target (current process is: %s) ---' % (bits , 64 if is_python_64bit () else 32 ))
153
281
154
282
with _win_write_to_shared_named_memory (python_code , pid ):
155
283
156
- filedir = os .path .dirname (__file__ )
157
- if is_64 :
158
- suffix = 'amd64'
159
- else :
160
- suffix = 'x86'
161
-
162
- target_executable = os .path .join (filedir , 'inject_dll_%s.exe' % suffix )
163
- if not os .path .exists (target_executable ):
164
- raise RuntimeError ('Could not find exe file to inject: %s' % target_executable )
284
+ target_executable = get_target_filename (is_target_process_64 , 'inject_dll_' , '.exe' )
285
+ if not target_executable :
286
+ raise RuntimeError ('Could not find expected .exe file to inject dll in attach to process.' )
165
287
166
- name = 'attach_%s.dll' % suffix
167
- target_dll = os .path .join (filedir , name )
168
- if not os .path .exists (target_dll ):
169
- raise RuntimeError ('Could not find dll file to inject: %s' % target_dll )
288
+ target_dll = get_target_filename (is_target_process_64 )
289
+ if not target_dll :
290
+ raise RuntimeError ('Could not find expected .dll file in attach to process.' )
170
291
171
- print ('\n --- Injecting attach dll: %s into pid: %s ---' % (name , pid ))
292
+ print ('\n --- Injecting attach dll: %s into pid: %s ---' % (os . path . basename ( target_dll ) , pid ))
172
293
args = [target_executable , str (pid ), target_dll ]
173
294
subprocess .check_call (args )
174
295
175
296
# Now, if the first injection worked, go on to the second which will actually
176
297
# run the code.
177
- name = 'run_code_on_dllmain_%s.dll' % suffix
178
- target_dll = os .path .join (filedir , name )
179
- if not os .path .exists (target_dll ):
180
- raise RuntimeError ('Could not find dll file to inject: %s' % target_dll )
298
+ target_dll_run_on_dllmain = get_target_filename (is_target_process_64 , 'run_code_on_dllmain_' , '.dll' )
299
+ if not target_dll_run_on_dllmain :
300
+ raise RuntimeError ('Could not find expected .dll in attach to process.' )
181
301
182
302
with _create_win_event ('_pydevd_pid_event_%s' % (pid ,)) as event :
183
- print ('\n --- Injecting run code dll: %s into pid: %s ---' % (name , pid ))
184
- args = [target_executable , str (pid ), target_dll ]
303
+ print ('\n --- Injecting run code dll: %s into pid: %s ---' % (os . path . basename ( target_dll_run_on_dllmain ) , pid ))
304
+ args = [target_executable , str (pid ), target_dll_run_on_dllmain ]
185
305
subprocess .check_call (args )
186
306
187
307
if not event .wait_for_event_set (10 ):
@@ -269,25 +389,10 @@ def _win_write_to_shared_named_memory(python_code, pid):
269
389
270
390
def run_python_code_linux (pid , python_code , connect_debugger_tracing = False , show_debug_info = 0 ):
271
391
assert '\' ' not in python_code , 'Having a single quote messes with our command.'
272
- filedir = os .path .dirname (__file__ )
273
-
274
- # Valid arguments for arch are i386, i386:x86-64, i386:x64-32, i8086,
275
- # i386:intel, i386:x86-64:intel, i386:x64-32:intel, i386:nacl,
276
- # i386:x86-64:nacl, i386:x64-32:nacl, auto.
277
-
278
- if is_python_64bit ():
279
- suffix = 'amd64'
280
- arch = 'i386:x86-64'
281
- else :
282
- suffix = 'x86'
283
- arch = 'i386'
284
392
285
- print ('Attaching with arch: %s' % (arch ,))
286
-
287
- target_dll = os .path .join (filedir , 'attach_linux_%s.so' % suffix )
288
- target_dll = os .path .abspath (os .path .normpath (target_dll ))
289
- if not os .path .exists (target_dll ):
290
- raise RuntimeError ('Could not find dll file to inject: %s' % target_dll )
393
+ target_dll = get_target_filename ()
394
+ if not target_dll :
395
+ raise RuntimeError ('Could not find .so for attach to process.' )
291
396
292
397
# Note: we currently don't support debug builds
293
398
is_debug = 0
@@ -306,7 +411,9 @@ def run_python_code_linux(pid, python_code, connect_debugger_tracing=False, show
306
411
307
412
cmd .extend (["--eval-command='set scheduler-locking off'" ]) # If on we'll deadlock.
308
413
309
- cmd .extend (["--eval-command='set architecture %s'" % arch ])
414
+ # Leave auto by default (it should do the right thing as we're attaching to a process in the
415
+ # current host).
416
+ cmd .extend (["--eval-command='set architecture auto'" ])
310
417
311
418
cmd .extend ([
312
419
"--eval-command='call (void*)dlopen(\" %s\" , 2)'" % target_dll ,
@@ -347,27 +454,13 @@ def find_helper_script(filedir, script_name):
347
454
348
455
def run_python_code_mac (pid , python_code , connect_debugger_tracing = False , show_debug_info = 0 ):
349
456
assert '\' ' not in python_code , 'Having a single quote messes with our command.'
350
- filedir = os .path .dirname (__file__ )
351
-
352
- # Valid arguments for arch are i386, i386:x86-64, i386:x64-32, i8086,
353
- # i386:intel, i386:x86-64:intel, i386:x64-32:intel, i386:nacl,
354
- # i386:x86-64:nacl, i386:x64-32:nacl, auto.
355
-
356
- if is_python_64bit ():
357
- suffix = 'x86_64.dylib'
358
- arch = 'i386:x86-64'
359
- else :
360
- suffix = 'x86.dylib'
361
- arch = 'i386'
362
457
363
- print ('Attaching with arch: %s' % (arch ,))
458
+ target_dll = get_target_filename ()
459
+ if not target_dll :
460
+ raise RuntimeError ('Could not find .dylib for attach to process.' )
364
461
365
- target_dll = os .path .join (filedir , 'attach_%s' % suffix )
366
- target_dll = os .path .normpath (target_dll )
367
- if not os .path .exists (target_dll ):
368
- raise RuntimeError ('Could not find dll file to inject: %s' % target_dll )
369
-
370
- lldb_prepare_file = find_helper_script (filedir , 'lldb_prepare.py' )
462
+ libdir = os .path .dirname (__file__ )
463
+ lldb_prepare_file = find_helper_script (libdir , 'lldb_prepare.py' )
371
464
# Note: we currently don't support debug builds
372
465
373
466
is_debug = 0
@@ -418,12 +511,16 @@ def run_python_code_mac(pid, python_code, connect_debugger_tracing=False, show_d
418
511
return out , err
419
512
420
513
421
- if sys . platform == 'win32' :
514
+ if IS_WINDOWS :
422
515
run_python_code = run_python_code_windows
423
- elif is_mac () :
516
+ elif IS_MAC :
424
517
run_python_code = run_python_code_mac
425
- else :
518
+ elif IS_LINUX :
426
519
run_python_code = run_python_code_linux
520
+ else :
521
+
522
+ def run_python_code (* args , ** kwargs ):
523
+ print ('Unable to attach to process in platform: %s' , sys .platform )
427
524
428
525
429
526
def test ():
0 commit comments