Skip to content

Commit 6d166f0

Browse files
committed
checkpoint: added very basic pytests
1 parent a277661 commit 6d166f0

4 files changed

Lines changed: 150 additions & 28 deletions

File tree

gef.py

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3530,33 +3530,31 @@ def is_target_remote_or_extended(conn: gdb.TargetConnection | None = None) -> bo
35303530
return is_target_remote(conn) or is_target_extended_remote(conn)
35313531

35323532

3533-
def is_running_under_qemu() -> bool:
3533+
def is_running_in_qemu() -> bool:
35343534
"See https://www.qemu.org/docs/master/system/gdb.html "
35353535
if not is_target_remote():
35363536
return False
35373537
response = gdb.execute("maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False) or ""
35383538
return "ENABLE=" in response
35393539

35403540

3541-
def is_running_under_qemu_user() -> bool:
3542-
if not is_running_under_qemu():
3541+
def is_running_in_qemu_user() -> bool:
3542+
if not is_running_in_qemu():
35433543
return False
35443544
response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or "" # Use `qAttached`?
35453545
return "Text=" in response
35463546

35473547

3548-
def is_running_under_qemu_system() -> bool:
3549-
if not is_running_under_qemu():
3548+
def is_running_in_qemu_system() -> bool:
3549+
if not is_running_in_qemu():
35503550
return False
35513551
# Use "maintenance packet qqemu.PhyMemMode"?
35523552
response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or ""
35533553
return "received: \"\"" in response
35543554

35553555

35563556
def is_running_in_gdbserver() -> bool:
3557-
if is_running_under_qemu():
3558-
return False
3559-
return not is_running_under_qemu()
3557+
return not is_running_in_qemu()
35603558

35613559

35623560
def is_running_in_rr() -> bool:
@@ -7447,7 +7445,7 @@ def __init__(self) -> None:
74477445
def do_invoke(self, _: list[str], **kwargs: Any) -> None:
74487446
args : argparse.Namespace = kwargs["arguments"]
74497447

7450-
if is_running_under_qemu_system():
7448+
if is_running_in_qemu_system():
74517449
err("Unsupported")
74527450
return
74537451

@@ -11345,7 +11343,7 @@ def __repr__(self) -> str:
1134511343
def auxiliary_vector(self) -> dict[str, int] | None:
1134611344
if not is_alive():
1134711345
return None
11348-
if is_running_under_qemu_system():
11346+
if is_running_in_qemu_system():
1134911347
return None
1135011348
if not self._auxiliary_vector:
1135111349
auxiliary_vector = {}
@@ -11493,9 +11491,9 @@ def prompt_string(self) -> str:
1149311491

1149411492
@staticmethod
1149511493
def init() -> "GefRemoteSessionManager.RemoteMode":
11496-
if is_running_under_qemu_system():
11494+
if is_running_in_qemu_system():
1149711495
return GefRemoteSessionManager.RemoteMode.QEMU_SYSTEM
11498-
if is_running_under_qemu_user():
11496+
if is_running_in_qemu_user():
1149911497
return GefRemoteSessionManager.RemoteMode.QEMU_USER
1150011498
if is_running_in_rr():
1150111499
return GefRemoteSessionManager.RemoteMode.RR
@@ -11619,7 +11617,7 @@ def connect(self, pid: int) -> bool:
1161911617

1162011618
def setup(self) -> bool:
1162111619
# setup remote adequately depending on remote or qemu mode
11622-
dbg(f"Setting up the {self._mode} session")
11620+
info(f"Setting up remote session as '{self._mode}'")
1162311621
match self.mode:
1162411622
case GefRemoteSessionManager.RemoteMode.QEMU_USER:
1162511623
self.__setup_qemu_user()
@@ -11826,21 +11824,24 @@ def reset_caches(self) -> None:
1182611824
return
1182711825

1182811826

11827+
def target_remote_hook():
11828+
# disable the context until the session has been fully established
11829+
gef.config["context.enable"] = False
11830+
11831+
1182911832
def target_remote_posthook():
11830-
print(f"{is_target_remote()=}")
11831-
print(f"{is_target_remote_or_extended()=}")
11832-
print(f"{is_target_extended_remote()=}")
11833-
print(f"{is_running_under_qemu()=}")
11834-
print(f"{is_running_under_qemu_system()=}")
11835-
print(f"{is_running_under_qemu_user()=}")
11836-
print(f"{is_running_in_gdbserver()=}")
11837-
print(f"{is_running_in_rr()=}")
1183811833
conn = gdb.selected_inferior().connection
1183911834
if not isinstance(conn, gdb.RemoteTargetConnection):
1184011835
raise TypeError("Expected type gdb.RemoteTargetConnection")
1184111836
assert is_target_remote_or_extended(conn), "Target is not remote"
1184211837
gef.session.remote = GefRemoteSessionManager(conn)
1184311838

11839+
# re-enable context
11840+
gef.config["context.enable"] = True
11841+
11842+
# if here, no exception was thrown, print context
11843+
gdb.execute("context")
11844+
1184411845

1184511846
if __name__ == "__main__":
1184611847
if sys.version_info[0] == 2:
@@ -11903,14 +11904,18 @@ def target_remote_posthook():
1190311904

1190411905
GefTmuxSetup()
1190511906

11906-
# Initialize `target *remote` post hooks
11907+
# Initialize `target *remote` pre/post hooks
1190711908
hook = """
11908-
define target hookpost-{0}
11909-
pi target_remote_posthook()
11909+
define target hook{1}-{0}
11910+
pi target_remote_{1}hook()
1191011911
end
1191111912
"""
11912-
gdb.execute(hook.format("remote"))
11913-
gdb.execute(hook.format("extended-remote"))
11913+
# pre-hooks
11914+
gdb.execute(hook.format("remote", ""))
11915+
gdb.execute(hook.format("extended-remote", ""))
11916+
# post-hooks
11917+
gdb.execute(hook.format("remote", "post"))
11918+
gdb.execute(hook.format("extended-remote", "post"))
1191411919

1191511920
# restore saved breakpoints (if any)
1191611921
bkp_fpath = pathlib.Path(gef.config["gef.autosave_breakpoints_file"]).expanduser().absolute()

tests/api/gef_remote.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
`target remote/extended-remote` test module.
3+
"""
4+
5+
6+
from tests.base import RemoteGefUnitTestGeneric
7+
from tests.utils import debug_target, gdbserver_session, gdbserver_multi_session, get_random_port, qemuuser_session
8+
9+
10+
class GefRemoteApi(RemoteGefUnitTestGeneric):
11+
12+
def setUp(self) -> None:
13+
self._target = debug_target("default")
14+
return super().setUp()
15+
16+
def test_gef_remote_test_gdbserver(self):
17+
"""Test `gdbserver file`"""
18+
_gdb = self._gdb
19+
_root = self._conn.root
20+
port = get_random_port()
21+
22+
with gdbserver_session(port=port):
23+
assert not _root.eval("is_target_remote()")
24+
assert not _root.eval("is_target_remote_or_extended()")
25+
assert not _root.eval("is_running_in_gdbserver()")
26+
27+
_gdb.execute(f"target remote :{port}")
28+
29+
assert _root.eval("is_target_remote()")
30+
assert _root.eval("is_target_remote_or_extended()")
31+
assert _root.eval("is_running_in_gdbserver()")
32+
33+
assert not _root.eval("is_target_extended_remote()")
34+
assert not _root.eval("is_running_under_qemu()")
35+
assert not _root.eval("is_running_under_qemu_system()")
36+
assert not _root.eval("is_running_under_qemu_user()")
37+
assert not _root.eval("is_running_in_rr()")
38+
39+
def test_gef_remote_test_gdbserver_multi(self):
40+
"""Test `gdbserver --multi file`"""
41+
_gdb = self._gdb
42+
_root = self._conn.root
43+
port = get_random_port()
44+
45+
with gdbserver_multi_session(port=port):
46+
assert not _root.eval("is_target_remote()")
47+
assert not _root.eval("is_target_remote_or_extended()")
48+
assert not _root.eval("is_running_in_gdbserver()")
49+
50+
_gdb.execute(f"target extended-remote :{port}")
51+
52+
assert _root.eval("is_target_remote()")
53+
assert _root.eval("is_target_remote_or_extended()")
54+
assert _root.eval("is_target_extended_remote()")
55+
assert _root.eval("is_running_in_gdbserver()")
56+
57+
assert not _root.eval("is_running_under_qemu()")
58+
assert not _root.eval("is_running_under_qemu_system()")
59+
assert not _root.eval("is_running_under_qemu_user()")
60+
assert not _root.eval("is_running_in_rr()")
61+
62+
def test_gef_remote_test_qemuuser(self):
63+
"""Test `qemu-user -g`"""
64+
_gdb = self._gdb
65+
_root = self._conn.root
66+
port = get_random_port()
67+
68+
with qemuuser_session(port=port):
69+
assert not _root.eval("is_target_remote()")
70+
assert not _root.eval("is_target_remote_or_extended()")
71+
assert not _root.eval("is_running_in_gdbserver()")
72+
73+
_gdb.execute(f"target remote :{port}")
74+
75+
assert _root.eval("is_target_remote()")
76+
assert _root.eval("is_target_remote_or_extended()")
77+
assert _root.eval("is_running_under_qemu()")
78+
assert _root.eval("is_running_under_qemu_user()")
79+
80+
assert not _root.eval("is_target_extended_remote()")
81+
assert not _root.eval("is_running_under_qemu_system()")
82+
assert not _root.eval("is_running_in_gdbserver()")
83+
assert not _root.eval("is_running_in_rr()")
84+
85+
# TODO add tests for
86+
# - [ ] qemu-system
87+
# - [ ] rr

tests/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import rpyc
1212

13-
from .utils import debug_target
13+
from .utils import debug_target, get_random_port
1414

1515
COVERAGE_DIR = os.getenv("COVERAGE_DIR", "")
1616
GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "gef.py")).absolute()
@@ -58,7 +58,7 @@ def __setup(self):
5858
#
5959
# Select a random tcp port for rpyc
6060
#
61-
self._port = random.randint(1025, 65535)
61+
self._port = get_random_port()
6262
self._commands = ""
6363

6464
if COVERAGE_DIR:

tests/utils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import subprocess
1313
import tempfile
1414
import time
15+
import random
1516

1617
from typing import Iterable, List, Optional, Union
1718
from urllib.request import urlopen
@@ -112,6 +113,13 @@ def start_gdbserver(
112113
logging.debug(f"Starting {cmd}")
113114
return subprocess.Popen(cmd)
114115

116+
def start_gdbserver_multi(
117+
host: str = GDBSERVER_DEFAULT_HOST,
118+
port: int = GDBSERVER_DEFAULT_PORT,
119+
) -> subprocess.Popen:
120+
cmd = [GDBSERVER_BINARY, "--multi", f"{host}:{port}"]
121+
logging.debug(f"Starting {cmd}")
122+
return subprocess.Popen(cmd)
115123

116124
def stop_gdbserver(gdbserver: subprocess.Popen) -> None:
117125
"""Stop the gdbserver and wait until it is terminated if it was
@@ -138,6 +146,17 @@ def gdbserver_session(
138146
finally:
139147
stop_gdbserver(sess)
140148

149+
@contextlib.contextmanager
150+
def gdbserver_multi_session(
151+
port: int = GDBSERVER_DEFAULT_PORT,
152+
host: str = GDBSERVER_DEFAULT_HOST,
153+
):
154+
sess = start_gdbserver_multi(host, port)
155+
try:
156+
time.sleep(1) # forced delay to allow gdbserver to start listening
157+
yield sess
158+
finally:
159+
stop_gdbserver(sess)
141160

142161
def start_qemuuser(
143162
exe: Union[str, pathlib.Path] = debug_target("default"),
@@ -301,3 +320,14 @@ def p32(x: int) -> bytes:
301320

302321
def p64(x: int) -> bytes:
303322
return struct.pack("<Q", x)
323+
324+
325+
__available_ports = list()
326+
def get_random_port() -> int:
327+
global __available_ports
328+
if len(__available_ports) < 2:
329+
__available_ports = list( range(1024, 65535) )
330+
idx = random.choice(range(len(__available_ports)))
331+
port = __available_ports[idx]
332+
__available_ports.pop(idx)
333+
return port

0 commit comments

Comments
 (0)