Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use target remote for remote connections #1151

Open
wants to merge 42 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
d28c4a3
added ruff linter + fixes
hugsy Sep 22, 2024
cde95b3
checkpoint
hugsy Sep 24, 2024
b83586e
removed `Set` and `Tuple` types (replaced with `tuple` and `set`)
hugsy Nov 1, 2024
32311d7
`Union` type -> `|`
hugsy Nov 1, 2024
741ce7c
removed Tuple in tests
hugsy Nov 1, 2024
854912b
Merge branch 'main' into hugsy/lint-fmt
hugsy Nov 2, 2024
f47f51e
fixed type typo
hugsy Nov 2, 2024
d5d544d
`Optional` is optional
hugsy Nov 2, 2024
60be6a1
fixed ruff toml
hugsy Nov 2, 2024
dc28938
revert changes, only focus on py3.10 improvements
hugsy Nov 2, 2024
417ac63
final fixes
hugsy Nov 2, 2024
9fb1d71
added `untracked` dir to gitignore
hugsy Nov 3, 2024
a277661
checkpoint: added new remote modes, gdbserver & gdbserver-multi work
hugsy Nov 3, 2024
6d166f0
checkpoint: added very basic pytests
hugsy Nov 4, 2024
771a598
removed all obsolete code
hugsy Nov 5, 2024
79447be
fixed `gef-remote` from tests, using only `target remote`
hugsy Nov 5, 2024
ea873bb
use `NotImplementedError`
hugsy Nov 7, 2024
4ae683f
plop
hugsy Nov 7, 2024
8130895
minor lint
hugsy Nov 7, 2024
7973a3f
allow mock mem layout for old qemu versions
hugsy Nov 7, 2024
8cc2231
added repr for Gef class
hugsy Nov 8, 2024
2ba9ac7
[tests] use `gdb-multiarch` by default
hugsy Nov 8, 2024
437cbe2
added regression for issue #1131
hugsy Nov 8, 2024
e5a02b1
constantify all the things
hugsy Nov 8, 2024
68fcc1d
officially deprecating `gef-remote`
hugsy Nov 8, 2024
e8527ad
duplicate test line
hugsy Nov 10, 2024
14c35bd
allow gef to save temporary values
hugsy Nov 10, 2024
03f9cf9
Merge branch 'main' into hugsy/revisit-target-remote
hugsy Nov 10, 2024
2a5c585
test fix
hugsy Nov 10, 2024
36c953c
oops
hugsy Nov 10, 2024
68505a6
bleh
hugsy Nov 10, 2024
02af4d4
revert
hugsy Nov 10, 2024
c89c280
damnit
hugsy Nov 10, 2024
77aa954
restored gdb-multiarch as default for tests
hugsy Nov 11, 2024
1d9f897
minor
hugsy Nov 11, 2024
623ebf1
test
hugsy Nov 11, 2024
532e45d
asd
hugsy Nov 11, 2024
fd0bf0a
Merge branch 'hugsy/revisit-target-remote' of https://github.com/hugs…
hugsy Nov 11, 2024
d262ca5
Merge branch 'main' into hugsy/revisit-target-remote
hugsy Nov 11, 2024
7a9e494
Update generate-coverage-docs.sh
hugsy Nov 11, 2024
b9f564f
Update generate-coverage-docs.sh
hugsy Nov 11, 2024
7ea8588
Update coverage.yml
hugsy Nov 11, 2024
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: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
matrix:
runner: [ubuntu-24.04, ubuntu-22.04]

name: "Run Unit tests on ${{ matrix.runner }}"
name: "Tests/${{ matrix.runner }}"
runs-on: ${{ matrix.runner }}
defaults:
run:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ debug.log
htmlcov
.benchmarks
site/
untracked/
13 changes: 3 additions & 10 deletions docs/commands/gef-remote.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
## Command `gef-remote`

