Skip to content

Commit

Permalink
Merge branch 'pwndbg:dev' into debugsyms_fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
chrf01 authored Jun 5, 2024
2 parents 88d6ed5 + 9111977 commit 1e02bd1
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,6 @@ gdb.txt

# Nix build stuff
result-*

# Vim
*.swp
3 changes: 3 additions & 0 deletions pwndbg/color/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
ColorParamSpec("data", "purple", "color for all other writable memory"),
ColorParamSpec("rodata", "normal", "color for all read only memory"),
ColorParamSpec("rwx", "underline", "color added to all RWX memory"),
ColorParamSpec("guard", "cyan", "color added to all guard pages (no perms)"),
],
)

Expand Down Expand Up @@ -76,6 +77,8 @@ def get(address: int | gdb.Value, text: str | None = None, prefix: str | None =
color = c.code
elif page.rw:
color = c.data
elif page.is_guard:
color = c.guard
else:
color = c.rodata

Expand Down
114 changes: 112 additions & 2 deletions pwndbg/commands/vmmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from __future__ import annotations

import argparse
from typing import Tuple

import gdb
from elftools.elf.constants import SH_FLAGS
Expand All @@ -14,8 +15,12 @@
import pwndbg.commands
import pwndbg.gdblib.elf
import pwndbg.gdblib.vmmap
from pwndbg.color import cyan
from pwndbg.color import green
from pwndbg.color import red
from pwndbg.commands import CommandCategory
from pwndbg.gdblib import gdb_version
from pwndbg.lib.memory import Page

integer_types = (int, gdb.Value)

Expand Down Expand Up @@ -44,6 +49,102 @@ def print_vmmap_table_header() -> None:
)


def print_vmmap_gaps_table_header() -> None:
"""
Prints the table header for the vmmap --gaps command.
"""
header = (
f"{'Start':>{2 + 2 * pwndbg.gdblib.arch.ptrsize}} "
f"{'End':>{2 + 2 * pwndbg.gdblib.arch.ptrsize}} "
f"{'Perm':>4} "
f"{'Size':>8} "
f"{'Note':>9} "
f"{'Accumulated Size':>{2 + 2 * pwndbg.gdblib.arch.ptrsize}}"
)
print(header)


def calculate_total_memory(pages: Tuple[Page, ...]) -> None:
total = 0
for page in pages:
total += page.memsz
if total > 1024 * 1024:
print(f"Total memory mapped: {total:#x} ({total//1024//1024} MB)")
else:
print(f"Total memory mapped: {total:#x} ({total//1024} KB)")


def gap_text(page: Page) -> str:
# Strip out offset and objfile from stringified page
display_text = " ".join(str(page).split(" ")[:-2])
return display_text.rstrip()


def print_map(page: Page) -> None:
print(green(gap_text(page)))


def print_adjacent_map(map_start: Page, map_end: Page) -> None:
print(
green(
f"{gap_text(map_end)} {'ADJACENT':>9} {hex(map_end.end - map_start.start):>{2 + 2 * pwndbg.gdblib.arch.ptrsize}}"
)
)


def print_guard(page: Page) -> None:
print(cyan(f"{gap_text(page)} {'GUARD':>9} "))


def print_gap(current: Page, last_map: Page):
print(
red(
" - " * int(51 / 3)
+ f" {'GAP':>9} {hex(current.start - last_map.end):>{2 + 2 * pwndbg.gdblib.arch.ptrsize}}"
)
)


def print_vmmap_gaps(pages: Tuple[Page, ...]) -> None:
"""
Indicates the size of adjacent memory regions and unmapped gaps between them in process memory
"""
print(f"LEGEND: {green('MAPPED')} | {cyan('GUARD')} | {red('GAP')}")
print_vmmap_gaps_table_header()

last_map = None # The last mapped region we looked at
last_start = None # The last starting region of a series of mapped regions

for page in pages:
if last_map:
# If there was a gap print it, and also print the last adjacent map set length
if last_map.end != page.start:
if last_start and last_start != last_map:
print_adjacent_map(last_start, last_map)
print_gap(page, last_map)

# If this is a guard page, print the last map and the guard page
elif page.is_guard:
if last_start and last_start != last_map:
print_adjacent_map(last_start, last_map)
print_guard(page)
last_start = None
last_map = page
continue

# If we are tracking an adjacent set, don't print the current one yet
elif last_start:
if last_start != last_map:
print_map(last_map)
last_map = page
continue

