Skip to content

Commit

Permalink
Fix remote use of gef.memory.maps (#1109)
Browse files Browse the repository at this point in the history
## Description

This change passes both the "display" path and the local "real" path for each section or file to the print_got_for function in GotCommand. Without this change, the `got` command does not work properly in a remote debugging session.
  • Loading branch information
gordonmessmer authored Jul 28, 2024
1 parent 22d3ab0 commit 8907935
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 @@ -693,10 +693,57 @@ def size(self) -> int:
raise AttributeError
return self.page_end - self.page_start

def _search_for_realpath_without_versions(self, path: pathlib.Path) -> Optional[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) -> Optional[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 @@ -9372,20 +9419,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 @@ -151,3 +151,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()
break

0 comments on commit 8907935

Please sign in to comment.