Skip to content

Commit 9086806

Browse files
committed
Add a function and cli command to find symbol definition by query
* bdx/binary.py (Definition): New class. (find_symbol_definition): New function. * bdx/cli.py (find_definition): New function. * tests/test_cli.py (test_cli_find_definition): New test.
1 parent 4291c9a commit 9086806

File tree

3 files changed

+118
-1
lines changed

3 files changed

+118
-1
lines changed

bdx/binary.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,55 @@ def is_readable_elf_file(path: Path) -> bool:
476476
return False
477477

478478

479+
@dataclass(frozen=True)
480+
class Definition:
481+
"""Contains the location where a Symbol is defined."""
482+
483+
source: Path
484+
line: int
485+
486+
487+
def find_symbol_definition(symbol: Symbol) -> Optional[Definition]:
488+
"""Attempt to find the definition of `symbol`.
489+
490+
This uses the addr2line program to find it.
491+
"""
492+
try:
493+
out = subprocess.check_output(
494+
[
495+
"addr2line",
496+
"-i",
497+
"-f",
498+
"-j",
499+
symbol.section,
500+
"-e",
501+
symbol.path,
502+
hex(symbol.address),
503+
]
504+
).decode()
505+
except Exception as e:
506+
debug("Failed to run addr2line program on {}:", symbol.name, str(e))
507+
return None
508+
509+
lines = out.splitlines()
510+
try:
511+
pos = lines.index(symbol.name)
512+
except ValueError:
513+
trace("Symbol not found in addr2line output: {}", symbol)
514+
return None
515+
516+
value = lines[pos + 1]
517+
518+
match = re.match("(.*):([0-9]+)(.*discriminator.*)?$", value)
519+
if not match:
520+
debug("Unexpected output of addr2line detected")
521+
return None
522+
523+
file, line, _ = match.groups()
524+
525+
return Definition(Path(file), int(line))
526+
527+
479528
@dataclass(frozen=True)
480529
class Exclusion:
481530
"""Represents a glob pattern for excluding files to index."""

bdx/cli.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616

1717
import bdx
1818
from bdx import debug, error, info, log, make_progress_bar, trace
19-
from bdx.binary import BinaryDirectory, Exclusion, find_compilation_database
19+
from bdx.binary import (
20+
BinaryDirectory,
21+
Exclusion,
22+
find_compilation_database,
23+
find_symbol_definition,
24+
)
2025
from bdx.index import (
2126
IndexingOptions,
2227
PathField,
@@ -652,6 +657,54 @@ def disass(
652657
subprocess.check_call(cmd, shell=True)
653658

654659

660+
@cli.command()
661+
@_common_options(index_must_exist=True)
662+
@click.argument(
663+
"query",
664+
nargs=-1,
665+
shell_complete=_complete_query,
666+
)
667+
@click.option(
668+
"-n",
669+
"--num",
670+
help="Limit the number of results",
671+
type=click.IntRange(1),
672+
metavar="LIMIT",
673+
default=None,
674+
)
675+
def find_definition(_directory, index_path, query, num):
676+
"""Find definition of symbols matching some query."""
677+
fmt = "{file}:{line}: {name}"
678+
679+
results = search_index(
680+
index_path=index_path, query=" ".join(query), limit=num
681+
)
682+
683+
while True:
684+
try:
685+
res = next(results)
686+
except QueryParser.Error as e:
687+
error(f"Invalid query: {str(e)}")
688+
exit(1)
689+
except StopIteration:
690+
break
691+
692+
if res.symbol_outdated:
693+
error("Information outdated, re-index needed")
694+
695+
defn = find_symbol_definition(res.symbol)
696+
debug("Found definition: {}", defn)
697+
698+
if defn:
699+
click.echo(
700+
fmt.format(
701+
file=defn.source,
702+
line=defn.line,
703+
name=res.symbol.name,
704+
)
705+
)
706+
707+
655708
@cli.command()
656709
@_common_options(index_must_exist=True)
657710
def files(_directory, index_path):

tests/test_cli.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,21 @@ def mock_check_call(*args, **kwargs):
214214
assert re.search("push[ ]+%rbp", output)
215215

216216

217+
def test_cli_find_definition(monkeypatch, fixture_path, index_path):
218+
runner = CliRunner()
219+
result = index_directory(runner, fixture_path, index_path)
220+
assert result.exit_code == 0
221+
222+
result = runner.invoke(
223+
cli,
224+
["find-definition", "--index-path", str(index_path), "fullname:main"],
225+
)
226+
227+
assert result.exit_code == 0
228+
229+
assert result.stdout.strip() == f"/src/tests/fixture/toplev.c:7: main"
230+
231+
217232
def test_cli_file_list(fixture_path, index_path):
218233
runner = CliRunner()
219234
result = index_directory(runner, fixture_path, index_path)

0 commit comments

Comments
 (0)