Support userspace program core dumps

We only need to add a little bit of infrastructure to map variable
addresses for ASLR/shared libraries, which we find using the NT_FILE
note.
This commit is contained in:
Omar Sandoval 2018-07-09 22:34:03 -07:00
parent 631f50a2f6
commit c2d51fe295
6 changed files with 174 additions and 28 deletions

View File

@ -5,8 +5,8 @@ drgn
which use the types and data of the program being debugged.
`drgn` was developed for debugging the Linux kernel (as an alternative to the
`crash` utility). Currently, it only supports debugging the kernel, but in the
future it will support debugging userspace programs, as well.
`crash` utility), but it can also debug userspace program written in C. C++
support is planned.
Installation
------------
@ -35,8 +35,8 @@ Or, pick your favorite Python package installation method.
Getting Started
---------------
To debug the running kernel, run `sudo drgn -k`. To debug a kernel core dump,
add `-c $PATH`.
To debug the running kernel, run `sudo drgn -k`. To debug a core dump (either a
kernel vmcore or a userspace core dump), run `drgn -c $PATH`.
`drgn` has an interactive mode and a script mode. If no arguments are passed,
`drgn` runs in interactive mode; otherwise, the given script is run with the

View File

@ -16,12 +16,13 @@ from typing import Any, Dict, List, Tuple, Union
import drgn
from drgn.corereader import CoreReader
from drgn.dwarfindex import DwarfIndex
from drgn.elf import ElfFile, ET_CORE, PT_LOAD
from drgn.elf import ElfFile, ET_CORE, NT_FILE, PT_LOAD
from drgn.kernelvariableindex import KernelVariableIndex
from drgn.program import Program, ProgramObject
from drgn.type import Type
from drgn.typeindex import DwarfTypeIndex, TypeIndex
from drgn.variableindex import VariableIndex
from drgn.util import FileMapping
from drgn.variableindex import UserspaceVariableIndex, VariableIndex
def displayhook(value: Any) -> None:
@ -78,7 +79,7 @@ def read_vmcoreinfo_from_sysfs(core_reader: CoreReader) -> bytes:
# formats). We can ignore the type.
namesz, descsz = struct.unpack_from('=II', note)
if namesz != 11 or note[12:22] != b'VMCOREINFO':
sys.exit('VMCOREINFO is invalid')
sys.exit('VMCOREINFO in /sys/kernel/vmcoreinfo is invalid')
# The name is padded up to 4 bytes, so the descriptor starts at
# byte 24.
return note[24:24 + descsz]
@ -128,17 +129,25 @@ def index_kernel(vmcoreinfo: Dict[str, Any],
return type_index, variable_index
def index_program(file_mappings: List[FileMapping],
verbose: bool) -> Tuple[TypeIndex, VariableIndex]:
dwarf_index = DwarfIndex(*{mapping.path for mapping in file_mappings})
type_index = DwarfTypeIndex(dwarf_index)
return type_index, UserspaceVariableIndex(type_index, file_mappings)
def main() -> None:
python_version = '.'.join(str(v) for v in sys.version_info[:3])
version = f'drgn {drgn.__version__} (using Python {python_version})'
parser = argparse.ArgumentParser(
prog='drgn', description='Scriptable debugger')
parser.add_argument(
'-k', '--kernel', action='store_true',
help='debug the kernel instead of a userspace program')
parser.add_argument(
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
'-c', '--core', metavar='PATH', type=str,
help='use the given core file (default: /proc/kcore in kernel mode)')
help='debug the given core dump')
group.add_argument(
'-k', '--kernel', action='store_const', const='/proc/kcore', dest='core',
help='debug the running kernel')
parser.add_argument(
'script', metavar='ARG', type=str, nargs='*',
help='script to execute instead of running in interactive mode')
@ -146,12 +155,6 @@ def main() -> None:
args = parser.parse_args()
if not args.kernel:
sys.exit('Only --kernel mode is currently implemented')
if args.core is None:
args.core = '/proc/kcore'
with open(args.core, 'rb') as core_file:
core_elf_file = ElfFile(core_file)
if core_elf_file.ehdr.e_type != ET_CORE:
@ -162,18 +165,29 @@ def main() -> None:
if phdr.p_type == PT_LOAD]
core_reader = CoreReader(core_file.fileno(), segments)
nt_file_data = None
vmcoreinfo_data = None
if os.path.abspath(args.core) == '/proc/kcore':
vmcoreinfo_data = read_vmcoreinfo_from_sysfs(core_reader)
else:
for name, _, vmcoreinfo_data in core_elf_file.notes():
if name == b'VMCOREINFO':
for note in core_elf_file.notes():
if note.name == b'CORE' and note.type == NT_FILE:
nt_file_data = note.data
break
elif note.name == b'VMCOREINFO':
vmcoreinfo_data = note.data
break
else:
sys.exit('Could not find VMCOREINFO note; not a kernel vmcore?')
vmcoreinfo = parse_vmcoreinfo(vmcoreinfo_data)
type_index, variable_index = index_kernel(vmcoreinfo,
verbose=not args.script)
if vmcoreinfo_data is not None:
vmcoreinfo = parse_vmcoreinfo(vmcoreinfo_data)
type_index, variable_index = index_kernel(
vmcoreinfo, verbose=not args.script)
elif nt_file_data is not None:
file_mappings = core_elf_file.parse_nt_file(nt_file_data)
type_index, variable_index = index_program(
file_mappings, verbose=not args.script)
else:
sys.exit('Core dump has no NT_FILE or VMCOREINFO note')
prog = Program(reader=core_reader, type_index=type_index,
variable_index=variable_index)

