add vmmap --gaps option (#2191)

pull/2208/head
Aaron Adams 2 years ago committed by GitHub
parent 5a90397be4
commit 8154470ae9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -21,6 +21,7 @@ c = ColorConfig(
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)"),
],
)
@ -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

@ -5,6 +5,7 @@ Command to print the virtual memory map a la /proc/self/maps.
from __future__ import annotations
import argparse
from typing import Tuple
import gdb
from elftools.elf.constants import SH_FLAGS
@ -14,8 +15,12 @@ import pwndbg.color.memory as M
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)
@ -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.
@ -78,6 +179,11 @@ parser.add_argument(
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(
@ -85,7 +191,7 @@ parser.add_argument(
)
@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
@ -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
@ -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()

@ -118,6 +118,10 @@ class Page:
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

@ -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();
}

@ -8,6 +8,7 @@ import pytest
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")
@ -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

Loading…
Cancel
Save