addr2line: Cache CU DIEs upon a successful address lookup.

Previously, addr2line would sequentially search all CUs for each input
address.  For some uses, notably syzkaller's code coverage map generator,
this was extremely slow.  Add a CU cache into which entries are added
following a successful lookup, and search the cache before falling back
to a scan.  When translating a large number of addresses this yields
slightly better performance than GNU addr2line.

Garbage-collect an unused hash table which appears to have been intended
for the same purpose.  A hash table doesn't seem particularly suitable
since each CU spans a range of addresses.

Submitted by:	Tiger Gao <tig@freebsdfoundation.org>
MFC after:	2 weeks
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D23418
This commit is contained in:
Mark Johnston 2020-02-03 16:41:40 +00:00
parent 4a3b87e295
commit af84329100
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=357450

View File

@ -25,6 +25,7 @@
*/ */
#include <sys/param.h> #include <sys/param.h>
#include <sys/tree.h>
#include <capsicum_helpers.h> #include <capsicum_helpers.h>
#include <dwarf.h> #include <dwarf.h>
@ -39,7 +40,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "uthash.h"
#include "_elftc.h" #include "_elftc.h"
ELFTC_VCSID("$Id: addr2line.c 3499 2016-11-25 16:06:29Z emaste $"); ELFTC_VCSID("$Id: addr2line.c 3499 2016-11-25 16:06:29Z emaste $");
@ -57,13 +57,14 @@ struct Func {
}; };
struct CU { struct CU {
RB_ENTRY(CU) entry;
Dwarf_Off off; Dwarf_Off off;
Dwarf_Unsigned lopc; Dwarf_Unsigned lopc;
Dwarf_Unsigned hipc; Dwarf_Unsigned hipc;
char **srcfiles; char **srcfiles;
Dwarf_Signed nsrcfiles; Dwarf_Signed nsrcfiles;
STAILQ_HEAD(, Func) funclist; STAILQ_HEAD(, Func) funclist;
UT_hash_handle hh; Dwarf_Die die;
}; };
static struct option longopts[] = { static struct option longopts[] = {
@ -80,10 +81,22 @@ static struct option longopts[] = {
{"version", no_argument, NULL, 'V'}, {"version", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0} {NULL, 0, NULL, 0}
}; };
static int demangle, func, base, inlines, print_addr, pretty_print; static int demangle, func, base, inlines, print_addr, pretty_print;
static char unknown[] = { '?', '?', '\0' }; static char unknown[] = { '?', '?', '\0' };
static Dwarf_Addr section_base; static Dwarf_Addr section_base;
static struct CU *culist; /* Need a new curlopc that stores last lopc value. */
static Dwarf_Unsigned curlopc = ~0ULL;
static RB_HEAD(cutree, CU) head = RB_INITIALIZER(&head);
static int
lopccmp(struct CU *e1, struct CU *e2)
{
return (e1->lopc < e2->lopc ? -1 : e1->lopc > e2->lopc);
}
RB_PROTOTYPE(cutree, CU, entry, lopccmp);
RB_GENERATE(cutree, CU, entry, lopccmp)
#define USAGE_MESSAGE "\ #define USAGE_MESSAGE "\
Usage: %s [options] hexaddress...\n\ Usage: %s [options] hexaddress...\n\
@ -378,6 +391,26 @@ print_inlines(struct CU *cu, struct Func *f, Dwarf_Unsigned call_file,
f->call_line); f->call_line);
} }
static struct CU *
culookup(Dwarf_Unsigned addr)
{
struct CU find, *res;
find.lopc = addr;
res = RB_NFIND(cutree, &head, &find);
if (res != NULL) {
if (res->lopc != addr)
res = RB_PREV(cutree, &head, res);
if (res != NULL && addr >= res->lopc && addr < res->hipc)
return (res);
} else {
res = RB_MAX(cutree, &head);
if (res != NULL && addr >= res->lopc && addr < res->hipc)
return (res);
}
return (NULL);
}
static void static void
translate(Dwarf_Debug dbg, Elf *e, const char* addrstr) translate(Dwarf_Debug dbg, Elf *e, const char* addrstr)
{ {
@ -400,11 +433,30 @@ translate(Dwarf_Debug dbg, Elf *e, const char* addrstr)
addr += section_base; addr += section_base;
lineno = 0; lineno = 0;
file = unknown; file = unknown;
cu = NULL;
die = NULL; die = NULL;
ret = DW_DLV_OK;
while ((ret = dwarf_next_cu_header(dbg, NULL, NULL, NULL, NULL, NULL, cu = culookup(addr);
&de)) == DW_DLV_OK) { if (cu != NULL) {
die = cu->die;
goto status_ok;
}
while (true) {
/*
* We resume the CU scan from the last place we found a match.
* Because when we have 2 sequential addresses, and the second
* one is of the next CU, it is faster to just go to the next CU
* instead of starting from the beginning.
*/
ret = dwarf_next_cu_header(dbg, NULL, NULL, NULL, NULL, NULL,
&de);
if (ret == DW_DLV_NO_ENTRY) {
if (curlopc == ~0ULL)
goto out;
ret = dwarf_next_cu_header(dbg, NULL, NULL, NULL, NULL,
NULL, &de);
}
die = NULL; die = NULL;
while (dwarf_siblingof(dbg, die, &ret_die, &de) == DW_DLV_OK) { while (dwarf_siblingof(dbg, die, &ret_die, &de) == DW_DLV_OK) {
if (die != NULL) if (die != NULL)
@ -420,12 +472,15 @@ translate(Dwarf_Debug dbg, Elf *e, const char* addrstr)
if (tag == DW_TAG_compile_unit) if (tag == DW_TAG_compile_unit)
break; break;
} }
if (ret_die == NULL) { if (ret_die == NULL) {
warnx("could not find DW_TAG_compile_unit die"); warnx("could not find DW_TAG_compile_unit die");
goto next_cu; goto next_cu;
} }
if (dwarf_attrval_unsigned(die, DW_AT_low_pc, &lopc, &de) == if (dwarf_attrval_unsigned(die, DW_AT_low_pc, &lopc, &de) ==
DW_DLV_OK) { DW_DLV_OK) {
if (lopc == curlopc)
goto out;
if (dwarf_attrval_unsigned(die, DW_AT_high_pc, &hipc, if (dwarf_attrval_unsigned(die, DW_AT_high_pc, &hipc,
&de) == DW_DLV_OK) { &de) == DW_DLV_OK) {
/* /*
@ -440,31 +495,27 @@ translate(Dwarf_Debug dbg, Elf *e, const char* addrstr)
hipc = ~0ULL; hipc = ~0ULL;
} }
/*
* Record the CU in the hash table for faster lookup
* later.
*/
if (dwarf_dieoffset(die, &off, &de) != DW_DLV_OK) { if (dwarf_dieoffset(die, &off, &de) != DW_DLV_OK) {
warnx("dwarf_dieoffset failed: %s", warnx("dwarf_dieoffset failed: %s",
dwarf_errmsg(de)); dwarf_errmsg(de));
goto out; goto out;
} }
HASH_FIND(hh, culist, &off, sizeof(off), cu);
if (cu == NULL) { if (addr >= lopc && addr < hipc) {
if ((cu = calloc(1, sizeof(*cu))) == NULL) if ((cu = calloc(1, sizeof(*cu))) == NULL)
err(EXIT_FAILURE, "calloc"); err(EXIT_FAILURE, "calloc");
cu->off = off; cu->off = off;
cu->lopc = lopc; cu->lopc = lopc;
cu->hipc = hipc; cu->hipc = hipc;
cu->die = die;
STAILQ_INIT(&cu->funclist); STAILQ_INIT(&cu->funclist);
HASH_ADD(hh, culist, off, sizeof(off), cu); RB_INSERT(cutree, &head, cu);
}
if (addr >= lopc && addr < hipc) curlopc = lopc;
break; break;
}
} }
next_cu:
next_cu:
if (die != NULL) { if (die != NULL) {
dwarf_dealloc(dbg, die, DW_DLA_DIE); dwarf_dealloc(dbg, die, DW_DLA_DIE);
die = NULL; die = NULL;
@ -474,6 +525,7 @@ translate(Dwarf_Debug dbg, Elf *e, const char* addrstr)
if (ret != DW_DLV_OK || die == NULL) if (ret != DW_DLV_OK || die == NULL)
goto out; goto out;
status_ok:
switch (dwarf_srclines(die, &lbuf, &lcount, &de)) { switch (dwarf_srclines(die, &lbuf, &lcount, &de)) {
case DW_DLV_OK: case DW_DLV_OK:
break; break;
@ -572,21 +624,6 @@ translate(Dwarf_Debug dbg, Elf *e, const char* addrstr)
cu->srcfiles != NULL && f != NULL && f->inlined_caller != NULL) cu->srcfiles != NULL && f != NULL && f->inlined_caller != NULL)
print_inlines(cu, f->inlined_caller, f->call_file, print_inlines(cu, f->inlined_caller, f->call_file,
f->call_line); f->call_line);
if (die != NULL)
dwarf_dealloc(dbg, die, DW_DLA_DIE);
/*
* Reset internal CU pointer, so we will start from the first CU
* next round.
*/
while (ret != DW_DLV_NO_ENTRY) {
if (ret == DW_DLV_ERROR)
errx(EXIT_FAILURE, "dwarf_next_cu_header: %s",
dwarf_errmsg(de));
ret = dwarf_next_cu_header(dbg, NULL, NULL, NULL, NULL, NULL,
&de);
}
} }
static void static void