Skip to content

Commit

Permalink
Fix the Section.realpath property in remote debugging sessions.
Browse files Browse the repository at this point in the history
  • Loading branch information
gordonmessmer committed Jul 23, 2024
1 parent f0b6d1d commit 9ee31c4
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 13 deletions.
71 changes: 58 additions & 13 deletions gef.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,10 +689,57 @@ def size(self) -> int:
raise AttributeError
return self.page_end - self.page_start

def _search_for_realpath_without_versions(self, path: pathlib.Path) -> str:
"""Given a path, search for a file that exists without numeric suffixes."""

# Match the path string against a regex that will remove a suffix
# consisting of a dot followed by numbers.
candidate = re.match(r"^(.*)\.(\d*)$", str(path))
while candidate:
candidate = candidate.group(1)
# If the prefix from the regex match is a file, return that path.
if pathlib.Path(candidate).is_file():
return candidate
# Otherwise, try to match again.
candidate = re.match(r"^(.*)\.(\d*)$", candidate)
return None

def _search_for_realpath(self) -> str:
"""This function is a workaround for gdb bug #23764
path might be wrong for remote sessions, so try a simple search for files
that aren't found at the path indicated, which should be canonical.
"""

assert gef.session.remote
remote_path = pathlib.Path(self.path)
# First, try the canonical path in the remote session root.
candidate1 = gef.session.remote.root / remote_path.relative_to(remote_path.anchor)
if candidate1.is_file():
return str(candidate1)
# Also try that path without version suffixes.
candidate = self._search_for_realpath_without_versions(candidate1)
if candidate:
return candidate

# On some systems, /lib(64) might be a symlink to /usr/lib(64), so try removing
# the /usr prefix.
if self.path.startswith("/usr"):
candidate = gef.session.remote.root / remote_path.relative_to("/usr")
if candidate.is_file():
return str(candidate)
# Also try that path without version suffixes.
candidate = self._search_for_realpath_without_versions(candidate)
if candidate:
return candidate

# Base case, return the original realpath
return str(candidate1)

@property
def realpath(self) -> str:
# when in a `gef-remote` session, realpath returns the path to the binary on the local disk, not remote
return self.path if gef.session.remote is None else f"/tmp/gef/{gef.session.remote:d}/{self.path}"
return self.path if gef.session.remote is None else self._search_for_realpath()

def __str__(self) -> str:
return (f"Section(start={self.page_start:#x}, end={self.page_end:#x}, "
Expand Down Expand Up @@ -9293,20 +9340,18 @@ def build_line(self, name: str, color: str, address_val: int, got_address: int)
@parse_arguments({"symbols": [""]}, {"--all": False})
def do_invoke(self, _: List[str], **kwargs: Any) -> None:
args : argparse.Namespace = kwargs["arguments"]
if args.all:
vmmap = gef.memory.maps
mapfiles = set(mapfile.path for mapfile in vmmap if
pathlib.Path(mapfile.realpath).is_file() and
mapfile.permission & Permission.EXECUTE)
for mapfile in mapfiles:
self.print_got_for(mapfile, args.symbols)
else:
self.print_got_for(str(gef.session.file), args.symbols)

def print_got_for(self, file: str, argv: List[str]) -> None:
vmmap = gef.memory.maps
mapfiles = [mapfile for mapfile in vmmap if
(args.all or mapfile.path == str(gef.session.file)) and
pathlib.Path(mapfile.realpath).is_file() and
mapfile.permission & Permission.EXECUTE]
for mapfile in mapfiles:
self.print_got_for(mapfile.path, mapfile.realpath, args.symbols)

def print_got_for(self, file: str, realpath: str, argv: List[str]) -> None:
readelf = gef.session.constants["readelf"]

elf_file = file
elf_file = realpath
elf_virtual_path = file

func_names_filter = argv if argv else []
Expand Down
27 changes: 27 additions & 0 deletions tests/api/gef_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,30 @@ def test_func_parse_maps_remote_qemu(self):
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

with gdbserver_session(port=port) as _:
gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}")
gdb.execute("b main")
gdb.execute("continue")
sections = gef.memory.maps
assert len(sections) > 0
#
# Iterate over each of the maps, checking each path. If the path from
# the /proc/pid/maps file indicates a path that starts with /usr, but
# realpath does not include "/usr", then _search_for_realpath has
# probably corrected the path to account for gdb bug #23764
#
for section in sections:
if(section.is_executable() and section.path.startswith("/usr") and
"/usr" not in section.realpath):
assert pathlib.Path(section.realpath).is_file() is True
break

0 comments on commit 9ee31c4

Please sign in to comment.