View File

@ -2,8 +2,11 @@
# SPDX-License-Identifier: GPL-3.0+
from collections import namedtuple
import os
import struct
from typing import BinaryIO, Dict, List, NamedTuple, Optional
from typing import BinaryIO, Dict, List, NamedTuple, Optional, Tuple
from drgn.util import FileMapping
# Automatically generated from elf.h
@ -111,6 +114,65 @@ PT_HISUNW = 0x6fffffff
PT_HIOS = 0x6fffffff
PT_LOPROC = 0x70000000
PT_HIPROC = 0x7fffffff
NT_PRSTATUS = 1
NT_FPREGSET = 2
NT_PRPSINFO = 3
NT_PRXREG = 4
NT_TASKSTRUCT = 4
NT_PLATFORM = 5
NT_AUXV = 6
NT_GWINDOWS = 7
NT_ASRS = 8
NT_PSTATUS = 10
NT_PSINFO = 13
NT_PRCRED = 14
NT_UTSNAME = 15
NT_LWPSTATUS = 16
NT_LWPSINFO = 17
NT_PRFPXREG = 20
NT_SIGINFO = 0x53494749
NT_FILE = 0x46494c45
NT_PRXFPREG = 0x46e62b7f
NT_PPC_VMX = 0x100
NT_PPC_SPE = 0x101
NT_PPC_VSX = 0x102
NT_PPC_TAR = 0x103
NT_PPC_PPR = 0x104
NT_PPC_DSCR = 0x105
NT_PPC_EBB = 0x106
NT_PPC_PMU = 0x107
NT_PPC_TM_CGPR = 0x108
NT_PPC_TM_CFPR = 0x109
NT_PPC_TM_CVMX = 0x10a
NT_PPC_TM_CVSX = 0x10b
NT_PPC_TM_SPR = 0x10c
NT_PPC_TM_CTAR = 0x10d
NT_PPC_TM_CPPR = 0x10e
NT_PPC_TM_CDSCR = 0x10f
NT_386_TLS = 0x200
NT_386_IOPERM = 0x201
NT_X86_XSTATE = 0x202
NT_S390_HIGH_GPRS = 0x300
NT_S390_TIMER = 0x301
NT_S390_TODCMP = 0x302
NT_S390_TODPREG = 0x303
NT_S390_CTRS = 0x304
NT_S390_PREFIX = 0x305
NT_S390_LAST_BREAK = 0x306
NT_S390_SYSTEM_CALL = 0x307
NT_S390_TDB = 0x308
NT_ARM_VFP = 0x400
NT_ARM_TLS = 0x401
NT_ARM_HW_BREAK = 0x402
NT_ARM_HW_WATCH = 0x403
NT_ARM_SYSTEM_CALL = 0x404
NT_ARM_SVE = 0x405
NT_VERSION = 1
NT_GNU_ABI_TAG = 1
NT_GNU_HWCAP = 2
NT_GNU_BUILD_ID = 3
NT_GNU_GOLD_VERSION = 4
NT_GNU_PROPERTY_TYPE_0 = 5
SHN_MIPS_ACOMMON = 0xff00
SHN_MIPS_TEXT = 0xff01
SHN_MIPS_DATA = 0xff02
@ -435,3 +497,32 @@ class ElfFile:
symbols[symbol_name] = [sym]
self._symbols = symbols
return self._symbols
def parse_nt_file(self, data: bytes) -> List[FileMapping]:
if self.ehdr.e_ident[EI_DATA] == ELFDATA2LSB:
fmt = '<'
else:
fmt = '>'
if self.ehdr.e_ident[EI_CLASS] == ELFCLASS64:
header_fmt = fmt + 'QQ'
fmt += 'QQQ'
else:
header_fmt = fmt + 'II'
fmt += 'III'
header_size = struct.calcsize(header_fmt)
count, page_size = struct.unpack_from(header_fmt, data)
i = header_size + struct.calcsize(fmt) * count
list = []
for start, end, offset in struct.iter_unpack('=QQQ', data[header_size:i]):
if i >= len(data):
raise ElfFormatError('invalid NT_FILE note')
try:
j = data.index(b'\0', i)
except ValueError:
j = len(data)
path = os.fsdecode(data[i:j])
i = j + 1
list.append(FileMapping(path, start, end, page_size * offset))
return list

