mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-24 10:03:05 +00:00
87b7292aa5
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 commitc4fbf7e589
("libdrgn: fix for compilation error"), who did not respond. That commit reverted a single line of code to one originally written by me in commit640b1c011d
("libdrgn: embed DWARF index in DWARF info cache"). Signed-off-by: Omar Sandoval <osandov@osandov.com>
289 lines
12 KiB
Python
289 lines
12 KiB
Python
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
|
import tempfile
|
|
from typing import NamedTuple
|
|
|
|
from drgn import Program, SymbolBinding, SymbolKind
|
|
from tests import TestCase
|
|
from tests.dwarfwriter import dwarf_sections
|
|
from tests.elf import ET, PT, SHT, STB, STT
|
|
from tests.elfwriter import ElfSection, ElfSymbol, create_elf_file
|
|
|
|
|
|
def create_elf_symbol_file(symbols):
|
|
# We need some DWARF data so that libdwfl will load the file.
|
|
sections = dwarf_sections(())
|
|
# Create a section for the symbols to reference and the corresponding
|
|
# segment for address lookups.
|
|
min_address = min(symbol.value for symbol in symbols)
|
|
max_address = max(symbol.value + symbol.size for symbol in symbols)
|
|
sections.append(
|
|
ElfSection(
|
|
name=".foo",
|
|
sh_type=SHT.NOBITS,
|
|
p_type=PT.LOAD,
|
|
vaddr=min_address,
|
|
memsz=max_address - min_address,
|
|
)
|
|
)
|
|
symbols = [
|
|
symbol._replace(
|
|
shindex=len(sections) if symbol.shindex is None else symbol.shindex
|
|
)
|
|
for symbol in symbols
|
|
]
|
|
return create_elf_file(ET.EXEC, sections, symbols)
|
|
|
|
|
|
def elf_symbol_program(*modules):
|
|
prog = Program()
|
|
for symbols in modules:
|
|
with tempfile.NamedTemporaryFile() as f:
|
|
f.write(create_elf_symbol_file(symbols))
|
|
f.flush()
|
|
prog.load_debug_info([f.name])
|
|
return prog
|
|
|
|
|
|
# We don't want to support creating drgn.Symbol instances yet, so use this dumb
|
|
# class for testing.
|
|
class Symbol(NamedTuple):
|
|
name: str
|
|
address: int
|
|
size: int
|
|
binding: SymbolBinding
|
|
kind: SymbolKind
|
|
|
|
|
|
class TestElfSymbol(TestCase):
|
|
def assert_symbol_equal(self, drgn_symbol, symbol):
|
|
self.assertEqual(
|
|
Symbol(
|
|
drgn_symbol.name,
|
|
drgn_symbol.address,
|
|
drgn_symbol.size,
|
|
drgn_symbol.binding,
|
|
drgn_symbol.kind,
|
|
),
|
|
symbol,
|
|
)
|
|
|
|
def assert_symbols_equal_unordered(self, drgn_symbols, symbols):
|
|
self.assertEqual(len(drgn_symbols), len(symbols))
|
|
drgn_symbols = sorted(drgn_symbols, key=lambda x: (x.address, x.name))
|
|
symbols = sorted(symbols, key=lambda x: (x.address, x.name))
|
|
for drgn_symbol, symbol in zip(drgn_symbols, symbols):
|
|
self.assert_symbol_equal(drgn_symbol, symbol)
|
|
|
|
def test_by_address(self):
|
|
elf_first = ElfSymbol("first", 0xFFFF0000, 0x8, STT.OBJECT, STB.LOCAL)
|
|
elf_second = ElfSymbol("second", 0xFFFF0008, 0x8, STT.OBJECT, STB.LOCAL)
|
|
first = Symbol("first", 0xFFFF0000, 0x8, SymbolBinding.LOCAL, SymbolKind.OBJECT)
|
|
second = Symbol(
|
|
"second", 0xFFFF0008, 0x8, SymbolBinding.LOCAL, SymbolKind.OBJECT
|
|
)
|
|
|
|
same_module = ((elf_first, elf_second),)
|
|
different_modules = ((elf_first,), (elf_second,))
|
|
|
|
for modules in same_module, different_modules:
|
|
with self.subTest(modules=len(modules)):
|
|
prog = elf_symbol_program(*modules)
|
|
self.assertRaises(LookupError, prog.symbol, 0xFFFEFFFF)
|
|
self.assertEqual(prog.symbols(0xFFFEFFFF), [])
|
|
self.assert_symbol_equal(prog.symbol(0xFFFF0000), first)
|
|
self.assert_symbols_equal_unordered(prog.symbols(0xFFFF0000), [first])
|
|
self.assert_symbol_equal(prog.symbol(0xFFFF0004), first)
|
|
self.assert_symbols_equal_unordered(prog.symbols(0xFFFF0004), [first])
|
|
self.assert_symbol_equal(prog.symbol(0xFFFF0008), second)
|
|
self.assert_symbols_equal_unordered(prog.symbols(0xFFFF0008), [second])
|
|
self.assert_symbol_equal(prog.symbol(0xFFFF000C), second)
|
|
self.assert_symbols_equal_unordered(prog.symbols(0xFFFF000C), [second])
|
|
self.assertRaises(LookupError, prog.symbol, 0xFFFF0010)
|
|
|
|
def test_by_address_precedence(self):
|
|
precedence = (STB.GLOBAL, STB.WEAK, STB.LOCAL)
|
|
drgn_precedence = (
|
|
SymbolBinding.GLOBAL,
|
|
SymbolBinding.WEAK,
|
|
SymbolBinding.LOCAL,
|
|
)
|
|
|
|
def assert_find_higher(*modules):
|
|
self.assertEqual(
|
|
elf_symbol_program(*modules).symbol(0xFFFF0000).name, "foo"
|
|
)
|
|
|
|
def assert_finds_both(symbols, *modules):
|
|
self.assert_symbols_equal_unordered(
|
|
elf_symbol_program(*modules).symbols(0xFFFF0000),
|
|
symbols,
|
|
)
|
|
|
|
for i in range(len(precedence) - 1):
|
|
higher_binding = precedence[i]
|
|
higher_binding_drgn = drgn_precedence[i]
|
|
for j in range(i + 1, len(precedence)):
|
|
lower_binding = precedence[j]
|
|
lower_binding_drgn = drgn_precedence[j]
|
|
with self.subTest(higher=higher_binding, lower=lower_binding):
|
|
higher = ElfSymbol(
|
|
"foo", 0xFFFF0000, 0x8, STT.OBJECT, higher_binding
|
|
)
|
|
lower = ElfSymbol("bar", 0xFFFF0000, 0x8, STT.OBJECT, lower_binding)
|
|
symbols = [
|
|
Symbol(
|
|
"foo",
|
|
0xFFFF0000,
|
|
0x8,
|
|
higher_binding_drgn,
|
|
SymbolKind.OBJECT,
|
|
),
|
|
Symbol(
|
|
"bar",
|
|
0xFFFF0000,
|
|
0x8,
|
|
lower_binding_drgn,
|
|
SymbolKind.OBJECT,
|
|
),
|
|
]
|
|
# Local symbols must be before global symbols.
|
|
if lower_binding != STB.LOCAL:
|
|
with self.subTest("higher before lower"):
|
|
assert_find_higher((higher, lower))
|
|
with self.subTest("lower before higher"):
|
|
assert_find_higher((lower, higher))
|
|
assert_finds_both(symbols, (lower, higher))
|
|
|
|
def test_by_name(self):
|
|
elf_first = ElfSymbol("first", 0xFFFF0000, 0x8, STT.OBJECT, STB.GLOBAL)
|
|
elf_second = ElfSymbol("second", 0xFFFF0008, 0x8, STT.OBJECT, STB.GLOBAL)
|
|
first = Symbol(
|
|
"first", 0xFFFF0000, 0x8, SymbolBinding.GLOBAL, SymbolKind.OBJECT
|
|
)
|
|
second = Symbol(
|
|
"second", 0xFFFF0008, 0x8, SymbolBinding.GLOBAL, SymbolKind.OBJECT
|
|
)
|
|
|
|
same_module = ((elf_first, elf_second),)
|
|
different_modules = ((elf_first,), (elf_second,))
|
|
|
|
for modules in same_module, different_modules:
|
|
with self.subTest(modules=len(modules)):
|
|
prog = elf_symbol_program(*modules)
|
|
self.assert_symbol_equal(prog.symbol("first"), first)
|
|
self.assert_symbol_equal(prog.symbol("second"), second)
|
|
self.assertRaises(LookupError, prog.symbol, "third")
|
|
|
|
self.assert_symbols_equal_unordered(prog.symbols("first"), [first])
|
|
self.assert_symbols_equal_unordered(prog.symbols("second"), [second])
|
|
self.assertEqual(prog.symbols("third"), [])
|
|
|
|
def test_by_name_precedence(self):
|
|
precedence = (
|
|
(STB.GLOBAL, STB.GNU_UNIQUE),
|
|
(STB.WEAK,),
|
|
(STB.LOCAL, STB.HIPROC),
|
|
)
|
|
|
|
expected = 0xFFFF0008
|
|
other = expected - 0x8
|
|
|
|
def assert_find_higher(*modules):
|
|
prog = elf_symbol_program(*modules)
|
|
self.assertEqual(prog.symbol("foo").address, expected)
|
|
# assert symbols() always finds both
|
|
symbols = sorted(prog.symbols("foo"), key=lambda s: s.address)
|
|
self.assertEqual(len(symbols), 2)
|
|
self.assertEqual(symbols[0].address, other)
|
|
self.assertEqual(symbols[1].address, expected)
|
|
|
|
for i in range(len(precedence) - 1):
|
|
for higher_binding in precedence[i]:
|
|
for j in range(i + 1, len(precedence)):
|
|
for lower_binding in precedence[j]:
|
|
with self.subTest(higher=higher_binding, lower=lower_binding):
|
|
higher = ElfSymbol(
|
|
"foo", expected, 0x8, STT.OBJECT, higher_binding
|
|
)
|
|
lower = ElfSymbol(
|
|
"foo", other, 0x8, STT.OBJECT, lower_binding
|
|
)
|
|
# Local symbols must be before global symbols.
|
|
if lower_binding not in precedence[-1]:
|
|
with self.subTest("same module, higher before lower"):
|
|
assert_find_higher((higher, lower))
|
|
with self.subTest("same module, lower before higher"):
|
|
assert_find_higher((lower, higher))
|
|
with self.subTest("different modules, higher before lower"):
|
|
assert_find_higher((higher,), (lower,))
|
|
with self.subTest("different modules, lower before higher"):
|
|
assert_find_higher((lower,), (higher,))
|
|
|
|
def test_binding(self):
|
|
for by in "name", "address":
|
|
for elf_binding, drgn_binding in (
|
|
(STB.LOCAL, SymbolBinding.LOCAL),
|
|
(STB.GLOBAL, SymbolBinding.GLOBAL),
|
|
(STB.WEAK, SymbolBinding.WEAK),
|
|
(STB.GNU_UNIQUE, SymbolBinding.UNIQUE),
|
|
(STB.HIPROC, SymbolBinding.UNKNOWN),
|
|
):
|
|
with self.subTest(by=by, binding=elf_binding):
|
|
prog = elf_symbol_program(
|
|
(ElfSymbol("foo", 0xFFFF0000, 1, STT.OBJECT, elf_binding),)
|
|
)
|
|
self.assertEqual(
|
|
prog.symbol("foo" if by == "name" else 0xFFFF0000).binding,
|
|
drgn_binding,
|
|
)
|
|
if by == "name":
|
|
symbols = prog.symbols("foo")
|
|
self.assertEqual(len(symbols), 1)
|
|
self.assertEqual(symbols[0].binding, drgn_binding)
|
|
|
|
def test_kind(self):
|
|
for elf_type, drgn_kind in (
|
|
(STT.NOTYPE, SymbolKind.UNKNOWN),
|
|
(STT.OBJECT, SymbolKind.OBJECT),
|
|
(STT.FUNC, SymbolKind.FUNC),
|
|
(STT.SECTION, SymbolKind.SECTION),
|
|
(STT.FILE, SymbolKind.FILE),
|
|
(STT.COMMON, SymbolKind.COMMON),
|
|
(STT.TLS, SymbolKind.TLS),
|
|
(STT.GNU_IFUNC, SymbolKind.IFUNC),
|
|
):
|
|
with self.subTest(type=elf_type):
|
|
prog = elf_symbol_program(
|
|
(ElfSymbol("foo", 0xFFFF0000, 1, elf_type, STB.GLOBAL),)
|
|
)
|
|
symbol = Symbol("foo", 0xFFFF0000, 1, SymbolBinding.GLOBAL, drgn_kind)
|
|
self.assert_symbol_equal(prog.symbol("foo"), symbol)
|
|
symbols = prog.symbols("foo")
|
|
self.assert_symbols_equal_unordered(symbols, [symbol])
|
|
|
|
def test_all_symbols(self):
|
|
elf_syms = (
|
|
(
|
|
ElfSymbol("two", 0xFFFF0012, 1, STT.OBJECT, STB.LOCAL),
|
|
ElfSymbol("three", 0xFFFF0013, 1, STT.OBJECT, STB.LOCAL),
|
|
ElfSymbol("one", 0xFFFF0011, 1, STT.OBJECT, STB.GLOBAL),
|
|
),
|
|
(
|
|
ElfSymbol("three", 0xFFFF0023, 1, STT.OBJECT, STB.LOCAL),
|
|
ElfSymbol("two", 0xFFFF0022, 1, STT.OBJECT, STB.GLOBAL),
|
|
),
|
|
(ElfSymbol("three", 0xFFFF0033, 1, STT.OBJECT, STB.GLOBAL),),
|
|
)
|
|
kind = SymbolKind.OBJECT
|
|
syms = [
|
|
Symbol("two", 0xFFFF0012, 1, SymbolBinding.LOCAL, kind),
|
|
Symbol("three", 0xFFFF0013, 1, SymbolBinding.LOCAL, kind),
|
|
Symbol("one", 0xFFFF0011, 1, SymbolBinding.GLOBAL, kind),
|
|
Symbol("three", 0xFFFF0023, 1, SymbolBinding.LOCAL, kind),
|
|
Symbol("two", 0xFFFF0022, 1, SymbolBinding.GLOBAL, kind),
|
|
Symbol("three", 0xFFFF0033, 1, SymbolBinding.GLOBAL, kind),
|
|
]
|
|
prog = elf_symbol_program(*elf_syms)
|
|
self.assert_symbols_equal_unordered(prog.symbols(), syms)
|