mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-23 09:43:06 +00:00
cli/probe: start implementing variable fetching
Resolving parameters, variables with function scope, and global variables should work. This is just the variable resolution, no fetching yet, but a bunch of refactors snuck in here so committing it all now.
This commit is contained in:
parent
e6a58f533a
commit
682fd172a4
@ -1,3 +1,4 @@
|
||||
from drgn import lldwarf
|
||||
from drgn.dwarf import DwarfFile
|
||||
from drgn.dwarf.defs import *
|
||||
import fnmatch
|
||||
@ -19,7 +20,8 @@ def dump_cu(dwarf_file, cu, cu_name, *, indent=0):
|
||||
print(f'{prefix} is_64_bit = {cu.is_64_bit}')
|
||||
|
||||
|
||||
def dump_die(dwarf_file, die, *, indent=0, recurse=False):
|
||||
def dump_die(dwarf_file: DwarfFile, die: lldwarf.DwarfDie, *,
|
||||
indent: int=0, recurse: bool=False) -> None:
|
||||
prefix = ' ' * indent
|
||||
print(f'{prefix}<{die.cu_offset}> {tag_name(die.tag)}')
|
||||
for name, form, value in die:
|
||||
@ -29,14 +31,8 @@ def dump_die(dwarf_file, die, *, indent=0, recurse=False):
|
||||
value = repr(value)[1:]
|
||||
print(f'{prefix} {at_name(name)} ({form_name(form)}) = {value}')
|
||||
if recurse:
|
||||
try:
|
||||
children = die.children
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if children is not None:
|
||||
for child in children:
|
||||
dump_die(dwarf_file, child, indent=indent + 2, recurse=True)
|
||||
for child in dwarf_file.die_children(die):
|
||||
dump_die(dwarf_file, child, indent=indent + 2, recurse=True)
|
||||
|
||||
|
||||
def dump_lnp_include_directories(lnp, *, indent=0):
|
||||
@ -139,9 +135,9 @@ def dump_line_number_matrix(cu, lnp, matrix, *, indent=0):
|
||||
print(f'{prefix}}}')
|
||||
|
||||
|
||||
def dump_cus(dwarf_file, args):
|
||||
def dump_cus(dwarf_file: DwarfFile, args) -> None:
|
||||
for cu in dwarf_file.cu_headers():
|
||||
cu_name = dwarf_file.cu_name(cu).decode()
|
||||
cu_name = dwarf_file.cu_name(cu)
|
||||
for pattern in args.cu:
|
||||
if fnmatch.fnmatch(cu_name, pattern):
|
||||
break
|
||||
@ -151,8 +147,6 @@ def dump_cus(dwarf_file, args):
|
||||
dump_cu(dwarf_file, cu, cu_name)
|
||||
if args.die:
|
||||
die = dwarf_file.cu_die(cu)
|
||||
if args.recursive:
|
||||
dwarf_file.parse_die_children(die, recurse=True)
|
||||
dump_die(dwarf_file, die, indent=2, recurse=args.recursive)
|
||||
if (args.include_directories or args.file_names or args.lines or
|
||||
args.line_number_program):
|
||||
|
@ -1,4 +1,5 @@
|
||||
from drgn.dwarf import DwarfProgram
|
||||
from drgn.dwarf.defs import *
|
||||
from drgn.ftrace import Kprobe, FtraceInstance
|
||||
import re
|
||||
import os
|
||||
@ -26,9 +27,26 @@ def cmd_probe(args):
|
||||
binary = f'/lib/modules/{os.uname().release}/build/vmlinux'
|
||||
with DwarfProgram(binary) as dwarf_program:
|
||||
if function is not None:
|
||||
scope = dwarf_program.find_subprogram_by_name(function)
|
||||
probe_addr = scope.cu.file.die_address(scope)
|
||||
probe_location = function
|
||||
else:
|
||||
probe_location = dwarf_program.find_breakpoint_location(filename, lineno)
|
||||
cu = dwarf_program.find_cu_by_name(filename)
|
||||
row = dwarf_program.find_breakpoint(cu, filename, lineno)
|
||||
scope = dwarf_program.find_scope_containing_address(cu, row.address)
|
||||
|
||||
subprogram = scope
|
||||
while subprogram.tag != DW_TAG.subprogram:
|
||||
subprogram = subprogram.parent
|
||||
subprogram_name = cu.file.die_name(subprogram)
|
||||
subprogram_addr = cu.file.die_address(subprogram)
|
||||
|
||||
assert row.address >= subprogram_addr
|
||||
probe_addr = row.address
|
||||
probe_location = f'{subprogram_name}+0x{row.address - subprogram_addr:x}'
|
||||
|
||||
for var in args.variables:
|
||||
resolved = dwarf_program.resolve_variable(scope, var)
|
||||
|
||||
# TODO: deal with probe name collisions
|
||||
with FtraceInstance(f'drgn_{os.getpid()}') as instance, \
|
||||
@ -49,6 +67,9 @@ def register(subparsers):
|
||||
'location', metavar='LOCATION',
|
||||
help='location to probe; either a function name or file:line')
|
||||
|
||||
subparser.add_argument(
|
||||
'variables', metavar='VAR', nargs='*', help='variables to fetch')
|
||||
|
||||
group = subparser.add_mutually_exclusive_group()
|
||||
group.add_argument(
|
||||
'--line', '-l', action='store_true',
|
||||
|
@ -38,6 +38,8 @@ class DwarfFile:
|
||||
self._sections = parse_elf_sections(self._mmap, self._ehdr)
|
||||
|
||||
self._abbrev_tables = {}
|
||||
self._cus = {}
|
||||
self._range_lists = {}
|
||||
self._symbols = None
|
||||
|
||||
def close(self):
|
||||
@ -60,7 +62,7 @@ class DwarfFile:
|
||||
def section(self, name: str):
|
||||
return self._sections[name]
|
||||
|
||||
def string_at(self, offset):
|
||||
def string_at(self, offset: int) -> bytes:
|
||||
nul = self._mmap.find(b'\0', offset)
|
||||
assert nul != -1 # XXX
|
||||
return self._mmap[offset:nul]
|
||||
@ -124,28 +126,32 @@ class DwarfFile:
|
||||
|
||||
# Compilation units
|
||||
|
||||
def cu_headers(self):
|
||||
debug_info = self.section('.debug_info')
|
||||
offset = debug_info.sh_offset
|
||||
end = debug_info.sh_offset + debug_info.sh_size
|
||||
while offset < end:
|
||||
cu = lldwarf.parse_compilation_unit_header(self._mmap, offset)
|
||||
cu.offset = offset
|
||||
yield cu
|
||||
offset += (12 if cu.is_64_bit else 4) + cu.unit_length
|
||||
|
||||
def cu_header(self, offset: int) -> lldwarf.CompilationUnitHeader:
|
||||
try:
|
||||
return self._cus[offset]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
debug_info = self.section('.debug_info')
|
||||
offset += debug_info.sh_offset
|
||||
cu = lldwarf.parse_compilation_unit_header(self._mmap, offset)
|
||||
cu.file = self
|
||||
cu.offset = offset
|
||||
return cu
|
||||
|
||||
def cu_name(self, cu: lldwarf.CompilationUnitHeader) -> bytes:
|
||||
def cu_headers(self):
|
||||
debug_info = self.section('.debug_info')
|
||||
offset = 0
|
||||
while offset < debug_info.sh_size:
|
||||
cu = self.cu_header(offset)
|
||||
yield cu
|
||||
offset += (12 if cu.is_64_bit else 4) + cu.unit_length
|
||||
|
||||
def cu_name(self, cu: lldwarf.CompilationUnitHeader) -> str:
|
||||
try:
|
||||
return self.die_name(self.cu_die(cu))
|
||||
except KeyError:
|
||||
return b''
|
||||
return ''
|
||||
|
||||
# Debugging information entries
|
||||
|
||||
@ -164,28 +170,44 @@ class DwarfFile:
|
||||
cu.die = die
|
||||
return die
|
||||
|
||||
def parse_die_children(self, die: lldwarf.DwarfDie, *,
|
||||
recurse: bool=False) -> None:
|
||||
if not hasattr(die, 'children'):
|
||||
cu = die.cu
|
||||
debug_info = self.section('.debug_info')
|
||||
abbrev_table = self.abbrev_table(cu.debug_abbrev_offset)
|
||||
offset = cu.offset + die.cu_offset + die.die_length
|
||||
die.children = lldwarf.parse_die_siblings(cu, die, abbrev_table,
|
||||
cu.offset, self._mmap,
|
||||
offset=offset,
|
||||
recurse=recurse)
|
||||
def die_children(self, die: lldwarf.DwarfDie, *,
|
||||
recurse: bool=False) -> List[lldwarf.DwarfDie]:
|
||||
try:
|
||||
return die.children
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def die_contains_address(self, die: lldwarf.DwarfDie, address: int) -> bool:
|
||||
cu = die.cu
|
||||
debug_info = self.section('.debug_info')
|
||||
abbrev_table = self.abbrev_table(cu.debug_abbrev_offset)
|
||||
offset = cu.offset + die.cu_offset + die.die_length
|
||||
children = lldwarf.parse_die_siblings(cu, die, abbrev_table, cu.offset,
|
||||
self._mmap, offset=offset,
|
||||
recurse=recurse)
|
||||
die.children = children
|
||||
return children
|
||||
|
||||
def die_contains_address(self, die: lldwarf.DwarfDie, addr: int) -> bool:
|
||||
try:
|
||||
ranges_form, ranges_value = die.find(DW_AT.ranges)
|
||||
assert False
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
cu = die.cu
|
||||
base_addr = self.die_address(cu.die)
|
||||
offset = self.at_sec_offset(ranges_form, ranges_value)
|
||||
for range in self.range_list(cu.address_size, offset):
|
||||
if range.start == 0xffffffffffffffff:
|
||||
base_addr = range.end
|
||||
elif base_addr + range.start <= addr < base_addr + range.end:
|
||||
return True
|
||||
return False
|
||||
|
||||
try:
|
||||
low_pc_form, low_pc = die.find(DW_AT.low_pc)
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
high_pc_form, high_pc_value = die.find(DW_AT.high_pc)
|
||||
assert low_pc_form == DW_FORM.addr
|
||||
if at_class_constant_int(high_pc_form):
|
||||
@ -195,11 +217,11 @@ class DwarfFile:
|
||||
else:
|
||||
assert high_pc_form == DW_FORM.addr
|
||||
high_pc = high_pc_value
|
||||
return low_pc <= address < high_pc
|
||||
return low_pc <= addr < high_pc
|
||||
|
||||
def die_name(self, die: lldwarf.DwarfDie) -> bytes:
|
||||
def die_name(self, die: lldwarf.DwarfDie) -> str:
|
||||
form, value = die.find(DW_AT.name)
|
||||
return self.at_string(die.cu, form, value)
|
||||
return self.at_string(die.cu, form, value).decode()
|
||||
|
||||
def die_address(self, die: lldwarf.DwarfDie) -> int:
|
||||
try:
|
||||
@ -240,6 +262,11 @@ class DwarfFile:
|
||||
# Line number programs
|
||||
|
||||
def cu_line_number_program_header(self, cu: lldwarf.CompilationUnitHeader) -> lldwarf.LineNumberProgramHeader:
|
||||
try:
|
||||
return cu.lnp
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
debug_line = self.section('.debug_line')
|
||||
die = self.cu_die(cu)
|
||||
try:
|
||||
@ -249,18 +276,26 @@ class DwarfFile:
|
||||
offset = debug_line.sh_offset + self.at_sec_offset(form, value)
|
||||
lnp = lldwarf.parse_line_number_program_header(self._mmap, offset)
|
||||
lnp.offset = offset
|
||||
cu.lnp = lnp
|
||||
return lnp
|
||||
|
||||
def execute_line_number_program(self, lnp: lldwarf.LineNumberProgramHeader) -> List[lldwarf.LineNumberRow]:
|
||||
return lldwarf.execute_line_number_program(lnp, lnp_end_offset(lnp),
|
||||
self._mmap,
|
||||
lnp_program_offset(lnp))
|
||||
try:
|
||||
return lnp.matrix
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
matrix= lldwarf.execute_line_number_program(lnp, lnp_end_offset(lnp),
|
||||
self._mmap,
|
||||
lnp_program_offset(lnp))
|
||||
lnp.matrix = matrix
|
||||
return matrix
|
||||
|
||||
def line_number_row_name(self, cu: lldwarf.CompilationUnitHeader,
|
||||
lnp: lldwarf.LineNumberProgramHeader,
|
||||
row: lldwarf.LineNumberRow) -> str:
|
||||
if row.file == 0:
|
||||
return self.cu_name(cu).decode()
|
||||
return self.cu_name(cu)
|
||||
|
||||
file_name, directory_index, mtime, file_size = lnp.file_names[row.file - 1]
|
||||
file_name = file_name.decode()
|
||||
@ -298,3 +333,17 @@ class DwarfFile:
|
||||
operation_advance = opcode // lnp.line_range
|
||||
line_increment = lnp.line_base + (opcode % lnp.line_range)
|
||||
yield 'special', opcode, (operation_advance, line_increment)
|
||||
|
||||
# Range lists
|
||||
|
||||
def range_list(self, address_size: int, offset: int) -> List[lldwarf.Range]:
|
||||
try:
|
||||
return self._range_lists[offset]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
debug_ranges = self.section('.debug_ranges')
|
||||
offset += debug_ranges.sh_offset
|
||||
ranges = lldwarf.parse_range_list(address_size, self._mmap, offset)
|
||||
self._range_lists[offset] = ranges
|
||||
return ranges
|
||||
|
@ -1,9 +1,19 @@
|
||||
import drgn.lldwarf as lldwarf
|
||||
from drgn import lldwarf
|
||||
from drgn.dwarf.defs import *
|
||||
from drgn.dwarf.file import DwarfFile
|
||||
from typing import List, Tuple
|
||||
|
||||
|
||||
def best_breakpoint(rows: List[lldwarf.LineNumberRow]) -> lldwarf.LineNumberRow:
|
||||
# The first row which is a statement, or the first row if none are
|
||||
# statements.
|
||||
for row in rows:
|
||||
if row.is_stmt:
|
||||
return row
|
||||
else:
|
||||
return rows[0]
|
||||
|
||||
|
||||
class DwarfProgram:
|
||||
def __init__(self, path):
|
||||
self._closed = False
|
||||
@ -24,82 +34,81 @@ class DwarfProgram:
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.close()
|
||||
|
||||
def find_cu_by_name(self, name: str) -> Tuple[DwarfFile, lldwarf.CompilationUnitHeader]:
|
||||
def find_cu_by_name(self, name: str) -> lldwarf.CompilationUnitHeader:
|
||||
for cu in self._file.cu_headers():
|
||||
die = self._file.cu_die(cu)
|
||||
try:
|
||||
cu_name = self._file.die_name(die).decode()
|
||||
except KeyError:
|
||||
continue
|
||||
if cu_name == name:
|
||||
return self._file, cu
|
||||
if self._file.cu_name(cu) == name:
|
||||
return cu
|
||||
else:
|
||||
raise ValueError('CU not found')
|
||||
|
||||
def find_cu_by_addr(self, addr: int) -> Tuple[DwarfFile, lldwarf.CompilationUnitHeader]:
|
||||
def find_cu_by_addr(self, addr: int) -> lldwarf.CompilationUnitHeader:
|
||||
dwarf_file = self._file
|
||||
for art in dwarf_file.arange_table_headers():
|
||||
for arange in dwarf_file.arange_table(art):
|
||||
if arange.address <= addr <= arange.address + arange.length:
|
||||
return dwarf_file, dwarf_file.cu_header(art.debug_info_offset)
|
||||
return dwarf_file.cu_header(art.debug_info_offset)
|
||||
else:
|
||||
raise ValueError('CU containing address not found')
|
||||
|
||||
|
||||
def find_subprogram_by_name(self, name: str):
|
||||
dwarf_file = self._file
|
||||
symbol = dwarf_file.symbol(name)
|
||||
dwarf_file, cu = self.find_cu_by_addr(symbol.st_value)
|
||||
def find_subprogram_by_name(self, name: str) -> lldwarf.DwarfDie:
|
||||
symbol = self._file.symbol(name)
|
||||
cu = self.find_cu_by_addr(symbol.st_value)
|
||||
dwarf_file = cu.file
|
||||
die = self._file.cu_die(cu)
|
||||
dwarf_file.parse_die_children(die)
|
||||
for child in die.children:
|
||||
for child in dwarf_file.die_children(die):
|
||||
if (child.tag == DW_TAG.subprogram and
|
||||
dwarf_file.die_name(child).decode() == name):
|
||||
dwarf_file.die_name(child) == name):
|
||||
return child
|
||||
else:
|
||||
raise ValueError('subprogram not found')
|
||||
|
||||
@staticmethod
|
||||
def _best_breakpoint_row(dwarf_file: DwarfFile,
|
||||
cu: lldwarf.CompilationUnitHeader,
|
||||
lnp: lldwarf.LineNumberProgramHeader,
|
||||
matrix: List[lldwarf.LineNumberRow],
|
||||
filename: str,
|
||||
lineno: int) -> lldwarf.LineNumberRow:
|
||||
# Find the first row which is a statement, or the first row if none are
|
||||
# statements.
|
||||
first_row = None
|
||||
for row in matrix:
|
||||
if (dwarf_file.line_number_row_name(cu, lnp, row) == filename and row.line == lineno):
|
||||
if row.is_stmt:
|
||||
return row
|
||||
if first_row is None:
|
||||
first_row = row
|
||||
else:
|
||||
assert first_row is not None # XXX
|
||||
return first_row
|
||||
|
||||
@staticmethod
|
||||
def _find_subprogram_containing_address(dwarf_file: DwarfFile,
|
||||
cu: lldwarf.CompilationUnitHeader,
|
||||
addr: int) -> lldwarf.DwarfDie:
|
||||
def find_subprogram_containing_address(self, cu: lldwarf.CompilationUnitHeader,
|
||||
addr: int) -> lldwarf.DwarfDie:
|
||||
dwarf_file = cu.file
|
||||
die = dwarf_file.cu_die(cu)
|
||||
dwarf_file.parse_die_children(die)
|
||||
for child in die.children:
|
||||
for child in dwarf_file.die_children(die):
|
||||
if (child.tag == DW_TAG.subprogram and
|
||||
dwarf_file.die_contains_address(child, addr)):
|
||||
return child
|
||||
assert False # XXX
|
||||
|
||||
def find_breakpoint_location(self, filename: str, lineno: int) -> str:
|
||||
dwarf_file, cu = self.find_cu_by_name(filename)
|
||||
def find_scope_containing_address(self, cu: lldwarf.CompilationUnitHeader,
|
||||
addr: int) -> lldwarf.DwarfDie:
|
||||
dwarf_file = cu.file
|
||||
die = dwarf_file.cu_die(cu)
|
||||
assert dwarf_file.die_contains_address(die, addr)
|
||||
while True:
|
||||
for child in dwarf_file.die_children(die):
|
||||
if ((child.tag == DW_TAG.subprogram or child.tag == DW_TAG.lexical_block) and
|
||||
dwarf_file.die_contains_address(child, addr)):
|
||||
die = child
|
||||
break
|
||||
else:
|
||||
return die
|
||||
|
||||
def find_lines(self, cu: lldwarf.CompilationUnitHeader,
|
||||
filename: str, lineno: int) -> List[lldwarf.LineNumberRow]:
|
||||
dwarf_file = cu.file
|
||||
lnp = dwarf_file.cu_line_number_program_header(cu)
|
||||
matrix = dwarf_file.execute_line_number_program(lnp)
|
||||
|
||||
row = self._best_breakpoint_row(dwarf_file, cu, lnp, matrix, filename, lineno)
|
||||
rows = []
|
||||
for row in dwarf_file.execute_line_number_program(lnp):
|
||||
if (dwarf_file.line_number_row_name(cu, lnp, row) == filename and row.line == lineno):
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
subprogram = self._find_subprogram_containing_address(dwarf_file, cu, row.address)
|
||||
subprogram_name = dwarf_file.die_name(subprogram).decode()
|
||||
subprogram_address = dwarf_file.die_address(subprogram)
|
||||
assert row.address >= subprogram_address
|
||||
return f'{subprogram_name}+0x{row.address - subprogram_address:x}'
|
||||
def find_breakpoint(self, cu: lldwarf.CompilationUnitHeader,
|
||||
filename: str, lineno: int) -> lldwarf.LineNumberRow:
|
||||
return best_breakpoint(self.find_lines(cu, filename, lineno))
|
||||
|
||||
def resolve_variable(self, die: lldwarf.DwarfDie, var: str):
|
||||
dwarf_file = die.cu.file
|
||||
while die is not None:
|
||||
for child in dwarf_file.die_children(die):
|
||||
if ((child.tag == DW_TAG.formal_parameter or
|
||||
child.tag == DW_TAG.variable) and
|
||||
dwarf_file.die_name(child) == var):
|
||||
return child
|
||||
die = die.parent
|
||||
raise ValueError('could not resolve variable')
|
||||
|
@ -180,10 +180,9 @@ PyObject *LLDwarf_ParseArangeTable(Py_buffer *buffer, Py_ssize_t *offset,
|
||||
if (segment == 0 && address == 0 && length == 0)
|
||||
break;
|
||||
|
||||
arange = PyMem_Malloc(sizeof(AddressRange));
|
||||
arange = PyObject_New(AddressRange, &AddressRange_type);
|
||||
if (!arange)
|
||||
goto err;
|
||||
PyObject_Init((PyObject *)arange, &AddressRange_type);
|
||||
arange->segment = segment;
|
||||
arange->address = address;
|
||||
arange->length = length;
|
@ -153,7 +153,7 @@ static PyObject *DwarfDie_new(PyTypeObject *subtype, PyObject *args,
|
||||
Py_INCREF(Py_None);
|
||||
die->children = Py_None;
|
||||
} else {
|
||||
die->children = PySequence_List(children);
|
||||
die->children = PySequence_Tuple(children);
|
||||
if (!die->children)
|
||||
goto err;
|
||||
}
|
||||
@ -517,11 +517,7 @@ PyObject *LLDwarf_ParseDieSiblings(Py_buffer *buffer, Py_ssize_t *offset,
|
||||
PyObject *abbrev_table, Py_ssize_t cu_offset,
|
||||
bool recurse)
|
||||
{
|
||||
PyObject *children;
|
||||
|
||||
children = PyList_New(0);
|
||||
if (!children)
|
||||
return NULL;
|
||||
PyObject *tmp = NULL;
|
||||
|
||||
for (;;) {
|
||||
PyObject *child;
|
||||
@ -533,17 +529,33 @@ PyObject *LLDwarf_ParseDieSiblings(Py_buffer *buffer, Py_ssize_t *offset,
|
||||
goto err;
|
||||
if (!child)
|
||||
break;
|
||||
if (PyList_Append(children, child) == -1) {
|
||||
if (!tmp) {
|
||||
tmp = PyList_New(0);
|
||||
if (!tmp) {
|
||||
Py_DECREF(child);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
if (PyList_Append(tmp, child) == -1) {
|
||||
Py_DECREF(child);
|
||||
goto err;
|
||||
}
|
||||
Py_DECREF(child);
|
||||
}
|
||||
|
||||
return children;
|
||||
if (tmp) {
|
||||
PyObject *children;
|
||||
|
||||
children = PyList_AsTuple(tmp);
|
||||
Py_DECREF(tmp);
|
||||
return children;
|
||||
} else {
|
||||
Py_INCREF(empty_tuple);
|
||||
return empty_tuple;
|
||||
}
|
||||
|
||||
err:
|
||||
Py_DECREF(children);
|
||||
Py_XDECREF(tmp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -611,8 +623,8 @@ PyObject *LLDwarf_ParseDie(Py_buffer *buffer, Py_ssize_t *offset,
|
||||
die->die_length = *offset - orig_offset;
|
||||
|
||||
if (!decl->children) {
|
||||
Py_INCREF(Py_None);
|
||||
die->children = Py_None;
|
||||
Py_INCREF(empty_tuple);
|
||||
die->children = empty_tuple;
|
||||
} else if (recurse || (jump_to_sibling && !sibling)) {
|
||||
die->children = LLDwarf_ParseDieSiblings(buffer, offset, cu,
|
||||
(PyObject *)die,
|
||||
@ -677,7 +689,7 @@ static PyMemberDef DwarfDie_members[] = {
|
||||
"cu_offset -- integer offset\n" \
|
||||
"die_length -- intger length\n" \
|
||||
"tag -- integer tag of the DIE\n" \
|
||||
"children -- list of children DIEs\n" \
|
||||
"children -- tuple of children DIEs\n" \
|
||||
"attribs -- iterable of (name, form, value) triples"
|
||||
|
||||
PyTypeObject DwarfDie_type = {
|
||||
|
@ -137,10 +137,25 @@ typedef struct {
|
||||
|
||||
extern PyTypeObject LineNumberRow_type;
|
||||
|
||||
typedef struct {
|
||||
PyObject_VAR_HEAD
|
||||
uint64_t start;
|
||||
uint64_t end;
|
||||
} Range;
|
||||
|
||||
extern PyTypeObject Range_type;
|
||||
|
||||
#ifdef TEST_LLDWARFOBJECT
|
||||
extern PyTypeObject TestObject_type;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This tuple is used for the children attribute of any DwarfDie with no
|
||||
* children. It's much cheaper to reuse this rather than create a new object
|
||||
* every time, and avoids special cases like if we used None.
|
||||
*/
|
||||
PyObject *empty_tuple;
|
||||
|
||||
void LLDwarfObject_dealloc(PyObject *self);
|
||||
int LLDwarfObject_traverse(PyObject *self, visitproc visit, void *arg);
|
||||
int LLDwarfObject_clear(PyObject *self);
|
||||
@ -170,6 +185,8 @@ PyObject *LLDwarf_ExecuteLineNumberProgram(Py_buffer *buffer,
|
||||
Py_ssize_t *offset,
|
||||
LineNumberProgramHeader *lnp,
|
||||
Py_ssize_t lnp_end_offset);
|
||||
PyObject *LLDwarf_ParseRangeList(Py_buffer *buffer, Py_ssize_t *offset,
|
||||
Py_ssize_t address_size);
|
||||
|
||||
int read_uleb128(Py_buffer *buffer, Py_ssize_t *offset, uint64_t *ret);
|
||||
int read_sleb128(Py_buffer *buffer, Py_ssize_t *offset, int64_t *ret);
|
||||
|
@ -380,6 +380,34 @@ static PyObject *execute_line_number_program(PyObject *self, PyObject *args,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PyObject *parse_range_list(PyObject *self, PyObject *args,
|
||||
PyObject *kwds)
|
||||
{
|
||||
static char *keywords[] = {
|
||||
"address_size", "buffer", "offset", NULL
|
||||
};
|
||||
Py_ssize_t address_size;
|
||||
Py_buffer buffer;
|
||||
Py_ssize_t offset = 0;
|
||||
PyObject *ret;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds,
|
||||
"ny*|n:execute_line_number_program",
|
||||
keywords, &address_size, &buffer,
|
||||
&offset))
|
||||
return NULL;
|
||||
|
||||
if (offset < 0) {
|
||||
PyErr_SetString(PyExc_ValueError, "offset cannot be negative");
|
||||
PyBuffer_Release(&buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = LLDwarf_ParseRangeList(&buffer, &offset, address_size);
|
||||
PyBuffer_Release(&buffer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PyMethodDef lldwarf_methods[] = {
|
||||
{"parse_uleb128", (PyCFunction)parse_uleb128,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
@ -474,6 +502,15 @@ static PyMethodDef lldwarf_methods[] = {
|
||||
"lnp_end_offset -- offset into the buffer where the line number program ends\n"
|
||||
"buffer -- readable source buffer\n"
|
||||
"offset -- optional offset into the buffer"},
|
||||
{"parse_range_list",
|
||||
(PyCFunction)parse_range_list,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"parse_range_list(address_size, buffer, offset=0) -> list of Range\n\n"
|
||||
"Parse a range list.\n\n"
|
||||
"Arguments:\n"
|
||||
"address_size -- size of an address in this range list\n"
|
||||
"buffer -- readable source buffer\n"
|
||||
"offset -- optional offset into the buffer"},
|
||||
{},
|
||||
};
|
||||
|
||||
@ -490,6 +527,10 @@ PyInit_lldwarf(void)
|
||||
{
|
||||
PyObject *m;
|
||||
|
||||
empty_tuple = PyTuple_New(0);
|
||||
if (!empty_tuple)
|
||||
return NULL;
|
||||
|
||||
if (PyType_Ready(&AbbrevDecl_type) < 0)
|
||||
return NULL;
|
||||
|
||||
@ -516,6 +557,10 @@ PyInit_lldwarf(void)
|
||||
if (PyType_Ready(&LineNumberRow_type) < 0)
|
||||
return NULL;
|
||||
|
||||
Range_type.tp_new = PyType_GenericNew;
|
||||
if (PyType_Ready(&Range_type) < 0)
|
||||
return NULL;
|
||||
|
||||
#ifdef TEST_LLDWARFOBJECT
|
||||
TestObject_type.tp_new = PyType_GenericNew;
|
||||
if (PyType_Ready(&TestObject_type) < 0)
|
||||
@ -549,6 +594,9 @@ PyInit_lldwarf(void)
|
||||
Py_INCREF(&LineNumberRow_type);
|
||||
PyModule_AddObject(m, "LineNumberRow", (PyObject *)&LineNumberRow_type);
|
||||
|
||||
Py_INCREF(&Range_type);
|
||||
PyModule_AddObject(m, "Range", (PyObject *)&Range_type);
|
||||
|
||||
#ifdef TEST_LLDWARFOBJECT
|
||||
Py_INCREF(&TestObject_type);
|
||||
PyModule_AddObject(m, "_TestObject", (PyObject *)&TestObject_type);
|
||||
|
116
lldwarf/ranges.c
Normal file
116
lldwarf/ranges.c
Normal file
@ -0,0 +1,116 @@
|
||||
#include "lldwarf.h"
|
||||
|
||||
PyObject *LLDwarf_ParseRangeList(Py_buffer *buffer, Py_ssize_t *offset,
|
||||
Py_ssize_t address_size)
|
||||
{
|
||||
PyObject *ranges;
|
||||
|
||||
ranges = PyList_New(0);
|
||||
if (!ranges)
|
||||
return NULL;
|
||||
|
||||
for (;;) {
|
||||
Range *range;
|
||||
uint64_t start, end;
|
||||
uint32_t tmp;
|
||||
int ret;
|
||||
|
||||
switch (address_size) {
|
||||
case 4:
|
||||
if (read_u32(buffer, offset, &tmp) == -1)
|
||||
goto err;
|
||||
if (tmp == UINT32_C(0xffffffff))
|
||||
start = UINT64_C(0xffffffffffffffff);
|
||||
else
|
||||
start = tmp;
|
||||
if (read_u32(buffer, offset, &tmp) == -1)
|
||||
goto err;
|
||||
end = tmp;
|
||||
break;
|
||||
case 8:
|
||||
if (read_u64(buffer, offset, &start) == -1)
|
||||
goto err;
|
||||
if (read_u64(buffer, offset, &end) == -1)
|
||||
goto err;
|
||||
break;
|
||||
default:
|
||||
PyErr_Format(PyExc_ValueError, "unsupported address size %ld",
|
||||
(long)address_size);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (start == 0 && end == 0)
|
||||
break;
|
||||
|
||||
range = PyObject_New(Range, &Range_type);
|
||||
if (!range)
|
||||
goto err;
|
||||
range->start = start;
|
||||
range->end = end;
|
||||
|
||||
ret = PyList_Append(ranges, (PyObject *)range);
|
||||
Py_DECREF((PyObject *)range);
|
||||
if (ret == -1)
|
||||
goto err;
|
||||
}
|
||||
|
||||
return ranges;
|
||||
|
||||
err:
|
||||
Py_DECREF(ranges);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PyMemberDef Range_members[] = {
|
||||
{"start", T_UINT64T, offsetof(Range, start), 0,
|
||||
"starting address (inclusive) of the range"},
|
||||
{"end", T_UINT64T, offsetof(Range, end), 0,
|
||||
"ending address (exclusive) of the range"},
|
||||
{},
|
||||
};
|
||||
|
||||
#define Range_DOC \
|
||||
"Range(address, length) -> new address range\n" \
|
||||
"Create a new address range.\n\n" \
|
||||
"Arguments:\n" \
|
||||
"start -- integer start address\n" \
|
||||
"end -- integer end address\n"
|
||||
|
||||
PyTypeObject Range_type = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"drgn.lldwarf.Range", /* tp_name */
|
||||
sizeof(Range), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
LLDwarfObject_dealloc, /* tp_dealloc */
|
||||
NULL, /* tp_print */
|
||||
NULL, /* tp_getattr */
|
||||
NULL, /* tp_setattr */
|
||||
NULL, /* tp_as_async */
|
||||
LLDwarfObject_repr, /* tp_repr */
|
||||
NULL, /* tp_as_number */
|
||||
NULL, /* tp_as_sequence */
|
||||
NULL, /* tp_as_mapping */
|
||||
NULL, /* tp_hash */
|
||||
NULL, /* tp_call */
|
||||
NULL, /* tp_str */
|
||||
NULL, /* tp_getattro */
|
||||
NULL, /* tp_setattro */
|
||||
NULL, /* tp_as_buffer */
|
||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
||||
Range_DOC, /* tp_doc */
|
||||
NULL, /* tp_traverse */
|
||||
NULL, /* tp_clear */
|
||||
LLDwarfObject_richcompare, /* tp_richcompare */
|
||||
0, /* tp_weaklistoffset */
|
||||
NULL, /* tp_iter */
|
||||
NULL, /* tp_iternext */
|
||||
NULL, /* tp_methods */
|
||||
Range_members, /* tp_members */
|
||||
NULL, /* tp_getset */
|
||||
NULL, /* tp_base */
|
||||
NULL, /* tp_dict */
|
||||
NULL, /* tp_descr_get */
|
||||
NULL, /* tp_descr_set */
|
||||
0, /* tp_dictoffset */
|
||||
LLDwarfObject_init, /* tp_init */
|
||||
};
|
3
setup.py
3
setup.py
@ -57,10 +57,11 @@ module = Extension(
|
||||
'lldwarf/module.c',
|
||||
'lldwarf/object.c',
|
||||
'lldwarf/abbrev.c',
|
||||
'lldwarf/arange.c',
|
||||
'lldwarf/aranges.c',
|
||||
'lldwarf/cu.c',
|
||||
'lldwarf/die.c',
|
||||
'lldwarf/line.c',
|
||||
'lldwarf/ranges.c',
|
||||
],
|
||||
extra_compile_args=['-DTEST_LLDWARFOBJECT'],
|
||||
)
|
||||
|
@ -162,9 +162,9 @@ class TestParseDie(unittest.TestCase):
|
||||
}
|
||||
|
||||
self.assertDie(header, abbrev_table, b'\x01\xff\xff\xff\xff\xff\xff\xff\x7f',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.addr, 2**63 - 1),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.addr, 2**63 - 1),)))
|
||||
self.assertDie(header32addr, abbrev_table, b'\x01\xff\xff\xff\x7f',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.addr, 2**31 - 1),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.addr, 2**31 - 1),)))
|
||||
|
||||
bogus_header = lldwarf.CompilationUnitHeader(
|
||||
unit_length=200,
|
||||
@ -186,15 +186,15 @@ class TestParseDie(unittest.TestCase):
|
||||
}
|
||||
|
||||
self.assertDie(header, abbrev_table, b'\x01\x04aaaa',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.block1, (2, 4)),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.block1, (2, 4)),)))
|
||||
self.assertDie(header, abbrev_table, b'\x02\x01\x00b',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.block2, (3, 1)),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.block2, (3, 1)),)))
|
||||
self.assertDie(header, abbrev_table, b'\x03\x10\x00\x00\x00' + b'z' * 16,
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.block4, (5, 16)),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.block4, (5, 16)),)))
|
||||
self.assertDie(header, abbrev_table, b'\x04\x03xyz',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.block, (2, 3)),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.block, (2, 3)),)))
|
||||
self.assertDie(header, abbrev_table, b'\x05\x0f012345678901234',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.exprloc, (2, 15)),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.exprloc, (2, 15)),)))
|
||||
with self.assertRaisesRegex(ValueError, 'attribute length too big'):
|
||||
lldwarf.parse_die(header, None, abbrev_table, 0, b'\x05\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01')
|
||||
|
||||
@ -207,16 +207,16 @@ class TestParseDie(unittest.TestCase):
|
||||
}
|
||||
|
||||
self.assertDie(header, abbrev_table, b'\x01a',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.data1, b'a'),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.data1, b'a'),)))
|
||||
|
||||
self.assertDie(header, abbrev_table, b'\x02ab',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.data2, b'ab'),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.data2, b'ab'),)))
|
||||
|
||||
self.assertDie(header, abbrev_table, b'\x03abcd',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.data4, b'abcd'),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.data4, b'abcd'),)))
|
||||
|
||||
self.assertDie(header, abbrev_table, b'\x04abcdefgh',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.data8, b'abcdefgh'),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.data8, b'abcdefgh'),)))
|
||||
|
||||
def test_constant(self):
|
||||
abbrev_table = {
|
||||
@ -225,10 +225,10 @@ class TestParseDie(unittest.TestCase):
|
||||
}
|
||||
|
||||
self.assertDie(header, abbrev_table, b'\x01\x64',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.udata, 100),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.udata, 100),)))
|
||||
|
||||
self.assertDie(header, abbrev_table, b'\x02\x7f',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.sdata, -1),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.sdata, -1),)))
|
||||
|
||||
with self.assertRaises(OverflowError):
|
||||
lldwarf.DwarfDie(None, None, 0, 0, DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.udata, 2**64),))
|
||||
@ -244,11 +244,11 @@ class TestParseDie(unittest.TestCase):
|
||||
}
|
||||
|
||||
self.assertDie(header, abbrev_table, b'\x01\x01',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.flag, True),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.flag, True),)))
|
||||
self.assertDie(header, abbrev_table, b'\x01\x00',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.flag, False),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.flag, False),)))
|
||||
self.assertDie(header, abbrev_table, b'\x02',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.flag_present, True),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.flag_present, True),)))
|
||||
|
||||
def test_reference(self):
|
||||
abbrev_table = {
|
||||
@ -261,17 +261,17 @@ class TestParseDie(unittest.TestCase):
|
||||
}
|
||||
|
||||
self.assertDie(header, abbrev_table, b'\x01\xff',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.ref1, 255),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.ref1, 255),)))
|
||||
self.assertDie(header, abbrev_table, b'\x02\x10\x27',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.ref2, 10000),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.ref2, 10000),)))
|
||||
self.assertDie(header, abbrev_table, b'\x03\x00\x00\x00\x80',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.ref4, 2**31),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.ref4, 2**31),)))
|
||||
self.assertDie(header, abbrev_table, b'\x04\x00\x00\x00\x00\x00\x00\x00\x80',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.ref8, 2**63),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.ref8, 2**63),)))
|
||||
self.assertDie(header, abbrev_table, b'\x05\x00\x00\x00\x00\x00\x00\x00\x80',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.ref_sig8, 2**63),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.ref_sig8, 2**63),)))
|
||||
self.assertDie(header, abbrev_table, b'\x06\x00',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.ref_udata, 0),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.ref_udata, 0),)))
|
||||
|
||||
def test_sec_offset(self):
|
||||
abbrev_table = {
|
||||
@ -279,9 +279,9 @@ class TestParseDie(unittest.TestCase):
|
||||
}
|
||||
|
||||
self.assertDie(header, abbrev_table, b'\x01\xff\xff\xff\x7f',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.sec_offset, 2**31 - 1),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.sec_offset, 2**31 - 1),)))
|
||||
self.assertDie(header64, abbrev_table, b'\x01\xff\xff\xff\xff\xff\xff\xff\x7f',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.sec_offset, 2**63 - 1),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.sec_offset, 2**63 - 1),)))
|
||||
|
||||
def test_strp(self):
|
||||
abbrev_table = {
|
||||
@ -289,9 +289,9 @@ class TestParseDie(unittest.TestCase):
|
||||
}
|
||||
|
||||
self.assertDie(header, abbrev_table, b'\x01\xff\xff\xff\x7f',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.strp, 2**31 - 1),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.strp, 2**31 - 1),)))
|
||||
self.assertDie(header64, abbrev_table, b'\x01\xff\xff\xff\xff\xff\xff\xff\x7f',
|
||||
(DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.strp, 2**63 - 1),)))
|
||||
(DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.strp, 2**63 - 1),)))
|
||||
|
||||
def test_string(self):
|
||||
abbrev_table = {
|
||||
@ -301,7 +301,7 @@ class TestParseDie(unittest.TestCase):
|
||||
}
|
||||
|
||||
self.assertDie(header, abbrev_table, b'\x01foo\0asdf\0',
|
||||
(DW_TAG.lo_user, None,
|
||||
(DW_TAG.lo_user, (),
|
||||
((DW_AT.lo_user, DW_FORM.string, (1, 3)),
|
||||
(DW_AT.lo_user, DW_FORM.string, (5, 4)))))
|
||||
|
||||
@ -328,10 +328,10 @@ class TestParseDie(unittest.TestCase):
|
||||
2: lldwarf.AbbrevDecl(DW_TAG.lo_user + 1, False, ((DW_AT.lo_user + 1, DW_FORM.sdata),)),
|
||||
}
|
||||
siblings = lldwarf.parse_die_siblings(header, None, abbrev_table, 0, b'\x01\x01\x02\x02\x00')
|
||||
self.assertEqual(siblings, [
|
||||
lldwarf.DwarfDie(header, None, 0, 2, DW_TAG.lo_user, None, ((DW_AT.lo_user, DW_FORM.udata, 1),)),
|
||||
lldwarf.DwarfDie(header, None, 2, 2, DW_TAG.lo_user + 1, None, ((DW_AT.lo_user + 1, DW_FORM.sdata, 2),)),
|
||||
])
|
||||
self.assertEqual(siblings, (
|
||||
lldwarf.DwarfDie(header, None, 0, 2, DW_TAG.lo_user, (), ((DW_AT.lo_user, DW_FORM.udata, 1),)),
|
||||
lldwarf.DwarfDie(header, None, 2, 2, DW_TAG.lo_user + 1, (), ((DW_AT.lo_user + 1, DW_FORM.sdata, 2),)),
|
||||
))
|
||||
|
||||
def test_siblings_skip(self):
|
||||
abbrev_table = {
|
||||
@ -341,7 +341,7 @@ class TestParseDie(unittest.TestCase):
|
||||
siblings = lldwarf.parse_die_siblings(header, None, abbrev_table, 0, b'\x01\x04\x02\x02\x02\x03\x00')
|
||||
parent_die = lldwarf.DwarfDie(header, None, 0, 2, DW_TAG.lo_user, None, ((DW_AT.sibling, DW_FORM.udata, 4),))
|
||||
del parent_die.children
|
||||
self.assertEqual(siblings, [
|
||||
self.assertEqual(siblings, (
|
||||
parent_die,
|
||||
lldwarf.DwarfDie(header, None, 4, 2, DW_TAG.lo_user + 1, None, ((DW_AT.lo_user + 1, DW_FORM.sdata, 3),)),
|
||||
])
|
||||
lldwarf.DwarfDie(header, None, 4, 2, DW_TAG.lo_user + 1, (), ((DW_AT.lo_user + 1, DW_FORM.sdata, 3),)),
|
||||
))
|
||||
|
Loading…
Reference in New Issue
Block a user