Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ core/adapters/LLDB.framework
/test/binaries/Darwin-arm64-signed/
/test/binaries/Darwin-x86_64-signed/
test/__pycache__
api/python/__pycache__
*/__pycache__/

/build
/artifacts
Expand Down
93 changes: 87 additions & 6 deletions api/python/debuggercontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def __setattr__(self, name, value):
raise AttributeError(f"attribute '{name}' is read only")

def __repr__(self):
return f"<DebugProcess: {self.pid:#x}, {self.name}>"
return f"<DebugProcess: pid={self.pid}, name='{self.name}'>"


class DebugThread:
Expand Down Expand Up @@ -131,7 +131,7 @@ def __ne__(self, other):
return not (self == other)

def __hash__(self):
return hash((self.name, self.short_name, self.address. self.size, self.loaded))
return hash((self.name, self.short_name, self.address, self.size, self.loaded))

def __setattr__(self, name, value):
try:
Expand All @@ -140,7 +140,7 @@ def __setattr__(self, name, value):
raise AttributeError(f"attribute '{name}' is read only")

def __repr__(self):
return f"<DebugModule: {self.name}, {self.address:#x}, {self.size:#x}>"
return f"<DebugModule: {self.short_name}, {self.address:#x}-{self.address+self.size:#x}, size={self.size:#x}>"


class DebugRegister:
Expand Down Expand Up @@ -174,7 +174,7 @@ def __ne__(self, other):
return not (self == other)

def __hash__(self):
return hash((self.name, self.value, self.width. self.index, self.hint))
return hash((self.name, self.value, self.width, self.index, self.hint))

def __setattr__(self, name, value):
try:
Expand Down Expand Up @@ -203,7 +203,21 @@ def __init__(self, handle):
dbgcore.BNDebuggerFreeRegisters(registers, count.value)

def __repr__(self) -> str:
return self.regs.__repr__()
if not self.regs:
return "<DebugRegisters: empty>"

# Show registers in a more readable format
reg_entries = []
# Sort registers by name for consistent output
for name in sorted(self.regs.keys()):
reg = self.regs[name]
hint_str = f" ({reg.hint})" if reg.hint else ""
reg_entries.append(f"{name}={reg.value:#x}{hint_str}")

# Show all registers
reg_list = ", ".join(reg_entries)

return f"<DebugRegisters: {reg_list}>"

def __getitem__(self, name):
if name not in self.regs:
Expand Down Expand Up @@ -248,7 +262,7 @@ def __ne__(self, other):
return not (self == other)

def __hash__(self):
return hash((self.module, self.offset, self.address. self.enabled))
return hash((self.module, self.offset, self.address, self.enabled))

def __setattr__(self, name, value):
try:
Expand Down Expand Up @@ -305,6 +319,9 @@ def __setattr__(self, name, value):
except AttributeError:
raise AttributeError(f"attribute '{name}' is read only")

def __repr__(self):
return f"<ModuleNameAndOffset: {self.module}+{self.offset:#x}>"


class DebugFrame:
"""
Expand Down Expand Up @@ -373,6 +390,9 @@ def __init__(self, reason: DebugStopReason, last_active_thread: int, exit_code:
self.exit_code = exit_code
self.data = data

def __repr__(self):
return f"<TargetStoppedEventData: reason={self.reason}, thread={self.last_active_thread}, exit_code={self.exit_code}>"


class ErrorEventData:
"""
Expand All @@ -386,6 +406,9 @@ def __init__(self, error: str, data):
self.error = error
self.data = data

def __repr__(self):
return f"<ErrorEventData: {self.error}>"


class TargetExitedEventData:
"""
Expand All @@ -397,6 +420,9 @@ class TargetExitedEventData:
def __init__(self, exit_code: int):
self.exit_code = exit_code

def __repr__(self):
return f"<TargetExitedEventData: exit_code={self.exit_code}>"


class StdOutMessageEventData:
"""
Expand All @@ -408,6 +434,11 @@ class StdOutMessageEventData:
def __init__(self, message: str):
self.message = message

def __repr__(self):
# Truncate long messages for readability
msg = self.message[:50] + "..." if len(self.message) > 50 else self.message
return f"<StdOutMessageEventData: '{msg}'>"


class DebuggerEventData:
"""
Expand All @@ -434,6 +465,25 @@ def __init__(self, target_stopped_data: TargetStoppedEventData,
self.exit_data = exit_data
self.message_data = message_data

def __repr__(self):
# Show only the data that's not None for better readability
parts = []
if self.target_stopped_data is not None:
parts.append(f"stopped={self.target_stopped_data.reason}")
if self.error_data is not None:
parts.append(f"error='{self.error_data.error[:30]}..'" if len(self.error_data.error) > 30 else f"error='{self.error_data.error}'")
if self.absolute_address:
parts.append(f"abs_addr={self.absolute_address:#x}")
if self.relative_address:
parts.append(f"rel_addr={self.relative_address}")
if self.exit_data is not None:
parts.append(f"exit_code={self.exit_data.exit_code}")
if self.message_data is not None:
msg = self.message_data.message[:20] + "..." if len(self.message_data.message) > 20 else self.message_data.message
parts.append(f"msg='{msg}'")

return f"<DebuggerEventData: {', '.join(parts) if parts else 'empty'}>"


class DebuggerEvent:
"""
Expand All @@ -447,6 +497,9 @@ def __init__(self, type: DebuggerEventType, data: DebuggerEventData):
self.type = type
self.data = data

def __repr__(self):
return f"<DebuggerEvent: type={self.type}, data={self.data}>"


DebuggerEventCallback = Callable[['DebuggerEvent'], None]

Expand Down Expand Up @@ -1602,3 +1655,31 @@ def __ne__(self, other):

def __hash__(self):
return hash(ctypes.addressof(self.handle.contents))

def __repr__(self):
try:
# Basic connection and status info
connected = "connected" if self.connected else "disconnected"
running = "running" if self.running else "stopped"

# Debug adapter name
adapter_name = self.adapter_type

# Determine if local or remote debugging
remote_host = self.remote_host
remote_port = self.remote_port

if remote_host and remote_port > 0:
# Remote debugging
debugging_type = f"remote {remote_host}:{remote_port}"
else:
# Local debugging
debugging_type = "local"

# Executable path
exec_path = self.executable_path or "unknown"

return f"<DebuggerController: {connected}, {running}, {debugging_type}, adapter={adapter_name}, {exec_path}>"
except:
# Fallback to basic representation if we can't get state info
return f"<DebuggerController: {hex(id(self))}>"