Skip to content

Commit

Permalink
Provide feedback/information in the debug console if attach to PID is…
Browse files Browse the repository at this point in the history
… slow. Fixes microsoft#1003
  • Loading branch information
fabioz committed Oct 14, 2022
1 parent 46efd10 commit 1891500
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ def run_python_code_windows(pid, python_code, connect_debugger_tracing=False, sh

with _acquire_mutex('_pydevd_pid_attach_mutex_%s' % (pid,), 10):
print('--- Connecting to %s bits target (current process is: %s) ---' % (bits, 64 if is_python_64bit() else 32))
sys.stdout.flush()

with _win_write_to_shared_named_memory(python_code, pid):

Expand All @@ -290,6 +291,7 @@ def run_python_code_windows(pid, python_code, connect_debugger_tracing=False, sh
raise RuntimeError('Could not find expected .dll file in attach to process.')

print('\n--- Injecting attach dll: %s into pid: %s ---' % (os.path.basename(target_dll), pid))
sys.stdout.flush()
args = [target_executable, str(pid), target_dll]
subprocess.check_call(args)

Expand All @@ -301,12 +303,15 @@ def run_python_code_windows(pid, python_code, connect_debugger_tracing=False, sh

with _create_win_event('_pydevd_pid_event_%s' % (pid,)) as event:
print('\n--- Injecting run code dll: %s into pid: %s ---' % (os.path.basename(target_dll_run_on_dllmain), pid))
sys.stdout.flush()
args = [target_executable, str(pid), target_dll_run_on_dllmain]
subprocess.check_call(args)

if not event.wait_for_event_set(10):
if not event.wait_for_event_set(15):
print('Timeout error: the attach may not have completed.')
sys.stdout.flush()
print('--- Finished dll injection ---\n')
sys.stdout.flush()

return 0

Expand Down
25 changes: 22 additions & 3 deletions src/debugpy/adapter/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,6 @@ def initialize_request(self, request):
# See https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522
# for the sequence of request and events necessary to orchestrate the start.
def _start_message_handler(f):

@components.Component.message_handler
def handle(self, request):
assert request.is_request("launch", "attach")
Expand Down Expand Up @@ -465,7 +464,9 @@ def attach_request(self, request):

if listen != ():
if servers.is_serving():
raise request.isnt_valid('Multiple concurrent "listen" sessions are not supported')
raise request.isnt_valid(
'Multiple concurrent "listen" sessions are not supported'
)
host = listen("host", "127.0.0.1")
port = listen("port", int)
adapter.access_token = None
Expand Down Expand Up @@ -507,7 +508,25 @@ def attach_request(self, request):
except Exception:
raise request.isnt_valid('"processId" must be parseable as int')
debugpy_args = request("debugpyArgs", json.array(str))
servers.inject(pid, debugpy_args)

def on_output(category, output):
self.channel.send_event(
"output",
{
"category": category,
"output": output,
},
)

try:
servers.inject(pid, debugpy_args, on_output)
except Exception as e:
log.swallow_exception()
self.session.finalize(
"Error when trying to attach to PID:\n%s" % (str(e),)
)
return

timeout = common.PROCESS_SPAWN_TIMEOUT
pred = lambda conn: conn.pid == pid
else:
Expand Down
110 changes: 96 additions & 14 deletions src/debugpy/adapter/servers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
from debugpy import adapter
from debugpy.common import json, log, messaging, sockets
from debugpy.adapter import components

import traceback
import io

access_token = None
"""Access token used to authenticate with the servers."""
Expand Down Expand Up @@ -471,7 +472,7 @@ def dont_wait_for_first_connection():
_connections_changed.set()


def inject(pid, debugpy_args):
def inject(pid, debugpy_args, on_output):
host, port = listener.getsockname()

cmdline = [
Expand Down Expand Up @@ -507,17 +508,98 @@ def inject(pid, debugpy_args):
# We need to capture the output of the injector - otherwise it can get blocked
# on a write() syscall when it tries to print something.

def capture_output():
output_collected = []
output_collected.append("--- Starting attach to pid: {0} ---\n".format(pid))

def capture(stream):
try:
while True:
line = stream.readline()
if not line:
break
line = line.decode("utf-8", "replace")
output_collected.append(line)
log.info("Injector[PID={0}] output: {1}", pid, line.rstrip())
log.info("Injector[PID={0}] exited.", pid)
except Exception:
s = io.StringIO()
traceback.print_exc(file=s)
on_output("stderr", s.getvalue())
log.swallow_exception()

threading.Thread(
target=capture,
name=f"Injector[PID={pid}] stdout",
args=(injector.stdout,),
daemon=True,
).start()

def info_on_timeout():
shown_taking_longer = False
initial_time = time.time()
while True:
line = injector.stdout.readline()
if not line:
time.sleep(1)
returncode = injector.poll()
if returncode is not None:
if returncode != 0:
# Something doesn't seem right. Let's print more info to the user
on_output(
"stderr",
"It seems that the attach to PID did not succeed.\nDetails:\n",
)
on_output("stderr", "".join(output_collected))
break
log.info("Injector[PID={0}] output:\n{1}", pid, line.rstrip())
log.info("Injector[PID={0}] exited.", pid)

thread = threading.Thread(
target=capture_output,
name=f"Injector[PID={pid}] output",
)
thread.daemon = True
thread.start()

elapsed = time.time() - initial_time
on_output(
"stdout", "Attaching to PID: %s (elapsed: %.2fs).\n" % (pid, elapsed)
)

if not shown_taking_longer:
if elapsed > 10:
shown_taking_longer = True
if sys.platform in ("linux", "linux2"):
on_output(
"stdout",
"\nThe attach to PID is taking longer than expected.\n",
)
on_output(
"stdout",
"On Linux it's possible to customize the value of\n",
)
on_output(
"stdout",
"`PYDEVD_GDB_SCAN_SHARED_LIBRARIES` so that fewer libraries.\n",
)
on_output(
"stdout",
"are scanned when searching for the needed symbols.\n\n",
)
on_output(
"stdout",
"i.e.: set in your environment variables (and restart your editor/client\n",
)
on_output(
"stdout",
"so that it picks up the updated environment variable value):\n\n",
)
on_output(
"stdout",
"PYDEVD_GDB_SCAN_SHARED_LIBRARIES=libdl, libltdl, libc, libfreebl3\n\n",
)
on_output(
"stdout",
"-- the actual library may be different (the gdb output typically\n",
)
on_output(
"stdout",
"-- writes the libraries that will be used, so, it should be possible\n",
)
on_output(
"stdout",
"-- to test other libraries if the above doesn't work).\n\n",
)

threading.Thread(
target=info_on_timeout, name=f"Injector[PID={pid}] info on timeout", daemon=True
).start()

0 comments on commit 1891500

Please sign in to comment.