print_map(page)
last_start = page
last_map = page
calculate_total_memory(pages)


parser = argparse.ArgumentParser(
formatter_class=argparse.RawTextHelpFormatter,
description="""Print virtual memory map pages.
Expand Down Expand Up @@ -78,14 +179,19 @@ def print_vmmap_table_header() -> None:
parser.add_argument(
"-B", "--lines-before", type=int, help="Number of pages to display before result", default=1
)
parser.add_argument(
"--gaps",
action="store_true",
help="Display unmapped memory gap information in the memory map.",
)


@pwndbg.commands.ArgparsedCommand(
parser, aliases=["lm", "address", "vprot", "libs"], category=CommandCategory.MEMORY
)
@pwndbg.commands.OnlyWhenRunning
def vmmap(
gdbval_or_str=None, writable=False, executable=False, lines_after=1, lines_before=1
gdbval_or_str=None, writable=False, executable=False, lines_after=1, lines_before=1, gaps=False
) -> None:
lookaround_lines_limit = 64

Expand All @@ -96,7 +202,7 @@ def vmmap(
# All displayed pages, including lines after and lines before
total_pages = pwndbg.gdblib.vmmap.get()

# Filtered memory pages, indicated by an backtrace arrow in results
# Filtered memory pages, indicated by a backtrace arrow in results
filtered_pages = []

# Only filter when -A and -B arguments are valid
Expand Down Expand Up @@ -133,6 +239,10 @@ def vmmap(
print("There are no mappings for specified address or module.")
return

if gaps:
print_vmmap_gaps(total_pages)
return

print(M.legend())
print_vmmap_table_header()

Expand Down
4 changes: 4 additions & 0 deletions pwndbg/lib/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ def rw(self) -> bool:
def rwx(self) -> bool:
return self.read and self.write and self.execute

@property
def is_guard(self) -> bool:
return not (self.read or self.write or self.execute)

@property
def permstr(self) -> str:
flags = self.flags
Expand Down
52 changes: 52 additions & 0 deletions tests/gdb-tests/tests/binaries/mmap_gaps.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

void break_here(void) {}

#define ADDR (void *)0xcafe0000
#define PGSZ 0x1000

void *xmmap(void *addr, size_t length, int prot, int flags, int fd,
off_t offset) {
void *p = mmap(addr, length, prot, flags, fd, offset);
if (MAP_FAILED == p) {
printf("Failed to map fixed address at %p\n", (void *)addr);
perror("mmap");
exit(EXIT_FAILURE);
}
return p;
}

int main(void) {
// We want to allocate multiple adjacent regions, too confirm that vmmap
// --gaps detects them properly. So iensure we have adjacent allocation,
// unmapped holes, as well as some guard page with no permissions.

uint64_t address = (uint64_t)ADDR;
void *p;

// 2 adjacent pages
p = xmmap((void *)address, PGSZ, PROT_READ,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
address += PGSZ;
p = xmmap((void *)address, PGSZ, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
address += PGSZ;

// GUARD page
p = xmmap((void *)address, PGSZ, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
mprotect(p, 0x1000, PROT_NONE);
address += PGSZ;

p = xmmap((void *)address, PGSZ, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
address += PGSZ;

break_here();
}
25 changes: 25 additions & 0 deletions tests/gdb-tests/tests/test_command_vmmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import pwndbg
import tests

GAPS_MAP_BINARY = tests.binaries.get("mmap_gaps.out")
CRASH_SIMPLE_BINARY = tests.binaries.get("crash_simple.out.hardcoded")
BINARY_ISSUE_1565 = tests.binaries.get("issue_1565.out")

Expand Down Expand Up @@ -182,3 +183,27 @@ def test_vmmap_issue_1565(start_binary):
gdb.execute("run")
gdb.execute("next")
gdb.execute("context")


def test_vmmap_gaps_option(start_binary):
start_binary(GAPS_MAP_BINARY)

gdb.execute("break break_here")
gdb.execute("continue")

# Test vmmap with gap option
vmmaps = gdb.execute("vmmap --gaps", to_string=True).splitlines()
seen_gap = False
seen_adjacent = False
seen_guard = False
# Skip the first line since the legend has gard and
for line in vmmaps[1:]:
if "GAP" in line:
seen_gap = True
if "ADJACENT" in line:
seen_adjacent = True
if "GUARD" in line:
seen_guard = True
assert seen_gap
assert seen_adjacent
assert seen_guard

0 comments on commit 1e02bd1

Please sign in to comment.