Comprehensive Python debugger backend for IDEs with remote debugging, breakpoints, variable inspection, and performance optimizations
—
Remote process attachment and code injection capabilities for debugging already-running Python processes. This functionality enables debugging scenarios where the target process wasn't started with debugging enabled, allowing developers to attach debuggers to production applications and long-running services.
Core functions for attaching the debugger to running Python processes using platform-specific injection techniques.
def run_python_code(pid, python_code, connect_debugger_tracing=False, show_debug_info=0):
"""
Inject and execute Python code in a running Python process.
This is a platform-specific function that routes to the appropriate implementation:
- Windows: run_python_code_windows
- Linux: run_python_code_linux
- macOS: run_python_code_mac
Parameters:
- pid (int): Process ID of the target Python process
- python_code (str): Python code to inject and execute (cannot contain single quotes)
- connect_debugger_tracing (bool): Whether to enable debugger tracing after injection
- show_debug_info (int): Debug information level (0=none, 1=basic, 2=detailed)
Returns:
None: Function executes injection but doesn't return a value
Raises:
- AssertionError: If python_code contains single quotes
- RuntimeError: If required libraries/executables are not found or injection fails
- Various platform-specific exceptions during injection process
"""
def run_python_code_windows(pid, python_code, connect_debugger_tracing=False, show_debug_info=0):
"""Windows-specific implementation of code injection using winappdbg and DLL injection."""
def run_python_code_linux(pid, python_code, connect_debugger_tracing=False, show_debug_info=0):
"""Linux-specific implementation of code injection using gdb and shared libraries."""
def run_python_code_mac(pid, python_code, connect_debugger_tracing=False, show_debug_info=0):
"""macOS-specific implementation of code injection using lldb and dynamic libraries."""High-level utilities for setting up debugger attachment to running processes.
def attach(port, host, protocol="", debug_mode=""):
"""
Attach debugger to the current process and connect to debugger server.
This function is typically called from code injected into a running process
to establish debugger connection.
Parameters:
- port (int): Port number of the debugger server
- host (str): Host address of the debugger server
- protocol (str): Communication protocol ("", "http", "json")
- debug_mode (str): Debug mode configuration
Returns:
None
"""
def get_main_thread_id(unlikely_thread_id=None):
"""
Identify the main thread in a multi-threaded process.
Parameters:
- unlikely_thread_id: Thread ID to exclude from consideration
Returns:
tuple: (thread_id, critical_warning) - thread ID and any warning message
"""Utilities for detecting platform capabilities and requirements for process attachment.
def is_windows():
"""
Check if running on Windows platform.
Returns:
bool: True if Windows, False otherwise
"""
def is_linux():
"""
Check if running on Linux platform.
Returns:
bool: True if Linux, False otherwise
"""
def is_mac():
"""
Check if running on macOS platform.
Returns:
bool: True if macOS, False otherwise
"""
def get_platform_attachment_library():
"""
Get the appropriate native library for process attachment.
Returns:
str: Path to platform-specific attachment library (.dll, .so, or .dylib)
Raises:
- RuntimeError: If no compatible library is available for current platform
"""import os
import subprocess
from pydevd_attach_to_process.add_code_to_python_process import run_python_code
# Start a target Python process
target_script = """
import time
counter = 0
while True:
counter += 1
print(f"Counter: {counter}")
time.sleep(1)
"""
# Write target script to file
with open('target_app.py', 'w') as f:
f.write(target_script)
# Start target process
process = subprocess.Popen(['python', 'target_app.py'])
target_pid = process.pid
print(f"Target process started with PID: {target_pid}")
# Inject debugger setup code into the running process
debugger_setup_code = """
import pydevd
print("Setting up debugger...")
pydevd.settrace(
host='localhost',
port=5678,
suspend=False,
trace_only_current_thread=False
)
print("Debugger attached successfully!")
"""
try:
result = run_python_code(
pid=target_pid,
python_code=debugger_setup_code,
connect_debugger_tracing=True,
show_debug_info=1
)
print(f"Code injection result: {result}")
print("You can now connect your IDE to port 5678")
except Exception as e:
print(f"Attachment error: {e}")
finally:
# Clean up
process.terminate()
os.remove('target_app.py')from pydevd_attach_to_process.add_code_to_python_process import add_code_to_python_process
# Code to inject for debugger setup
debugger_setup_code = """
import pydevd
print("Setting up debugger...")
pydevd.settrace(
host='localhost',
port=5678,
suspend=False,
trace_only_current_thread=False
)
print("Debugger attached successfully!")
"""
# Inject debugger setup code into running process
result = add_code_to_python_process(
pid=target_pid,
python_code=debugger_setup_code,
connect_debugger_tracing=True,
show_debug_info=1
)
print(f"Code injection result: {result}")from pydevd_attach_to_process import attach_pydevd
import psutil
def attach_with_compatibility_check(pid, debugger_port):
"""
Attach to process with comprehensive compatibility checking.
"""
try:
# Get process information
process_info = attach_pydevd.get_process_info(pid)
print(f"Process Information:")
print(f" PID: {pid}")
print(f" Python Version: {process_info['python_version']}")
print(f" Architecture: {process_info['architecture']}")
print(f" Executable: {process_info['executable']}")
print(f" Compatible: {process_info['compatible']}")
if not process_info['compatible']:
print(f" Incompatibility Reason: {process_info['reason']}")
return False
# Check if process is still running
if not psutil.pid_exists(pid):
print(f"Process {pid} no longer exists")
return False
# Get additional process details
proc = psutil.Process(pid)
print(f" Process Name: {proc.name()}")
print(f" Memory Usage: {proc.memory_info().rss / 1024 / 1024:.1f} MB")
print(f" CPU Percent: {proc.cpu_percent():.1f}%")
# Attempt attachment
print(f"\nAttempting to attach debugger to PID {pid}...")
success = attach_pydevd.attach_to_process(
pid=pid,
debugger_port=debugger_port,
timeout=15.0
)
if success:
print(f"✓ Successfully attached debugger")
print(f" Connect your IDE to localhost:{debugger_port}")
return True
else:
print("✗ Failed to attach debugger")
return False
except Exception as e:
print(f"Error during attachment: {e}")
return False
# Usage
target_pid = 12345 # Replace with actual PID
attach_with_compatibility_check(target_pid, 5678)from pydevd_attach_to_process import attach_pydevd
from pydevd_attach_to_process.add_code_to_python_process import (
run_python_code_windows,
run_python_code_linux,
run_python_code_mac,
is_windows,
is_linux,
is_mac
)
def platform_specific_attach(pid, debugger_code):
"""
Perform platform-specific code injection and attachment.
"""
show_debug_info = 2 # Detailed debug information
connect_tracing = True
try:
if is_windows():
print("Using Windows attachment method...")
result = run_python_code_windows(
pid, debugger_code, connect_tracing, show_debug_info
)
elif is_linux():
print("Using Linux attachment method...")
result = run_python_code_linux(
pid, debugger_code, connect_tracing, show_debug_info
)
elif is_mac():
print("Using macOS attachment method...")
result = run_python_code_mac(
pid, debugger_code, connect_tracing, show_debug_info
)
else:
raise RuntimeError("Unsupported platform for process attachment")
print(f"Platform-specific attachment result: {result}")
return True
except Exception as e:
print(f"Platform-specific attachment failed: {e}")
return False
# Debugger setup code
setup_code = """
import sys
import pydevd
# Verify Python version compatibility
if sys.version_info >= (3, 8):
print("Python version compatible, setting up debugger...")
pydevd.settrace(
host='0.0.0.0',
port=5678,
suspend=False,
stdout_to_server=True,
stderr_to_server=True
)
print("Remote debugger attached and ready!")
else:
print(f"Python version {sys.version_info} not supported")
"""
# Perform platform-specific attachment
platform_specific_attach(target_pid, setup_code)import time
import signal
from pydevd_attach_to_process import attach_pydevd
class ProductionDebugger:
def __init__(self, target_pid, debugger_port=5678):
self.target_pid = target_pid
self.debugger_port = debugger_port
self.attached = False
def safe_attach(self):
"""
Safely attach to production process with error handling.
"""
try:
# First, verify process exists and is compatible
process_info = attach_pydevd.get_process_info(self.target_pid)
if not process_info['compatible']:
raise RuntimeError(f"Process incompatible: {process_info['reason']}")
# Prepare minimal debugger code
minimal_debugger_code = """
import pydevd
import threading
def setup_debugger():
try:
pydevd.settrace(
host='localhost',
port={port},
suspend=False,
trace_only_current_thread=False,
patch_multiprocessing=False # Don't interfere with child processes
)
print(f"Debugger attached on port {port}")
except Exception as e:
print(f"Debugger setup failed: {{e}}")
# Run in separate thread to avoid blocking main application
debug_thread = threading.Thread(target=setup_debugger, daemon=True)
debug_thread.start()
""".format(port=self.debugger_port)
# Inject debugger code
success = attach_pydevd.attach_to_process(
pid=self.target_pid,
debugger_port=self.debugger_port,
timeout=5.0 # Short timeout for production
)
if success:
self.attached = True
print(f"✓ Production debugger attached to PID {self.target_pid}")
print(f" IDE connection: localhost:{self.debugger_port}")
return True
else:
print("✗ Failed to attach to production process")
return False
except Exception as e:
print(f"Production debugging setup failed: {e}")
return False
def detach(self):
"""
Detach debugger from production process.
"""
if self.attached:
detach_code = """
import pydevd
try:
pydevd.stoptrace()
print("Debugger detached successfully")
except:
pass
"""
try:
attach_pydevd.inject_debugger_code(self.target_pid, detach_code)
self.attached = False
print(f"Debugger detached from PID {self.target_pid}")
except Exception as e:
print(f"Failed to detach debugger: {e}")
# Usage for production debugging
production_pid = 98765 # Replace with production process PID
debugger = ProductionDebugger(production_pid, debugger_port=5679)
# Set up signal handler for clean detachment
def signal_handler(signum, frame):
print("\nDetaching debugger...")
debugger.detach()
exit(0)
signal.signal(signal.SIGINT, signal_handler)
# Attach to production process
if debugger.safe_attach():
print("Production debugging active. Press Ctrl+C to detach.")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
pass
finally:
debugger.detach()attach_windows.dll (32-bit) or attach_windows_amd64.dll (64-bit)attach_linux.so (32-bit) or attach_linux_amd64.so (64-bit)ptrace capability (may need CAP_SYS_PTRACE)attach_mac.dylib (32-bit) or attach_mac_amd64.dylib (64-bit)Install with Tessl CLI
npx tessl i tessl/pypi-pydevd