libdrgn: use libdwfl

libdwfl is the elfutils "DWARF frontend library". It has high-level
functionality for looking up symbols, walking stack traces, etc. In
order to use this functionality, we need to report our debugging
information through libdwfl. For userspace programs, libdwfl has a much
better implementation than drgn for automatically finding debug
information from a core dump or PID. However, for the kernel, libdwfl
has a few issues:

- It only supports finding debug information for the running kernel, not
  vmcores.
- It determines the vmlinux address range by reading /proc/kallsyms,
  which is slow (~70ms on my machine).
- If separate debug information isn't available for a kernel module, it
  finds it by walking /lib/modules/$(uname -r)/kernel; this is repeated
  for every module.
- It doesn't find kernel modules with names containing both dashes and
  underscores (e.g., aes-x86_64).

Luckily, drgn already solved all of these problems, and with some
effort, we can keep doing it ourselves and report it to libdwfl.

The conversion replaces a bunch of code for dealing with userspace core
dump notes, /proc/$pid/maps, and relocations.
This commit is contained in:
Omar Sandoval 2019-07-15 00:51:30 -07:00
parent a9a2cb7cac
commit e5874ad18a
12 changed files with 1625 additions and 1526 deletions

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@
#ifndef DRGN_DWARF_INDEX_H
#define DRGN_DWARF_INDEX_H
#include <elfutils/libdw.h>
#include <elfutils/libdwfl.h>
#include <libelf.h>
#include <omp.h>
#include <stddef.h>
@ -20,6 +20,7 @@
#include "drgn.h"
#include "hash_table.h"
#include "string_builder.h"
#include "vector.h"
/**
@ -44,38 +45,53 @@
* @{
*/
enum {
SECTION_SYMTAB,
SECTION_DEBUG_ABBREV,
SECTION_DEBUG_INFO,
SECTION_DEBUG_LINE,
SECTION_DEBUG_STR,
DRGN_DWARF_INDEX_NUM_SECTIONS,
};
struct drgn_dwarf_index_file {
Elf_Data *sections[DRGN_DWARF_INDEX_NUM_SECTIONS];
bool failed;
/**
* drgn-specific data for the @c Dwfl_Module userdata pointer.
*
* For a newly created userdata, @c indexed is @c false and @c err is @c NULL.
* They are updated by @ref drgn_dwarf_index_update(). @c err may be set with
* @ref drgn_dwfl_module_userdata_set_error() before @ref
* drgn_dwarf_index_update() is called to skip indexing for that module; the
* error message will be added to the @ref DRGN_ERROR_MISSING_DEBUG_INFO error.
*
* @sa drgn_dwfl_find_elf(), drgn_dwfl_section_address()
*/
struct drgn_dwfl_module_userdata {
/** Whether the module is indexed in a @ref drgn_dwarf_index. */
bool indexed;
/** File descriptor of @ref drgn_dwfl_module_userdata::elf. */
int fd;
/*
* If this is NULL, then we didn't open the file and don't own the Elf
* handle.
*/
const char *path;
/** Error encountered while indexing. */
struct drgn_error *err;
/** Path of @ref drgn_dwfl_module_userdata::elf. */
char *path;
/** ELF handle to use. */
Elf *elf;
Dwarf *dwarf;
Elf_Data *rela_sections[DRGN_DWARF_INDEX_NUM_SECTIONS];
struct drgn_dwarf_index_file *next;
};
static inline const char *
drgn_dwarf_index_file_to_key(struct drgn_dwarf_index_file * const *entry)
struct drgn_dwfl_module_userdata *drgn_dwfl_module_userdata_create(void);
void
drgn_dwfl_module_userdata_destroy(struct drgn_dwfl_module_userdata *userdata);
/* This takes ownership of err. */
void
drgn_dwfl_module_userdata_set_error(struct drgn_dwfl_module_userdata *userdata,
const char *message,
struct drgn_error *err);
extern const Dwfl_Callbacks drgn_dwfl_callbacks;
/** Get the @ref drgn_dwfl_module_userdata for a @c Dwfl_Module. */
static inline struct drgn_dwfl_module_userdata *
drgn_dwfl_module_userdata(Dwfl_Module *module)
{
return (*entry)->path;
void **userdatap;
dwfl_module_info(module, &userdatap, NULL, NULL, NULL, NULL, NULL,
NULL);
return *userdatap;
}
DEFINE_HASH_TABLE_TYPE(drgn_dwarf_index_file_table,
struct drgn_dwarf_index_file *,
drgn_dwarf_index_file_to_key)
struct drgn_dwarf_index_die;
DEFINE_HASH_MAP_TYPE(drgn_dwarf_index_die_map, struct string, size_t)
@ -102,23 +118,14 @@ struct drgn_dwarf_index_shard {
* files. It is much faster for this task than other generic DWARF parsing
* libraries.
*
* A new DWARF index is created by @ref drgn_dwarf_index_create(). It is freed
* by @ref drgn_dwarf_index_destroy().
*
* Indexing happens in two steps: the files to index are opened using @ref
* drgn_dwarf_index_open(), then they all are parsed and indexed by @ref
* drgn_dwarf_index_update(). The update step is parallelized across CPUs, so it
* is most efficient to open as many files as possible before indexing them all
* at once in parallel.
*
* Searches in the index are done with a @ref drgn_dwarf_index_iterator.
*/
struct drgn_dwarf_index {
/** @privatesection */
struct drgn_dwarf_index_file_table files;
struct drgn_dwarf_index_file *opened_first, *opened_last;
struct drgn_dwarf_index_file *indexed_first, *indexed_last;
/* The index is sharded to reduce lock contention. */
/**
* Index shards.
*
* This is sharded to reduce lock contention.
*/
struct drgn_dwarf_index_shard shards[1 << DRGN_DWARF_INDEX_SHARD_BITS];
};
@ -134,44 +141,36 @@ void drgn_dwarf_index_init(struct drgn_dwarf_index *dindex);
void drgn_dwarf_index_deinit(struct drgn_dwarf_index *dindex);
/**
* Open a file and add it to a DWARF index.
* Index new DWARF information.
*
* This function does the first part of indexing a file: it opens the file,
* reads or maps it, and checks that it contains the required debugging
* information. However, it does not actually parse the debugging information.
* To do so, call drgn_dwarf_index_update() once all of the files to index have
* been opened.
* This parses and indexes the debugging information for all modules in @p dwfl
* that have not yet been indexed.
*
* If this fails, the file is not opened, but previously opened files are not
* affected.
* On success, @ref drgn_dwfl_module_userdata::indexed is set to @c true for all
* modules that we were able to index, and @ref drgn_dwfl_module_userdata::err
* is set to non-@c NULL for all other modules.
*
* If debug information was not available for one or more modules, a @ref
* DRGN_ERROR_MISSING_DEBUG_INFO error is returned.
*
* On any other error, no new debugging information is indexed.
*
* @param[in] dindex DWARF index.
* @param[in] path Path to open.
* @param[out] elf If not @c NULL, the opened ELF file. It is valid until @ref
* drgn_dwarf_index_destroy() is called.
* @return @c NULL on success, non-@c NULL on error.
*/
struct drgn_error *drgn_dwarf_index_open(struct drgn_dwarf_index *dindex,
const char *path, Elf **elf);
/** Close any files which haven't been indexed yet. */
void drgn_dwarf_index_close_unindexed(struct drgn_dwarf_index *dindex);
struct drgn_error *drgn_dwarf_index_update(struct drgn_dwarf_index *dindex,
Dwfl *dwfl);
/**
* Index newly opened files.
* Remove all @c Dwfl_Modules that aren't indexed (see @ref
* drgn_dwfl_module_userdata::indexed) from @p dwfl.
*
* This function does the second part of indexing a file: it applies ELF
* relocations, then parses and indexes the debugging information in all of the
* files opened by @ref drgn_dwarf_index_open() since the last call to @ref
* drgn_dwarf_index_update() or @ref drgn_dwarf_index_create().
*
* If this fails, no new debugging information is indexed and all opened files
* which were not already indexed are closed.
*
* @param[in] dindex DWARF index.
* @return @c NULL on success, non-@c NULL on error.
* This should be called if @ref drgn_dwarf_index_update() returned an error or
* if modules were reported and @ref drgn_dwarf_index_update() was not called.
*/
struct drgn_error *drgn_dwarf_index_update(struct drgn_dwarf_index *dindex);
void drgn_remove_unindexed_dwfl_modules(Dwfl *dwfl);
/** Remove all @Dwfl_Modules from @p dwfl. */
void drgn_remove_all_dwfl_modules(Dwfl *dwfl);
/**
* Iterator over DWARF debugging information.
@ -215,6 +214,9 @@ void drgn_dwarf_index_iterator_init(struct drgn_dwarf_index_iterator *it,
*
* @param[in] it DWARF index iterator.
* @param[out] die_ret Returned DIE.
* @param[out] bias_ret Returned difference between addresses in the loaded
* module and addresses in the debugging information. This may be @c NULL if it
* is not needed.
* @return @c NULL on success, non-@c NULL on error. In particular, when there
* are no more matching DIEs, @p die_ret is not modified and an error with code
* @ref DRGN_ERROR_STOP is returned; this @ref DRGN_ERROR_STOP error does not
@ -222,7 +224,7 @@ void drgn_dwarf_index_iterator_init(struct drgn_dwarf_index_iterator *it,
*/
struct drgn_error *
drgn_dwarf_index_iterator_next(struct drgn_dwarf_index_iterator *it,
Dwarf_Die *die_ret);
Dwarf_Die *die_ret, uint64_t *bias_ret);
/** @} */

View File

@ -369,14 +369,14 @@ drgn_dwarf_info_cache_find_complete(struct drgn_dwarf_info_cache *dicache,
* Find a matching DIE. Note that drgn_dwarf_index does not contain DIEs
* with DW_AT_declaration, so this will always be a complete type.
*/
err = drgn_dwarf_index_iterator_next(&it, &die);
err = drgn_dwarf_index_iterator_next(&it, &die, NULL);
if (err)
return err;
/*
* Look for another matching DIE. If there is one, then we can't be sure
* which type this is, so leave it incomplete rather than guessing.
*/
err = drgn_dwarf_index_iterator_next(&it, &die);
err = drgn_dwarf_index_iterator_next(&it, &die, NULL);
if (!err)
return &drgn_stop;
else if (err->code != DRGN_ERROR_STOP)
@ -1387,7 +1387,7 @@ struct drgn_error *drgn_dwarf_type_find(enum drgn_type_kind kind,
drgn_dwarf_index_iterator_init(&it, &dicache->dindex, name, name_len,
&tag, 1);
while (!(err = drgn_dwarf_index_iterator_next(&it, &die))) {
while (!(err = drgn_dwarf_index_iterator_next(&it, &die, NULL))) {
if (die_matches_filename(&die, filename)) {
err = drgn_type_from_dwarf(dicache, &die, ret);
if (err)
@ -1408,8 +1408,8 @@ struct drgn_error *drgn_dwarf_type_find(enum drgn_type_kind kind,
static struct drgn_error *
drgn_symbol_from_dwarf_subprogram(struct drgn_dwarf_info_cache *dicache,
Dwarf_Die *die, const char *name,
struct drgn_symbol *ret)
Dwarf_Die *die, uint64_t bias,
const char *name, struct drgn_symbol *ret)
{
struct drgn_error *err;
struct drgn_qualified_type qualified_type;
@ -1427,20 +1427,14 @@ drgn_symbol_from_dwarf_subprogram(struct drgn_dwarf_info_cache *dicache,
"could not find address of '%s'",
name);
}
ret->address = low_pc;
ret->address = low_pc + bias;
ret->little_endian = dwarf_die_is_little_endian(die);
if (dicache->relocation_hook) {
err = dicache->relocation_hook(name, die, ret,
dicache->relocation_arg);
if (err)
return err;
}
return NULL;
}
static struct drgn_error *
drgn_symbol_from_dwarf_variable(struct drgn_dwarf_info_cache *dicache,
Dwarf_Die *die, const char *name,
Dwarf_Die *die, uint64_t bias, const char *name,
struct drgn_symbol *ret)
{
struct drgn_error *err;
@ -1470,14 +1464,8 @@ drgn_symbol_from_dwarf_variable(struct drgn_dwarf_info_cache *dicache,
return drgn_error_create(DRGN_ERROR_DWARF_FORMAT,
"DW_AT_location has unimplemented operation");
}
ret->address = loc[0].number;
ret->address = loc[0].number + bias;
ret->little_endian = dwarf_die_is_little_endian(die);
if (dicache->relocation_hook) {
err = dicache->relocation_hook(name, die, ret,
dicache->relocation_arg);
if (err)
return err;
}
return NULL;
}
@ -1492,6 +1480,7 @@ drgn_dwarf_symbol_find(const char *name, size_t name_len, const char *filename,
size_t num_tags;
struct drgn_dwarf_index_iterator it;
Dwarf_Die die;
uint64_t bias;
num_tags = 0;
if (flags & DRGN_FIND_OBJECT_CONSTANT)
@ -1503,7 +1492,7 @@ drgn_dwarf_symbol_find(const char *name, size_t name_len, const char *filename,
drgn_dwarf_index_iterator_init(&it, &dicache->dindex, name,
strlen(name), tags, num_tags);
while (!(err = drgn_dwarf_index_iterator_next(&it, &die))) {
while (!(err = drgn_dwarf_index_iterator_next(&it, &die, &bias))) {
if (!die_matches_filename(&die, filename))
continue;
switch (dwarf_tag(&die)) {
@ -1521,10 +1510,11 @@ drgn_dwarf_symbol_find(const char *name, size_t name_len, const char *filename,
}
case DW_TAG_subprogram:
return drgn_symbol_from_dwarf_subprogram(dicache, &die,
name, ret);
bias, name,
ret);
case DW_TAG_variable:
return drgn_symbol_from_dwarf_variable(dicache, &die,
name, ret);
bias, name, ret);
default:
DRGN_UNREACHABLE();
}
@ -1549,8 +1539,6 @@ drgn_dwarf_info_cache_create(struct drgn_type_index *tindex,
dwarf_type_map_init(&dicache->cant_be_incomplete_array_map);
dicache->depth = 0;
dicache->tindex = tindex;
dicache->relocation_hook = NULL;
dicache->relocation_arg = NULL;
*ret = dicache;
return NULL;
}

View File

@ -77,28 +77,6 @@ struct drgn_dwarf_info_cache {
int depth;
/** Type index. */
struct drgn_type_index *tindex;
/**
* Relocation callback.
*
* Objects in an ELF file are often relocated when they are loaded into
* a program (e.g., shared libraries or position-independent
* executables). This callback can be used to adjust the address which
* was found in the DWARF debugging information entry for the symbol.
*
* On entry, @p sym is fully initialized and not a constant. This
* should look at @c sym->address and modify it as appropriate.
*
* @param[in] prog @ref drgn_dwarf_symbol_index::prog.
* @param[in] name Name of the symbol.
* @param[in] die DWARF DIE of the symbol.
* @param[in,out] sym Symbol to relocate.
* @return @c NULL on success, non-@c NULL on error.
*/
struct drgn_error *(*relocation_hook)(const char *name, Dwarf_Die *die,
struct drgn_symbol *sym,
void *arg);
/* Argument to pass to @ref drgn_dwarf_info_cache::relocation_hook. */
void *relocation_arg;
};
/** Create a @ref drgn_dwarf_info_cache. */

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0+
#include <errno.h>
#include <elfutils/libdw.h>
#include <elfutils/libdwfl.h>
#include <libelf.h>
#include <stdarg.h>
#include <stdio.h>
@ -22,11 +22,6 @@ struct drgn_error drgn_stop = {
.message = "stop iteration",
};
struct drgn_error drgn_not_elf = {
.code = DRGN_ERROR_ELF_FORMAT,
.message = "not an ELF file",
};
static struct drgn_error *drgn_error_create_nodup(enum drgn_error_code code,
char *message)
{
@ -194,3 +189,9 @@ struct drgn_error *drgn_error_libdw(void)
return drgn_error_format(DRGN_ERROR_DWARF_FORMAT, "libdw error: %s",
dwarf_errmsg(-1));
}
struct drgn_error *drgn_error_libdwfl(void)
{
return drgn_error_format(DRGN_ERROR_DWARF_FORMAT, "libdwfl error: %s",
dwfl_errmsg(-1));
}

View File

@ -37,9 +37,6 @@ struct drgn_object_type;
*/
extern struct drgn_error drgn_stop;
/** Not an ELF file. */
extern struct drgn_error drgn_not_elf;
struct string_builder;
/**
@ -67,6 +64,10 @@ struct drgn_error *drgn_error_libelf(void)
struct drgn_error *drgn_error_libdw(void)
__attribute__((returns_nonnull));
/** Create a @ref drgn_error from the libdwfl error indicator. */
struct drgn_error *drgn_error_libdwfl(void)
__attribute__((returns_nonnull));
/**
* Create a @ref drgn_error with a type name.
*

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
#ifndef DRGN_LINUX_KERNEL_H
#define DRGN_LINUX_KERNEL_H
#include <elfutils/libdw.h>
#include <elfutils/libdwfl.h>
#include "drgn.h"
@ -18,10 +18,11 @@ struct drgn_error *read_vmcoreinfo_fallback(struct drgn_memory_reader *reader,
bool have_non_zero_phys_addr,
struct vmcoreinfo *ret);
struct drgn_error *kernel_relocation_hook(struct drgn_program *prog,
const char *name, Dwarf_Die *die,
struct drgn_symbol *sym);
struct drgn_error *
linux_kernel_load_debug_info(struct drgn_program *prog, const char **paths,
size_t n);
struct drgn_error *load_kernel_debug_info(struct drgn_program *prog);
struct drgn_error *
linux_kernel_load_default_debug_info(struct drgn_program *prog);
#endif /* DRGN_LINUX_KERNEL_H */

View File

@ -65,41 +65,32 @@ static void drgn_program_update_arch(struct drgn_program *prog,
void drgn_program_init(struct drgn_program *prog,
enum drgn_architecture_flags arch)
{
memset(prog, 0, sizeof(*prog));
drgn_memory_reader_init(&prog->reader);
drgn_type_index_init(&prog->tindex);
drgn_symbol_index_init(&prog->sindex);
prog->file_segments = NULL;
prog->num_file_segments = 0;
prog->mappings = NULL;
prog->num_mappings = 0;
memset(&prog->vmcoreinfo, 0, sizeof(prog->vmcoreinfo));
prog->dicache = NULL;
prog->core_fd = -1;
prog->flags = 0;
prog->arch = DRGN_ARCH_AUTO;
if (arch != DRGN_ARCH_AUTO)
drgn_program_update_arch(prog, arch);
prog->added_vmcoreinfo_symbol_finder = false;
}
void drgn_program_deinit(struct drgn_program *prog)
{
size_t i;
drgn_symbol_index_deinit(&prog->sindex);
drgn_type_index_deinit(&prog->tindex);
drgn_memory_reader_deinit(&prog->reader);
free(prog->file_segments);
for (i = 0; i < prog->num_mappings; i++)
free(prog->mappings[i].path);
free(prog->mappings);
if (prog->core_fd != -1)
close(prog->core_fd);
drgn_dwarf_info_cache_destroy(prog->dicache);
if (prog->_dwfl) {
drgn_remove_all_dwfl_modules(prog->_dwfl);
dwfl_end(prog->_dwfl);
}
}
LIBDRGN_PUBLIC struct drgn_error *
@ -151,135 +142,6 @@ drgn_program_add_symbol_finder(struct drgn_program *prog,
return drgn_symbol_index_add_finder(&prog->sindex, fn, arg);
}
/*
* Returns NULL if a mapping was appended, &drgn_stop if the mapping was merged,
* non-NULL on error.
*/
static struct drgn_error *append_file_mapping(uint64_t start, uint64_t end,
uint64_t file_offset, char *path,
struct file_mapping **mappings,
size_t *num_mappings,
size_t *capacity)
{
struct file_mapping *mapping;
if (start > end) {
return drgn_error_create(DRGN_ERROR_OTHER,
"file memory mapping has negative length");
} else if (start == end) {
return NULL;
}
/*
* There may be separate mappings for adjacent areas of a file (e.g., if
* the mappings have different permissions). Make sure to merge those.
*/
if (*num_mappings) {
uint64_t length;
mapping = &(*mappings)[*num_mappings - 1];
length = mapping->end - mapping->start;
if (mapping->end == start &&
mapping->file_offset + length == file_offset &&
strcmp(mapping->path, path) == 0) {
mapping->end = end;
return &drgn_stop;
}
}
if (*num_mappings >= *capacity) {
size_t new_capacity;
if (*capacity == 0)
new_capacity = 1;
else
new_capacity = *capacity * 2;
if (!resize_array(mappings, new_capacity))
return &drgn_enomem;
*capacity = new_capacity;
}
mapping = &(*mappings)[(*num_mappings)++];
mapping->start = start;
mapping->end = end;
mapping->file_offset = file_offset;
mapping->path = path;
mapping->elf = NULL;
return NULL;
}
static struct drgn_error *parse_nt_file(const char *desc, size_t descsz,
bool is_64_bit,
struct file_mapping **mappings,
size_t *num_mappings,
size_t *mappings_capacity)
{
struct drgn_error *err;
uint64_t count, page_size, i;
const char *p = desc, *q, *end = &desc[descsz];
size_t paths_offset;
bool bswap = false;
if (is_64_bit) {
if (!read_u64(&p, end, bswap, &count) ||
!read_u64(&p, end, bswap, &page_size) ||
__builtin_mul_overflow(count, 24U, &paths_offset))
goto invalid;
} else {
if (!read_u32_into_u64(&p, end, bswap, &count) ||
!read_u32_into_u64(&p, end, bswap, &page_size) ||
__builtin_mul_overflow(count, 12U, &paths_offset))
goto invalid;
}
if (!read_in_bounds(p, end, paths_offset))
goto invalid;
q = p + paths_offset;
for (i = 0; i < count; i++) {
uint64_t mapping_start, mapping_end, file_offset;
const char *path;
size_t len;
/* We already did the bounds check above. */
if (is_64_bit) {
read_u64_nocheck(&p, bswap, &mapping_start);
read_u64_nocheck(&p, bswap, &mapping_end);
read_u64_nocheck(&p, bswap, &file_offset);
} else {
read_u32_into_u64_nocheck(&p, bswap, &mapping_start);
read_u32_into_u64_nocheck(&p, bswap, &mapping_end);
read_u32_into_u64_nocheck(&p, bswap, &file_offset);
}
file_offset *= page_size;
if (!read_string(&q, end, &path, &len))
goto invalid;
err = append_file_mapping(mapping_start, mapping_end, file_offset,
(char *)path, mappings, num_mappings,
mappings_capacity);
if (!err) {
struct file_mapping *mapping;
/*
* The mapping wasn't merged, so actually allocate the
* path now.
*/
mapping = &(*mappings)[*num_mappings - 1];
mapping->path = malloc(len + 1);
if (!mapping->path)
return &drgn_enomem;
memcpy(mapping->path, path, len + 1);
} else if (err->code != DRGN_ERROR_STOP) {
return err;
}
}
return NULL;
invalid:
return drgn_error_create(DRGN_ERROR_ELF_FORMAT, "invalid NT_FILE note");
}
static enum drgn_architecture_flags drgn_architecture_from_elf(Elf *elf)
{
char *e_ident = elf_getident(elf, NULL);
@ -310,7 +172,7 @@ drgn_program_set_core_dump(struct drgn_program *prog, const char *path)
GElf_Ehdr ehdr_mem, *ehdr;
enum drgn_architecture_flags arch;
bool is_64_bit;
size_t phnum, i, mappings_capacity = 0;
size_t phnum, i;
bool have_non_zero_phys_addr = false;
struct drgn_memory_file_segment *current_file_segment;
bool have_nt_taskstruct = false, have_vmcoreinfo = false, is_proc_kcore;
@ -332,12 +194,7 @@ drgn_program_set_core_dump(struct drgn_program *prog, const char *path)
}
ehdr = gelf_getehdr(elf, &ehdr_mem);
if (!ehdr) {
err = &drgn_not_elf;
goto out_elf;
}
if (ehdr->e_type != ET_CORE) {
if (!ehdr || ehdr->e_type != ET_CORE) {
err = drgn_error_format(DRGN_ERROR_INVALID_ARGUMENT,
"not an ELF core file");
goto out_elf;
@ -387,7 +244,7 @@ drgn_program_set_core_dump(struct drgn_program *prog, const char *path)
phdr = gelf_getphdr(elf, i, &phdr_mem);
if (!phdr) {
err = drgn_error_libelf();
goto out_mappings;
goto out_segments;
}
if (phdr->p_type == PT_LOAD) {
@ -409,7 +266,7 @@ drgn_program_set_core_dump(struct drgn_program *prog, const char *path)
current_file_segment,
false);
if (err)
goto out_mappings;
goto out_segments;
if (have_non_zero_phys_addr &&
phdr->p_paddr !=
(is_64_bit ? UINT64_MAX : UINT32_MAX)) {
@ -420,7 +277,7 @@ drgn_program_set_core_dump(struct drgn_program *prog, const char *path)
current_file_segment,
true);
if (err)
goto out_mappings;
goto out_segments;
}
current_file_segment++;
} else if (phdr->p_type == PT_NOTE) {
@ -434,7 +291,7 @@ drgn_program_set_core_dump(struct drgn_program *prog, const char *path)
note_header_type(phdr));
if (!data) {
err = drgn_error_libelf();
goto out_mappings;
goto out_segments;
}
offset = 0;
@ -447,25 +304,15 @@ drgn_program_set_core_dump(struct drgn_program *prog, const char *path)
name = (char *)data->d_buf + name_offset;
desc = (char *)data->d_buf + desc_offset;
if (strncmp(name, "CORE", nhdr.n_namesz) == 0) {
if (nhdr.n_type == NT_FILE) {
err = parse_nt_file(desc,
nhdr.n_descsz,
is_64_bit,
&prog->mappings,
&prog->num_mappings,
&mappings_capacity);
if (err)
goto out_mappings;
} else if (nhdr.n_type == NT_TASKSTRUCT) {
if (nhdr.n_type == NT_TASKSTRUCT)
have_nt_taskstruct = true;
}
} else if (strncmp(name, "VMCOREINFO",
nhdr.n_namesz) == 0) {
err = parse_vmcoreinfo(desc,
nhdr.n_descsz,
&prog->vmcoreinfo);
if (err)
goto out_mappings;
goto out_segments;
have_vmcoreinfo = true;
}
}
@ -474,11 +321,6 @@ drgn_program_set_core_dump(struct drgn_program *prog, const char *path)
elf_end(elf);
elf = NULL;
if (mappings_capacity > prog->num_mappings) {
/* We don't care if this fails. */
resize_array(&prog->mappings, prog->num_mappings);
}
if (have_nt_taskstruct) {
/*
* If the core file has an NT_TASKSTRUCT note and is in /proc,
@ -489,7 +331,7 @@ drgn_program_set_core_dump(struct drgn_program *prog, const char *path)
if (fstatfs(prog->core_fd, &fs) == -1) {
err = drgn_error_create_os("fstatfs", errno, path);
if (err)
goto out_mappings;
goto out_segments;
}
is_proc_kcore = fs.f_type == 0x9fa0; /* PROC_SUPER_MAGIC */
} else {
@ -501,7 +343,7 @@ drgn_program_set_core_dump(struct drgn_program *prog, const char *path)
have_non_zero_phys_addr,
&prog->vmcoreinfo);
if (err)
goto out_mappings;
goto out_segments;
have_vmcoreinfo = true;
}
@ -512,10 +354,6 @@ drgn_program_set_core_dump(struct drgn_program *prog, const char *path)
drgn_program_update_arch(prog, arch);
return NULL;
out_mappings:
free(prog->mappings);
prog->mappings = NULL;
prog->num_mappings = 0;
out_segments:
drgn_memory_reader_deinit(&prog->reader);
drgn_memory_reader_init(&prog->reader);
@ -536,59 +374,6 @@ drgn_program_set_kernel(struct drgn_program *prog)
return drgn_program_set_core_dump(prog, "/proc/kcore");
}
static struct drgn_error *parse_proc_maps(const char *maps_path,
struct file_mapping **mappings,
size_t *num_mappings)
{
struct drgn_error *err;
FILE *file;
size_t capacity = 0;
file = fopen(maps_path, "r");
if (!file)
return drgn_error_create_os("fopen", errno, maps_path);
for (;;) {
unsigned long mapping_start, mapping_end;
uint64_t file_offset;
char *path;
int ret;
ret = fscanf(file, "%lx-%lx %*c%*c%*c%*c %" SCNx64 " "
"%*x:%*x %*d%*[ ]%m[^\n]", &mapping_start,
&mapping_end, &file_offset, &path);
if (ret == EOF) {
break;
} else if (ret == 3) {
/* This is an anonymous mapping; skip it. */
continue;
} else if (ret != 4) {
err = drgn_error_format(DRGN_ERROR_OTHER,
"could not parse %s", maps_path);
goto out;
}
err = append_file_mapping(mapping_start, mapping_end,
file_offset, path, mappings,
num_mappings, &capacity);
if (err && err->code == DRGN_ERROR_STOP) {
/* The mapping was merged, so free the path. */
free(path);
} else if (err) {
goto out;
}
}
if (capacity > *num_mappings) {
/* We don't care if this fails. */
resize_array(mappings, *num_mappings);
}
err = NULL;
out:
fclose(file);
return err;
}
LIBDRGN_PUBLIC struct drgn_error *
drgn_program_set_pid(struct drgn_program *prog, pid_t pid)
{
@ -619,19 +404,11 @@ drgn_program_set_pid(struct drgn_program *prog, pid_t pid)
if (err)
goto out_segments;
sprintf(buf, "/proc/%ld/maps", (long)pid);
err = parse_proc_maps(buf, &prog->mappings, &prog->num_mappings);
if (err)
goto out_mappings;
prog->pid = pid;
prog->flags |= DRGN_PROGRAM_IS_LIVE;
drgn_program_update_arch(prog, DRGN_ARCH_HOST);
return NULL;
out_mappings:
free(prog->mappings);
prog->mappings = NULL;
prog->num_mappings = 0;
out_segments:
drgn_memory_reader_deinit(&prog->reader);
drgn_memory_reader_init(&prog->reader);
@ -644,84 +421,38 @@ out_fd:
return err;
}
static const Dwfl_Callbacks linux_proc_dwfl_callbacks = {
.find_elf = dwfl_linux_proc_find_elf,
.find_debuginfo = dwfl_standard_find_debuginfo,
};
static struct drgn_error *
userspace_relocation_hook(struct drgn_program *prog, const char *name,
Dwarf_Die *die, struct drgn_symbol *sym)
{
Elf *elf;
size_t phnum, i;
uint64_t file_offset;
static const Dwfl_Callbacks userspace_core_dump_dwfl_callbacks = {
.find_elf = dwfl_build_id_find_elf,
.find_debuginfo = dwfl_standard_find_debuginfo,
};
elf = dwarf_getelf(dwarf_cu_getdwarf(die->cu));
if (elf_getphdrnum(elf, &phnum) != 0)
return drgn_error_libelf();
for (i = 0; i < phnum; i++) {
GElf_Phdr phdr_mem, *phdr;
phdr = gelf_getphdr(elf, i, &phdr_mem);
if (!phdr)
return drgn_error_libelf();
if (phdr->p_type == PT_LOAD &&
phdr->p_vaddr <= sym->address &&
sym->address < phdr->p_vaddr + phdr->p_memsz) {
file_offset = (phdr->p_offset + sym->address -
phdr->p_vaddr);
break;
}
}
if (i >= phnum) {
return drgn_error_format(DRGN_ERROR_LOOKUP,
"could not find segment containing %s",
name);
}
for (i = 0; i < prog->num_mappings; i++) {
struct file_mapping *mapping = &prog->mappings[i];
uint64_t mapping_size;
mapping_size = mapping->end - mapping->start;
if (mapping->elf == elf &&
mapping->file_offset <= file_offset &&
file_offset < mapping->file_offset + mapping_size) {
sym->address = (mapping->start + file_offset -
mapping->file_offset);
return NULL;
}
}
return drgn_error_format(DRGN_ERROR_LOOKUP,
"could not find file mapping containing %s",
name);
}
static struct drgn_error *drgn_program_relocation_hook(const char *name,
Dwarf_Die *die,
struct drgn_symbol *sym,
void *arg)
{
struct drgn_program *prog = arg;
if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)
return kernel_relocation_hook(prog, name, die, sym);
else if (prog->num_mappings)
return userspace_relocation_hook(prog, name, die, sym);
else
return NULL;
}
struct drgn_error *drgn_program_open_debug_info(struct drgn_program *prog,
const char *path, Elf **elf_ret)
struct drgn_error *drgn_program_get_dwarf(struct drgn_program *prog,
Dwfl **dwfl_ret,
struct drgn_dwarf_index **dindex_ret)
{
struct drgn_error *err;
Elf *elf;
const Dwfl_Callbacks *dwfl_callbacks;
if (!prog->_dwfl) {
if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)
dwfl_callbacks = &drgn_dwfl_callbacks;
else if (prog->flags & DRGN_PROGRAM_IS_LIVE)
dwfl_callbacks = &linux_proc_dwfl_callbacks;
else
dwfl_callbacks = &userspace_core_dump_dwfl_callbacks;
prog->_dwfl = dwfl_begin(dwfl_callbacks);
if (!prog->_dwfl)
return drgn_error_libdwfl();
}
if (!prog->dicache) {
struct drgn_dwarf_info_cache *dicache;
err = drgn_dwarf_info_cache_create(&prog->tindex,
&dicache);
err = drgn_dwarf_info_cache_create(&prog->tindex, &dicache);
if (err)
return err;
err = drgn_program_add_type_finder(prog, drgn_dwarf_type_find,
@ -739,30 +470,25 @@ struct drgn_error *drgn_program_open_debug_info(struct drgn_program *prog,
return err;
}
prog->dicache = dicache;
dicache->relocation_hook = drgn_program_relocation_hook;
dicache->relocation_arg = prog;
}
err = drgn_dwarf_index_open(&prog->dicache->dindex, path, &elf);
if (err)
return err;
drgn_program_update_arch(prog, drgn_architecture_from_elf(elf));
if (elf_ret)
*elf_ret = elf;
*dwfl_ret = prog->_dwfl;
*dindex_ret = &prog->dicache->dindex;
return NULL;
}
void drgn_program_close_unindexed_debug_info(struct drgn_program *prog)
static int drgn_architecture_from_dwfl_module(Dwfl_Module *module,
void **userdatap,
const char *name, Dwarf_Addr base,
void *arg)
{
if (prog->dicache)
drgn_dwarf_index_close_unindexed(&prog->dicache->dindex);
}
Elf *elf;
GElf_Addr load_base;
struct drgn_error *drgn_program_update_debug_info(struct drgn_program *prog)
{
if (!prog->dicache)
return NULL;
return drgn_dwarf_index_update(&prog->dicache->dindex);
elf = dwfl_module_getelf(module, &load_base);
if (!elf)
return DWARF_CB_OK;
drgn_program_update_arch(arg, drgn_architecture_from_elf(elf));
return DWARF_CB_ABORT;
}
LIBDRGN_PUBLIC struct drgn_error *
@ -770,60 +496,90 @@ drgn_program_load_debug_info(struct drgn_program *prog, const char **paths,
size_t n)
{
struct drgn_error *err;
size_t i;
Dwfl *dwfl;
struct drgn_dwarf_index *dindex;
for (i = 0; i < n; i++) {
err = drgn_program_open_debug_info(prog, paths[i], NULL);
if (err) {
drgn_program_close_unindexed_debug_info(prog);
err = drgn_program_get_dwarf(prog, &dwfl, &dindex);
if (err)
return err;
if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL) {
err = linux_kernel_load_debug_info(prog, paths, n);
if (err)
return err;
}
}
return drgn_program_update_debug_info(prog);
}
} else {
size_t i;
static struct drgn_error *load_userspace_debug_info(struct drgn_program *prog)
{
struct drgn_error *err;
struct file_mapping *mappings;
size_t i, num_mappings;
bool success = false;
mappings = prog->mappings;
num_mappings = prog->num_mappings;
for (i = 0; i < num_mappings; i++) {
if (prog->mappings[i].elf)
continue;
err = drgn_program_open_debug_info(prog, mappings[i].path,
&mappings[i].elf);
if (err) {
mappings[i].elf = NULL;
if ((err->code == DRGN_ERROR_OS &&
err->errnum == ENOENT) ||
err == &drgn_not_elf ||
err->code == DRGN_ERROR_MISSING_DEBUG_INFO) {
drgn_error_destroy(err);
continue;
for (i = 0; i < n; i++) {
if (!dwfl_report_elf(dwfl, paths[i], paths[i], -1, 0,
true)) {
err = drgn_error_libdwfl();
drgn_remove_unindexed_dwfl_modules(dwfl);
return err;
}
drgn_dwarf_index_close_unindexed(&prog->dicache->dindex);
}
err = drgn_dwarf_index_update(dindex, dwfl);
if (err) {
drgn_remove_unindexed_dwfl_modules(dwfl);
return err;
}
success = true;
}
if (!success) {
return drgn_error_create(DRGN_ERROR_MISSING_DEBUG_INFO,
"no debug information found");
if (prog->arch == DRGN_ARCH_AUTO) {
dwfl_getmodules(dwfl, drgn_architecture_from_dwfl_module, prog,
0);
}
return drgn_program_update_debug_info(prog);
return NULL;
}
LIBDRGN_PUBLIC struct drgn_error *
drgn_program_load_default_debug_info(struct drgn_program *prog)
{
struct drgn_error *err;
Dwfl *dwfl;
struct drgn_dwarf_index *dindex;
if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)
return load_kernel_debug_info(prog);
else
return load_userspace_debug_info(prog);
return linux_kernel_load_default_debug_info(prog);
err = drgn_program_get_dwarf(prog, &dwfl, &dindex);
if (err)
return err;
dwfl_report_begin_add(dwfl);
if (prog->flags & DRGN_PROGRAM_IS_LIVE) {
int ret;
ret = dwfl_linux_proc_report(dwfl, prog->pid);
if (ret == -1) {
err = drgn_error_libdwfl();
} else if (ret) {
err = drgn_error_create_os("dwfl_linux_proc_report",
ret, NULL);
} else {
err = NULL;
}
} else {
Elf *elf;
elf = elf_begin(prog->core_fd, ELF_C_READ, NULL);
if (elf) {
if (dwfl_core_file_report(dwfl, elf, NULL) == -1)
err = drgn_error_libdwfl();
else
err = NULL;
elf_end(elf);
} else {
err = drgn_error_libelf();
}
}
dwfl_report_end(dwfl, NULL, NULL);
if (err)
goto out;
err = drgn_dwarf_index_update(dindex, dwfl);
out:
if (err)
drgn_remove_unindexed_dwfl_modules(dwfl);
return err;
}
struct drgn_error *drgn_program_init_core_dump(struct drgn_program *prog,

View File

@ -12,6 +12,8 @@
#ifndef DRGN_PROGRAM_H
#define DRGN_PROGRAM_H
#include <elfutils/libdwfl.h>
#include "memory_reader.h"
#include "symbol_index.h"
#include "type_index.h"
@ -42,29 +44,8 @@ struct vmcoreinfo {
uint64_t kaslr_offset;
};
/**
* An ELF file which is mapped into a program.
*
* This is parsed from the @c NT_FILE note of a crash dump or
* <tt>/proc/$pid/maps</tt> of a running program.
*/
struct file_mapping {
/** Path of the file. */
char *path;
/** ELF handle. */
Elf *elf;
/** Starting virtual address in the program's address space. */
uint64_t start;
/**
* One byte after the last virtual address in the program's address
* space.
*/
uint64_t end;
/** Starting offset in the file. */
uint64_t file_offset;
};
struct drgn_dwarf_info_cache;
struct drgn_dwarf_index;
struct drgn_program {
/** @privatesection */
@ -73,9 +54,17 @@ struct drgn_program {
struct drgn_symbol_index sindex;
struct drgn_memory_file_segment *file_segments;
size_t num_file_segments;
struct file_mapping *mappings;
size_t num_mappings;
/*
* Valid iff <tt>flags & DRGN_PROGRAM_IS_LINUX_KERNEL</tt>.
*/
struct vmcoreinfo vmcoreinfo;
/*
* Valid iff
* <tt>(flags & (DRGN_PROGRAM_IS_LINUX_KERNEL | DRGN_PROGRAM_IS_LIVE)) ==
* DRGN_PROGRAM_IS_LIVE</tt>.
*/
pid_t pid;
Dwfl *_dwfl;
struct drgn_dwarf_info_cache *dicache;
int core_fd;
enum drgn_program_flags flags;
@ -114,12 +103,14 @@ static inline uint64_t drgn_program_word_mask(struct drgn_program *prog)
return prog->arch & DRGN_ARCH_IS_64_BIT ? UINT64_MAX : UINT32_MAX;
}
struct drgn_error *drgn_program_open_debug_info(struct drgn_program *prog,
const char *path, Elf **elf_ret);
void drgn_program_close_unindexed_debug_info(struct drgn_program *prog);
struct drgn_error *drgn_program_update_debug_info(struct drgn_program *prog);
/*
* Get the @c Dwfl handle and @ref drgn_dwarf_index for a @ref drgn_program.
*
* These are created the first time that this is called.
*/
struct drgn_error *drgn_program_get_dwarf(struct drgn_program *prog,
Dwfl **dwfl_ret,
struct drgn_dwarf_index **dindex_ret);
/** @} */

View File

@ -1,7 +1,7 @@
from collections import namedtuple
import os.path
from tests.elf import ET, SHT
from tests.elf import ET, PT, SHT
from tests.elfwriter import ElfSection, create_elf_file
from tests.dwarf import DW_AT, DW_FORM, DW_TAG
@ -187,6 +187,11 @@ def compile_dwarf(dies, little_endian=True, bits=64):
], dies)
return create_elf_file(ET.EXEC, [
ElfSection(
p_type=PT.LOAD,
vaddr=0xffff0000,
data=b'',
),
ElfSection(
name='.debug_abbrev',
sh_type=SHT.PROGBITS,

View File

@ -578,13 +578,10 @@ class TestSymbols(unittest.TestCase):
class TestCoreDump(unittest.TestCase):
def test_not_elf(self):
prog = Program()
self.assertRaisesRegex(FileFormatError, 'not an ELF file',
prog.set_core_dump, '/dev/null')
def test_not_core_dump(self):
prog = Program()
self.assertRaisesRegex(ValueError, 'not an ELF core file',
prog.set_core_dump, '/dev/null')
with tempfile.NamedTemporaryFile() as f:
f.write(create_elf_file(ET.EXEC, []))
f.flush()