Skip to content

Commit 633565a

Browse files
committed
Add debugger interface
1 parent 35cf5ee commit 633565a

6 files changed

Lines changed: 579 additions & 42 deletions

File tree

ptrlib/connection/tube.py

Lines changed: 72 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
bytes2str, str2bytes, bytes2hex, bytes2utf8, \
1313
hexdump, AnsiParser, AnsiInstruction
1414
from ptrlib.console.color import Color
15+
from ptrlib.types import PtrlibIntLikeT
1516

1617
_is_windows = os.name == 'nt'
1718

@@ -51,6 +52,8 @@ def decorator(self, *args, **kwargs):
5152

5253

5354
TubeType = TypeVar('TubeType', bound='Tube')
55+
AtomicSendT = Union[PtrlibIntLikeT, float, str, bytes]
56+
AtomicRecvT = Union[str, bytes]
5457

5558
class Tube(metaclass=abc.ABCMeta):
5659
"""Abstract class for streaming data
@@ -169,7 +172,7 @@ def newline(self, line: bytes):
169172
#
170173
# Methods
171174
#
172-
def settimeout(self, timeout: Optional[Union[int, float]]=None):
175+
def settimeout(self, timeout: Optional[Union[float, PtrlibIntLikeT]]=None):
173176
"""Set timeout
174177
175178
Args:
@@ -187,17 +190,16 @@ def settimeout(self, timeout: Optional[Union[int, float]]=None):
187190
p.settimeout() # Timeout is set to 3
188191
```
189192
"""
190-
assert timeout is None or (isinstance(timeout, (int, float)) and timeout >= 0), \
191-
"`timeout` must be positive and either int or float"
192-
193193
if timeout is None:
194194
self._settimeout_impl(self._default_timeout)
195+
elif not isinstance(timeout, float):
196+
self._settimeout_impl(int(timeout))
195197
else:
196198
self._settimeout_impl(timeout)
197199

198200
def recv(self,
199-
size: int=4096,
200-
timeout: Optional[Union[int, float]]=None) -> bytes:
201+
size: PtrlibIntLikeT=4096,
202+
timeout: Optional[Union[PtrlibIntLikeT, float]]=None) -> bytes:
201203
"""Receive data with buffering
202204
203205
Receive raw data of at most `size` bytes.
@@ -224,8 +226,9 @@ def recv(self,
224226
pass
225227
```
226228
"""
227-
assert size is None or (isinstance(size, int) and size >= 0), \
228-
"`size` must be a positive integer"
229+
if size is not None:
230+
size = int(size)
231+
assert size >= 0, "`size` must be a positive integer"
229232

230233
# NOTE: We always return buffer if it's not empty
231234
# This is because we do not know how many bytes we can read.
@@ -300,7 +303,7 @@ def recvonce(self,
300303
return data[:size]
301304

302305
def recvuntil(self,
303-
delim: Union[str, bytes, List[Union[str, bytes]]],
306+
delim: Union[AtomicRecvT, List[AtomicRecvT]],
304307
size: int=4096,
305308
timeout: Optional[Union[int, float]]=None,
306309
drop: bool=False,
@@ -336,14 +339,14 @@ def recvuntil(self,
336339
echo.recvonce(6) # 123def
337340
```
338341
"""
339-
assert isinstance(delim, (str, bytes, list)), \
342+
assert isinstance(delim, (str, bytes, bytearray, list)), \
340343
"`delim` must be either str, bytes, or list"
341344

