drgn/tests/dwarfwriter.py
Omar Sandoval 87b7292aa5 Relicense drgn from GPLv3+ to LGPLv2.1+
drgn is currently licensed as GPLv3+. Part of the long term vision for
drgn is that other projects can use it as a library providing
programmatic interfaces for debugger functionality. A more permissive
license is better suited to this goal. We decided on LGPLv2.1+ as a good
balance between software freedom and permissiveness.

All contributors not employed by Meta were contacted via email and
consented to the license change. The only exception was the author of
commit c4fbf7e589 ("libdrgn: fix for compilation error"), who did not
respond. That commit reverted a single line of code to one originally
written by me in commit 640b1c011d ("libdrgn: embed DWARF index in
DWARF info cache").

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-11-01 17:05:16 -07:00

302 lines
9.6 KiB
Python

# Copyright (c) Meta Platforms, Inc. and affiliates.
# SPDX-License-Identifier: LGPL-2.1-or-later
import os.path
from typing import Any, NamedTuple, Optional, Sequence, Union
from tests.assembler import _append_sleb128, _append_uleb128
from tests.dwarf import DW_AT, DW_FORM, DW_TAG
from tests.elf import ET, SHT
from tests.elfwriter import ElfSection, create_elf_file
class DwarfAttrib(NamedTuple):
name: str
form: DW_FORM
value: Any
class DwarfLabel(NamedTuple):
name: str
class DwarfDie(NamedTuple):
tag: DW_TAG
attribs: Sequence[DwarfAttrib]
children: Sequence[Union["DwarfDie", DwarfLabel]] = ()
type_signature: Optional[int] = None
type_offset: Optional[str] = None
def _compile_debug_abbrev(unit_dies, use_dw_form_indirect):
buf = bytearray()
code = 1
def aux(die):
if isinstance(die, DwarfLabel):
return
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, DW_FORM.indirect if use_dw_form_indirect else attrib.form
)
buf.append(0)
buf.append(0)
if die.children:
for child in die.children:
aux(child)
for die in unit_dies:
aux(die)
buf.append(0)
return buf
def _compile_debug_info(unit_dies, little_endian, bits, use_dw_form_indirect):
byteorder = "little" if little_endian else "big"
all_labels = set()
labels = {}
relocations = []
code = 1
decl_file = 1
def aux(buf, die, depth):
if isinstance(die, DwarfLabel):
# For now, labels are only supported within a unit, but make sure
# they're unique across all units.
if die.name in all_labels:
raise ValueError(f"duplicate label {die.name!r}")
all_labels.add(die.name)
labels[die.name] = len(buf)
return
nonlocal code, decl_file
_append_uleb128(buf, code)
code += 1
for attrib in die.attribs:
if use_dw_form_indirect:
_append_uleb128(buf, attrib.form)
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.data2:
buf.extend(value.to_bytes(2, byteorder))
elif attrib.form == DW_FORM.data4:
buf.extend(value.to_bytes(4, byteorder))
elif attrib.form == DW_FORM.data8:
buf.extend(value.to_bytes(8, byteorder))
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.block:
_append_uleb128(buf, len(value))
buf.extend(value)
elif attrib.form == DW_FORM.block1:
buf.append(len(value))
buf.extend(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.ref_sig8:
buf.extend(value.to_bytes(8, byteorder))
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(buf, child, depth + 1)
buf.append(0)
debug_info = bytearray()
debug_types = bytearray()
for die in unit_dies:
labels.clear()
relocations.clear()
buf = debug_info if die.tag == DW_TAG.compile_unit else debug_types
orig_len = len(buf)
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
if die.tag == DW_TAG.type_unit:
buf.extend(die.type_signature.to_bytes(8, byteorder))
relocations.append((len(buf), die.type_offset))
buf.extend(b"\0\0\0\0") # type_offset
else:
assert die.type_signature is None
assert die.type_offset is None
aux(buf, die, 0)
unit_length = len(buf) - orig_len - 4
buf[orig_len : orig_len + 4] = unit_length.to_bytes(4, byteorder)
for offset, label in relocations:
die_offset = labels[label] - orig_len
buf[offset : offset + 4] = die_offset.to_bytes(4, byteorder)
return debug_info, debug_types
def _compile_debug_line(unit_dies, 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):
if isinstance(die, DwarfLabel):
return
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)
for die in unit_dies:
compile_include_directories(die)
buf.append(0)
decl_file = 1
directory = 1
def compile_file_names(die):
if isinstance(die, DwarfLabel):
return
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)
for die in unit_dies:
compile_file_names(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
_UNIT_TAGS = frozenset({DW_TAG.type_unit, DW_TAG.compile_unit})
def dwarf_sections(
dies, little_endian=True, bits=64, *, lang=None, use_dw_form_indirect=False
):
if isinstance(dies, DwarfDie):
dies = (dies,)
assert all(isinstance(die, (DwarfDie, DwarfLabel)) for die in dies)
if any(isinstance(die, DwarfDie) and die.tag in _UNIT_TAGS for die in dies):
assert all(isinstance(die, DwarfLabel) or die.tag in _UNIT_TAGS for die in dies)
unit_dies = dies
else:
unit_dies = (DwarfDie(DW_TAG.compile_unit, (), dies),)
unit_attribs = [DwarfAttrib(DW_AT.stmt_list, DW_FORM.sec_offset, 0)]
if lang is not None:
unit_attribs.append(DwarfAttrib(DW_AT.language, DW_FORM.data1, lang))
cu_attribs = unit_attribs + [
DwarfAttrib(DW_AT.comp_dir, DW_FORM.string, "/usr/src")
]
unit_dies = [
die._replace(
attribs=list(die.attribs)
+ (cu_attribs if die.tag == DW_TAG.compile_unit else unit_attribs)
)
for die in unit_dies
]
debug_info, debug_types = _compile_debug_info(
unit_dies, little_endian, bits, use_dw_form_indirect
)
sections = [
ElfSection(
name=".debug_abbrev",
sh_type=SHT.PROGBITS,
data=_compile_debug_abbrev(unit_dies, use_dw_form_indirect),
),
ElfSection(name=".debug_info", sh_type=SHT.PROGBITS, data=debug_info),
ElfSection(
name=".debug_line",
sh_type=SHT.PROGBITS,
data=_compile_debug_line(unit_dies, little_endian),
),
ElfSection(name=".debug_str", sh_type=SHT.PROGBITS, data=b"\0"),
]
if debug_types:
sections.append(
ElfSection(name=".debug_types", sh_type=SHT.PROGBITS, data=debug_types)
)
return sections
def compile_dwarf(
dies, little_endian=True, bits=64, *, lang=None, use_dw_form_indirect=False
):
return create_elf_file(
ET.EXEC,
dwarf_sections(
dies,
little_endian=little_endian,
bits=bits,
lang=lang,
use_dw_form_indirect=use_dw_form_indirect,
),
little_endian=little_endian,
bits=bits,
)