mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-24 18:03:07 +00:00
e5874ad18a
libdwfl is the elfutils "DWARF frontend library". It has high-level functionality for looking up symbols, walking stack traces, etc. In order to use this functionality, we need to report our debugging information through libdwfl. For userspace programs, libdwfl has a much better implementation than drgn for automatically finding debug information from a core dump or PID. However, for the kernel, libdwfl has a few issues: - It only supports finding debug information for the running kernel, not vmcores. - It determines the vmlinux address range by reading /proc/kallsyms, which is slow (~70ms on my machine). - If separate debug information isn't available for a kernel module, it finds it by walking /lib/modules/$(uname -r)/kernel; this is repeated for every module. - It doesn't find kernel modules with names containing both dashes and underscores (e.g., aes-x86_64). Luckily, drgn already solved all of these problems, and with some effort, we can keep doing it ourselves and report it to libdwfl. The conversion replaces a bunch of code for dealing with userspace core dump notes, /proc/$pid/maps, and relocations.
216 lines
6.5 KiB
Python
216 lines
6.5 KiB
Python
from collections import namedtuple
|
|
import os.path
|
|
|
|
from tests.elf import ET, PT, SHT
|
|
from tests.elfwriter import ElfSection, create_elf_file
|
|
from tests.dwarf import DW_AT, DW_FORM, DW_TAG
|
|
|
|
|
|
DwarfAttrib = namedtuple('DwarfAttrib', ['name', 'form', 'value'])
|
|
DwarfDie = namedtuple('DwarfAttrib', ['tag', 'attribs', 'children'])
|
|
DwarfDie.__new__.__defaults__ = (None,)
|
|
|
|
|
|
def _append_uleb128(buf, value):
|
|
while True:
|
|
byte = value & 0x7f
|
|
value >>= 7
|
|
if value:
|
|
buf.append(byte | 0x80)
|
|
else:
|
|
buf.append(byte)
|
|
break
|
|
|
|
|
|
def _append_sleb128(buf, value):
|
|
while True:
|
|
byte = value & 0x7f
|
|
value >>= 7
|
|
if (not value and not (byte & 0x40)) or (value == -1 and (byte & 0x40)):
|
|
buf.append(byte)
|
|
break
|
|
else:
|
|
buf.append(byte | 0x80)
|
|
|
|
|
|
def _compile_debug_abbrev(cu_die):
|
|
buf = bytearray()
|
|
code = 1
|
|
def aux(die):
|
|
nonlocal code
|
|
_append_uleb128(buf, code)
|
|
code += 1
|
|
_append_uleb128(buf, die.tag)
|
|
buf.append(bool(die.children))
|
|
for attrib in die.attribs:
|
|
_append_uleb128(buf, attrib.name)
|
|
_append_uleb128(buf, attrib.form)
|
|
buf.append(0)
|
|
buf.append(0)
|
|
if die.children:
|
|
for child in die.children:
|
|
aux(child)
|
|
aux(cu_die)
|
|
buf.append(0)
|
|
return buf
|
|
|
|
|
|
def _compile_debug_info(cu_die, little_endian, bits):
|
|
buf = bytearray()
|
|
byteorder = 'little' if little_endian else 'big'
|
|
|
|
buf.extend(b'\0\0\0\0') # unit_length
|
|
buf.extend((4).to_bytes(2, byteorder)) # version
|
|
buf.extend((0).to_bytes(4, byteorder)) # debug_abbrev_offset
|
|
buf.append(bits // 8) # address_size
|
|
|
|
die_offsets = []
|
|
relocations = []
|
|
code = 1
|
|
decl_file = 1
|
|
def aux(die, depth):
|
|
nonlocal code, decl_file
|
|
if depth == 1:
|
|
die_offsets.append(len(buf))
|
|
_append_uleb128(buf, code)
|
|
code += 1
|
|
for attrib in die.attribs:
|
|
if attrib.name == DW_AT.decl_file:
|
|
value = decl_file
|
|
decl_file += 1
|
|
else:
|
|
value = attrib.value
|
|
if attrib.form == DW_FORM.addr:
|
|
buf.extend(value.to_bytes(bits // 8, byteorder))
|
|
elif attrib.form == DW_FORM.data1:
|
|
buf.append(value)
|
|
elif attrib.form == DW_FORM.udata:
|
|
_append_uleb128(buf, value)
|
|
elif attrib.form == DW_FORM.sdata:
|
|
_append_sleb128(buf, value)
|
|
elif attrib.form == DW_FORM.string:
|
|
buf.extend(value.encode())
|
|
buf.append(0)
|
|
elif attrib.form == DW_FORM.ref4:
|
|
relocations.append((len(buf), value))
|
|
buf.extend(b'\0\0\0\0')
|
|
elif attrib.form == DW_FORM.sec_offset:
|
|
buf.extend(b'\0\0\0\0')
|
|
elif attrib.form == DW_FORM.flag_present:
|
|
pass
|
|
elif attrib.form == DW_FORM.exprloc:
|
|
_append_uleb128(buf, len(value))
|
|
buf.extend(value)
|
|
else:
|
|
assert False, attrib.form
|
|
if die.children:
|
|
for child in die.children:
|
|
aux(child, depth + 1)
|
|
buf.append(0)
|
|
aux(cu_die, 0)
|
|
|
|
unit_length = len(buf) - 4
|
|
buf[:4] = unit_length.to_bytes(4, byteorder)
|
|
|
|
for offset, index in relocations:
|
|
buf[offset:offset + 4] = die_offsets[index].to_bytes(4, byteorder)
|
|
return buf
|
|
|
|
|
|
def _compile_debug_line(cu_die, little_endian):
|
|
buf = bytearray()
|
|
byteorder = 'little' if little_endian else 'big'
|
|
|
|
buf.extend(b'\0\0\0\0') # unit_length
|
|
buf.extend((4).to_bytes(2, byteorder)) # version
|
|
buf.extend(b'\0\0\0\0') # header_length
|
|
buf.append(1) # minimum_instruction_length
|
|
buf.append(1) # maximum_operations_per_instruction
|
|
buf.append(1) # default_is_stmt
|
|
buf.append(1) # line_base
|
|
buf.append(1) # line_range
|
|
buf.append(1) # opcode_base
|
|
# Don't need standard_opcode_length
|
|
|
|
def compile_include_directories(die):
|
|
for attrib in die.attribs:
|
|
if attrib.name != DW_AT.decl_file:
|
|
continue
|
|
dirname = os.path.dirname(attrib.value)
|
|
if dirname:
|
|
buf.extend(dirname.encode('ascii'))
|
|
buf.append(0)
|
|
if die.children:
|
|
for child in die.children:
|
|
compile_include_directories(child)
|
|
compile_include_directories(cu_die)
|
|
buf.append(0)
|
|
|
|
decl_file = 1
|
|
directory = 1
|
|
def compile_file_names(die):
|
|
nonlocal decl_file, directory
|
|
for attrib in die.attribs:
|
|
if attrib.name != DW_AT.decl_file:
|
|
continue
|
|
dirname, basename = os.path.split(attrib.value)
|
|
buf.extend(basename.encode('ascii'))
|
|
buf.append(0)
|
|
# directory index
|
|
if dirname:
|
|
_append_uleb128(buf, directory)
|
|
directory += 1
|
|
else:
|
|
_append_uleb128(buf, 0)
|
|
_append_uleb128(buf, 0) # mtime
|
|
_append_uleb128(buf, 0) # size
|
|
if die.children:
|
|
for child in die.children:
|
|
compile_file_names(child)
|
|
compile_file_names(cu_die)
|
|
buf.append(0)
|
|
|
|
unit_length = len(buf) - 4
|
|
buf[:4] = unit_length.to_bytes(4, byteorder)
|
|
header_length = unit_length - 6
|
|
buf[6:10] = header_length.to_bytes(4, byteorder)
|
|
return buf
|
|
|
|
|
|
def compile_dwarf(dies, little_endian=True, bits=64):
|
|
if isinstance(dies, DwarfDie):
|
|
dies = (dies,)
|
|
assert all(isinstance(die, DwarfDie) for die in dies)
|
|
cu_die = DwarfDie(DW_TAG.compile_unit, [
|
|
DwarfAttrib(DW_AT.comp_dir, DW_FORM.string, '/usr/src'),
|
|
DwarfAttrib(DW_AT.stmt_list, DW_FORM.sec_offset, 0),
|
|
], dies)
|
|
|
|
return create_elf_file(ET.EXEC, [
|
|
ElfSection(
|
|
p_type=PT.LOAD,
|
|
vaddr=0xffff0000,
|
|
data=b'',
|
|
),
|
|
ElfSection(
|
|
name='.debug_abbrev',
|
|
sh_type=SHT.PROGBITS,
|
|
data=_compile_debug_abbrev(cu_die),
|
|
),
|
|
ElfSection(
|
|
name='.debug_info',
|
|
sh_type=SHT.PROGBITS,
|
|
data=_compile_debug_info(cu_die, little_endian, bits),
|
|
),
|
|
ElfSection(
|
|
name='.debug_line',
|
|
sh_type=SHT.PROGBITS,
|
|
data=_compile_debug_line(cu_die, little_endian),
|
|
),
|
|
ElfSection(
|
|
name='.debug_str',
|
|
sh_type=SHT.PROGBITS,
|
|
data=b'\0',
|
|
),
|
|
], little_endian=little_endian, bits=bits)
|