mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-22 17:23:06 +00:00
18b12a5c7b
We're currently getting .eh_frame from the debug file. However, since .eh_frame is an SHF_ALLOC section, it is actually in the loaded file, and may not be in the debug file. This causes us to fail to unwind in modules whose debug file was created with objcopy --only-keep-debug (which is typical for Linux distro debug files). Fix it by getting .eh_frame from the loaded file. To make this easier, we split .eh_frame and .debug_frame data into two separate tables. We also don't bother deduplicating them anymore, since GCC and Clang only seem to generate one or the other in practice. Signed-off-by: Omar Sandoval <osandov@osandov.com>
2302 lines
63 KiB
C
2302 lines
63 KiB
C
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
#include <assert.h>
|
|
#include <byteswap.h>
|
|
#include <elf.h>
|
|
#include <elfutils/libdw.h>
|
|
#include <elfutils/libdwelf.h>
|
|
#include <elfutils/version.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <gelf.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "debug_info.h"
|
|
#include "elf_file.h"
|
|
#include "error.h"
|
|
#include "linux_kernel.h"
|
|
#include "program.h"
|
|
#include "util.h"
|
|
|
|
DEFINE_VECTOR_FUNCTIONS(drgn_module_vector)
|
|
|
|
struct drgn_module_key {
|
|
const void *build_id;
|
|
size_t build_id_len;
|
|
uint64_t start, end;
|
|
};
|
|
|
|
static inline struct drgn_module_key
|
|
drgn_module_key(struct drgn_module * const *entry)
|
|
{
|
|
return (struct drgn_module_key){
|
|
.build_id = (*entry)->build_id,
|
|
.build_id_len = (*entry)->build_id_len,
|
|
.start = (*entry)->start,
|
|
.end = (*entry)->end,
|
|
};
|
|
}
|
|
|
|
static inline struct hash_pair
|
|
drgn_module_key_hash_pair(const struct drgn_module_key *key)
|
|
{
|
|
size_t hash = hash_bytes(key->build_id, key->build_id_len);
|
|
hash = hash_combine(hash, key->start);
|
|
hash = hash_combine(hash, key->end);
|
|
return hash_pair_from_avalanching_hash(hash);
|
|
}
|
|
static inline bool drgn_module_key_eq(const struct drgn_module_key *a,
|
|
const struct drgn_module_key *b)
|
|
{
|
|
return (a->build_id_len == b->build_id_len &&
|
|
memcmp(a->build_id, b->build_id, a->build_id_len) == 0 &&
|
|
a->start == b->start && a->end == b->end);
|
|
}
|
|
DEFINE_HASH_TABLE_FUNCTIONS(drgn_module_table, drgn_module_key,
|
|
drgn_module_key_hash_pair, drgn_module_key_eq)
|
|
|
|
DEFINE_HASH_SET_FUNCTIONS(c_string_set, c_string_key_hash_pair, c_string_key_eq)
|
|
|
|
/**
|
|
* @c Dwfl_Callbacks::find_elf() implementation.
|
|
*
|
|
* Ideally we'd use @c dwfl_report_elf() instead, but that doesn't take an @c
|
|
* Elf handle, which we need for a couple of reasons:
|
|
*
|
|
* - We usually already have the @c Elf handle open in order to identify the
|
|
* file.
|
|
* - For kernel modules, we set the section addresses in the @c Elf handle
|
|
* ourselves instead of using @c Dwfl_Callbacks::section_address().
|
|
*
|
|
* Additionally, there's a special case for vmlinux. It is usually an @c ET_EXEC
|
|
* ELF file, but when KASLR is enabled, it needs to be handled like an @c ET_DYN
|
|
* file. libdwfl has a hack for this when @c dwfl_report_module() is used, but
|
|
* @ref dwfl_report_elf() bypasses this hack.
|
|
*
|
|
* So, we're stuck using @c dwfl_report_module() and this dummy callback.
|
|
*/
|
|
static int drgn_dwfl_find_elf(Dwfl_Module *dwfl_module, void **userdatap,
|
|
const char *name, Dwarf_Addr base,
|
|
char **file_name, Elf **elfp)
|
|
{
|
|
struct drgn_module *module = *userdatap;
|
|
/*
|
|
* libdwfl consumes the returned path, file descriptor, and ELF handle,
|
|
* so clear the fields.
|
|
*/
|
|
*file_name = module->path;
|
|
int fd = module->fd;
|
|
*elfp = module->elf;
|
|
module->path = NULL;
|
|
module->fd = -1;
|
|
module->elf = NULL;
|
|
return fd;
|
|
}
|
|
|
|
/*
|
|
* Uses drgn_dwfl_find_elf() if the ELF file was reported directly and falls
|
|
* back to dwfl_linux_proc_find_elf() otherwise.
|
|
*/
|
|
static int drgn_dwfl_linux_proc_find_elf(Dwfl_Module *dwfl_module,
|
|
void **userdatap, const char *name,
|
|
Dwarf_Addr base, char **file_name,
|
|
Elf **elfp)
|
|
{
|
|
struct drgn_module *module = *userdatap;
|
|
if (module->elf) {
|
|
return drgn_dwfl_find_elf(dwfl_module, userdatap, name, base,
|
|
file_name, elfp);
|
|
}
|
|
return dwfl_linux_proc_find_elf(dwfl_module, userdatap, name, base,
|
|
file_name, elfp);
|
|
}
|
|
|
|
/*
|
|
* Uses drgn_dwfl_find_elf() if the ELF file was reported directly and falls
|
|
* back to dwfl_build_id_find_elf() otherwise.
|
|
*/
|
|
static int drgn_dwfl_build_id_find_elf(Dwfl_Module *dwfl_module,
|
|
void **userdatap, const char *name,
|
|
Dwarf_Addr base, char **file_name,
|
|
Elf **elfp)
|
|
{
|
|
struct drgn_module *module = *userdatap;
|
|
if (module->elf) {
|
|
return drgn_dwfl_find_elf(dwfl_module, userdatap, name, base,
|
|
file_name, elfp);
|
|
}
|
|
return dwfl_build_id_find_elf(dwfl_module, userdatap, name, base,
|
|
file_name, elfp);
|
|
}
|
|
|
|
/**
|
|
* @c Dwfl_Callbacks::section_address() implementation.
|
|
*
|
|
* We set the section header @c sh_addr in memory instead of using this, but
|
|
* libdwfl requires the callback pointer to be non-@c NULL. It will be called
|
|
* for any sections that still have a zero @c sh_addr, meaning they are not
|
|
* present in memory.
|
|
*/
|
|
static int drgn_dwfl_section_address(Dwfl_Module *module, void **userdatap,
|
|
const char *name, Dwarf_Addr base,
|
|
const char *secname, Elf32_Word shndx,
|
|
const GElf_Shdr *shdr, Dwarf_Addr *addr)
|
|
{
|
|
*addr = -1;
|
|
return DWARF_CB_OK;
|
|
}
|
|
|
|
static const Dwfl_Callbacks drgn_dwfl_callbacks = {
|
|
.find_elf = drgn_dwfl_find_elf,
|
|
.find_debuginfo = dwfl_standard_find_debuginfo,
|
|
.section_address = drgn_dwfl_section_address,
|
|
};
|
|
|
|
static const Dwfl_Callbacks drgn_linux_proc_dwfl_callbacks = {
|
|
.find_elf = drgn_dwfl_linux_proc_find_elf,
|
|
.find_debuginfo = dwfl_standard_find_debuginfo,
|
|
.section_address = drgn_dwfl_section_address,
|
|
};
|
|
|
|
static const Dwfl_Callbacks drgn_userspace_core_dump_dwfl_callbacks = {
|
|
.find_elf = drgn_dwfl_build_id_find_elf,
|
|
.find_debuginfo = dwfl_standard_find_debuginfo,
|
|
.section_address = drgn_dwfl_section_address,
|
|
};
|
|
|
|
static void drgn_module_destroy(struct drgn_module *module)
|
|
{
|
|
if (module) {
|
|
drgn_error_destroy(module->err);
|
|
drgn_module_orc_info_deinit(module);
|
|
drgn_module_dwarf_info_deinit(module);
|
|
elf_end(module->elf);
|
|
if (module->fd != -1)
|
|
close(module->fd);
|
|
free(module->path);
|
|
if (module->debug_file != module->loaded_file)
|
|
drgn_elf_file_destroy(module->debug_file);
|
|
drgn_elf_file_destroy(module->loaded_file);
|
|
free(module->name);
|
|
free(module);
|
|
}
|
|
}
|
|
|
|
static void drgn_module_finish_indexing(struct drgn_debug_info *dbinfo,
|
|
struct drgn_module *module)
|
|
{
|
|
module->state = DRGN_DEBUG_INFO_MODULE_INDEXED;
|
|
if (module->name) {
|
|
int ret = c_string_set_insert(&dbinfo->module_names,
|
|
(const char **)&module->name,
|
|
NULL);
|
|
/* drgn_debug_info_update_index() should've reserved enough. */
|
|
assert(ret != -1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Wrapper around dwfl_report_end() that works around a libdwfl bug which causes
|
|
* it to close stdin when it frees some modules that were reported by
|
|
* dwfl_core_file_report(). This was fixed in elfutils 0.177 by commit
|
|
* d37f6ea7e3e5 ("libdwfl: Fix fd leak/closing wrong fd after
|
|
* dwfl_core_file_report()"), but we support older versions.
|
|
*/
|
|
static int my_dwfl_report_end(struct drgn_debug_info *dbinfo,
|
|
int (*removed)(Dwfl_Module *, void *,
|
|
const char *, Dwarf_Addr, void *),
|
|
void *arg)
|
|
{
|
|
int fd = -1;
|
|
if ((dbinfo->prog->flags
|
|
& (DRGN_PROGRAM_IS_LINUX_KERNEL | DRGN_PROGRAM_IS_LIVE)) == 0)
|
|
fd = dup(0);
|
|
int ret = dwfl_report_end(dbinfo->dwfl, removed, arg);
|
|
if (fd != -1) {
|
|
dup2(fd, 0);
|
|
close(fd);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
struct drgn_dwfl_module_removed_arg {
|
|
struct drgn_debug_info *dbinfo;
|
|
bool finish_indexing;
|
|
bool free_all;
|
|
};
|
|
|
|
static int drgn_dwfl_module_removed(Dwfl_Module *dwfl_module, void *userdatap,
|
|
const char *name, Dwarf_Addr base,
|
|
void *_arg)
|
|
{
|
|
struct drgn_dwfl_module_removed_arg *arg = _arg;
|
|
/*
|
|
* userdatap is actually a void ** like for the other libdwfl callbacks,
|
|
* but dwfl_report_end() has the wrong signature for the removed
|
|
* callback.
|
|
*/
|
|
struct drgn_module *module = *(void **)userdatap;
|
|
if (arg->finish_indexing && module &&
|
|
module->state == DRGN_DEBUG_INFO_MODULE_INDEXING)
|
|
drgn_module_finish_indexing(arg->dbinfo, module);
|
|
if (arg->free_all || !module ||
|
|
module->state != DRGN_DEBUG_INFO_MODULE_INDEXED) {
|
|
drgn_module_destroy(module);
|
|
} else {
|
|
/*
|
|
* The module was already indexed. Report it again so libdwfl
|
|
* doesn't remove it.
|
|
*/
|
|
Dwarf_Addr end;
|
|
dwfl_module_info(dwfl_module, NULL, NULL, &end, NULL, NULL,
|
|
NULL, NULL);
|
|
dwfl_report_module(arg->dbinfo->dwfl, name, base, end);
|
|
}
|
|
return DWARF_CB_OK;
|
|
}
|
|
|
|
static void drgn_debug_info_free_modules(struct drgn_debug_info *dbinfo,
|
|
bool finish_indexing, bool free_all)
|
|
{
|
|
for (struct drgn_module_table_iterator it =
|
|
drgn_module_table_first(&dbinfo->modules); it.entry; ) {
|
|
struct drgn_module *module = *it.entry;
|
|
struct drgn_module **nextp = it.entry;
|
|
do {
|
|
struct drgn_module *next = module->next;
|
|
if (finish_indexing &&
|
|
module->state == DRGN_DEBUG_INFO_MODULE_INDEXING)
|
|
drgn_module_finish_indexing(dbinfo, module);
|
|
if (free_all ||
|
|
module->state != DRGN_DEBUG_INFO_MODULE_INDEXED) {
|
|
if (module == *nextp) {
|
|
if (nextp == it.entry && !next) {
|
|
it = drgn_module_table_delete_iterator(&dbinfo->modules,
|
|
it);
|
|
} else {
|
|
if (!next)
|
|
it = drgn_module_table_next(it);
|
|
*nextp = next;
|
|
}
|
|
}
|
|
void **userdatap;
|
|
dwfl_module_info(module->dwfl_module,
|
|
&userdatap, NULL, NULL, NULL,
|
|
NULL, NULL, NULL);
|
|
*userdatap = NULL;
|
|
drgn_module_destroy(module);
|
|
} else {
|
|
if (!next)
|
|
it = drgn_module_table_next(it);
|
|
nextp = &module->next;
|
|
}
|
|
module = next;
|
|
} while (module);
|
|
}
|
|
|
|
dwfl_report_begin(dbinfo->dwfl);
|
|
struct drgn_dwfl_module_removed_arg arg = {
|
|
.dbinfo = dbinfo,
|
|
.finish_indexing = finish_indexing,
|
|
.free_all = free_all,
|
|
};
|
|
my_dwfl_report_end(dbinfo, drgn_dwfl_module_removed, &arg);
|
|
}
|
|
|
|
struct drgn_error *
|
|
drgn_debug_info_report_error(struct drgn_debug_info_load_state *load,
|
|
const char *name, const char *message,
|
|
struct drgn_error *err)
|
|
{
|
|
if (err && err->code == DRGN_ERROR_NO_MEMORY) {
|
|
/* Always fail hard if we're out of memory. */
|
|
goto err;
|
|
}
|
|
if (load->num_errors == 0 &&
|
|
!string_builder_append(&load->errors,
|
|
"could not get debugging information for:"))
|
|
goto err;
|
|
if (load->num_errors < load->max_errors) {
|
|
if (!string_builder_line_break(&load->errors))
|
|
goto err;
|
|
if (name && !string_builder_append(&load->errors, name))
|
|
goto err;
|
|
if (name && (message || err) &&
|
|
!string_builder_append(&load->errors, " ("))
|
|
goto err;
|
|
if (message && !string_builder_append(&load->errors, message))
|
|
goto err;
|
|
if (message && err &&
|
|
!string_builder_append(&load->errors, ": "))
|
|
goto err;
|
|
if (err && !string_builder_append_error(&load->errors, err))
|
|
goto err;
|
|
if (name && (message || err) &&
|
|
!string_builder_appendc(&load->errors, ')'))
|
|
goto err;
|
|
}
|
|
load->num_errors++;
|
|
drgn_error_destroy(err);
|
|
return NULL;
|
|
|
|
err:
|
|
drgn_error_destroy(err);
|
|
return &drgn_enomem;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
drgn_debug_info_report_module(struct drgn_debug_info_load_state *load,
|
|
const void *build_id, size_t build_id_len,
|
|
uint64_t start, uint64_t end, const char *name,
|
|
Dwfl_Module *dwfl_module, const char *path,
|
|
int fd, Elf *elf, bool *new_ret)
|
|
{
|
|
struct drgn_debug_info *dbinfo = load->dbinfo;
|
|
struct drgn_error *err;
|
|
char *path_key = NULL;
|
|
|
|
if (new_ret)
|
|
*new_ret = false;
|
|
|
|
struct hash_pair hp;
|
|
struct drgn_module_table_iterator it;
|
|
if (build_id_len) {
|
|
struct drgn_module_key key = {
|
|
.build_id = build_id,
|
|
.build_id_len = build_id_len,
|
|
.start = start,
|
|
.end = end,
|
|
};
|
|
hp = drgn_module_table_hash(&key);
|
|
it = drgn_module_table_search_hashed(&dbinfo->modules, &key,
|
|
hp);
|
|
if (it.entry &&
|
|
(*it.entry)->state == DRGN_DEBUG_INFO_MODULE_INDEXED) {
|
|
/* We've already indexed this module. */
|
|
err = NULL;
|
|
goto free;
|
|
}
|
|
}
|
|
|
|
if (!dwfl_module) {
|
|
path_key = realpath(path, NULL);
|
|
if (!path_key) {
|
|
path_key = strdup(path);
|
|
if (!path_key) {
|
|
err = &drgn_enomem;
|
|
goto free;
|
|
}
|
|
}
|
|
|
|
dwfl_module = dwfl_report_module(dbinfo->dwfl, path_key, start,
|
|
end);
|
|
if (!dwfl_module) {
|
|
err = drgn_error_libdwfl();
|
|
goto free;
|
|
}
|
|
}
|
|
|
|
void **userdatap;
|
|
dwfl_module_info(dwfl_module, &userdatap, NULL, NULL, NULL, NULL, NULL,
|
|
NULL);
|
|
if (*userdatap) {
|
|
/* We've already reported this file at this offset. */
|
|
err = NULL;
|
|
goto free;
|
|
}
|
|
if (new_ret)
|
|
*new_ret = true;
|
|
|
|
struct drgn_module *module = calloc(1, sizeof(*module));
|
|
if (!module) {
|
|
err = &drgn_enomem;
|
|
goto free;
|
|
}
|
|
module->state = DRGN_DEBUG_INFO_MODULE_NEW;
|
|
module->build_id = build_id;
|
|
module->build_id_len = build_id_len;
|
|
module->start = start;
|
|
module->end = end;
|
|
if (name) {
|
|
module->name = strdup(name);
|
|
if (!module->name) {
|
|
err = &drgn_enomem;
|
|
free(module);
|
|
goto free;
|
|
}
|
|
}
|
|
module->dwfl_module = dwfl_module;
|
|
module->path = path_key;
|
|
module->fd = fd;
|
|
module->elf = elf;
|
|
|
|
/* path_key, fd and elf are owned by the module now. */
|
|
|
|
if (!drgn_module_vector_append(&load->new_modules, &module)) {
|
|
drgn_module_destroy(module);
|
|
return &drgn_enomem;
|
|
}
|
|
if (build_id_len) {
|
|
if (it.entry) {
|
|
/*
|
|
* The first module with this build ID is in
|
|
* new_modules, so insert it after in the list, not
|
|
* before.
|
|
*/
|
|
module->next = (*it.entry)->next;
|
|
(*it.entry)->next = module;
|
|
} else if (drgn_module_table_insert_searched(&dbinfo->modules,
|
|
&module, hp,
|
|
NULL) < 0) {
|
|
load->new_modules.size--;
|
|
drgn_module_destroy(module);
|
|
return &drgn_enomem;
|
|
}
|
|
}
|
|
*userdatap = module;
|
|
return NULL;
|
|
|
|
free:
|
|
elf_end(elf);
|
|
if (fd != -1)
|
|
close(fd);
|
|
free(path_key);
|
|
return err;
|
|
}
|
|
|
|
struct drgn_error *
|
|
drgn_debug_info_report_elf(struct drgn_debug_info_load_state *load,
|
|
const char *path, int fd, Elf *elf, uint64_t start,
|
|
uint64_t end, const char *name, bool *new_ret)
|
|
{
|
|
|
|
struct drgn_error *err;
|
|
const void *build_id;
|
|
ssize_t build_id_len = dwelf_elf_gnu_build_id(elf, &build_id);
|
|
if (build_id_len < 0) {
|
|
err = drgn_debug_info_report_error(load, path, NULL,
|
|
drgn_error_libelf());
|
|
elf_end(elf);
|
|
close(fd);
|
|
return err;
|
|
} else if (build_id_len == 0) {
|
|
build_id = NULL;
|
|
}
|
|
return drgn_debug_info_report_module(load, build_id, build_id_len,
|
|
start, end, name, NULL, path, fd,
|
|
elf, new_ret);
|
|
}
|
|
|
|
static int drgn_debug_info_report_dwfl_module(Dwfl_Module *dwfl_module,
|
|
void **userdatap,
|
|
const char *name, Dwarf_Addr base,
|
|
void *arg)
|
|
{
|
|
struct drgn_debug_info_load_state *load = arg;
|
|
struct drgn_error *err;
|
|
|
|
if (*userdatap) {
|
|
/*
|
|
* This was either reported from drgn_debug_info_report_elf() or
|
|
* already indexed.
|
|
*/
|
|
return DWARF_CB_OK;
|
|
}
|
|
|
|
const unsigned char *build_id;
|
|
GElf_Addr build_id_vaddr;
|
|
int build_id_len = dwfl_module_build_id(dwfl_module, &build_id,
|
|
&build_id_vaddr);
|
|
if (build_id_len < 0) {
|
|
err = drgn_debug_info_report_error(load, name, NULL,
|
|
drgn_error_libdwfl());
|
|
if (err)
|
|
goto err;
|
|
} else if (build_id_len == 0) {
|
|
build_id = NULL;
|
|
}
|
|
Dwarf_Addr end;
|
|
dwfl_module_info(dwfl_module, NULL, NULL, &end, NULL, NULL, NULL, NULL);
|
|
err = drgn_debug_info_report_module(load, build_id, build_id_len, base,
|
|
end, NULL, dwfl_module, name, -1,
|
|
NULL, NULL);
|
|
if (err)
|
|
goto err;
|
|
return DWARF_CB_OK;
|
|
|
|
err:
|
|
drgn_error_destroy(err);
|
|
return DWARF_CB_ABORT;
|
|
}
|
|
|
|
static struct drgn_error *drgn_get_nt_file(Elf *elf, const char **ret,
|
|
size_t *len_ret)
|
|
{
|
|
size_t phnum;
|
|
if (elf_getphdrnum(elf, &phnum) != 0)
|
|
return drgn_error_libelf();
|
|
for (size_t i = 0; i < phnum; i++) {
|
|
GElf_Phdr phdr_mem, *phdr = gelf_getphdr(elf, i, &phdr_mem);
|
|
if (!phdr)
|
|
return drgn_error_libelf();
|
|
if (phdr->p_type == PT_NOTE) {
|
|
Elf_Data *data = elf_getdata_rawchunk(elf,
|
|
phdr->p_offset,
|
|
phdr->p_filesz,
|
|
note_header_type(phdr->p_align));
|
|
if (!data)
|
|
return drgn_error_libelf();
|
|
GElf_Nhdr nhdr;
|
|
size_t offset = 0, name_offset, desc_offset;
|
|
while (offset < data->d_size &&
|
|
(offset = gelf_getnote(data, offset, &nhdr,
|
|
&name_offset,
|
|
&desc_offset))) {
|
|
const char *name =
|
|
(char *)data->d_buf + name_offset;
|
|
if (nhdr.n_namesz == sizeof("CORE") &&
|
|
memcmp(name, "CORE", sizeof("CORE")) == 0 &&
|
|
nhdr.n_type == NT_FILE) {
|
|
*ret = (char *)data->d_buf + desc_offset;
|
|
*len_ret = nhdr.n_descsz;
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*ret = NULL;
|
|
*len_ret = 0;
|
|
return NULL;
|
|
}
|
|
|
|
struct drgn_mapped_file_segment {
|
|
uint64_t start;
|
|
uint64_t end;
|
|
uint64_t file_offset;
|
|
};
|
|
|
|
DEFINE_VECTOR(drgn_mapped_file_segment_vector, struct drgn_mapped_file_segment)
|
|
|
|
DEFINE_HASH_MAP(drgn_mapped_files, const char *,
|
|
struct drgn_mapped_file_segment_vector, c_string_key_hash_pair,
|
|
c_string_key_eq)
|
|
|
|
struct userspace_core_report_state {
|
|
struct drgn_mapped_files files;
|
|
void *phdr_buf;
|
|
size_t phdr_buf_capacity;
|
|
void *segment_buf;
|
|
size_t segment_buf_capacity;
|
|
};
|
|
|
|
static struct drgn_error *parse_nt_file_error(struct binary_buffer *bb,
|
|
const char *pos,
|
|
const char *message)
|
|
{
|
|
return drgn_error_create(DRGN_ERROR_OTHER, "couldn't parse NT_FILE");
|
|
}
|
|
|
|
static bool
|
|
drgn_mapped_file_segments_contiguous(const struct drgn_mapped_file_segment *segment1,
|
|
const struct drgn_mapped_file_segment *segment2)
|
|
{
|
|
if (segment1->end != segment2->start)
|
|
return false;
|
|
uint64_t size = segment1->end - segment1->start;
|
|
return segment1->file_offset + size == segment2->file_offset;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
userspace_core_get_mapped_files(struct drgn_debug_info_load_state *load,
|
|
struct userspace_core_report_state *core,
|
|
const char *nt_file, size_t nt_file_len)
|
|
{
|
|
struct drgn_error *err;
|
|
|
|
GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr(load->dbinfo->prog->core,
|
|
&ehdr_mem);
|
|
if (!ehdr)
|
|
return drgn_error_libelf();
|
|
bool is_64_bit = ehdr->e_ident[EI_CLASS] == ELFCLASS64;
|
|
bool little_endian = ehdr->e_ident[EI_DATA] == ELFDATA2LSB;
|
|
|
|
struct binary_buffer bb;
|
|
binary_buffer_init(&bb, nt_file, nt_file_len, little_endian,
|
|
parse_nt_file_error);
|
|
|
|
/*
|
|
* fs/binfmt_elf.c in the Linux kernel source code documents the format
|
|
* of NT_FILE as:
|
|
*
|
|
* long count -- how many files are mapped
|
|
* long page_size -- units for file_ofs
|
|
* array of [COUNT] elements of
|
|
* long start
|
|
* long end
|
|
* long file_ofs
|
|
* followed by COUNT filenames in ASCII: "FILE1" NUL "FILE2" NUL...
|
|
*/
|
|
uint64_t count, page_size;
|
|
if (is_64_bit) {
|
|
if ((err = binary_buffer_next_u64(&bb, &count)))
|
|
return err;
|
|
if (count > UINT64_MAX / 24)
|
|
return binary_buffer_error(&bb, "count is too large");
|
|
if ((err = binary_buffer_next_u64(&bb, &page_size)) ||
|
|
(err = binary_buffer_skip(&bb, count * 24)))
|
|
return err;
|
|
} else {
|
|
if ((err = binary_buffer_next_u32_into_u64(&bb, &count)))
|
|
return err;
|
|
if (count > UINT64_MAX / 12)
|
|
return binary_buffer_error(&bb, "count is too large");
|
|
if ((err = binary_buffer_next_u32_into_u64(&bb, &page_size)) ||
|
|
(err = binary_buffer_skip(&bb, count * 12)))
|
|
return err;
|
|
}
|
|
|
|
for (uint64_t i = 0; i < count; i++) {
|
|
struct drgn_mapped_file_segment segment;
|
|
if (is_64_bit) {
|
|
memcpy(&segment, nt_file + 16 + i * 24, 24);
|
|
if (bb.bswap) {
|
|
segment.start = bswap_64(segment.start);
|
|
segment.end = bswap_64(segment.end);
|
|
segment.file_offset = bswap_64(segment.file_offset);
|
|
}
|
|
} else {
|
|
struct {
|
|
uint32_t start;
|
|
uint32_t end;
|
|
uint32_t file_offset;
|
|
} segment32;
|
|
memcpy(&segment32, nt_file + 8 + i * 12, 12);
|
|
if (bb.bswap) {
|
|
segment.start = bswap_32(segment32.start);
|
|
segment.end = bswap_32(segment32.end);
|
|
segment.file_offset = bswap_32(segment32.file_offset);
|
|
} else {
|
|
segment.start = segment32.start;
|
|
segment.end = segment32.end;
|
|
segment.file_offset = segment32.file_offset;
|
|
}
|
|
}
|
|
segment.file_offset *= page_size;
|
|
|
|
struct drgn_mapped_files_entry entry = {
|
|
.key = bb.pos,
|
|
};
|
|
if ((err = binary_buffer_skip_string(&bb)))
|
|
return err;
|
|
struct drgn_mapped_files_iterator it;
|
|
int r = drgn_mapped_files_insert(&core->files, &entry, &it);
|
|
if (r < 0)
|
|
return &drgn_enomem;
|
|
if (r == 1)
|
|
drgn_mapped_file_segment_vector_init(&it.entry->value);
|
|
|
|
/*
|
|
* The Linux kernel creates separate entries for contiguous
|
|
* mappings with different memory protections even though the
|
|
* protection is not included in NT_FILE. Merge them if we can.
|
|
*/
|
|
if (it.entry->value.size > 0 &&
|
|
drgn_mapped_file_segments_contiguous(&it.entry->value.data[it.entry->value.size - 1],
|
|
&segment))
|
|
it.entry->value.data[it.entry->value.size - 1].end = segment.end;
|
|
else if (!drgn_mapped_file_segment_vector_append(&it.entry->value,
|
|
&segment))
|
|
return &drgn_enomem;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool build_id_matches(Elf *elf, const void *build_id,
|
|
size_t build_id_len)
|
|
{
|
|
const void *elf_build_id;
|
|
ssize_t elf_build_id_len = dwelf_elf_gnu_build_id(elf, &elf_build_id);
|
|
if (elf_build_id_len < 0)
|
|
return false;
|
|
return (elf_build_id_len == build_id_len &&
|
|
memcmp(elf_build_id, build_id, build_id_len) == 0);
|
|
}
|
|
|
|
static struct drgn_error *
|
|
userspace_core_elf_address_range(uint16_t e_type, size_t phnum,
|
|
struct drgn_error *(*get_phdr)(void *, size_t, GElf_Phdr *),
|
|
void *arg,
|
|
const struct drgn_mapped_file_segment *segments,
|
|
size_t num_segments,
|
|
const struct drgn_mapped_file_segment *ehdr_segment,
|
|
uint64_t *bias_ret, uint64_t *start_ret,
|
|
uint64_t *end_ret)
|
|
{
|
|
struct drgn_error *err;
|
|
|
|
/*
|
|
* First, find the virtual address of the ELF header so that we can
|
|
* calculate the bias.
|
|
*/
|
|
uint64_t ehdr_vaddr;
|
|
size_t i;
|
|
for (i = 0; i < phnum; i++) {
|
|
GElf_Phdr phdr;
|
|
err = get_phdr(arg, i, &phdr);
|
|
if (err)
|
|
return err;
|
|
if (phdr.p_type == PT_LOAD) {
|
|
uint64_t align = phdr.p_align ? phdr.p_align : 1;
|
|
if ((phdr.p_offset & -align) == 0) {
|
|
ehdr_vaddr = phdr.p_vaddr & -align;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (i >= phnum) {
|
|
/*
|
|
* No loadable segments contain the ELF header. This can't be
|
|
* our file.
|
|
*/
|
|
*bias_ret = 0;
|
|
not_loaded:
|
|
*start_ret = *end_ret = 0;
|
|
return NULL;
|
|
}
|
|
*bias_ret = ehdr_segment->start - ehdr_vaddr;
|
|
if (*bias_ret != 0 && e_type == ET_EXEC) {
|
|
/* The executable is not loaded at the correct address. */
|
|
goto not_loaded;
|
|
}
|
|
|
|
/*
|
|
* Now check all of the program headers to (1) get the module address
|
|
* range and (2) make sure that they are mapped as expected. If we're
|
|
* lucky, this can detect a file that was mmap'd and not actually loaded
|
|
* by the kernel or dynamic loader. This could also be the wrong file.
|
|
*/
|
|
const struct drgn_mapped_file_segment *segment = segments;
|
|
const struct drgn_mapped_file_segment *end_segment =
|
|
segments + num_segments;
|
|
uint64_t start = 0, end = 0;
|
|
bool first = true;
|
|
for (i = 0; i < phnum; i++) {
|
|
GElf_Phdr phdr;
|
|
err = get_phdr(arg, i, &phdr);
|
|
if (err)
|
|
return err;
|
|
if (phdr.p_type != PT_LOAD)
|
|
continue;
|
|
uint64_t vaddr = phdr.p_vaddr + *bias_ret;
|
|
if (phdr.p_filesz != 0) {
|
|
/*
|
|
* Advance to the mapped segment containing the start
|
|
* address.
|
|
*/
|
|
while (vaddr >= segment->end) {
|
|
if (++segment == end_segment)
|
|
goto not_loaded;
|
|
if (vaddr < segment->start)
|
|
goto not_loaded;
|
|
}
|
|
if (segment->file_offset + (vaddr - segment->start) !=
|
|
phdr.p_offset) {
|
|
/*
|
|
* The address in the core dump does not map to
|
|
* the segment's file offset.
|
|
*/
|
|
goto not_loaded;
|
|
}
|
|
if (phdr.p_filesz > segment->end - vaddr) {
|
|
/* Part of the segment is not mapped. */
|
|
goto not_loaded;
|
|
}
|
|
}
|
|
if (first) {
|
|
uint64_t align = phdr.p_align ? phdr.p_align : 1;
|
|
start = vaddr & -align;
|
|
first = false;
|
|
}
|
|
end = vaddr + phdr.p_memsz;
|
|
}
|
|
if (start >= end)
|
|
goto not_loaded;
|
|
*start_ret = start;
|
|
*end_ret = end;
|
|
return NULL;
|
|
}
|
|
|
|
/* ehdr_buf must be aligned as Elf64_Ehdr. */
|
|
static void read_ehdr(const void *ehdr_buf, GElf_Ehdr *ret, bool *is_64_bit_ret,
|
|
bool *bswap_ret)
|
|
{
|
|
*is_64_bit_ret = ((unsigned char *)ehdr_buf)[EI_CLASS] == ELFCLASS64;
|
|
bool little_endian =
|
|
((unsigned char *)ehdr_buf)[EI_DATA] == ELFDATA2LSB;
|
|
*bswap_ret = little_endian != HOST_LITTLE_ENDIAN;
|
|
if (*is_64_bit_ret) {
|
|
const Elf64_Ehdr *ehdr64 = ehdr_buf;
|
|
if (*bswap_ret) {
|
|
memcpy(ret->e_ident, ehdr64->e_ident, EI_NIDENT);
|
|
ret->e_type = bswap_16(ehdr64->e_type);
|
|
ret->e_machine = bswap_16(ehdr64->e_machine);
|
|
ret->e_version = bswap_32(ehdr64->e_version);
|
|
ret->e_entry = bswap_64(ehdr64->e_entry);
|
|
ret->e_phoff = bswap_64(ehdr64->e_phoff);
|
|
ret->e_shoff = bswap_64(ehdr64->e_shoff);
|
|
ret->e_flags = bswap_32(ehdr64->e_flags);
|
|
ret->e_ehsize = bswap_16(ehdr64->e_ehsize);
|
|
ret->e_phentsize = bswap_16(ehdr64->e_phentsize);
|
|
ret->e_phnum = bswap_16(ehdr64->e_phnum);
|
|
ret->e_shentsize = bswap_16(ehdr64->e_shentsize);
|
|
ret->e_shnum = bswap_16(ehdr64->e_shnum);
|
|
ret->e_shstrndx = bswap_16(ehdr64->e_shstrndx);
|
|
} else {
|
|
*ret = *ehdr64;
|
|
}
|
|
} else {
|
|
const Elf32_Ehdr *ehdr32 = ehdr_buf;
|
|
memcpy(ret->e_ident, ehdr32->e_ident, EI_NIDENT);
|
|
if (*bswap_ret) {
|
|
ret->e_type = bswap_16(ehdr32->e_type);
|
|
ret->e_machine = bswap_16(ehdr32->e_machine);
|
|
ret->e_version = bswap_32(ehdr32->e_version);
|
|
ret->e_entry = bswap_32(ehdr32->e_entry);
|
|
ret->e_phoff = bswap_32(ehdr32->e_phoff);
|
|
ret->e_shoff = bswap_32(ehdr32->e_shoff);
|
|
ret->e_flags = bswap_32(ehdr32->e_flags);
|
|
ret->e_ehsize = bswap_16(ehdr32->e_ehsize);
|
|
ret->e_phentsize = bswap_16(ehdr32->e_phentsize);
|
|
ret->e_phnum = bswap_16(ehdr32->e_phnum);
|
|
ret->e_shentsize = bswap_16(ehdr32->e_shentsize);
|
|
ret->e_shnum = bswap_16(ehdr32->e_shnum);
|
|
ret->e_shstrndx = bswap_16(ehdr32->e_shstrndx);
|
|
} else {
|
|
ret->e_type = ehdr32->e_type;
|
|
ret->e_machine = ehdr32->e_machine;
|
|
ret->e_version = ehdr32->e_version;
|
|
ret->e_entry = ehdr32->e_entry;
|
|
ret->e_phoff = ehdr32->e_phoff;
|
|
ret->e_shoff = ehdr32->e_shoff;
|
|
ret->e_flags = ehdr32->e_flags;
|
|
ret->e_ehsize = ehdr32->e_ehsize;
|
|
ret->e_phentsize = ehdr32->e_phentsize;
|
|
ret->e_phnum = ehdr32->e_phnum;
|
|
ret->e_shentsize = ehdr32->e_shentsize;
|
|
ret->e_shnum = ehdr32->e_shnum;
|
|
ret->e_shstrndx = ehdr32->e_shstrndx;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* phdr_buf must be aligned as Elf64_Phdr. */
|
|
static void read_phdr(const void *phdr_buf, size_t i, bool is_64_bit,
|
|
bool bswap, GElf_Phdr *ret)
|
|
{
|
|
if (is_64_bit) {
|
|
const Elf64_Phdr *phdr64 = (Elf64_Phdr *)phdr_buf + i;
|
|
if (bswap) {
|
|
ret->p_type = bswap_32(phdr64->p_type);
|
|
ret->p_flags = bswap_32(phdr64->p_flags);
|
|
ret->p_offset = bswap_64(phdr64->p_offset);
|
|
ret->p_vaddr = bswap_64(phdr64->p_vaddr);
|
|
ret->p_paddr = bswap_64(phdr64->p_paddr);
|
|
ret->p_filesz = bswap_64(phdr64->p_filesz);
|
|
ret->p_memsz = bswap_64(phdr64->p_memsz);
|
|
ret->p_align = bswap_64(phdr64->p_align);
|
|
} else {
|
|
*ret = *phdr64;
|
|
}
|
|
} else {
|
|
const Elf32_Phdr *phdr32 = (Elf32_Phdr *)phdr_buf + i;
|
|
if (bswap) {
|
|
ret->p_type = bswap_32(phdr32->p_type);
|
|
ret->p_offset = bswap_32(phdr32->p_offset);
|
|
ret->p_vaddr = bswap_32(phdr32->p_vaddr);
|
|
ret->p_paddr = bswap_32(phdr32->p_paddr);
|
|
ret->p_filesz = bswap_32(phdr32->p_filesz);
|
|
ret->p_memsz = bswap_32(phdr32->p_memsz);
|
|
ret->p_flags = bswap_32(phdr32->p_flags);
|
|
ret->p_align = bswap_32(phdr32->p_align);
|
|
} else {
|
|
ret->p_type = phdr32->p_type;
|
|
ret->p_offset = phdr32->p_offset;
|
|
ret->p_vaddr = phdr32->p_vaddr;
|
|
ret->p_paddr = phdr32->p_paddr;
|
|
ret->p_filesz = phdr32->p_filesz;
|
|
ret->p_memsz = phdr32->p_memsz;
|
|
ret->p_flags = phdr32->p_flags;
|
|
ret->p_align = phdr32->p_align;
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char *read_build_id(const char *buf, size_t buf_len,
|
|
uint64_t align, bool bswap,
|
|
size_t *len_ret)
|
|
{
|
|
/*
|
|
* Build IDs are usually 16 or 20 bytes (MD5 or SHA-1, respectively), so
|
|
* these arbitrary limits are generous.
|
|
*/
|
|
static const uint32_t build_id_min_size = 2;
|
|
static const uint32_t build_id_max_size = 1024;
|
|
/* Elf32_Nhdr is the same as Elf64_Nhdr. */
|
|
Elf64_Nhdr nhdr;
|
|
const char *p = buf;
|
|
while (buf + buf_len - p >= sizeof(nhdr)) {
|
|
memcpy(&nhdr, p, sizeof(nhdr));
|
|
if (bswap) {
|
|
nhdr.n_namesz = bswap_32(nhdr.n_namesz);
|
|
nhdr.n_descsz = bswap_32(nhdr.n_descsz);
|
|
nhdr.n_type = bswap_32(nhdr.n_type);
|
|
}
|
|
p += sizeof(nhdr);
|
|
|
|
uint64_t namesz = (nhdr.n_namesz + align - 1) & ~(align - 1);
|
|
if (namesz > buf + buf_len - p)
|
|
return NULL;
|
|
const char *name = p;
|
|
p += namesz;
|
|
|
|
if (nhdr.n_namesz == sizeof("GNU") &&
|
|
memcmp(name, "GNU", sizeof("GNU")) == 0 &&
|
|
nhdr.n_type == NT_GNU_BUILD_ID &&
|
|
nhdr.n_descsz >= build_id_min_size &&
|
|
nhdr.n_descsz <= build_id_max_size) {
|
|
if (nhdr.n_descsz > buf + buf_len - p)
|
|
return NULL;
|
|
*len_ret = nhdr.n_descsz;
|
|
return p;
|
|
}
|
|
|
|
uint64_t descsz = (nhdr.n_descsz + align - 1) & ~(align - 1);
|
|
if (descsz > buf + buf_len - p)
|
|
return NULL;
|
|
p += descsz;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct core_get_phdr_arg {
|
|
const void *phdr_buf;
|
|
bool is_64_bit;
|
|
bool bswap;
|
|
};
|
|
|
|
static struct drgn_error *
|
|
core_get_phdr(void *arg_, size_t i, GElf_Phdr *ret)
|
|
{
|
|
struct core_get_phdr_arg *arg = arg_;
|
|
read_phdr(arg->phdr_buf, i, arg->is_64_bit, arg->bswap, ret);
|
|
return NULL;
|
|
}
|
|
|
|
struct userspace_core_identified_file {
|
|
const void *build_id;
|
|
size_t build_id_len;
|
|
uint64_t start, end;
|
|
bool ignore;
|
|
bool have_address_range;
|
|
};
|
|
|
|
static struct drgn_error *
|
|
userspace_core_identify_file(struct drgn_program *prog,
|
|
struct userspace_core_report_state *core,
|
|
const struct drgn_mapped_file_segment *segments,
|
|
size_t num_segments,
|
|
const struct drgn_mapped_file_segment *ehdr_segment,
|
|
struct userspace_core_identified_file *ret)
|
|
{
|
|
struct drgn_error *err;
|
|
|
|
Elf64_Ehdr ehdr_buf;
|
|
err = drgn_program_read_memory(prog, &ehdr_buf, ehdr_segment->start,
|
|
sizeof(ehdr_buf), false);
|
|
if (err) {
|
|
if (err->code == DRGN_ERROR_FAULT) {
|
|
drgn_error_destroy(err);
|
|
err = NULL;
|
|
}
|
|
return err;
|
|
}
|
|
if (memcmp(&ehdr_buf, ELFMAG, SELFMAG) != 0) {
|
|
ret->ignore = true;
|
|
return NULL;
|
|
}
|
|
|
|
GElf_Ehdr ehdr;
|
|
struct core_get_phdr_arg arg;
|
|
read_ehdr(&ehdr_buf, &ehdr, &arg.is_64_bit, &arg.bswap);
|
|
if (ehdr.e_type == ET_CORE ||
|
|
ehdr.e_phnum == 0 ||
|
|
ehdr.e_phentsize !=
|
|
(arg.is_64_bit ? sizeof(Elf64_Phdr) : sizeof(Elf32_Phdr))) {
|
|
ret->ignore = true;
|
|
return NULL;
|
|
}
|
|
|
|
if (ehdr.e_phnum > SIZE_MAX / ehdr.e_phentsize ||
|
|
!alloc_or_reuse(&core->phdr_buf, &core->phdr_buf_capacity,
|
|
ehdr.e_phnum * ehdr.e_phentsize))
|
|
return &drgn_enomem;
|
|
|
|
/*
|
|
* Check whether the mapped segment containing the file header also
|
|
* contains the program headers. This seems to be the case in practice.
|
|
*/
|
|
uint64_t ehdr_segment_file_end =
|
|
(ehdr_segment->file_offset +
|
|
(ehdr_segment->end - ehdr_segment->start));
|
|
if (ehdr_segment_file_end < ehdr.e_phoff ||
|
|
ehdr_segment_file_end - ehdr.e_phoff <
|
|
ehdr.e_phnum * ehdr.e_phentsize)
|
|
return NULL;
|
|
|
|
err = drgn_program_read_memory(prog, core->phdr_buf,
|
|
ehdr_segment->start + ehdr.e_phoff,
|
|
ehdr.e_phnum * ehdr.e_phentsize, false);
|
|
if (err) {
|
|
if (err->code == DRGN_ERROR_FAULT) {
|
|
drgn_error_destroy(err);
|
|
err = NULL;
|
|
}
|
|
return err;
|
|
}
|
|
arg.phdr_buf = core->phdr_buf;
|
|
|
|
/*
|
|
* In theory, if the program has a huge number of program headers, they
|
|
* may not all be dumped. However, the largest binary I was able to find
|
|
* still had all program headers within 1k.
|
|
*
|
|
* It'd be more reliable to determine the bias based on the headers that
|
|
* were saved, use that to read the build ID, use that to find the ELF
|
|
* file, and then determine the address range directly from the ELF
|
|
* file. However, we need the address range to report the build ID to
|
|
* libdwfl, so we do it this way.
|
|
*/
|
|
uint64_t bias;
|
|
err = userspace_core_elf_address_range(ehdr.e_type, ehdr.e_phnum,
|
|
core_get_phdr, &arg, segments,
|
|
num_segments, ehdr_segment,
|
|
&bias, &ret->start, &ret->end);
|
|
if (err)
|
|
return err;
|
|
if (ret->start >= ret->end) {
|
|
ret->ignore = true;
|
|
return NULL;
|
|
}
|
|
ret->have_address_range = true;
|
|
|
|
for (uint16_t i = 0; i < ehdr.e_phnum; i++) {
|
|
GElf_Phdr phdr;
|
|
core_get_phdr(&arg, i, &phdr);
|
|
if (phdr.p_type == PT_NOTE) {
|
|
if (phdr.p_filesz > SIZE_MAX ||
|
|
!alloc_or_reuse(&core->segment_buf,
|
|
&core->segment_buf_capacity,
|
|
phdr.p_filesz))
|
|
return &drgn_enomem;
|
|
err = drgn_program_read_memory(prog, core->segment_buf,
|
|
phdr.p_vaddr + bias,
|
|
phdr.p_filesz, false);
|
|
if (err) {
|
|
if (err->code == DRGN_ERROR_FAULT) {
|
|
drgn_error_destroy(err);
|
|
continue;
|
|
} else {
|
|
return err;
|
|
}
|
|
}
|
|
ret->build_id = read_build_id(core->segment_buf,
|
|
phdr.p_filesz,
|
|
phdr.p_align, arg.bswap,
|
|
&ret->build_id_len);
|
|
if (ret->build_id)
|
|
break;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct drgn_error *elf_file_get_phdr(void *arg, size_t i,
|
|
GElf_Phdr *phdr)
|
|
{
|
|
if (!gelf_getphdr(arg, i, phdr))
|
|
return drgn_error_libelf();
|
|
return NULL;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
userspace_core_maybe_report_file(struct drgn_debug_info_load_state *load,
|
|
struct userspace_core_report_state *core,
|
|
const char *path,
|
|
const struct drgn_mapped_file_segment *segments,
|
|
size_t num_segments)
|
|
{
|
|
struct drgn_error *err;
|
|
struct drgn_program *prog = load->dbinfo->prog;
|
|
for (size_t ehdr_idx = 0; ehdr_idx < num_segments; ehdr_idx++) {
|
|
const struct drgn_mapped_file_segment *ehdr_segment =
|
|
&segments[ehdr_idx];
|
|
/*
|
|
* There should always be a full page mapped, so even if it's a
|
|
* 32-bit file, we can read the 64-bit size.
|
|
*/
|
|
if (ehdr_segment->file_offset != 0 ||
|
|
ehdr_segment->end - ehdr_segment->start < sizeof(Elf64_Ehdr))
|
|
continue;
|
|
|
|
/*
|
|
* This logic is complicated because we're dealing with two data
|
|
* sources that we can't completely trust: the memory in the
|
|
* core dump and the file at the path found in the core dump.
|
|
*
|
|
* First, we try to identify the mapped file contents in the
|
|
* core dump. Ideally, this will find a build ID. However, this
|
|
* can fail for a few reasons:
|
|
*
|
|
* 1. The file is not an ELF file.
|
|
* 2. The ELF file is not an executable or library.
|
|
* 3. The ELF file does not have a build ID.
|
|
* 4. The file header was not dumped to the core dump, in which
|
|
* case we can't tell whether this is an ELF file. Dumping
|
|
* the first page of an executable file has been the default
|
|
* behavior since Linux kernel commit 895021552d6f
|
|
* ("coredump: default
|
|
* CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS=y") (in v2.6.37), but
|
|
* it can be disabled at kernel build time or toggled at
|
|
* runtime.
|
|
* 5. The build ID or the necessary ELF metadata were not dumped
|
|
* in the core dump. This can happen if the necessary program
|
|
* headers or note segment were not in the first page of the
|
|
* file.
|
|
* 6. The file is mapped but not actually loaded into the
|
|
* program (e.g., if the program is a tool like a profiler or
|
|
* a debugger that mmaps binaries [like drgn itself!]).
|
|
*
|
|
* In cases 1 and 2, we can simply ignore the file. In cases
|
|
* 3-5, we blindly trust the path in the core dump. We can
|
|
* sometimes detect case 6 in
|
|
* userspace_core_elf_address_range().
|
|
*
|
|
* There is also the possibility that the program modified or
|
|
* corrupted the ELF metadata in memory (more likely if the file
|
|
* was explicitly mmap'd, since the metadata will usually be
|
|
* read-only if it was loaded properly). We don't deal with that
|
|
* yet.
|
|
*/
|
|
struct userspace_core_identified_file identity = {};
|
|
err = userspace_core_identify_file(prog, core, segments,
|
|
num_segments, ehdr_segment,
|
|
&identity);
|
|
if (err)
|
|
return err;
|
|
if (identity.ignore)
|
|
continue;
|
|
|
|
#define CLEAR_ELF() do { \
|
|
elf = NULL; \
|
|
fd = -1; \
|
|
} while (0)
|
|
#define CLOSE_ELF() do { \
|
|
elf_end(elf); \
|
|
close(fd); \
|
|
CLEAR_ELF(); \
|
|
} while (0)
|
|
int fd;
|
|
Elf *elf;
|
|
/*
|
|
* There are a few things that can go wrong here:
|
|
*
|
|
* 1. The path no longer exists.
|
|
* 2. The path refers to a different ELF file than was in the
|
|
* core dump.
|
|
* 3. The path refers to something which isn't a valid ELF file.
|
|
*/
|
|
err = open_elf_file(path, &fd, &elf);
|
|
if (err) {
|
|
drgn_error_destroy(err);
|
|
CLEAR_ELF();
|
|
} else if (identity.build_id_len > 0) {
|
|
if (!build_id_matches(elf, identity.build_id,
|
|
identity.build_id_len))
|
|
CLOSE_ELF();
|
|
}
|
|
|
|
if (elf && !identity.have_address_range) {
|
|
GElf_Ehdr ehdr_mem, *ehdr;
|
|
size_t phnum;
|
|
if ((ehdr = gelf_getehdr(elf, &ehdr_mem)) &&
|
|
(elf_getphdrnum(elf, &phnum) == 0)) {
|
|
uint64_t bias;
|
|
err = userspace_core_elf_address_range(ehdr->e_type,
|
|
phnum,
|
|
elf_file_get_phdr,
|
|
elf,
|
|
segments,
|
|
num_segments,
|
|
ehdr_segment,
|
|
&bias,
|
|
&identity.start,
|
|
&identity.end);
|
|
if (err || identity.start >= identity.end) {
|
|
drgn_error_destroy(err);
|
|
CLOSE_ELF();
|
|
} else {
|
|
identity.have_address_range = true;
|
|
}
|
|
} else {
|
|
CLOSE_ELF();
|
|
}
|
|
}
|
|
|
|
if (elf) {
|
|
err = drgn_debug_info_report_elf(load, path, fd, elf,
|
|
identity.start,
|
|
identity.end, NULL,
|
|
NULL);
|
|
if (err)
|
|
return err;
|
|
} else {
|
|
if (!identity.have_address_range)
|
|
identity.start = identity.end = 0;
|
|
Dwfl_Module *dwfl_module =
|
|
dwfl_report_module(load->dbinfo->dwfl, path,
|
|
identity.start,
|
|
identity.end);
|
|
if (!dwfl_module)
|
|
return drgn_error_libdwfl();
|
|
if (identity.build_id_len > 0 &&
|
|
dwfl_module_report_build_id(dwfl_module,
|
|
identity.build_id,
|
|
identity.build_id_len,
|
|
0))
|
|
return drgn_error_libdwfl();
|
|
}
|
|
#undef CLOSE_ELF
|
|
#undef CLEAR_ELF
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
userspace_core_report_mapped_files(struct drgn_debug_info_load_state *load,
|
|
struct userspace_core_report_state *core)
|
|
{
|
|
|
|
struct drgn_error *err;
|
|
for (struct drgn_mapped_files_iterator it =
|
|
drgn_mapped_files_first(&core->files);
|
|
it.entry; it = drgn_mapped_files_next(it)) {
|
|
err = userspace_core_maybe_report_file(load, core,
|
|
it.entry->key,
|
|
it.entry->value.data,
|
|
it.entry->value.size);
|
|
if (err)
|
|
return err;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
userspace_core_report_debug_info(struct drgn_debug_info_load_state *load,
|
|
const char *nt_file, size_t nt_file_len)
|
|
{
|
|
struct drgn_error *err;
|
|
|
|
struct userspace_core_report_state core = {
|
|
.files = HASH_TABLE_INIT,
|
|
};
|
|
err = userspace_core_get_mapped_files(load, &core, nt_file,
|
|
nt_file_len);
|
|
if (err)
|
|
goto out;
|
|
err = userspace_core_report_mapped_files(load, &core);
|
|
out:
|
|
free(core.segment_buf);
|
|
free(core.phdr_buf);
|
|
for (struct drgn_mapped_files_iterator it =
|
|
drgn_mapped_files_first(&core.files);
|
|
it.entry; it = drgn_mapped_files_next(it))
|
|
drgn_mapped_file_segment_vector_deinit(&it.entry->value);
|
|
drgn_mapped_files_deinit(&core.files);
|
|
return err;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
userspace_report_elf_file(struct drgn_debug_info_load_state *load,
|
|
const char *path)
|
|
{
|
|
struct drgn_error *err;
|
|
|
|
int fd;
|
|
Elf *elf;
|
|
err = open_elf_file(path, &fd, &elf);
|
|
if (err)
|
|
goto err;
|
|
|
|
GElf_Ehdr ehdr_mem, *ehdr;
|
|
ehdr = gelf_getehdr(elf, &ehdr_mem);
|
|
if (!ehdr) {
|
|
err = drgn_error_libelf();
|
|
goto err_close;
|
|
}
|
|
/*
|
|
* We haven't implemented a way to get the load address for dynamically
|
|
* loaded or relocatable files, so for now we report those as unloaded.
|
|
*/
|
|
uint64_t start = 0, end = 0;
|
|
if (ehdr->e_type == ET_EXEC || ehdr->e_type == ET_CORE) {
|
|
err = elf_address_range(elf, 0, &start, &end);
|
|
if (err)
|
|
goto err_close;
|
|
}
|
|
|
|
return drgn_debug_info_report_elf(load, path, fd, elf, start, end, NULL,
|
|
NULL);
|
|
|
|
err_close:
|
|
elf_end(elf);
|
|
close(fd);
|
|
err:
|
|
return drgn_debug_info_report_error(load, path, NULL, err);
|
|
}
|
|
|
|
static struct drgn_error *
|
|
userspace_report_debug_info(struct drgn_debug_info_load_state *load)
|
|
{
|
|
struct drgn_error *err;
|
|
|
|
for (size_t i = 0; i < load->num_paths; i++) {
|
|
err = userspace_report_elf_file(load, load->paths[i]);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
if (load->load_default) {
|
|
Dwfl *dwfl = load->dbinfo->dwfl;
|
|
struct drgn_program *prog = load->dbinfo->prog;
|
|
if (prog->flags & DRGN_PROGRAM_IS_LIVE) {
|
|
int ret = dwfl_linux_proc_report(dwfl, prog->pid);
|
|
if (ret == -1) {
|
|
return drgn_error_libdwfl();
|
|
} else if (ret) {
|
|
return drgn_error_create_os("dwfl_linux_proc_report",
|
|
ret, NULL);
|
|
}
|
|
} else {
|
|
const char *nt_file;
|
|
size_t nt_file_len;
|
|
char *env = getenv("DRGN_USE_LIBDWFL_REPORT");
|
|
if (env && atoi(env)) {
|
|
nt_file = NULL;
|
|
nt_file_len = 0;
|
|
} else {
|
|
err = drgn_get_nt_file(prog->core, &nt_file,
|
|
&nt_file_len);
|
|
if (err)
|
|
return err;
|
|
}
|
|
if (nt_file) {
|
|
err = userspace_core_report_debug_info(load,
|
|
nt_file,
|
|
nt_file_len);
|
|
if (err)
|
|
return err;
|
|
} else if (dwfl_core_file_report(dwfl, prog->core,
|
|
NULL) == -1) {
|
|
return drgn_error_libdwfl();
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int should_apply_relocation_section(Elf *elf, size_t shstrndx,
|
|
const GElf_Shdr *shdr)
|
|
{
|
|
if (shdr->sh_type != SHT_RELA && shdr->sh_type != SHT_REL)
|
|
return 0;
|
|
|
|
const char *scnname = elf_strptr(elf, shstrndx, shdr->sh_name);
|
|
if (!scnname)
|
|
return -1;
|
|
if (shdr->sh_type == SHT_RELA) {
|
|
if (!strstartswith(scnname, ".rela."))
|
|
return 0;
|
|
scnname += sizeof(".rela.") - 1;
|
|
} else {
|
|
if (!strstartswith(scnname, ".rel."))
|
|
return 0;
|
|
scnname += sizeof(".rel.") - 1;
|
|
}
|
|
return (strstartswith(scnname, "debug_") ||
|
|
strstartswith(scnname, "orc_"));
|
|
}
|
|
|
|
static inline struct drgn_error *get_reloc_sym_value(const void *syms,
|
|
size_t num_syms,
|
|
const uint64_t *sh_addrs,
|
|
size_t shdrnum,
|
|
bool is_64_bit,
|
|
bool bswap,
|
|
uint32_t r_sym,
|
|
uint64_t *ret)
|
|
{
|
|
if (r_sym >= num_syms) {
|
|
return drgn_error_create(DRGN_ERROR_OTHER,
|
|
"invalid ELF relocation symbol");
|
|
}
|
|
uint16_t st_shndx;
|
|
uint64_t st_value;
|
|
if (is_64_bit) {
|
|
const Elf64_Sym *sym = (Elf64_Sym *)syms + r_sym;
|
|
memcpy(&st_shndx, &sym->st_shndx, sizeof(st_shndx));
|
|
memcpy(&st_value, &sym->st_value, sizeof(st_value));
|
|
if (bswap) {
|
|
st_shndx = bswap_16(st_shndx);
|
|
st_value = bswap_64(st_value);
|
|
}
|
|
} else {
|
|
const Elf32_Sym *sym = (Elf32_Sym *)syms + r_sym;
|
|
memcpy(&st_shndx, &sym->st_shndx, sizeof(st_shndx));
|
|
uint32_t st_value32;
|
|
memcpy(&st_value32, &sym->st_value, sizeof(st_value32));
|
|
if (bswap) {
|
|
st_shndx = bswap_16(st_shndx);
|
|
st_value32 = bswap_32(st_value32);
|
|
}
|
|
st_value = st_value32;
|
|
}
|
|
if (st_shndx >= shdrnum) {
|
|
return drgn_error_create(DRGN_ERROR_OTHER,
|
|
"invalid ELF symbol section index");
|
|
}
|
|
*ret = sh_addrs[st_shndx] + st_value;
|
|
return NULL;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
apply_elf_relas(const struct drgn_relocating_section *relocating,
|
|
Elf_Data *reloc_data, Elf_Data *symtab_data,
|
|
const uint64_t *sh_addrs, size_t shdrnum,
|
|
const struct drgn_platform *platform)
|
|
{
|
|
struct drgn_error *err;
|
|
|
|
bool is_64_bit = drgn_platform_is_64_bit(platform);
|
|
bool bswap = drgn_platform_bswap(platform);
|
|
apply_elf_reloc_fn *apply_elf_reloc = platform->arch->apply_elf_reloc;
|
|
|
|
const void *relocs = reloc_data->d_buf;
|
|
size_t reloc_size = is_64_bit ? sizeof(Elf64_Rela) : sizeof(Elf32_Rela);
|
|
size_t num_relocs = reloc_data->d_size / reloc_size;
|
|
|
|
const void *syms = symtab_data->d_buf;
|
|
size_t sym_size = is_64_bit ? sizeof(Elf64_Sym) : sizeof(Elf32_Sym);
|
|
size_t num_syms = symtab_data->d_size / sym_size;
|
|
|
|
for (size_t i = 0; i < num_relocs; i++) {
|
|
uint64_t r_offset;
|
|
uint32_t r_sym;
|
|
uint32_t r_type;
|
|
int64_t r_addend;
|
|
if (is_64_bit) {
|
|
const Elf64_Rela *rela = (Elf64_Rela *)relocs + i;
|
|
uint64_t r_info;
|
|
memcpy(&r_offset, &rela->r_offset, sizeof(r_offset));
|
|
memcpy(&r_info, &rela->r_info, sizeof(r_info));
|
|
memcpy(&r_addend, &rela->r_addend, sizeof(r_addend));
|
|
if (bswap) {
|
|
r_offset = bswap_64(r_offset);
|
|
r_info = bswap_64(r_info);
|
|
r_addend = bswap_64(r_addend);
|
|
}
|
|
r_sym = ELF64_R_SYM(r_info);
|
|
r_type = ELF64_R_TYPE(r_info);
|
|
} else {
|
|
const Elf32_Rela *rela32 = (Elf32_Rela *)relocs + i;
|
|
uint32_t r_offset32;
|
|
uint32_t r_info32;
|
|
int32_t r_addend32;
|
|
memcpy(&r_offset32, &rela32->r_offset, sizeof(r_offset32));
|
|
memcpy(&r_info32, &rela32->r_info, sizeof(r_info32));
|
|
memcpy(&r_addend32, &rela32->r_addend, sizeof(r_addend32));
|
|
if (bswap) {
|
|
r_offset32 = bswap_32(r_offset32);
|
|
r_info32 = bswap_32(r_info32);
|
|
r_addend32 = bswap_32(r_addend32);
|
|
}
|
|
r_offset = r_offset32;
|
|
r_sym = ELF32_R_SYM(r_info32);
|
|
r_type = ELF32_R_TYPE(r_info32);
|
|
r_addend = r_addend32;
|
|
}
|
|
uint64_t sym_value;
|
|
err = get_reloc_sym_value(syms, num_syms, sh_addrs, shdrnum,
|
|
is_64_bit, bswap, r_sym, &sym_value);
|
|
if (err)
|
|
return err;
|
|
|
|
err = apply_elf_reloc(relocating, r_offset, r_type, &r_addend,
|
|
sym_value);
|
|
if (err)
|
|
return err;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
apply_elf_rels(const struct drgn_relocating_section *relocating,
|
|
Elf_Data *reloc_data, Elf_Data *symtab_data,
|
|
const uint64_t *sh_addrs, size_t shdrnum,
|
|
const struct drgn_platform *platform)
|
|
{
|
|
struct drgn_error *err;
|
|
|
|
bool is_64_bit = drgn_platform_is_64_bit(platform);
|
|
bool bswap = drgn_platform_bswap(platform);
|
|
apply_elf_reloc_fn *apply_elf_reloc = platform->arch->apply_elf_reloc;
|
|
|
|
const void *relocs = reloc_data->d_buf;
|
|
size_t reloc_size = is_64_bit ? sizeof(Elf64_Rel) : sizeof(Elf32_Rel);
|
|
size_t num_relocs = reloc_data->d_size / reloc_size;
|
|
|
|
const void *syms = symtab_data->d_buf;
|
|
size_t sym_size = is_64_bit ? sizeof(Elf64_Sym) : sizeof(Elf32_Sym);
|
|
size_t num_syms = symtab_data->d_size / sym_size;
|
|
|
|
for (size_t i = 0; i < num_relocs; i++) {
|
|
uint64_t r_offset;
|
|
uint32_t r_sym;
|
|
uint32_t r_type;
|
|
if (is_64_bit) {
|
|
const Elf64_Rel *rel = (Elf64_Rel *)relocs + i;
|
|
uint64_t r_info;
|
|
memcpy(&r_offset, &rel->r_offset, sizeof(r_offset));
|
|
memcpy(&r_info, &rel->r_info, sizeof(r_info));
|
|
if (bswap) {
|
|
r_offset = bswap_64(r_offset);
|
|
r_info = bswap_64(r_info);
|
|
}
|
|
r_sym = ELF64_R_SYM(r_info);
|
|
r_type = ELF64_R_TYPE(r_info);
|
|
} else {
|
|
const Elf32_Rel *rel32 = (Elf32_Rel *)relocs + i;
|
|
uint32_t r_offset32;
|
|
uint32_t r_info32;
|
|
memcpy(&r_offset32, &rel32->r_offset, sizeof(r_offset32));
|
|
memcpy(&r_info32, &rel32->r_info, sizeof(r_info32));
|
|
if (bswap) {
|
|
r_offset32 = bswap_32(r_offset32);
|
|
r_info32 = bswap_32(r_info32);
|
|
}
|
|
r_offset = r_offset32;
|
|
r_sym = ELF32_R_SYM(r_info32);
|
|
r_type = ELF32_R_TYPE(r_info32);
|
|
}
|
|
uint64_t sym_value;
|
|
err = get_reloc_sym_value(syms, num_syms, sh_addrs, shdrnum,
|
|
is_64_bit, bswap, r_sym, &sym_value);
|
|
if (err)
|
|
return err;
|
|
|
|
err = apply_elf_reloc(relocating, r_offset, r_type, NULL,
|
|
sym_value);
|
|
if (err)
|
|
return err;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Before the debugging information in a relocatable ELF file (e.g., Linux
|
|
* kernel module) can be used, it must have ELF relocations applied. This is
|
|
* usually done by libdwfl. However, libdwfl is relatively slow at it. This is a
|
|
* much faster implementation.
|
|
*/
|
|
static struct drgn_error *relocate_elf_file(Elf *elf)
|
|
{
|
|
struct drgn_error *err;
|
|
|
|
GElf_Ehdr ehdr_mem, *ehdr;
|
|
ehdr = gelf_getehdr(elf, &ehdr_mem);
|
|
if (!ehdr)
|
|
return drgn_error_libelf();
|
|
|
|
if (ehdr->e_type != ET_REL) {
|
|
/* Not a relocatable file. */
|
|
return NULL;
|
|
}
|
|
|
|
struct drgn_platform platform;
|
|
drgn_platform_from_elf(ehdr, &platform);
|
|
if (!platform.arch->apply_elf_reloc) {
|
|
/* Unsupported; fall back to libdwfl. */
|
|
return NULL;
|
|
}
|
|
|
|
size_t shdrnum;
|
|
if (elf_getshdrnum(elf, &shdrnum))
|
|
return drgn_error_libelf();
|
|
uint64_t *sh_addrs = calloc(shdrnum, sizeof(sh_addrs[0]));
|
|
if (!sh_addrs && shdrnum > 0)
|
|
return &drgn_enomem;
|
|
|
|
Elf_Scn *scn = NULL;
|
|
while ((scn = elf_nextscn(elf, scn))) {
|
|
GElf_Shdr *shdr, shdr_mem;
|
|
shdr = gelf_getshdr(scn, &shdr_mem);
|
|
if (!shdr) {
|
|
err = drgn_error_libelf();
|
|
goto out;
|
|
}
|
|
sh_addrs[elf_ndxscn(scn)] = shdr->sh_addr;
|
|
}
|
|
|
|
size_t shstrndx;
|
|
if (elf_getshdrstrndx(elf, &shstrndx)) {
|
|
err = drgn_error_libelf();
|
|
goto out;
|
|
}
|
|
|
|
Elf_Scn *reloc_scn = NULL;
|
|
while ((reloc_scn = elf_nextscn(elf, reloc_scn))) {
|
|
GElf_Shdr *reloc_shdr, reloc_shdr_mem;
|
|
reloc_shdr = gelf_getshdr(reloc_scn, &reloc_shdr_mem);
|
|
if (!reloc_shdr) {
|
|
err = drgn_error_libelf();
|
|
goto out;
|
|
}
|
|
/* We don't support any architectures that use SHT_REL yet. */
|
|
if (reloc_shdr->sh_type != SHT_RELA)
|
|
continue;
|
|
|
|
int r = should_apply_relocation_section(elf, shstrndx,
|
|
reloc_shdr);
|
|
if (r < 0) {
|
|
err = drgn_error_libelf();
|
|
goto out;
|
|
}
|
|
if (r) {
|
|
Elf_Scn *scn = elf_getscn(elf, reloc_shdr->sh_info);
|
|
if (!scn) {
|
|
err = drgn_error_libelf();
|
|
goto out;
|
|
}
|
|
GElf_Shdr *shdr, shdr_mem;
|
|
shdr = gelf_getshdr(scn, &shdr_mem);
|
|
if (!shdr) {
|
|
err = drgn_error_libelf();
|
|
goto out;
|
|
}
|
|
if (shdr->sh_type == SHT_NOBITS)
|
|
continue;
|
|
|
|
Elf_Scn *symtab_scn = elf_getscn(elf,
|
|
reloc_shdr->sh_link);
|
|
if (!symtab_scn) {
|
|
err = drgn_error_libelf();
|
|
goto out;
|
|
}
|
|
shdr = gelf_getshdr(symtab_scn, &shdr_mem);
|
|
if (!shdr) {
|
|
err = drgn_error_libelf();
|
|
goto out;
|
|
}
|
|
if (shdr->sh_type == SHT_NOBITS) {
|
|
err = drgn_error_create(DRGN_ERROR_OTHER,
|
|
"relocation symbol table has no data");
|
|
goto out;
|
|
}
|
|
|
|
Elf_Data *data, *reloc_data, *symtab_data;
|
|
if ((err = read_elf_section(scn, &data)) ||
|
|
(err = read_elf_section(reloc_scn, &reloc_data)) ||
|
|
(err = read_elf_section(symtab_scn, &symtab_data)))
|
|
goto out;
|
|
|
|
struct drgn_relocating_section relocating = {
|
|
.buf = data->d_buf,
|
|
.buf_size = data->d_size,
|
|
.addr = sh_addrs[elf_ndxscn(scn)],
|
|
.bswap = drgn_platform_bswap(&platform),
|
|
};
|
|
|
|
if (reloc_shdr->sh_type == SHT_RELA) {
|
|
err = apply_elf_relas(&relocating, reloc_data,
|
|
symtab_data, sh_addrs,
|
|
shdrnum, &platform);
|
|
} else {
|
|
err = apply_elf_rels(&relocating, reloc_data,
|
|
symtab_data, sh_addrs,
|
|
shdrnum, &platform);
|
|
}
|
|
if (err)
|
|
goto out;
|
|
|
|
/*
|
|
* Mark the relocation section as empty so that libdwfl
|
|
* doesn't try to apply it again.
|
|
*/
|
|
reloc_shdr->sh_size = 0;
|
|
if (!gelf_update_shdr(reloc_scn, reloc_shdr)) {
|
|
err = drgn_error_libelf();
|
|
goto out;
|
|
}
|
|
reloc_data->d_size = 0;
|
|
}
|
|
}
|
|
err = NULL;
|
|
out:
|
|
free(sh_addrs);
|
|
return err;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
drgn_module_find_files(struct drgn_debug_info_load_state *load,
|
|
struct drgn_module *module)
|
|
{
|
|
struct drgn_error *err;
|
|
|
|
if (module->elf) {
|
|
err = relocate_elf_file(module->elf);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
GElf_Addr loaded_file_bias;
|
|
Elf *loaded_elf = NULL;
|
|
Dwarf_Addr debug_file_bias;
|
|
Dwarf *dwarf;
|
|
err = NULL;
|
|
#pragma omp critical(drgn_module_find_files)
|
|
{
|
|
// We don't need the loaded file for the Linux kernel, and we
|
|
// always report the debug file as the main file to libdwfl.
|
|
if (!(load->dbinfo->prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)) {
|
|
loaded_elf = dwfl_module_getelf(module->dwfl_module,
|
|
&loaded_file_bias);
|
|
if (!loaded_elf)
|
|
err = drgn_error_libdwfl();
|
|
}
|
|
if (!err) {
|
|
dwarf = dwfl_module_getdwarf(module->dwfl_module,
|
|
&debug_file_bias);
|
|
if (!dwarf)
|
|
err = drgn_error_libdwfl();
|
|
}
|
|
}
|
|
if (err)
|
|
return err;
|
|
|
|
const char *loaded_file_path;
|
|
const char *debug_file_path;
|
|
dwfl_module_info(module->dwfl_module, NULL, NULL, NULL, NULL, NULL,
|
|
&loaded_file_path, &debug_file_path);
|
|
|
|
module->debug_file_bias = debug_file_bias;
|
|
err = drgn_elf_file_create(module, debug_file_path, dwarf_getelf(dwarf),
|
|
&module->debug_file);
|
|
if (err) {
|
|
module->debug_file = NULL;
|
|
return err;
|
|
}
|
|
module->debug_file->dwarf = dwarf;
|
|
if (!module->debug_file->scns[DRGN_SCN_DEBUG_INFO] ||
|
|
!module->debug_file->scns[DRGN_SCN_DEBUG_ABBREV]) {
|
|
return drgn_error_create(DRGN_ERROR_OTHER,
|
|
"missing debugging information sections");
|
|
}
|
|
|
|
Dwarf *altdwarf = dwarf_getalt(dwarf);
|
|
if (altdwarf) {
|
|
Elf *altelf = dwarf_getelf(altdwarf);
|
|
if (!altelf)
|
|
return drgn_error_libdw();
|
|
size_t shstrndx;
|
|
if (elf_getshdrstrndx(altelf, &shstrndx))
|
|
return drgn_error_libelf();
|
|
|
|
Elf_Scn *scn = NULL;
|
|
while ((scn = elf_nextscn(altelf, scn))) {
|
|
GElf_Shdr shdr_mem;
|
|
GElf_Shdr *shdr = gelf_getshdr(scn, &shdr_mem);
|
|
if (!shdr)
|
|
return drgn_error_libelf();
|
|
|
|
if (shdr->sh_type != SHT_PROGBITS)
|
|
continue;
|
|
const char *scnname = elf_strptr(altelf, shstrndx,
|
|
shdr->sh_name);
|
|
if (!scnname)
|
|
return drgn_error_libelf();
|
|
|
|
/*
|
|
* TODO: save more sections and support imported units.
|
|
*/
|
|
if (strcmp(scnname, ".debug_info") == 0 &&
|
|
!module->debug_file->alt_debug_info_data) {
|
|
err = read_elf_section(scn,
|
|
&module->debug_file->alt_debug_info_data);
|
|
if (err)
|
|
return err;
|
|
} else if (strcmp(scnname, ".debug_str") == 0 &&
|
|
!module->debug_file->alt_debug_str_data) {
|
|
err = read_elf_section(scn,
|
|
&module->debug_file->alt_debug_str_data);
|
|
if (err)
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
err = drgn_elf_file_precache_sections(module->debug_file);
|
|
if (err)
|
|
return err;
|
|
|
|
if (loaded_elf) {
|
|
module->loaded_file_bias = loaded_file_bias;
|
|
if (loaded_elf == module->debug_file->elf) {
|
|
module->loaded_file = module->debug_file;
|
|
} else {
|
|
err = drgn_elf_file_create(module, loaded_file_path,
|
|
loaded_elf,
|
|
&module->loaded_file);
|
|
if (err) {
|
|
module->loaded_file = NULL;
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
drgn_debug_info_read_module(struct drgn_debug_info_load_state *load,
|
|
struct drgn_dwarf_index_state *index,
|
|
struct drgn_module *head)
|
|
{
|
|
struct drgn_error *err;
|
|
struct drgn_module *module;
|
|
for (module = head; module; module = module->next) {
|
|
err = drgn_module_find_files(load, module);
|
|
if (err) {
|
|
module->err = err;
|
|
continue;
|
|
}
|
|
module->state = DRGN_DEBUG_INFO_MODULE_INDEXING;
|
|
return drgn_dwarf_index_read_module(index, module);
|
|
}
|
|
/*
|
|
* We checked all of the files and didn't find debugging information.
|
|
* Report why for each one.
|
|
*
|
|
* (If we did find debugging information, we discard errors on the
|
|
* unused files.)
|
|
*/
|
|
err = NULL;
|
|
#pragma omp critical(drgn_debug_info_read_module_error)
|
|
for (module = head; module; module = module->next) {
|
|
const char *name =
|
|
dwfl_module_info(module->dwfl_module, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL);
|
|
if (module->err) {
|
|
err = drgn_debug_info_report_error(load, name, NULL,
|
|
module->err);
|
|
module->err = NULL;
|
|
} else {
|
|
err = drgn_debug_info_report_error(load, name,
|
|
"no debugging information",
|
|
NULL);
|
|
}
|
|
if (err)
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
drgn_debug_info_update_index(struct drgn_debug_info_load_state *load)
|
|
{
|
|
if (!load->new_modules.size)
|
|
return NULL;
|
|
struct drgn_debug_info *dbinfo = load->dbinfo;
|
|
if (!c_string_set_reserve(&dbinfo->module_names,
|
|
c_string_set_size(&dbinfo->module_names) +
|
|
load->new_modules.size))
|
|
return &drgn_enomem;
|
|
|
|
struct drgn_dwarf_index_state index;
|
|
if (!drgn_dwarf_index_state_init(&index, dbinfo))
|
|
return &drgn_enomem;
|
|
struct drgn_error *err = NULL;
|
|
#pragma omp parallel for schedule(dynamic)
|
|
for (size_t i = 0; i < load->new_modules.size; i++) {
|
|
if (err)
|
|
continue;
|
|
struct drgn_error *module_err =
|
|
drgn_debug_info_read_module(load, &index,
|
|
load->new_modules.data[i]);
|
|
if (module_err) {
|
|
#pragma omp critical(drgn_debug_info_update_index_error)
|
|
if (err)
|
|
drgn_error_destroy(module_err);
|
|
else
|
|
err = module_err;
|
|
}
|
|
}
|
|
if (!err)
|
|
err = drgn_dwarf_info_update_index(&index);
|
|
drgn_dwarf_index_state_deinit(&index);
|
|
if (!err)
|
|
drgn_debug_info_free_modules(dbinfo, true, false);
|
|
return err;
|
|
}
|
|
|
|
struct drgn_error *
|
|
drgn_debug_info_report_flush(struct drgn_debug_info_load_state *load)
|
|
{
|
|
struct drgn_debug_info *dbinfo = load->dbinfo;
|
|
my_dwfl_report_end(dbinfo, NULL, NULL);
|
|
struct drgn_error *err = drgn_debug_info_update_index(load);
|
|
dwfl_report_begin_add(dbinfo->dwfl);
|
|
if (err)
|
|
return err;
|
|
load->new_modules.size = 0;
|
|
return NULL;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
drgn_debug_info_report_finalize_errors(struct drgn_debug_info_load_state *load)
|
|
{
|
|
if (load->num_errors > load->max_errors &&
|
|
(!string_builder_line_break(&load->errors) ||
|
|
!string_builder_appendf(&load->errors, "... %u more",
|
|
load->num_errors - load->max_errors))) {
|
|
free(load->errors.str);
|
|
return &drgn_enomem;
|
|
}
|
|
if (load->num_errors) {
|
|
return drgn_error_from_string_builder(DRGN_ERROR_MISSING_DEBUG_INFO,
|
|
&load->errors);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
struct drgn_error *drgn_debug_info_load(struct drgn_debug_info *dbinfo,
|
|
const char **paths, size_t n,
|
|
bool load_default, bool load_main)
|
|
{
|
|
struct drgn_program *prog = dbinfo->prog;
|
|
struct drgn_error *err;
|
|
|
|
if (load_default)
|
|
load_main = true;
|
|
|
|
const char *max_errors = getenv("DRGN_MAX_DEBUG_INFO_ERRORS");
|
|
struct drgn_debug_info_load_state load = {
|
|
.dbinfo = dbinfo,
|
|
.paths = paths,
|
|
.num_paths = n,
|
|
.load_default = load_default,
|
|
.load_main = load_main,
|
|
.new_modules = VECTOR_INIT,
|
|
.errors = STRING_BUILDER_INIT,
|
|
.max_errors = max_errors ? atoi(max_errors) : 5,
|
|
};
|
|
dwfl_report_begin_add(dbinfo->dwfl);
|
|
if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)
|
|
err = linux_kernel_report_debug_info(&load);
|
|
else
|
|
err = userspace_report_debug_info(&load);
|
|
my_dwfl_report_end(dbinfo, NULL, NULL);
|
|
if (err)
|
|
goto err;
|
|
|
|
/*
|
|
* userspace_report_debug_info() reports the main debugging information
|
|
* directly with libdwfl, so we need to report it to dbinfo.
|
|
*/
|
|
if (!(prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL) && load_main &&
|
|
dwfl_getmodules(dbinfo->dwfl, drgn_debug_info_report_dwfl_module,
|
|
&load, 0)) {
|
|
err = &drgn_enomem;
|
|
goto err;
|
|
}
|
|
|
|
err = drgn_debug_info_update_index(&load);
|
|
if (err)
|
|
goto err;
|
|
|
|
/*
|
|
* TODO: for core dumps, we need to add memory reader segments for
|
|
* read-only segments of the loaded binaries since those aren't saved in
|
|
* the core dump.
|
|
*/
|
|
|
|
/*
|
|
* If this fails, it's too late to roll back. This can only fail with
|
|
* enomem, so it's not a big deal.
|
|
*/
|
|
err = drgn_debug_info_report_finalize_errors(&load);
|
|
out:
|
|
drgn_module_vector_deinit(&load.new_modules);
|
|
return err;
|
|
|
|
err:
|
|
drgn_debug_info_free_modules(dbinfo, false, false);
|
|
free(load.errors.str);
|
|
goto out;
|
|
}
|
|
|
|
bool drgn_debug_info_is_indexed(struct drgn_debug_info *dbinfo,
|
|
const char *name)
|
|
{
|
|
return c_string_set_search(&dbinfo->module_names, &name).entry != NULL;
|
|
}
|
|
|
|
struct drgn_error *drgn_debug_info_create(struct drgn_program *prog,
|
|
struct drgn_debug_info **ret)
|
|
{
|
|
struct drgn_debug_info *dbinfo = malloc(sizeof(*dbinfo));
|
|
if (!dbinfo)
|
|
return &drgn_enomem;
|
|
dbinfo->prog = prog;
|
|
const Dwfl_Callbacks *dwfl_callbacks;
|
|
if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)
|
|
dwfl_callbacks = &drgn_dwfl_callbacks;
|
|
else if (prog->flags & DRGN_PROGRAM_IS_LIVE)
|
|
dwfl_callbacks = &drgn_linux_proc_dwfl_callbacks;
|
|
else
|
|
dwfl_callbacks = &drgn_userspace_core_dump_dwfl_callbacks;
|
|
dbinfo->dwfl = dwfl_begin(dwfl_callbacks);
|
|
if (!dbinfo->dwfl) {
|
|
free(dbinfo);
|
|
return drgn_error_libdwfl();
|
|
}
|
|
drgn_module_table_init(&dbinfo->modules);
|
|
c_string_set_init(&dbinfo->module_names);
|
|
drgn_dwarf_info_init(dbinfo);
|
|
*ret = dbinfo;
|
|
return NULL;
|
|
}
|
|
|
|
void drgn_debug_info_destroy(struct drgn_debug_info *dbinfo)
|
|
{
|
|
if (!dbinfo)
|
|
return;
|
|
drgn_dwarf_info_deinit(dbinfo);
|
|
c_string_set_deinit(&dbinfo->module_names);
|
|
drgn_debug_info_free_modules(dbinfo, false, true);
|
|
assert(drgn_module_table_empty(&dbinfo->modules));
|
|
drgn_module_table_deinit(&dbinfo->modules);
|
|
dwfl_end(dbinfo->dwfl);
|
|
free(dbinfo);
|
|
}
|
|
|
|
struct drgn_error *
|
|
drgn_module_find_cfi(struct drgn_program *prog, struct drgn_module *module,
|
|
uint64_t pc, struct drgn_elf_file **file_ret,
|
|
struct drgn_cfi_row **row_ret, bool *interrupted_ret,
|
|
drgn_register_number *ret_addr_regno_ret)
|
|
{
|
|
struct drgn_error *err;
|
|
|
|
// If the file's platform doesn't match the program's, we can't use its
|
|
// CFI.
|
|
const bool can_use_loaded_file =
|
|
(module->loaded_file &&
|
|
drgn_platforms_equal(&module->loaded_file->platform,
|
|
&prog->platform));
|
|
const bool can_use_debug_file =
|
|
(module->debug_file &&
|
|
drgn_platforms_equal(&module->debug_file->platform,
|
|
&prog->platform));
|
|
|
|
if (prog->prefer_orc_unwinder) {
|
|
if (can_use_debug_file) {
|
|
*file_ret = module->debug_file;
|
|
err = drgn_module_find_orc_cfi(module, pc, row_ret,
|
|
interrupted_ret,
|
|
ret_addr_regno_ret);
|
|
if (err != &drgn_not_found)
|
|
return err;
|
|
err = drgn_module_find_dwarf_cfi(module, pc, row_ret,
|
|
interrupted_ret,
|
|
ret_addr_regno_ret);
|
|
if (err != &drgn_not_found)
|
|
return err;
|
|
}
|
|
if (can_use_loaded_file) {
|
|
*file_ret = module->loaded_file;
|
|
return drgn_module_find_eh_cfi(module, pc, row_ret,
|
|
interrupted_ret,
|
|
ret_addr_regno_ret);
|
|
}
|
|
} else {
|
|
if (can_use_debug_file) {
|
|
*file_ret = module->debug_file;
|
|
err = drgn_module_find_dwarf_cfi(module, pc, row_ret,
|
|
interrupted_ret,
|
|
ret_addr_regno_ret);
|
|
if (err != &drgn_not_found)
|
|
return err;
|
|
}
|
|
if (can_use_loaded_file) {
|
|
*file_ret = module->loaded_file;
|
|
err = drgn_module_find_eh_cfi(module, pc, row_ret,
|
|
interrupted_ret,
|
|
ret_addr_regno_ret);
|
|
if (err != &drgn_not_found)
|
|
return err;
|
|
}
|
|
if (can_use_debug_file) {
|
|
*file_ret = module->debug_file;
|
|
return drgn_module_find_orc_cfi(module, pc, row_ret,
|
|
interrupted_ret,
|
|
ret_addr_regno_ret);
|
|
}
|
|
}
|
|
return &drgn_not_found;
|
|
}
|
|
|
|
#if !_ELFUTILS_PREREQ(0, 175)
|
|
static Elf *dwelf_elf_begin(int fd)
|
|
{
|
|
return elf_begin(fd, ELF_C_READ_MMAP_PRIVATE, NULL);
|
|
}
|
|
#endif
|
|
|
|
struct drgn_error *open_elf_file(const char *path, int *fd_ret, Elf **elf_ret)
|
|
{
|
|
struct drgn_error *err;
|
|
|
|
*fd_ret = open(path, O_RDONLY);
|
|
if (*fd_ret == -1)
|
|
return drgn_error_create_os("open", errno, path);
|
|
*elf_ret = dwelf_elf_begin(*fd_ret);
|
|
if (!*elf_ret) {
|
|
err = drgn_error_libelf();
|
|
goto err_fd;
|
|
}
|
|
if (elf_kind(*elf_ret) != ELF_K_ELF) {
|
|
err = drgn_error_create(DRGN_ERROR_OTHER, "not an ELF file");
|
|
goto err_elf;
|
|
}
|
|
return NULL;
|
|
|
|
err_elf:
|
|
elf_end(*elf_ret);
|
|
err_fd:
|
|
close(*fd_ret);
|
|
return err;
|
|
}
|
|
|
|
struct drgn_error *find_elf_file(char **path_ret, int *fd_ret, Elf **elf_ret,
|
|
const char * const *path_formats, ...)
|
|
{
|
|
struct drgn_error *err;
|
|
size_t i;
|
|
|
|
for (i = 0; path_formats[i]; i++) {
|
|
va_list ap;
|
|
int ret;
|
|
char *path;
|
|
int fd;
|
|
Elf *elf;
|
|
|
|
va_start(ap, path_formats);
|
|
ret = vasprintf(&path, path_formats[i], ap);
|
|
va_end(ap);
|
|
if (ret == -1)
|
|
return &drgn_enomem;
|
|
fd = open(path, O_RDONLY);
|
|
if (fd == -1) {
|
|
free(path);
|
|
continue;
|
|
}
|
|
elf = dwelf_elf_begin(fd);
|
|
if (!elf) {
|
|
close(fd);
|
|
free(path);
|
|
continue;
|
|
}
|
|
if (elf_kind(elf) != ELF_K_ELF) {
|
|
err = drgn_error_format(DRGN_ERROR_OTHER,
|
|
"%s: not an ELF file", path);
|
|
elf_end(elf);
|
|
close(fd);
|
|
free(path);
|
|
return err;
|
|
}
|
|
*path_ret = path;
|
|
*fd_ret = fd;
|
|
*elf_ret = elf;
|
|
return NULL;
|
|
}
|
|
*path_ret = NULL;
|
|
*fd_ret = -1;
|
|
*elf_ret = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Get the start address from the first loadable segment and the end address
|
|
* from the last loadable segment.
|
|
*
|
|
* The ELF specification states that loadable segments are sorted on p_vaddr.
|
|
* However, vmlinux on x86-64 has an out of order segment for .data..percpu, and
|
|
* Arm has a couple for .vector and .stubs. Thankfully, those are placed in the
|
|
* middle by the vmlinux linker script, so we can still rely on the first and
|
|
* last loadable segments.
|
|
*/
|
|
struct drgn_error *elf_address_range(Elf *elf, uint64_t bias,
|
|
uint64_t *start_ret, uint64_t *end_ret)
|
|
{
|
|
size_t phnum;
|
|
if (elf_getphdrnum(elf, &phnum) != 0)
|
|
return drgn_error_libelf();
|
|
|
|
GElf_Phdr phdr_mem, *phdr;
|
|
size_t i;
|
|
for (i = 0; i < phnum; i++) {
|
|
phdr = gelf_getphdr(elf, i, &phdr_mem);
|
|
if (!phdr)
|
|
return drgn_error_libelf();
|
|
if (phdr->p_type == PT_LOAD) {
|
|
uint64_t align = phdr->p_align ? phdr->p_align : 1;
|
|
*start_ret = (phdr->p_vaddr & -align) + bias;
|
|
break;
|
|
}
|
|
}
|
|
if (i >= phnum) {
|
|
/* There were no loadable segments. */
|
|
*start_ret = *end_ret = 0;
|
|
return NULL;
|
|
}
|
|
|
|
for (i = phnum; i-- > 0;) {
|
|
phdr = gelf_getphdr(elf, i, &phdr_mem);
|
|
if (!phdr)
|
|
return drgn_error_libelf();
|
|
if (phdr->p_type == PT_LOAD) {
|
|
*end_ret = (phdr->p_vaddr + phdr->p_memsz) + bias;
|
|
if (*start_ret >= *end_ret)
|
|
*start_ret = *end_ret = 0;
|
|
return NULL;
|
|
}
|
|
}
|
|
/* We found a loadable segment earlier, so this shouldn't happen. */
|
|
assert(!"PT_LOAD segment disappeared");
|
|
*end_ret = 0;
|
|
return NULL;
|
|
}
|