drgn/libdrgn/dwarf_index.c
Omar Sandoval dcddaa2cc1 libdrgn: revamp hash table API
This makes several improvements to the hash table API.

The first two changes make things more general in order to be consistent
with the upcoming binary search tree API:

- Items are renamed to entries.
- Positions are renamed to iterators.
- hash_table_empty() is added.

One change makes the definition API more convenient:

- It is no longer necessary to pass the types into
  DEFINE_HASH_{MAP,SET}_FUNCTIONS().

A few changes take some good ideas from the C++ STL:

- hash_table_insert() now fails on duplicates instead of overwriting.
- hash_table_delete_iterator() returns the next iterator.
- hash_table_next() returns an iterator instead of modifying it.

One change reduces memory usage:

- The lower-level DEFINE_HASH_TABLE() is cleaned up and exposed as an
  alternative to DEFINE_HASH_MAP() and DEFINE_HASH_SET(). This allows us
  to get rid of the duplicated key where a hash map value already embeds
  the key (the DWARF index file table) and gets rid of the need to make
  a dummy hash set entry to do a search (the pointer and array type
  caches).
2019-05-24 17:48:05 -07:00

1836 lines
44 KiB
C

// Copyright 2018-2019 - Omar Sandoval
// SPDX-License-Identifier: GPL-3.0+
#include <assert.h>
#include <dwarf.h>
#include <elfutils/libdw.h>
#include <fcntl.h>
#include <gelf.h>
#include <inttypes.h>
#include <libelf.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "internal.h"
#include "dwarf_index.h"
#include "read.h"
#include "siphash.h"
static const char * const section_name[DRGN_DWARF_INDEX_NUM_SECTIONS] = {
[SECTION_SYMTAB] = ".symtab",
[SECTION_DEBUG_ABBREV] = ".debug_abbrev",
[SECTION_DEBUG_INFO] = ".debug_info",
[SECTION_DEBUG_LINE] = ".debug_line",
[SECTION_DEBUG_STR] = ".debug_str",
};
static const bool section_optional[DRGN_DWARF_INDEX_NUM_SECTIONS] = {
[SECTION_SYMTAB] = true,
[SECTION_DEBUG_LINE] = true,
};
/*
* The DWARF abbreviation table gets translated into a series of instructions.
* An instruction <= INSN_MAX_SKIP indicates a number of bytes to be skipped
* over. The next few instructions mean that the corresponding attribute can be
* skipped over. The remaining instructions indicate that the corresponding
* attribute should be parsed. Finally, every sequence of instructions
* corresponding to a DIE is terminated by a zero byte followed by a bitmask of
* TAG_FLAG_* bits combined with the DWARF tag (which may be set to zero if the
* tag is not of interest).
*/
enum {
INSN_MAX_SKIP = 229,
ATTRIB_BLOCK1,
ATTRIB_BLOCK2,
ATTRIB_BLOCK4,
ATTRIB_EXPRLOC,
ATTRIB_LEB128,
ATTRIB_STRING,
ATTRIB_SIBLING_REF1,
ATTRIB_SIBLING_REF2,
ATTRIB_SIBLING_REF4,
ATTRIB_SIBLING_REF8,
ATTRIB_SIBLING_REF_UDATA,
ATTRIB_NAME_STRP4,
ATTRIB_NAME_STRP8,
ATTRIB_NAME_STRING,
ATTRIB_STMT_LIST_LINEPTR4,
ATTRIB_STMT_LIST_LINEPTR8,
ATTRIB_DECL_FILE_DATA1,
ATTRIB_DECL_FILE_DATA2,
ATTRIB_DECL_FILE_DATA4,
ATTRIB_DECL_FILE_DATA8,
ATTRIB_DECL_FILE_UDATA,
ATTRIB_SPECIFICATION_REF1,
ATTRIB_SPECIFICATION_REF2,
ATTRIB_SPECIFICATION_REF4,
ATTRIB_SPECIFICATION_REF8,
ATTRIB_SPECIFICATION_REF_UDATA,
ATTRIB_MAX_INSN = ATTRIB_SPECIFICATION_REF_UDATA,
};
enum {
/* Maximum number of bits used by the tags we care about. */
TAG_BITS = 6,
TAG_MASK = (1 << TAG_BITS) - 1,
/* The remaining bits can be used for other purposes. */
TAG_FLAG_DECLARATION = 0x40,
TAG_FLAG_CHILDREN = 0x80,
};
struct abbrev_table {
/*
* This array is indexed on the DWARF abbreviation code minus one. It
* maps the abbreviation code to an index in the insns array where the
* instruction stream for that code begins.
*
* Technically, abbreviation codes don't have to be sequential. In
* practice, GCC seems to always generate sequential codes starting at
* one, so we can get away with a flat array.
*/
uint32_t *decls;
size_t num_decls;
uint8_t *insns;
};
struct file_name_table {
uint64_t *file_name_hashes;
size_t num_files;
};
struct compilation_unit {
struct drgn_dwarf_index_file *file;
const char *ptr;
uint64_t unit_length;
uint16_t version;
uint64_t debug_abbrev_offset;
uint8_t address_size;
bool is_64_bit;
};
static inline const char *section_ptr(Elf_Data *data, size_t offset)
{
return &((char *)data->d_buf)[offset];
}
static inline const char *section_end(Elf_Data *data)
{
return section_ptr(data, data->d_size);
}
DEFINE_HASH_TABLE_FUNCTIONS(drgn_dwarf_index_file_table, c_string_hash,
c_string_eq)
/*
* An indexed DIE.
*
* DIEs with the same name but different tags or files are considered distinct.
* We only compare the hash of the file name, not the string value, because a
* 64-bit collision is unlikely enough, especially when also considering the
* name and tag.
*/
struct drgn_dwarf_index_die {
uint64_t tag;
uint64_t file_name_hash;
/*
* The next DIE with the same name (as an index into
* drgn_dwarf_index_shard::dies), or SIZE_MAX if this is the last DIE.
*/
size_t next;
struct drgn_dwarf_index_file *file;
uint64_t offset;
};
/*
* The key is the DIE name. The value is the first DIE with that name (as an
* index into drgn_dwarf_index_shard::dies).
*/
DEFINE_HASH_TABLE_FUNCTIONS(drgn_dwarf_index_die_map, string_hash, string_eq)
static inline size_t hash_pair_to_shard(struct hash_pair hp)
{
/*
* The 8 most significant bits of the hash are used as the F14 tag, so
* we don't want to use those for sharding.
*/
return ((hp.first >>
(8 * sizeof(size_t) - 8 - DRGN_DWARF_INDEX_SHARD_BITS)) &
(((size_t)1 << DRGN_DWARF_INDEX_SHARD_BITS) - 1));
}
static inline struct drgn_error *drgn_eof(void)
{
return drgn_error_create(DRGN_ERROR_DWARF_FORMAT,
"debug information is truncated");
}
static inline bool skip_leb128(const char **ptr, const char *end)
{
for (;;) {
if (*ptr >= end)
return false;
if (!(*(const uint8_t *)(*ptr)++ & 0x80))
return true;
}
}
static inline struct drgn_error *read_uleb128(const char **ptr, const char *end,
uint64_t *value)
{
int shift = 0;
uint8_t byte;
*value = 0;
for (;;) {
if (*ptr >= end)
return drgn_eof();
byte = *(const uint8_t *)*ptr;
(*ptr)++;
if (shift == 63 && byte > 1) {
return drgn_error_create(DRGN_ERROR_OVERFLOW,
"ULEB128 overflowed unsigned 64-bit integer");
}
*value |= (uint64_t)(byte & 0x7f) << shift;
shift += 7;
if (!(byte & 0x80))
break;
}
return NULL;
}
static inline struct drgn_error *read_uleb128_into_size_t(const char **ptr,
const char *end,
size_t *value)
{
struct drgn_error *err;
uint64_t tmp;
if ((err = read_uleb128(ptr, end, &tmp)))
return err;
if (tmp > SIZE_MAX)
return drgn_eof();
*value = tmp;
return NULL;
}
static void free_shards(struct drgn_dwarf_index *dindex, size_t n)
{
size_t i;
for (i = 0; i < n; i++) {
free(dindex->shards[i].dies);
drgn_dwarf_index_die_map_deinit(&dindex->shards[i].map);
omp_destroy_lock(&dindex->shards[i].lock);
}
}
struct drgn_error *drgn_dwarf_index_init(struct drgn_dwarf_index *dindex,
enum drgn_dwarf_index_flags flags)
{
static const size_t initial_shard_capacity =
max(1024 >> DRGN_DWARF_INDEX_SHARD_BITS, 1);
struct drgn_error *err;
size_t i;
if (flags & ~DRGN_DWARF_INDEX_ALL) {
return drgn_error_create(DRGN_ERROR_INVALID_ARGUMENT,
"invalid flags");
}
dindex->flags = flags;
drgn_dwarf_index_file_table_init(&dindex->files);
dindex->opened_first = dindex->opened_last = NULL;
dindex->indexed_first = dindex->indexed_last = NULL;
for (i = 0; i < ARRAY_SIZE(dindex->shards); i++) {
struct drgn_dwarf_index_shard *shard = &dindex->shards[i];
omp_init_lock(&shard->lock);
drgn_dwarf_index_die_map_init(&shard->map);
shard->num_entries = 0;
shard->entries_capacity = initial_shard_capacity;
shard->dies = malloc_array(initial_shard_capacity,
sizeof(*shard->dies));
if (!shard->dies ||
!drgn_dwarf_index_die_map_reserve(&shard->map,
initial_shard_capacity)) {
free_shards(dindex, i + 1);
err = &drgn_enomem;
goto err;
}
}
return NULL;
err:
drgn_dwarf_index_file_table_deinit(&dindex->files);
return err;
}
static void free_files(struct drgn_dwarf_index *dindex,
struct drgn_dwarf_index_file *files)
{
struct drgn_dwarf_index_file *file, *next;
file = files;
while (file) {
next = file->next;
dwarf_end(file->dwarf);
if (file->path) {
elf_end(file->elf);
close(file->fd);
drgn_dwarf_index_file_table_delete(&dindex->files,
&file->path);
free((char *)file->path);
}
free(file);
file = next;
}
}
void drgn_dwarf_index_deinit(struct drgn_dwarf_index *dindex)
{
if (dindex) {
free_shards(dindex, ARRAY_SIZE(dindex->shards));
free_files(dindex, dindex->opened_first);
free_files(dindex, dindex->indexed_first);
drgn_dwarf_index_file_table_deinit(&dindex->files);
}
}
static struct drgn_error *read_sections(struct drgn_dwarf_index_file *file)
{
struct drgn_error *err;
GElf_Ehdr ehdr_mem, *ehdr;
size_t shstrndx;
Elf_Scn *scn = NULL;
size_t section_index[DRGN_DWARF_INDEX_NUM_SECTIONS] = {};
size_t i;
ehdr = gelf_getehdr(file->elf, &ehdr_mem);
if (!ehdr)
return &drgn_not_elf;
file->bswap = (ehdr->e_ident[EI_DATA] !=
(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ ?
ELFDATA2LSB : ELFDATA2MSB));
if (elf_getshdrstrndx(file->elf, &shstrndx))
return drgn_error_libelf();
while ((scn = elf_nextscn(file->elf, scn))) {
GElf_Shdr *shdr, shdr_mem;
const char *scnname;
shdr = gelf_getshdr(scn, &shdr_mem);
if (!shdr)
return drgn_error_libelf();
if (shdr->sh_type == SHT_NOBITS || (shdr->sh_flags & SHF_GROUP))
continue;
scnname = elf_strptr(file->elf, shstrndx, shdr->sh_name);
if (!scnname)
continue;
for (i = 0; i < ARRAY_SIZE(file->sections); i++) {
if (file->sections[i])
continue;
if (strcmp(scnname, section_name[i]) != 0)
continue;
err = read_elf_section(scn, &file->sections[i]);
if (err)
return err;
section_index[i] = elf_ndxscn(scn);
}
}
for (i = 0; i < ARRAY_SIZE(file->sections); i++) {
if (!file->sections[i] && !section_optional[i]) {
return drgn_error_format(DRGN_ERROR_MISSING_DEBUG_INFO,
"ELF file has no %s section",
section_name[i]);
}
}
if (ehdr->e_type != ET_REL)
return NULL;
/* Make a second pass to get the relocation sections, if needed. */
while ((scn = elf_nextscn(file->elf, scn))) {
GElf_Shdr *shdr, shdr_mem;
shdr = gelf_getshdr(scn, &shdr_mem);
if (!shdr)
return drgn_error_libelf();
if (shdr->sh_type != SHT_RELA)
continue;
for (i = 0; i < ARRAY_SIZE(file->rela_sections); i++) {
if (file->rela_sections[i])
continue;
if (shdr->sh_info != section_index[i])
continue;
if (ehdr->e_ident[EI_CLASS] != ELFCLASS64) {
return drgn_error_create(DRGN_ERROR_ELF_FORMAT,
"32-bit ELF relocations are not implemented");
}
if (!file->sections[SECTION_SYMTAB]) {
return drgn_error_create(DRGN_ERROR_ELF_FORMAT,
"ELF file has no .symtab section");
}
if (shdr->sh_link != section_index[SECTION_SYMTAB]) {
return drgn_error_create(DRGN_ERROR_ELF_FORMAT,
"relocation symbol table section is not .symtab");
}
err = read_elf_section(scn, &file->rela_sections[i]);
if (err)
return err;
}
}
return NULL;
}
struct drgn_error *drgn_dwarf_index_open(struct drgn_dwarf_index *dindex,
const char *path, Elf **elf)
{
struct drgn_error *err;
const char *key;
struct hash_pair hp;
struct drgn_dwarf_index_file_table_iterator it;
struct drgn_dwarf_index_file *file;
key = realpath(path, NULL);
if (!key)
return drgn_error_create_os(errno, path, "realpath");
hp = drgn_dwarf_index_file_table_hash(&path);
it = drgn_dwarf_index_file_table_search_hashed(&dindex->files, &key, hp);
if (it.entry) {
file = *it.entry;
free((char *)key);
goto out;
}
file = calloc(1, sizeof(*file));
if (!file) {
err = &drgn_enomem;
goto err_key;
}
file->path = key;
file->fd = open(path, O_RDONLY);
if (file->fd == -1) {
err = drgn_error_create_os(errno, path, "open");
goto err_file;
}
elf_version(EV_CURRENT);
file->elf = elf_begin(file->fd, ELF_C_READ_MMAP_PRIVATE, NULL);
if (!file->elf) {
err = drgn_error_libelf();
goto err_fd;
}
if (drgn_dwarf_index_file_table_insert_searched(&dindex->files, &file,
hp, &it) == -1) {
err = &drgn_enomem;
goto err_elf;
}
err = read_sections(file);
if (err)
goto err_hash;
if (dindex->opened_last)
dindex->opened_last->next = file;
else
dindex->opened_first = file;
dindex->opened_last = file;
out:
if (elf)
*elf = file->elf;
return NULL;
err_hash:
drgn_dwarf_index_file_table_delete_iterator_hashed(&dindex->files, it,
hp);
err_elf:
elf_end(file->elf);
err_fd:
close(file->fd);
err_file:
free(file);
err_key:
free((char *)key);
return err;
}
void drgn_dwarf_index_close_unindexed(struct drgn_dwarf_index *dindex)
{
struct drgn_dwarf_index_file *files;
files = dindex->opened_first;
dindex->opened_first = dindex->opened_last = NULL;
free_files(dindex, files);
}
static struct drgn_error *apply_relocation(Elf_Data *section,
Elf_Data *rela_section,
Elf_Data *symtab, size_t i)
{
const Elf64_Rela *reloc;
const Elf64_Sym *syms;
size_t num_syms;
uint32_t r_sym;
uint32_t r_type;
char *p;
reloc = &((Elf64_Rela *)rela_section->d_buf)[i];
syms = (Elf64_Sym *)symtab->d_buf;
num_syms = symtab->d_size / sizeof(Elf64_Sym);
p = (char *)section->d_buf + reloc->r_offset;
r_sym = reloc->r_info >> 32;
r_type = reloc->r_info & UINT32_C(0xffffffff);
switch (r_type) {
case R_X86_64_NONE:
break;
case R_X86_64_32:
if (r_sym >= num_syms) {
return drgn_error_create(DRGN_ERROR_ELF_FORMAT,
"invalid relocation symbol");
}
if (reloc->r_offset > SIZE_MAX - sizeof(uint32_t) ||
reloc->r_offset + sizeof(uint32_t) > section->d_size) {
return drgn_error_create(DRGN_ERROR_ELF_FORMAT,
"invalid relocation offset");
}
*(uint32_t *)p = syms[r_sym].st_value + reloc->r_addend;
break;
case R_X86_64_64:
if (r_sym >= num_syms) {
return drgn_error_create(DRGN_ERROR_ELF_FORMAT,
"invalid relocation symbol");
}
if (reloc->r_offset > SIZE_MAX - sizeof(uint64_t) ||
reloc->r_offset + sizeof(uint64_t) > section->d_size) {
return drgn_error_create(DRGN_ERROR_ELF_FORMAT,
"invalid relocation offset");
}
*(uint64_t *)p = syms[r_sym].st_value + reloc->r_addend;
break;
default:
return drgn_error_format(DRGN_ERROR_ELF_FORMAT,
"unimplemented relocation type %" PRIu32,
r_type);
}
return NULL;
}
static size_t count_relocations(struct drgn_dwarf_index_file *files)
{
struct drgn_dwarf_index_file *file = files;
size_t count = 0;
size_t i;
while (file) {
for (i = 0; i < ARRAY_SIZE(file->rela_sections); i++) {
Elf_Data *data;
data = file->rela_sections[i];
if (data)
count += data->d_size / sizeof(Elf64_Rela);
}
file = file->next;
}
return count;
}
static struct drgn_error *apply_relocations(struct drgn_dwarf_index_file *files)
{
struct drgn_error *err = NULL;
size_t total_num_relocs;
total_num_relocs = count_relocations(files);
#pragma omp parallel
{
struct drgn_dwarf_index_file *file;
size_t section_idx = 0, reloc_idx = 0;
size_t i;
bool first = true;
size_t num_relocs = 0;
struct drgn_error *err2;
#pragma omp for
for (i = 0; i < total_num_relocs; i++) {
if (err)
continue;
if (first) {
size_t cur = 0;
file = files;
while (file) {
for (section_idx = 0;
section_idx < ARRAY_SIZE(file->rela_sections);
section_idx++) {
Elf_Data *data;
data = file->rela_sections[section_idx];
if (!data)
continue;
num_relocs = (data->d_size /
sizeof(Elf64_Rela));
if (cur + num_relocs > i) {
reloc_idx = i - cur;
goto done;
} else {
cur += num_relocs;
}
}
file = file->next;
}
done:
first = false;
}
if ((err2 = apply_relocation(file->sections[section_idx],
file->rela_sections[section_idx],
file->sections[SECTION_SYMTAB],
reloc_idx))) {
#pragma omp critical(relocations_err)
{
if (err)
drgn_error_destroy(err2);
else
err = err2;
}
continue;
}
if (file) {
reloc_idx++;
while (reloc_idx >= num_relocs) {
Elf_Data *data;
reloc_idx = 0;
if (++section_idx >=
ARRAY_SIZE(file->rela_sections)) {
section_idx = 0;
file = file->next;
if (!file)
break;
}
data = file->rela_sections[section_idx];
if (data)
num_relocs = (data->d_size /
sizeof(Elf64_Rela));
else
num_relocs = 0;
}
}
}
}
return err;
}
static struct drgn_error *read_compilation_unit_header(const char *ptr,
const char *end,
struct compilation_unit *cu)
{
uint32_t tmp;
if (!read_u32(&ptr, end, cu->file->bswap, &tmp))
return drgn_eof();
cu->is_64_bit = tmp == UINT32_C(0xffffffff);
if (cu->is_64_bit) {
if (!read_u64(&ptr, end, cu->file->bswap, &cu->unit_length))
return drgn_eof();
} else {
cu->unit_length = tmp;
}
if (!read_u16(&ptr, end, cu->file->bswap, &cu->version))
return drgn_eof();
if (cu->version != 2 && cu->version != 3 && cu->version != 4) {
return drgn_error_format(DRGN_ERROR_DWARF_FORMAT,
"unknown DWARF CU version %" PRIu16,
cu->version);
}
if (cu->is_64_bit) {
if (!read_u64(&ptr, end, cu->file->bswap,
&cu->debug_abbrev_offset))
return drgn_eof();
} else {
if (!read_u32_into_u64(&ptr, end, cu->file->bswap,
&cu->debug_abbrev_offset))
return drgn_eof();
}
if (!read_u8(&ptr, end, &cu->address_size))
return drgn_eof();
return NULL;
}
static struct drgn_error *read_cus(struct drgn_dwarf_index_file *file,
struct compilation_unit **cus,
size_t *num_cus, size_t *cus_capacity)
{
struct drgn_error *err;
Elf_Data *debug_info = file->sections[SECTION_DEBUG_INFO];
const char *ptr = section_ptr(debug_info, 0);
const char *end = section_end(debug_info);
while (ptr < end) {
struct compilation_unit *cu;
if (*num_cus >= *cus_capacity) {
size_t capacity = *cus_capacity;
if (capacity == 0)
capacity = 1;
else
capacity *= 2;
if (!resize_array(cus, capacity))
return &drgn_enomem;
*cus_capacity = capacity;
}
cu = &(*cus)[(*num_cus)++];
cu->file = file;
cu->ptr = ptr;
if ((err = read_compilation_unit_header(ptr, end, cu)))
return err;
ptr += (cu->is_64_bit ? 12 : 4) + cu->unit_length;
}
if (ptr > end)
return drgn_eof();
return NULL;
}
static struct drgn_error *append_insn(struct abbrev_table *table, uint8_t insn,
size_t *num_insns, size_t *insns_capacity)
{
if (*num_insns >= *insns_capacity) {
if (*insns_capacity == 0)
*insns_capacity = 32;
else
*insns_capacity *= 2;
if (!resize_array(&table->insns, *insns_capacity))
return &drgn_enomem;
}
table->insns[(*num_insns)++] = insn;
return NULL;
}
static inline bool is_type_tag(uint64_t tag)
{
return (tag == DW_TAG_base_type ||
tag == DW_TAG_class_type ||
tag == DW_TAG_enumeration_type ||
tag == DW_TAG_structure_type ||
tag == DW_TAG_typedef ||
tag == DW_TAG_union_type);
}
static struct drgn_error *read_abbrev_decl(int flags, const char **ptr,
const char *end,
const struct compilation_unit *cu,
struct abbrev_table *table,
size_t *decls_capacity,
size_t *num_insns,
size_t *insns_capacity)
{
struct drgn_error *err;
uint64_t code;
uint64_t tag;
uint8_t children;
uint8_t die_flags;
bool should_index;
bool first = true;
static_assert(ATTRIB_MAX_INSN == UINT8_MAX,
"maximum DWARF attribute instruction is invalid");
if ((err = read_uleb128(ptr, end, &code)))
return err;
if (code == 0)
return (struct drgn_error *)-1;
if (code != table->num_decls + 1) {
return drgn_error_create(DRGN_ERROR_DWARF_FORMAT,
"DWARF abbreviation table is not sequential");
}
if (table->num_decls >= *decls_capacity) {
if (*decls_capacity == 0)
*decls_capacity = 1;
else
*decls_capacity *= 2;
if (!resize_array(&table->decls, *decls_capacity))
return &drgn_enomem;
}
table->decls[table->num_decls++] = *num_insns;
if ((err = read_uleb128(ptr, end, &tag)))
return err;
should_index = (((flags & DRGN_DWARF_INDEX_TYPES) && is_type_tag(tag)) ||
((flags & DRGN_DWARF_INDEX_VARIABLES) && tag == DW_TAG_variable) ||
((flags & DRGN_DWARF_INDEX_ENUMERATORS) && tag == DW_TAG_enumerator) ||
((flags & DRGN_DWARF_INDEX_FUNCTIONS) && tag == DW_TAG_subprogram));
if (should_index || tag == DW_TAG_compile_unit ||
((flags & DRGN_DWARF_INDEX_ENUMERATORS) &&
tag == DW_TAG_enumeration_type))
die_flags = tag;
else
die_flags = 0;
if (!read_u8(ptr, end, &children))
return drgn_eof();
if (children)
die_flags |= TAG_FLAG_CHILDREN;
for (;;) {
uint64_t name, form;
uint8_t insn;
if ((err = read_uleb128(ptr, end, &name)))
return err;
if ((err = read_uleb128(ptr, end, &form)))
return err;
if (name == 0 && form == 0)
break;
if (name == DW_AT_sibling &&
!((flags & DRGN_DWARF_INDEX_ENUMERATORS) &&
tag == DW_TAG_enumeration_type)) {
/*
* If we are indexing enumerators, we must descend into
* DW_TAG_enumeration_type to find the DW_TAG_enumerator
* children instead of skipping to the sibling DIE.
*/
switch (form) {
case DW_FORM_ref1:
insn = ATTRIB_SIBLING_REF1;
goto append_insn;
case DW_FORM_ref2:
insn = ATTRIB_SIBLING_REF2;
goto append_insn;
case DW_FORM_ref4:
insn = ATTRIB_SIBLING_REF4;
goto append_insn;
case DW_FORM_ref8:
insn = ATTRIB_SIBLING_REF8;
goto append_insn;
case DW_FORM_ref_udata:
insn = ATTRIB_SIBLING_REF_UDATA;
goto append_insn;
default:
break;
}
} else if (name == DW_AT_name && should_index) {
switch (form) {
case DW_FORM_strp:
if (cu->is_64_bit)
insn = ATTRIB_NAME_STRP8;
else
insn = ATTRIB_NAME_STRP4;
goto append_insn;
case DW_FORM_string:
insn = ATTRIB_NAME_STRING;
goto append_insn;
default:
break;
}
} else if (name == DW_AT_stmt_list &&
tag == DW_TAG_compile_unit &&
cu->file->sections[SECTION_DEBUG_LINE]) {
switch (form) {
case DW_FORM_data4:
insn = ATTRIB_STMT_LIST_LINEPTR4;
goto append_insn;
case DW_FORM_data8:
insn = ATTRIB_STMT_LIST_LINEPTR8;
goto append_insn;
case DW_FORM_sec_offset:
if (cu->is_64_bit)
insn = ATTRIB_STMT_LIST_LINEPTR8;
else
insn = ATTRIB_STMT_LIST_LINEPTR4;
goto append_insn;
default:
break;
}
} else if (name == DW_AT_decl_file && should_index) {
switch (form) {
case DW_FORM_data1:
insn = ATTRIB_DECL_FILE_DATA1;
goto append_insn;
case DW_FORM_data2:
insn = ATTRIB_DECL_FILE_DATA2;
goto append_insn;
case DW_FORM_data4:
insn = ATTRIB_DECL_FILE_DATA4;
goto append_insn;
case DW_FORM_data8:
insn = ATTRIB_DECL_FILE_DATA8;
goto append_insn;
/*
* decl_file must be positive, so if the compiler uses
* DW_FORM_sdata for some reason, just treat it as
* udata.
*/
case DW_FORM_sdata:
case DW_FORM_udata:
insn = ATTRIB_DECL_FILE_UDATA;
goto append_insn;
default:
break;
}
} else if (name == DW_AT_declaration) {
/*
* In theory, this could be DW_FORM_flag with a value of
* zero, but in practice, GCC always uses
* DW_FORM_flag_present.
*/
die_flags |= TAG_FLAG_DECLARATION;
} else if (name == DW_AT_specification && should_index) {
switch (form) {
case DW_FORM_ref1:
insn = ATTRIB_SPECIFICATION_REF1;
goto append_insn;
case DW_FORM_ref2:
insn = ATTRIB_SPECIFICATION_REF2;
goto append_insn;
case DW_FORM_ref4:
insn = ATTRIB_SPECIFICATION_REF4;
goto append_insn;
case DW_FORM_ref8:
insn = ATTRIB_SPECIFICATION_REF8;
goto append_insn;
case DW_FORM_ref_udata:
insn = ATTRIB_SPECIFICATION_REF_UDATA;
goto append_insn;
default:
break;
}
}
switch (form) {
case DW_FORM_addr:
insn = cu->address_size;
break;
case DW_FORM_data1:
case DW_FORM_ref1:
case DW_FORM_flag:
insn = 1;
break;
case DW_FORM_data2:
case DW_FORM_ref2:
insn = 2;
break;
case DW_FORM_data4:
case DW_FORM_ref4:
insn = 4;
break;
case DW_FORM_data8:
case DW_FORM_ref8:
case DW_FORM_ref_sig8:
insn = 8;
break;
case DW_FORM_block1:
insn = ATTRIB_BLOCK1;
goto append_insn;
case DW_FORM_block2:
insn = ATTRIB_BLOCK2;
goto append_insn;
case DW_FORM_block4:
insn = ATTRIB_BLOCK4;
goto append_insn;
case DW_FORM_exprloc:
insn = ATTRIB_EXPRLOC;
goto append_insn;
case DW_FORM_sdata:
case DW_FORM_udata:
case DW_FORM_ref_udata:
insn = ATTRIB_LEB128;
goto append_insn;
case DW_FORM_ref_addr:
case DW_FORM_sec_offset:
case DW_FORM_strp:
insn = cu->is_64_bit ? 8 : 4;
break;
case DW_FORM_string:
insn = ATTRIB_STRING;
goto append_insn;
case DW_FORM_flag_present:
continue;
case DW_FORM_indirect:
return drgn_error_create(DRGN_ERROR_DWARF_FORMAT,
"DW_FORM_indirect is not implemented");
default:
return drgn_error_format(DRGN_ERROR_DWARF_FORMAT,
"unknown attribute form %" PRIu64,
form);
}
if (!first && table->insns[*num_insns - 1] < INSN_MAX_SKIP) {
if ((uint16_t)table->insns[*num_insns - 1] + insn <= INSN_MAX_SKIP) {
table->insns[*num_insns - 1] += insn;
continue;
} else {
insn = (uint16_t)table->insns[*num_insns - 1] + insn - INSN_MAX_SKIP;
table->insns[*num_insns - 1] = INSN_MAX_SKIP;
}
}
append_insn:
first = false;
if ((err = append_insn(table, insn, num_insns, insns_capacity)))
return err;
}
if ((err = append_insn(table, 0, num_insns, insns_capacity)))
return err;
return append_insn(table, die_flags, num_insns, insns_capacity);
}
static struct drgn_error *read_abbrev_table(int flags, const char *ptr,
const char *end,
const struct compilation_unit *cu,
struct abbrev_table *table)
{
struct drgn_error *err;
size_t decls_capacity = 0;
size_t num_insns = 0;
size_t insns_capacity = 0;
for (;;) {
err = read_abbrev_decl(flags, &ptr, end, cu, table,
&decls_capacity, &num_insns,
&insns_capacity);
if (err == (struct drgn_error *)-1)
break;
else if (err)
return err;
}
return NULL;
}
static struct drgn_error *skip_lnp_header(struct drgn_dwarf_index_file *file,
const char **ptr, const char *end)
{
uint32_t tmp;
bool is_64_bit;
uint16_t version;
uint8_t opcode_base;
if (!read_u32(ptr, end, file->bswap, &tmp))
return drgn_eof();
is_64_bit = tmp == UINT32_C(0xffffffff);
if (is_64_bit)
*ptr += sizeof(uint64_t);
if (!read_u16(ptr, end, file->bswap, &version))
return drgn_eof();
if (version != 2 && version != 3 && version != 4) {
return drgn_error_format(DRGN_ERROR_DWARF_FORMAT,
"unknown DWARF LNP version %" PRIu16,
version);
}
/*
* header_length
* minimum_instruction_length
* maximum_operations_per_instruction (DWARF 4 only)
* default_is_stmt
* line_base
* line_range
*/
*ptr += (is_64_bit ? 8 : 4) + 4 + (version >= 4);
if (!read_u8(ptr, end, &opcode_base))
return drgn_eof();
/* standard_opcode_lengths */
*ptr += opcode_base - 1;
return NULL;
}
/*
* Hash the canonical path of a directory. Components are hashed in reverse
* order. We always include a trailing slash.
*/
static void hash_directory(struct siphash *hash, const char *path,
size_t path_len)
{
struct path_iterator it = {
.components = (struct path_iterator_component []){
{ path, path_len, },
},
.num_components = 1,
};
const char *component;
size_t component_len;
while (path_iterator_next(&it, &component, &component_len)) {
siphash_update(hash, component, component_len);
siphash_update(hash, "/", 1);
}
}
static struct drgn_error *read_file_name_table(struct drgn_dwarf_index *dindex,
struct compilation_unit *cu,
size_t stmt_list,
struct file_name_table *table)
{
/*
* We don't care about hash flooding attacks, so don't bother with the
* random key.
*/
static const uint64_t siphash_key[2];
struct drgn_error *err;
struct drgn_dwarf_index_file *file = cu->file;
Elf_Data *debug_line = file->sections[SECTION_DEBUG_LINE];
const char *ptr = section_ptr(debug_line, stmt_list);
const char *end = section_end(debug_line);
struct siphash *directories = NULL;
size_t num_directories = 0;
size_t directories_capacity = 0;
size_t files_capacity = 0;
if ((err = skip_lnp_header(file, &ptr, end)))
return err;
for (;;) {
struct siphash *hash;
const char *path;
size_t path_len;
if (!read_string(&ptr, end, &path, &path_len))
return drgn_eof();
if (!path_len)
break;
if (num_directories >= directories_capacity) {
if (directories_capacity == 0)
directories_capacity = 16;
else
directories_capacity *= 2;
if (!resize_array(&directories, directories_capacity)) {
err = &drgn_enomem;
goto out;
}
}
hash = &directories[num_directories++];
siphash_init(hash, siphash_key);
hash_directory(hash, path, path_len);
}
for (;;) {
const char *path;
size_t path_len;
uint64_t directory_index;
struct siphash hash;
if (!read_string(&ptr, end, &path, &path_len)) {
err = drgn_eof();
goto out;
}
if (!path_len)
break;
if ((err = read_uleb128(&ptr, end, &directory_index)))
goto out;
/* mtime, size */
if (!skip_leb128(&ptr, end) || !skip_leb128(&ptr, end)) {
err = drgn_eof();
goto out;
}
if (directory_index > num_directories) {
err = drgn_error_format(DRGN_ERROR_DWARF_FORMAT,
"directory index %" PRIu64 " is invalid",
directory_index);
goto out;
}
if (directory_index)
hash = directories[directory_index - 1];
else
siphash_init(&hash, siphash_key);
siphash_update(&hash, path, path_len);
if (table->num_files >= files_capacity) {
if (files_capacity == 0)
files_capacity = 16;
else
files_capacity *= 2;
if (!resize_array(&table->file_name_hashes,
files_capacity)) {
err = &drgn_enomem;
goto out;
}
}
table->file_name_hashes[table->num_files++] = siphash_final(&hash);
}
err = NULL;
out:
free(directories);
return err;
}
static bool append_die_entry(struct drgn_dwarf_index_shard *shard, uint64_t tag,
uint64_t file_name_hash,
struct drgn_dwarf_index_file *file,
uint64_t offset)
{
struct drgn_dwarf_index_die *die;
if (shard->num_entries >= shard->entries_capacity) {
size_t new_capacity;
new_capacity = shard->entries_capacity * 2;
if (!resize_array(&shard->dies, new_capacity))
return false;
shard->entries_capacity = new_capacity;
}
die = &shard->dies[shard->num_entries++];
die->tag = tag;
die->file_name_hash = file_name_hash;
die->file = file;
die->offset = offset;
die->next = SIZE_MAX;
return true;
}
static struct drgn_error *index_die(struct drgn_dwarf_index *dindex,
const char *name, uint64_t tag,
uint64_t file_name_hash,
struct drgn_dwarf_index_file *file,
uint64_t offset)
{
struct drgn_error *err;
struct drgn_dwarf_index_die_map_entry entry = {
.key = {
.str = name,
.len = strlen(name),
},
};
struct hash_pair hp;
struct drgn_dwarf_index_shard *shard;
struct drgn_dwarf_index_die_map_iterator it;
size_t index;
struct drgn_dwarf_index_die *die;
hp = drgn_dwarf_index_die_map_hash(&entry.key);
shard = &dindex->shards[hash_pair_to_shard(hp)];
omp_set_lock(&shard->lock);
it = drgn_dwarf_index_die_map_search_hashed(&shard->map, &entry.key,
hp);
if (!it.entry) {
if (!append_die_entry(shard, tag, file_name_hash, file,
offset)) {
err = &drgn_enomem;
goto out;
}
entry.value = shard->num_entries - 1;
if (drgn_dwarf_index_die_map_insert_searched(&shard->map,
&entry, hp,
NULL) == 1)
err = NULL;
else
err = &drgn_enomem;
goto out;
}
die = &shard->dies[it.entry->value];
for (;;) {
if (die->tag == tag &&
die->file_name_hash == file_name_hash) {
err = NULL;
goto out;
}
if (die->next == SIZE_MAX)
break;
die = &shard->dies[die->next];
}
index = die - shard->dies;
if (!append_die_entry(shard, tag, file_name_hash, file, offset)) {
err = &drgn_enomem;
goto out;
}
shard->dies[index].next = shard->num_entries - 1;
err = NULL;
out:
omp_unset_lock(&shard->lock);
return err;
}
struct die {
const char *sibling;
const char *name;
size_t stmt_list;
size_t decl_file;
const char *specification;
uint8_t flags;
};
static struct drgn_error *read_die(struct compilation_unit *cu,
const struct abbrev_table *abbrev_table,
const char **ptr, const char *end,
const char *debug_str_buffer,
const char *debug_str_end, struct die *die)
{
struct drgn_error *err;
uint64_t code;
uint8_t *insnp;
uint8_t insn;
if ((err = read_uleb128(ptr, end, &code)))
return err;
if (code == 0)
return (struct drgn_error *)-1;
if (code < 1 || code > abbrev_table->num_decls) {
return drgn_error_format(DRGN_ERROR_DWARF_FORMAT,
"unknown abbreviation code %" PRIu64,
code);
}
insnp = &abbrev_table->insns[abbrev_table->decls[code - 1]];
while ((insn = *insnp++)) {
size_t skip, tmp;
switch (insn) {
case ATTRIB_BLOCK1:
if (!read_u8_into_size_t(ptr, end, &skip))
return drgn_eof();
goto skip;
case ATTRIB_BLOCK2:
if (!read_u16_into_size_t(ptr, end, cu->file->bswap,
&skip))
return drgn_eof();
goto skip;
case ATTRIB_BLOCK4:
if (!read_u32_into_size_t(ptr, end, cu->file->bswap,
&skip))
return drgn_eof();
goto skip;
case ATTRIB_EXPRLOC:
if ((err = read_uleb128_into_size_t(ptr, end, &skip)))
return err;
goto skip;
case ATTRIB_LEB128:
if (!skip_leb128(ptr, end))
return drgn_eof();
break;
case ATTRIB_NAME_STRING:
die->name = *ptr;
/* fallthrough */
case ATTRIB_STRING:
if (!skip_string(ptr, end))
return drgn_eof();
break;
case ATTRIB_SIBLING_REF1:
if (!read_u8_into_size_t(ptr, end, &tmp))
return drgn_eof();
goto sibling;
case ATTRIB_SIBLING_REF2:
if (!read_u16_into_size_t(ptr, end, cu->file->bswap,
&tmp))
return drgn_eof();
goto sibling;
case ATTRIB_SIBLING_REF4:
if (!read_u32_into_size_t(ptr, end, cu->file->bswap,
&tmp))
return drgn_eof();
goto sibling;
case ATTRIB_SIBLING_REF8:
if (!read_u64_into_size_t(ptr, end, cu->file->bswap,
&tmp))
return drgn_eof();
goto sibling;
case ATTRIB_SIBLING_REF_UDATA:
if ((err = read_uleb128_into_size_t(ptr, end, &tmp)))
return err;
sibling:
if (!read_in_bounds(cu->ptr, end, tmp))
return drgn_eof();
die->sibling = &cu->ptr[tmp];
__builtin_prefetch(die->sibling);
break;
case ATTRIB_NAME_STRP4:
if (!read_u32_into_size_t(ptr, end, cu->file->bswap,
&tmp))
return drgn_eof();
goto strp;
case ATTRIB_NAME_STRP8:
if (!read_u64_into_size_t(ptr, end, cu->file->bswap,
&tmp))
return drgn_eof();
strp:
if (!read_in_bounds(debug_str_buffer, debug_str_end,
tmp))
return drgn_eof();
die->name = &debug_str_buffer[tmp];
__builtin_prefetch(die->name);
break;
case ATTRIB_STMT_LIST_LINEPTR4:
if (!read_u32_into_size_t(ptr, end, cu->file->bswap,
&die->stmt_list))
return drgn_eof();
break;
case ATTRIB_STMT_LIST_LINEPTR8:
if (!read_u64_into_size_t(ptr, end, cu->file->bswap,
&die->stmt_list))
return drgn_eof();
break;
case ATTRIB_DECL_FILE_DATA1:
if (!read_u8_into_size_t(ptr, end, &die->decl_file))
return drgn_eof();
break;
case ATTRIB_DECL_FILE_DATA2:
if (!read_u16_into_size_t(ptr, end, cu->file->bswap,
&die->decl_file))
return drgn_eof();
break;
case ATTRIB_DECL_FILE_DATA4:
if (!read_u32_into_size_t(ptr, end, cu->file->bswap,
&die->decl_file))
return drgn_eof();
break;
case ATTRIB_DECL_FILE_DATA8:
if (!read_u64_into_size_t(ptr, end, cu->file->bswap,
&die->decl_file))
return drgn_eof();
break;
case ATTRIB_DECL_FILE_UDATA:
if ((err = read_uleb128_into_size_t(ptr, end,
&die->decl_file)))
return err;
break;
case ATTRIB_SPECIFICATION_REF1:
if (!read_u8_into_size_t(ptr, end, &tmp))
return drgn_eof();
goto specification;
case ATTRIB_SPECIFICATION_REF2:
if (!read_u16_into_size_t(ptr, end, cu->file->bswap,
&tmp))
return drgn_eof();
goto specification;
case ATTRIB_SPECIFICATION_REF4:
if (!read_u32_into_size_t(ptr, end, cu->file->bswap,
&tmp))
return drgn_eof();
goto specification;
case ATTRIB_SPECIFICATION_REF8:
if (!read_u64_into_size_t(ptr, end, cu->file->bswap,
&tmp))
return drgn_eof();
goto specification;
case ATTRIB_SPECIFICATION_REF_UDATA:
if ((err = read_uleb128_into_size_t(ptr, end, &tmp)))
return err;
specification:
if (!read_in_bounds(cu->ptr, end, tmp))
return drgn_eof();
die->specification = &cu->ptr[tmp];
__builtin_prefetch(die->specification);
break;
default:
skip = insn;
skip:
if (!read_in_bounds(*ptr, end, skip))
return drgn_eof();
*ptr += skip;
break;
}
}
die->flags = *insnp;
return NULL;
}
static struct drgn_error *index_cu(struct drgn_dwarf_index *dindex,
struct compilation_unit *cu)
{
struct drgn_error *err;
struct abbrev_table abbrev_table = {};
struct file_name_table file_name_table = {};
struct drgn_dwarf_index_file *file = cu->file;
Elf_Data *debug_abbrev = file->sections[SECTION_DEBUG_ABBREV];
const char *debug_abbrev_end = section_end(debug_abbrev);
const char *ptr = &cu->ptr[cu->is_64_bit ? 23 : 11];
const char *end = &cu->ptr[(cu->is_64_bit ? 12 : 4) + cu->unit_length];
Elf_Data *debug_info = file->sections[SECTION_DEBUG_INFO];
const char *debug_info_buffer = section_ptr(debug_info, 0);
Elf_Data *debug_str = file->sections[SECTION_DEBUG_STR];
const char *debug_str_buffer = section_ptr(debug_str, 0);
const char *debug_str_end = section_end(debug_str);
unsigned int depth = 0;
uint64_t enum_die_offset = 0;
if ((err = read_abbrev_table(dindex->flags,
section_ptr(debug_abbrev,
cu->debug_abbrev_offset),
debug_abbrev_end, cu, &abbrev_table)))
goto out;
for (;;) {
struct die die = {
.stmt_list = SIZE_MAX,
};
uint64_t die_offset = ptr - debug_info_buffer;
uint64_t tag;
err = read_die(cu, &abbrev_table, &ptr, end, debug_str_buffer,
debug_str_end, &die);
if (err == (struct drgn_error *)-1) {
depth--;
if (depth == 1)
enum_die_offset = 0;
else if (depth == 0)
break;
continue;
} else if (err) {
goto out;
}
tag = die.flags & TAG_MASK;
if (tag == DW_TAG_compile_unit) {
if (depth == 0 && die.stmt_list != SIZE_MAX &&
(err = read_file_name_table(dindex, cu,
die.stmt_list,
&file_name_table)))
goto out;
} else if (tag && !(die.flags & TAG_FLAG_DECLARATION)) {
uint64_t file_name_hash;
/*
* NB: the enumerator name points to the
* enumeration_type DIE instead of the enumerator DIE.
*/
if (depth == 1 && tag == DW_TAG_enumeration_type)
enum_die_offset = die_offset;
else if (depth == 2 && tag == DW_TAG_enumerator &&
enum_die_offset)
die_offset = enum_die_offset;
else if (depth != 1)
goto next;
if (die.specification && (!die.name || !die.decl_file)) {
struct die decl = {};
const char *decl_ptr = die.specification;
if ((err = read_die(cu, &abbrev_table, &decl_ptr, end,
debug_str_buffer, debug_str_end,
&decl)))
goto out;
if (!die.name && decl.name)
die.name = decl.name;
if (!die.decl_file && decl.decl_file)
die.decl_file = decl.decl_file;
}
if (die.name) {
if (die.decl_file > file_name_table.num_files) {
err = drgn_error_format(DRGN_ERROR_DWARF_FORMAT,
"invalid DW_AT_decl_file %zu",
die.decl_file);
goto out;
}
if (die.decl_file)
file_name_hash = file_name_table.file_name_hashes[die.decl_file - 1];
else
file_name_hash = 0;
if ((err = index_die(dindex, die.name, tag,
file_name_hash, file,
die_offset)))
goto out;
}
}
next:
if (die.flags & TAG_FLAG_CHILDREN) {
if (die.sibling)
ptr = die.sibling;
else
depth++;
} else if (depth == 0) {
break;
}
}
err = NULL;
out:
free(file_name_table.file_name_hashes);
free(abbrev_table.decls);
free(abbrev_table.insns);
return err;
}
static struct drgn_error *index_cus(struct drgn_dwarf_index *dindex,
struct compilation_unit *cus,
size_t num_cus)
{
struct drgn_error *err = NULL;
#pragma omp parallel
{
size_t i;
struct drgn_error *err2;
#pragma omp for schedule(dynamic)
for (i = 0; i < num_cus; i++) {
if (err)
continue;
if ((err2 = index_cu(dindex, &cus[i])))
#pragma omp critical(cus_err)
{
if (err)
drgn_error_destroy(err2);
else
err = err2;
}
}
}
return err;
}
static void unindex_files(struct drgn_dwarf_index *dindex,
struct drgn_dwarf_index_file *files)
{
struct drgn_dwarf_index_file *file;
size_t i;
/* First, mark all of the files that failed. */
file = files;
do {
file->failed = true;
file = file->next;
} while (file);
/* Then, delete all of the dies pointing to those files. */
for (i = 0; i < ARRAY_SIZE(dindex->shards); i++) {
struct drgn_dwarf_index_shard *shard = &dindex->shards[i];
struct drgn_dwarf_index_die_map_iterator it;
/*
* Because we're deleting everything that was added since the
* last update, we can just shrink the dies array to the first
* entry that was added for this update.
*/
while (shard->num_entries) {
struct drgn_dwarf_index_die *die;
die = &shard->dies[shard->num_entries - 1];
if (die->file->failed)
shard->num_entries--;
else
break;
}
/*
* We also need to delete those dies in the map. Note that any
* dies chained on the dies we delete must have also been added
* for this update, so there's no need to preserve them.
*/
for (it = drgn_dwarf_index_die_map_first(&shard->map);
it.entry; ) {
if (it.entry->value >= shard->num_entries) {
it = drgn_dwarf_index_die_map_delete_iterator(&shard->map,
it);
} else {
it = drgn_dwarf_index_die_map_next(it);
}
}
}
}
struct drgn_error *drgn_dwarf_index_update(struct drgn_dwarf_index *dindex)
{
struct drgn_error *err;
struct drgn_dwarf_index_file *first, *last, *file;
struct compilation_unit *cus = NULL;
size_t num_cus = 0, cus_capacity = 0;
first = dindex->opened_first;
last = dindex->opened_last;
dindex->opened_first = dindex->opened_last = NULL;
if (!first)
return NULL;
if ((err = apply_relocations(first)))
goto out;
file = first;
do {
Elf_Data *debug_str;
debug_str = file->sections[SECTION_DEBUG_STR];
if (debug_str->d_size == 0 ||
((char *)debug_str->d_buf)[debug_str->d_size - 1] != '\0') {
err = drgn_error_create(DRGN_ERROR_DWARF_FORMAT,
".debug_str is not null terminated");
goto out;
}
if ((err = read_cus(file, &cus, &num_cus, &cus_capacity)))
goto out;
file = file->next;
} while (file);
if ((err = index_cus(dindex, cus, num_cus))) {
unindex_files(dindex, first);
goto out;
}
if (dindex->indexed_last)
dindex->indexed_last->next = first;
else
dindex->indexed_first = first;
dindex->indexed_last = last;
first = NULL;
out:
free_files(dindex, first);
free(cus);
return err;
}
void drgn_dwarf_index_iterator_init(struct drgn_dwarf_index_iterator *it,
struct drgn_dwarf_index *dindex,
const char *name, size_t name_len,
const uint64_t *tags, size_t num_tags)
{
it->dindex = dindex;
if (name) {
struct string key = {
.str = name,
.len = name_len,
};
struct hash_pair hp;
struct drgn_dwarf_index_shard *shard;
struct drgn_dwarf_index_die_map_iterator map_it;
hp = drgn_dwarf_index_die_map_hash(&key);
it->shard = hash_pair_to_shard(hp);
shard = &dindex->shards[it->shard];
map_it = drgn_dwarf_index_die_map_search_hashed(&shard->map,
&key, hp);
it->index = map_it.entry ? map_it.entry->value : SIZE_MAX;
it->any_name = false;
} else {
it->index = 0;
for (it->shard = 0; it->shard < ARRAY_SIZE(dindex->shards);
it->shard++) {
if (dindex->shards[it->shard].num_entries)
break;
}
it->any_name = true;
}
it->tags = tags;
it->num_tags = num_tags;
}
static inline bool
drgn_dwarf_index_iterator_matches_tag(struct drgn_dwarf_index_iterator *it,
struct drgn_dwarf_index_die *die)
{
size_t i;
if (it->num_tags == 0)
return true;
for (i = 0; i < it->num_tags; i++) {
if (die->tag == it->tags[i])
return true;
}
return false;
}
struct drgn_error *
drgn_dwarf_index_iterator_next(struct drgn_dwarf_index_iterator *it,
Dwarf_Die *die)
{
struct drgn_dwarf_index *dindex = it->dindex;
struct drgn_dwarf_index_die *index_die;
struct drgn_dwarf_index_file *file;
if (it->any_name) {
for (;;) {
struct drgn_dwarf_index_shard *shard;
if (it->shard >= ARRAY_SIZE(dindex->shards))
return &drgn_stop;
shard = &dindex->shards[it->shard];
index_die = &shard->dies[it->index];
if (++it->index >= shard->num_entries) {
it->index = 0;
while (++it->shard < ARRAY_SIZE(dindex->shards)) {
if (dindex->shards[it->shard].num_entries)
break;
}
}
if (drgn_dwarf_index_iterator_matches_tag(it,
index_die))
break;
}
} else {
for (;;) {
struct drgn_dwarf_index_shard *shard;
if (it->index == SIZE_MAX)
return &drgn_stop;
shard = &dindex->shards[it->shard];
index_die = &shard->dies[it->index];
it->index = index_die->next;
if (drgn_dwarf_index_iterator_matches_tag(it,
index_die))
break;
}
}
file = index_die->file;
if (!file->dwarf) {
file->dwarf = dwarf_begin_elf(file->elf,
DWARF_C_READ,
NULL);
if (!file->dwarf)
return drgn_error_libdw();
}
if (!dwarf_offdie(file->dwarf, index_die->offset, die))
return drgn_error_libdw();
return NULL;
}