mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-23 09:43:06 +00:00
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:
parent
631f50a2f6
commit
c2d51fe295
@ -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
|
||||
|
56
drgn/cli.py
56
drgn/cli.py
@ -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)
|
||||
|
93
drgn/elf.py
93
drgn/elf.py
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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}')
|
||||
|
@ -10,6 +10,7 @@ constant_prefixes = [
|
||||
'ELFMAG',
|
||||
'ET_',
|
||||
'EV_',
|
||||
'NT_',
|
||||
'PT_',
|
||||
'SHN_',
|
||||
'SHT_',
|
||||
|
Loading…
Reference in New Issue
Block a user