[`target remote`](https://sourceware.org/gdb/onlinedocs/gdb/Remote-Debugging.html#Remote-Debugging)
is the traditional GDB way of debugging process or system remotely. However this command by itself
does a limited job (80's bandwith FTW) to collect more information about the target, making the
process of debugging more cumbersome. GEF greatly improves that state with the `gef-remote` command.

📝 **Note**: If using GEF, `gef-remote` **must** be your way or debugging remote processes, never
`target remote`. Maintainers will provide minimal support or help if you decide to use the
traditional `target remote` command. For many reasons, you **should not** use `target remote` alone
with GEF. It is still important to note that the default `target remote` command has been
overwritten by a minimal copy `gef-remote`, in order to make most tools relying on this command work.
📝 **IMPORTANT NOTE**: `gef-remote` is deprecated since 2024.09 in favor of `target remote`. The
command will be removed in a future release. Do not rely on it.


`gef-remote` can function in 2 ways:

Expand Down
1,197 changes: 575 additions & 622 deletions gef.py

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[lint.per-file-ignores]
"gef.py" = ["E701"]
30 changes: 9 additions & 21 deletions tests/api/gef_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
ARCH,
debug_target,
gdbserver_session,
get_random_port,
qemuuser_session,
GDBSERVER_DEFAULT_HOST,
)
Expand Down Expand Up @@ -121,48 +122,35 @@ def test_func_parse_maps_local_procfs(self):
@pytest.mark.slow
def test_func_parse_maps_remote_gdbserver(self):
gef, gdb = self._gef, self._gdb
# When in a gef-remote session `parse_gdb_info_proc_maps` should work to
# When in a remote session `parse_gdb_info_proc_maps` should work to
# query the memory maps
while True:
port = random.randint(1025, 65535)
if port != self._port:
break
port = get_random_port()

with pytest.raises(Exception):
gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}")
gdb.execute(f"target remote :{port}")

with gdbserver_session(port=port) as _:
gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}")
gdb.execute(f"target remote :{port}")
sections = gef.memory.maps
assert len(sections) > 0

@pytest.mark.skipif(ARCH not in ("x86_64",), reason=f"Skipped for {ARCH}")
def test_func_parse_maps_remote_qemu(self):
gdb, gef = self._gdb, self._gef
# When in a gef-remote qemu-user session `parse_gdb_info_proc_maps`
# should work to query the memory maps
while True:
port = random.randint(1025, 65535)
if port != self._port:
break
port = get_random_port()

with qemuuser_session(port=port) as _:
cmd = f"gef-remote --qemu-user --qemu-binary {self._target} {GDBSERVER_DEFAULT_HOST} {port}"
cmd = f"target remote :{port}"
gdb.execute(cmd)
sections = gef.memory.maps
assert len(sections) > 0

def test_func_parse_maps_realpath(self):
gef, gdb = self._gef, self._gdb
# When in a gef-remote session `parse_gdb_info_proc_maps` should work to
# query the memory maps
while True:
port = random.randint(1025, 65535)
if port != self._port:
break
port = get_random_port()

