Skip to content

Commit dbe1561

Browse files
committed
Implement GDB interaction
1 parent 633565a commit dbe1561

2 files changed

Lines changed: 79 additions & 15 deletions

File tree

ptrlib/debugger/unix/debug.py

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import getpass
44
import re
55
from logging import getLogger
6-
from typing import TYPE_CHECKING, List, Union
6+
from typing import TYPE_CHECKING, List, Union, overload
77
if TYPE_CHECKING:
88
from ptrlib.connection.unixproc import UnixProcess
99

@@ -45,17 +45,31 @@ def __init__(self, pid: int):
4545
@property
4646
@attached
4747
def gdb(self) -> UnixProcess:
48+
"""Debugger session
49+
"""
4850
return self._gdb
51+
52+
@property
53+
def debug(self) -> bool:
54+
"""Debug mode
55+
"""
56+
return self._gdb.debug
4957

58+
@debug.setter
59+
def debug(self, mode: bool):
60+
self._gdb.debug = mode
61+
5062
@property
5163
def pid(self) -> int:
64+
"""PID of the target process
65+
"""
5266
return self._pid
5367

5468
def _attach_direct(self):
5569
self._gdb = unix_process()(["gdb", "-q", "-p", str(self._pid)])
5670
init_msg = self._gdb.recvuntil(self._gdb_prompt, lookahead=True)
5771
if GDB_PTRACE_ERROR in init_msg:
58-
raise PermissionError
72+
raise PermissionError(f"Cannot attach pid={self._pid}")
5973

6074
def _attach_with_sudo(self):
6175
self._gdb = unix_process()([
@@ -70,17 +84,24 @@ def _attach_with_sudo(self):
7084
init_msg = self._gdb.recvuntil(self._gdb_prompt, lookahead=True)
7185

7286
if GDB_PTRACE_ERROR in init_msg:
73-
raise PermissionError
87+
raise PermissionError(f"Cannot attach pid={self._pid}")
7488

7589
@detached
7690
def attach(self) -> 'UnixProcessDebugger':
7791
"""Attach to process with GDB.
92+
93+
Return:
94+
UnixProcessDebugger: This debugger instance.
7895
"""
96+
# TODO: Check if target process has already been attached
97+
# TODO: Check if we have root privilege
7998
try:
8099
self._attach_direct()
100+
return self
81101
except PermissionError:
82-
self._attach_with_sudo()
102+
pass
83103

104+
self._attach_with_sudo()
84105
return self
85106

86107
@attached
@@ -90,19 +111,55 @@ def detach(self):
90111
self._gdb.close()
91112
del self._gdb
92113

114+
@overload
115+
def execute(self, command: str, resume: bool=False) -> str: ...
116+
@overload
117+
def execute(self, command: List[str], resume: bool=False) -> List[str]: ...
93118
@attached
94-
def execute(self, command: str) -> str:
119+
def execute(self,
120+
command: Union[str, List[str]],
121+
resume: bool=False) -> Union[str, List[str]]:
122+
"""Execute a GDB command.
123+
124+
Args:
125+
command (str): A command to execute, or a list of commands.
126+
resume (bool): If true, continue execution after all commands are done.
127+
128+
Returns:
129+
str: Result of the command.
130+
131+
Examples:
132+
```
133+
conn = sock.process.attach()
134+
stdout = int(conn.execute("p/x &_IO_2_1_stdout_").split(' = ')[1], 16)
135+
conn.execute("break puts", resume=True)
136+
sock.sendline(b"Hello")
137+
res = conn.execute([
138+
f"set {{long}}{stdout} = 0xfbad1887",
139+
f"x/4xg {stdout}"
140+
])
141+
print(res[1])
142+
```
143+
"""
144+
if isinstance(command, list):
145+
result = [self.execute(c) for c in command]
146+
if resume:
147+
self.execute('continue')
148+
return result
149+
95150
self._gdb.after(self._gdb_prompt).sendline(command)
96-
# TODO: self._gdb.before(self._gdb_prompt).lastline()
151+
# TODO: self._gdb.before(self._gdb_prompt).lastline() to keep color sequence
97152
result = self._gdb.recvuntil(self._gdb_prompt, drop=True, lookahead=True)
98153
# Remove ANSI escape sequences aggressively
99-
return CTRL_RE.sub(b'', ANSI_RE.sub(b'', result)).decode().strip()
154+
result = CTRL_RE.sub(b'', ANSI_RE.sub(b'', result)).decode().strip()
155+
if resume:
156+
self._gdb.after(self._gdb_prompt).sendline("continue")
157+
return result
100158

101-
def batch_execute(self, commands: List[str]) -> List[str]:
102-
return [self.execute(command) for command in commands]
103-
104159
@attached
105160
def interactive(self):
161+
"""Interact with GDB terminal
162+
"""
106163
self._gdb.interactive(prompt='')
107164
# Return a prompt for further use
108165
self._gdb.unget(b'(gdb) ')

ptrlib/debugger/unix/process.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,25 @@ def vmmap(self) -> List[UnixMemoryRegion]:
152152
return maps
153153

154154
def attach(self, pid: Optional[Union[int, Callable[['UnixProcessManager'], int]]]=None) -> UnixProcessDebugger:
155-
"""
155+
"""Attach to a process with given pid.
156+
157+
Args:
158+
pid (int): Process ID or a function or lambda that returns a pid.
159+
156160
Examples:
157161
```
158162
sock = Process("/bin/cat")
159163
conn = sock.process.attach()
160-
conn.execute(["break write", "continue"])
164+
#conn.debug = True
161165
162-
sock.sendline(b"Hello!")
163-
a = conn.execute("p/x $rsi")
164-
conn.detach()
166+
conn.execute("break write", resume=True)
167+
sock.sendline(b"Hello, World!")
165168
169+
a, b = conn.execute(["p/x $rsi", "x/1s $rsi"])
170+
print(a) # $1 = 0x7d0f5d1de000
171+
print(b) # 0x7d0f5d1de000: "Hello, World!\\n"
166172
173+
conn.detach()
167174
```
168175
"""
169176
if pid is None:

0 commit comments

Comments
 (0)