Skip to content

Commit

Permalink
Fix coredump debugging (#1079)
Browse files Browse the repository at this point in the history
* Fix coredump debugging

This commit fixes our headaches with core files debugging.

The TL;DR is that we will now try to parse `info proc mappings` and
`maintenance info sections` to give users best possible UX/vmmaps
information.

Related:
* https://sourceware.org/bugzilla/show_bug.cgi?id=29508
* #985
* #954

* cleanup

* cleanup

* Fix core dbg when EHDR map is not mapped
  • Loading branch information
disconnect3d committed Aug 20, 2022
1 parent a00ba56 commit c10c8f8
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 0 deletions.
5 changes: 5 additions & 0 deletions pwndbg/elf.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,11 @@ def get_ehdr(pointer):
vmmap = pwndbg.vmmap.find(pointer)
base = None

# If there is no vmmap for the requested address, we can't do much
# (e.g. it could have been unmapped for whatever reason)
if vmmap is None:
return None, None

# We first check if the beginning of the page contains the ELF magic
if pwndbg.memory.read(vmmap.start, 4) == b'\x7fELF':
base = vmmap.start
Expand Down
93 changes: 93 additions & 0 deletions pwndbg/vmmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@

kernel_vmmap_via_pt = pwndbg.config.Parameter('kernel-vmmap-via-page-tables', True, 'When on, it reads vmmap for kernels via page tables, otherwise uses QEMU kernel\'s `monitor info mem` command')


@pwndbg.memoize.reset_on_objfile
@pwndbg.memoize.reset_on_start
def is_corefile():
return gdb.execute('info target', to_string=True).startswith('Local core dump file')


@pwndbg.memoize.reset_on_start
@pwndbg.memoize.reset_on_stop
def get():
Expand All @@ -49,6 +56,12 @@ def get():
else:
pages.extend(kernel_vmmap_via_monitor_info_mem())

if not pages:
pages.extend(coredump_maps())

# TODO/FIXME: Do we still need it after coredump_maps()?
# Add tests for other cases and see if this is needed e.g. for QEMU user
# if not, remove the code below & cleanup other parts of Pwndbg codebase
if not pages:
# If debuggee is launched from a symlink the debuggee memory maps will be
# labeled with symlink path while in normal scenario the /proc/pid/maps
Expand Down Expand Up @@ -154,6 +167,86 @@ def clear_custom_page():
pwndbg.memoize.reset()


@pwndbg.memoize.reset_on_objfile
@pwndbg.memoize.reset_on_start
def coredump_maps():
"""
Parses `info proc mappings` and `maintenance info sections`
and tries to make sense out of the result :)
"""
pages = []

for line in gdb.execute('info proc mappings', to_string=True).splitlines():
# We look for lines like:
# ['0x555555555000', '0x555555556000', '0x1000', '0x1000', '/home/user/a.out']
try:
start, _end, size, offset, objfile = line.split()
start, size, offset = int(start, 16), int(size, 16), int(offset, 16)
except (IndexError, ValueError):
continue

# Note: we set flags=0 because we do not have this information here
pages.append(pwndbg.memory.Page(start, size, 0, offset, objfile))

for line in gdb.execute('maintenance info sections', to_string=True).splitlines():
# We look for lines like:
# ['[9]', '0x00000000->0x00000150', 'at', '0x00098c40:', '.auxv', 'HAS_CONTENTS']
# ['[15]', '0x555555555000->0x555555556000', 'at', '0x00001430:', 'load2', 'ALLOC', 'LOAD', 'READONLY', 'CODE', 'HAS_CONTENTS']
try:
_idx, start_end, _at, offset, name, *flags_list = line.split()
start, end = map(lambda v: int(v, 16), start_end.split('->'))
offset = int(offset[:-1], 16)
except (IndexError, ValueError):
continue

# Note: can we deduce anything from 'ALLOC', 'HAS_CONTENTS' or 'LOAD' flags?
flags = 0
if 'READONLY' in flags_list: flags |= 4
if 'DATA' in flags_list: flags |= 2
if 'CODE' in flags_list: flags |= 1

# Now, if the section is already in pages, just add its perms
known_page = False

for page in pages:
if start in page:
page.flags |= flags
known_page = True
break

if known_page:
continue

pages.append(pwndbg.memory.Page(start, end-start, flags, offset, name))

# If the last page starts on e.g. 0xffffffffff600000 it must be vsyscall
vsyscall_page = pages[-1]
if vsyscall_page.start > 0xffffffffff000000 and vsyscall_page.flags & 1:
vsyscall_page.objfile = '[vsyscall]'

# Detect stack based on addresses in AUXV from stack memory
stack_addr = None

# TODO/FIXME: Can we uxe `pwndbg.auxv.get()` for this somehow?
auxv = gdb.execute('info auxv', to_string=True).splitlines()
for line in auxv:
if 'AT_EXECFN' in line:
try:
stack_addr = int(line.split()[-2], 16)
except Exception as e:
pass
break

if stack_addr is not None:
for page in pages:
if stack_addr in page:
page.objfile = '[stack]'
page.flags |= 6
break

return tuple(pages)


@pwndbg.memoize.reset_on_start
@pwndbg.memoize.reset_on_stop
def proc_pid_maps():
Expand Down

0 comments on commit c10c8f8

Please sign in to comment.