with gdbserver_session(port=port) as _:
gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}")
gdb.execute(f"target remote :{port}")
gdb.execute("b main")
gdb.execute("continue")
sections = gef.memory.maps
Expand Down
108 changes: 108 additions & 0 deletions tests/api/gef_remote.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""
`target remote/extended-remote` test module.
"""


from tests.base import RemoteGefUnitTestGeneric
from tests.utils import debug_target, gdbserver_session, gdbserver_multi_session, get_random_port, qemuuser_session


class GefRemoteApi(RemoteGefUnitTestGeneric):

def setUp(self) -> None:
self._target = debug_target("default")
return super().setUp()

def test_gef_remote_test_gdbserver(self):
"""Test `gdbserver file`"""
_gdb = self._gdb
_gef = self._gef
_root = self._conn.root
port = get_random_port()

with gdbserver_session(port=port):
assert not _root.eval("is_target_remote()")
assert not _root.eval("is_target_remote_or_extended()")
assert not _root.eval("is_running_in_gdbserver()")
assert not _root.eval("is_running_in_rr()")
assert not _root.eval("is_running_in_qemu()")

_gdb.execute(f"target remote :{port}")

assert _root.eval("is_target_remote()")
assert _root.eval("is_target_remote_or_extended()")
assert _root.eval("is_running_in_gdbserver()")
assert _root.eval("is_running_in_gdbserver()")
hugsy marked this conversation as resolved.
Show resolved Hide resolved

assert not _root.eval("is_target_extended_remote()")
assert not _root.eval("is_running_in_qemu()")
assert not _root.eval("is_running_in_qemu_system()")
assert not _root.eval("is_running_in_qemu_user()")
assert not _root.eval("is_running_in_rr()")

assert hasattr(_gef.session, "remote")
assert "GDBSERVER" in str(_gef.session.remote)
assert "GDBSERVER_MULTI" not in str(_gef.session.remote)

def test_gef_remote_test_gdbserver_multi(self):
"""Test `gdbserver --multi file`"""
_gdb = self._gdb
_gef = self._gef
_root = self._conn.root
port = get_random_port()

with gdbserver_multi_session(port=port):
assert not _root.eval("is_target_remote()")
assert not _root.eval("is_target_remote_or_extended()")
assert not _root.eval("is_running_in_gdbserver()")
assert not _root.eval("is_running_in_rr()")
assert not _root.eval("is_running_in_qemu()")

_gdb.execute(f"target extended-remote :{port}")
_gdb.execute(f"set remote exec-file {self._target}")
_gdb.execute(f"file {self._target}")
_gdb.execute(f"start {self._target}")

assert _root.eval("is_target_remote_or_extended()")
assert _root.eval("is_target_extended_remote()")
assert _root.eval("is_running_in_gdbserver()")
assert not _root.eval("is_target_remote()")

assert not _root.eval("is_running_in_qemu()")
assert not _root.eval("is_running_in_qemu_system()")
assert not _root.eval("is_running_in_qemu_user()")
assert not _root.eval("is_running_in_rr()")

assert hasattr(_gef.session, "remote")
assert "GDBSERVER_MULTI" in str(_gef.session.remote)

def test_gef_remote_test_qemuuser(self):
"""Test `qemu-user -g`"""
_gdb = self._gdb
_gef = self._gef
_root = self._conn.root
port = get_random_port()

with qemuuser_session(port=port):
assert not _root.eval("is_target_remote()")
assert not _root.eval("is_target_remote_or_extended()")
assert not _root.eval("is_running_in_gdbserver()")

_gdb.execute(f"target remote :{port}")

assert _root.eval("is_target_remote()")
assert _root.eval("is_target_remote_or_extended()")
assert _root.eval("is_running_in_qemu()")
assert _root.eval("is_running_in_qemu_user()")

assert not _root.eval("is_target_extended_remote()")
assert not _root.eval("is_running_in_qemu_system()")
assert not _root.eval("is_running_in_gdbserver()")
assert not _root.eval("is_running_in_rr()")

assert hasattr(_gef.session, "remote")
assert "QEMU_USER" in str(_gef.session.remote)

# TODO add tests for
# - [ ] qemu-system
# - [ ] rr
15 changes: 6 additions & 9 deletions tests/api/gef_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
ARCH,
debug_target,
gdbserver_session,
get_random_port,
qemuuser_session,
GDBSERVER_DEFAULT_HOST,
)
Expand Down Expand Up @@ -63,12 +64,11 @@ def test_root_dir_local(self):
def test_root_dir_remote(self):
gdb = self._gdb
gdb.execute("start")

expected = os.stat("/")
host = GDBSERVER_DEFAULT_HOST
port = random.randint(1025, 65535)
port = get_random_port()

with gdbserver_session(port=port):
gdb.execute(f"gef-remote {host} {port}")
gdb.execute(f"target remote :{port}")
result = self._conn.root.eval("os.stat(gef.session.root)")
assert (expected.st_dev == result.st_dev) and (
expected.st_ino == result.st_ino
Expand All @@ -77,11 +77,8 @@ def test_root_dir_remote(self):
@pytest.mark.skipif(ARCH not in ("x86_64",), reason=f"Skipped for {ARCH}")
def test_root_dir_qemu(self):
gdb, gef = self._gdb, self._gef
port = get_random_port()

host = GDBSERVER_DEFAULT_HOST
port = random.randint(1025, 65535)
with qemuuser_session(port=port):
gdb.execute(
f"gef-remote --qemu-user --qemu-binary {self._target} {host} {port}"
)
gdb.execute(f"target remote :{port}")
assert re.search(r"\/proc\/[0-9]+/root", str(gef.session.root))
52 changes: 25 additions & 27 deletions tests/base.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import os
import pathlib
import random
import re
import subprocess
import tempfile
import time
from typing import Tuple

import unittest

import rpyc

from .utils import debug_target
from .utils import debug_target, get_random_port, which

COVERAGE_DIR = os.getenv("COVERAGE_DIR", "")
GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "gef.py")).absolute()
Expand All @@ -19,6 +17,8 @@
RPYC_PORT = 18812
RPYC_SPAWN_TIME = 1.0
RPYC_MAX_REMOTE_CONNECTION_ATTEMPTS = 5
GDB_BINARY_PATH = which("gdb-multiarch")
RPYC_CONNECT_FAILURE_DELAY = 0.2


class RemoteGefUnitTestGeneric(unittest.TestCase):
Expand All @@ -28,6 +28,7 @@ class RemoteGefUnitTestGeneric(unittest.TestCase):
"""