View File

@ -1,7 +1,14 @@
# Copyright 2018 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
from typing import Iterable
from typing import Iterable, NamedTuple
class FileMapping(NamedTuple):
path: str
start: int
end: int
file_offset: int
def escape_character(c: int, escape_single_quote: bool = False,

View File

@ -1,11 +1,13 @@
# Copyright 2018 - Omar Sandoval
# SPDX-License-Identifier: GPL-3.0+
from typing import cast, Any, Optional, Tuple
from typing import cast, Any, List, Optional, Tuple
from drgn.elf import PT_LOAD
from drgn.dwarf import Die, DW_TAG, DwarfAttribNotFoundError
from drgn.type import Type, EnumType
from drgn.typeindex import DwarfTypeIndex, TypeIndex
from drgn.util import FileMapping
class VariableIndex:
@ -57,3 +59,34 @@ class DwarfVariableIndex(VariableIndex):
else: # die.tag == DW_TAG.enumeration_type
type_ = cast(EnumType, self._type_index.find_dwarf_type(die))
return type_, getattr(type_.enum, name), None
class UserspaceVariableIndex(DwarfVariableIndex):
def __init__(self, type_index: DwarfTypeIndex,
file_mappings: List[FileMapping]) -> None:
super().__init__(type_index)
self._file_mappings = file_mappings
def _find_variable_address(self, name: str, die: Die) -> int:
address = die.location()
dwarf_file = die.cu.dwarf_file
path = dwarf_file.path
elf_file = dwarf_file.elf_file
for phdr in elf_file.phdrs:
if phdr.p_type != PT_LOAD:
continue
if phdr.p_vaddr <= address < phdr.p_vaddr + phdr.p_memsz:
break
else:
raise ValueError(f'Could not find segment containing {name}')
file_offset = phdr.p_offset + address - phdr.p_vaddr
for mapping in self._file_mappings:
mapping_size = mapping.end - mapping.start
if (mapping.path == path and
mapping.file_offset <= file_offset <
mapping.file_offset + mapping_size):
return mapping.start + file_offset - mapping.file_offset
else:
raise ValueError(f'Could not find file mapping containing {name}')

View File

@ -10,6 +10,7 @@ constant_prefixes = [
'ELFMAG',
'ET_',
'EV_',
'NT_',
'PT_',
'SHN_',
'SHT_',