342345
# Preprocess
343346
delim_list: List[bytes] = []
344347
if isinstance(delim, list):
345348
for i, d in enumerate(delim):
346-
assert isinstance(d, (str, bytes)), \
349+
assert isinstance(d, (str, bytes, bytearray)), \
347350
f"`delim[{i}]` must be either str or bytes"
348351
delim_list.append(str2bytes(delim[i]))
349352
else:
@@ -410,7 +413,7 @@ def recvline(self,
410413
return line.rstrip() if drop else line
411414

412415
def recvlineafter(self,
413-
delim: Union[str, bytes],
416+
delim: Union[AtomicRecvT, List[AtomicRecvT]],
414417
size: int=4096,
415418
timeout: Optional[Union[int, float]]=None,
416419
drop: bool=True,
@@ -519,21 +522,50 @@ def _ansi_stream(self):
519522
self.unget(ansi.buffer)
520523
return scr
521524

522-
def before(self: TubeType, data: Union[str, bytes]) -> TubeType:
523-
raise NotImplementedError("Method not implemented yet")
525+
def before(self: TubeType,
526+
delim: Union[AtomicRecvT, List[AtomicRecvT]],
527+
size: int = 4096,
528+
timeout: Optional[Union[int, float]] = None) -> TubeType:
529+
"""Wait until data arrives.
530+
531+
This method works in the same as `recvuntil` except that this method returns `self`
532+
and received data is buffered.
533+
534+
Args:
535+
delim : The delimiter bytes
536+
size : The data size to receive at once
537+
timeout : Timeout in second
538+
539+
Returns:
540+
Tube: Self.
541+
542+
Raises:
543+
ConnectionAbortedError: Connection is aborted by process
544+
ConnectionResetError: Connection is closed by peer
545+
TimeoutError: Timeout exceeded
546+
OSError: System error
547+
548+
Examples:
549+
```
550+
msg = tube.after("Message: ").recvline()
551+
tube.after("[42] ").recvregex(r"ID: (\\d+)")
552+
```
553+
"""
554+
raise NotImplementedError("Not implemented yet")
524555

525556
def after(self: TubeType,
526-
delim: Union[str, bytes],
557+
delim: Union[AtomicRecvT, List[AtomicRecvT]],
527558
size: int = 4096,
528559
timeout: Optional[Union[int, float]] = None,
529560
lookahead: bool = False) -> TubeType:
530561
"""Wait until data arrives.
531562
563+
This method works in the same as `recvuntil` except that this method returns `self`.
564+
532565
Args:
533566
delim : The delimiter bytes
534567
size : The data size to receive at once
535568
timeout : Timeout in second
536-
drop : Discard delimiter or not
537569
lookahead: Unget delimiter to buffer or not
538570
539571
Returns:
@@ -575,7 +607,7 @@ def send(self, data: Union[str, bytes]) -> int:
575607
tube.send(b"\xde\xad\xbe\xef")
576608
```
577609
"""
578-
assert isinstance(data, (str, bytes)), "`data` must be either str or bytes"
610+
assert isinstance(data, (str, bytes, bytearray)), "`data` must be either str or bytes"
579611
data = str2bytes(data)
580612

581613
size = self._send_impl(data)
@@ -619,32 +651,34 @@ def sendonce(self, data: Union[str, bytes]):
619651
to_send -= sent
620652

621653
def sendline(self,
622-
data: Union[int, float, str, bytes, List[Union[int, float, str, bytes]]]):
654+
data: Union[AtomicSendT, List[AtomicSendT]]):
623655
"""Send a line
624656
625657
Send a line of data.
626658
627659
Args:
628660
data (bytes) : Data to send
629661
"""
630-
assert isinstance(data, (int, float, str, bytes, list)), \
631-
"`data` must be int, float, str, bytes, or list"
632-
633662
if isinstance(data, list):
634663
for d in data:
635664
self.sendline(d)
636665
return
666+
667+
if isinstance(data, (str, bytes, bytearray)):
668+
data = str2bytes(data)
637669

638-
if isinstance(data, (int, float)):
670+
elif isinstance(data, float):
639671
data = str(data).encode()
672+
640673
else:
641-
data = str2bytes(data)
674+
# Explicitly call "__int__"
675+
data = str(int(data)).encode()
642676

643677
self.send(data + self._newline)
644678

645679
def sendafter(self,
646-
delim: Union[str, bytes, List[Union[str, bytes]]],
647-
data: Union[int, float, str, bytes],
680+
delim: Union[AtomicRecvT, List[AtomicRecvT]],
681+
data: AtomicSendT,
648682
size: int=4096,
649683
timeout: Optional[Union[int, float]]=None,
650684
drop: bool=False,
@@ -670,20 +704,24 @@ def sendafter(self,
670704
tube.sendafter("command: ", 1) # b"1" is sent
671705
```
672706
"""
673-
assert isinstance(data, (int, float, str, bytes)), \
674-
"`data` must be int, float, str, or bytes"
675-
676707
recv_data = self.recvuntil(delim, size, timeout, drop, lookahead)
677708

678-
if isinstance(data, (int, float)):
679-
data = str(data)
680-
self.send(data)
709+
if isinstance(data, (str, bytes, bytearray)):
710+
data = str2bytes(data)
681711

712+
elif isinstance(data, float):
713+
data = str(data).encode()
714+
715+
else:
716+
# Explicitly call "__int__"
717+
data = str(int(data)).encode()
718+
719+
self.send(data)
682720
return recv_data
683721

684722
def sendlineafter(self,
685-
delim: Union[str, bytes],
686-
data: Union[str, bytes, int],
723+
delim: Union[AtomicRecvT, List[AtomicRecvT]],
724+
data: AtomicSendT,
687725
size: int=4096,
688726
timeout: Optional[Union[int, float]]=None,
689727
drop: bool=False,
@@ -786,8 +824,6 @@ def pretty_print(data: bytes, prev: bytes=b''):
786824
def thread_recv():
787825
"""Receive data from tube and print to stdout
788826
"""
789-
import signal
790-
signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGINT})
791827
nonlocal stop_event
792828
leftover = b''
793829
while self.is_alive() and not stop_event.is_set():
@@ -810,6 +846,7 @@ def thread_recv():
810846
def thread_send():
811847
"""Read user input and send it to tube
812848
"""
849+
import time
813850
nonlocal stop_event
814851
while self.is_alive() and not stop_event.is_set():
815852
sys.stdout.write(prompt)

ptrlib/connection/unixproc.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import tty
99
from logging import getLogger
1010
from typing import List, Mapping, Optional, Union, cast
11-
from ptrlib.arch.linux.memory import LinuxProcessMemory
11+
from ptrlib.debugger.unix import UnixProcessManager
1212
from ptrlib.arch.linux.sig import signal_name
1313
from ptrlib.binary.encoding import bytes2str
1414
from .tube import Tube, tube_is_open
@@ -109,7 +109,6 @@ def __init__(self,
109109
raise err from None
110110

111111
self._filepath = args[0]
112-
113112
self._returncode = None
114113

115114
# Duplicate master
@@ -123,8 +122,8 @@ def __init__(self,
123122
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
124123
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
125124

126-
# Memory interface
127-
self._memory = LinuxProcessMemory(self.pid)
125+
# Debugger interface
126+
self._process = UnixProcessManager(self.pid)
128127

129128
logger.info("Successfully created a new process %s", str(self))
130129
self._init_done = True
@@ -144,10 +143,10 @@ def pid(self) -> int:
144143

145144
@property
146145
@tube_is_open
147-
def memory(self) -> LinuxProcessMemory:
148-
"""Get a `LinuxProcessMemory` instance for this process.
146+
def process(self) -> UnixProcessManager:
147+
"""Get a `UnixProcessManager` instance for this process.
149148
"""
150-
return self._memory
149+
return self._process
151150

152151
#
153152
# Implementation of Tube methods

ptrlib/debugger/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .unix import *
2+
from .windows import *

ptrlib/debugger/unix/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .debug import *
2+
from .process import *

0 commit comments

Comments
 (0)