def setUp(self) -> None:
self._gdb_path = GDB_BINARY_PATH
attempt = RPYC_MAX_REMOTE_CONNECTION_ATTEMPTS
while True:
try:
Expand All @@ -41,7 +42,7 @@ def setUp(self) -> None:
attempt -= 1
if attempt == 0:
raise
time.sleep(0.2)
time.sleep(RPYC_CONNECT_FAILURE_DELAY)
continue

self._gdb = self._conn.root.gdb
Expand All @@ -58,7 +59,7 @@ def __setup(self):
#
# Select a random tcp port for rpyc
#
self._port = random.randint(1025, 65535)
self._port = get_random_port()
self._commands = ""

if COVERAGE_DIR:
Expand All @@ -71,25 +72,20 @@ def __setup(self):
pi cov.start()
"""

self._commands += f"""
source {GEF_PATH}
gef config gef.debug True
gef config gef.propagate_debug_exception True
gef config gef.disable_color True
source {RPYC_GEF_PATH}
pi start_rpyc_service({self._port})
"""

self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False)
self._initfile.write(self._commands)
self._initfile.flush()
# self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False)
# self._initfile.write(self._commands)
# self._initfile.flush()
self._command = [
"gdb",
"-q",
"-nx",
"-ex",
f"source {self._initfile.name}",
# fmt: off
self._gdb_path, "-q", "-nx",
"-ex", f"source {GEF_PATH}",
"-ex", "gef config gef.debug True",
"-ex", "gef config gef.propagate_debug_exception True",
"-ex", "gef config gef.disable_color True",
"-ex", f"source {RPYC_GEF_PATH}",
"-ex", f"pi start_rpyc_service({self._port})",
"--",
# fmt: off
str(self._target.absolute()), # type: ignore pylint: disable=E1101
]
self._process = subprocess.Popen(self._command)
Expand All @@ -109,7 +105,9 @@ def tearDown(self) -> None:
return super().tearDown()

@property
def gdb_version(self) -> Tuple[int, int]:
res = [int(d) for d in re.search(r"(\d+)\D(\d+)", self._gdb.VERSION).groups()]
assert len(res) >= 2
return tuple(res)
def gdb_version(self) -> tuple[int, int]:
res = re.search(r"(\d+)\D(\d+)", self._gdb.VERSION)
assert res
groups = [int(d) for d in res.groups()]
assert len(groups) == 2
return groups[0], groups[1]
Loading
Loading