mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-24 10:03:05 +00:00
423d2cd500
Currently, the interface between the DWARF index, libdwfl, and the code which finds and reports vmlinux/kernel modules is spaghetti. The DWARF index tracks Dwfl_Modules via their userdata. However, despite conceptually being owned by the DWARF index, the reporting code reports the Dwfl_Modules and sets up the userdata. These Dwfl_Modules and drgn_dwfl_module_userdatas are messy to track and pass between the layers. This reworks the architecture so that the DWARF index owns the Dwfl instance and files are reported to the DWARF index; the DWARF index takes care of reporting to libdwfl internally. In addition to making the interface for the reporter much cleaner, this improves a few things as a side-effect: - We now deduplicate on build ID in addition to path. - We now skip searching for vmlinux and/or kernel modules if they were already indexed. - We now support compressed ELF files via libdwelf. - We can now load default debug info at the same time as additional debug info.
173 lines
3.7 KiB
C
173 lines
3.7 KiB
C
// Copyright 2018-2019 - Omar Sandoval
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include <elfutils/libdwelf.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include "internal.h"
|
|
|
|
/*
|
|
* glibc added reallocarray() in 2.26, but since it's so trivial, it's easier to
|
|
* duplicate it here than it is to do feature detection.
|
|
*/
|
|
void *realloc_array(void *ptr, size_t nmemb, size_t size)
|
|
{
|
|
size_t bytes;
|
|
|
|
if (__builtin_mul_overflow(nmemb, size, &bytes)) {
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
return realloc(ptr, bytes);
|
|
}
|
|
|
|
void *malloc_array(size_t nmemb, size_t size)
|
|
{
|
|
size_t bytes;
|
|
|
|
if (__builtin_mul_overflow(nmemb, size, &bytes)) {
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
return malloc(bytes);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
struct drgn_error *read_elf_section(Elf_Scn *scn, Elf_Data **ret)
|
|
{
|
|
GElf_Shdr shdr_mem, *shdr;
|
|
Elf_Data *data;
|
|
|
|
shdr = gelf_getshdr(scn, &shdr_mem);
|
|
if (!shdr)
|
|
return drgn_error_libelf();
|
|
if ((shdr->sh_flags & SHF_COMPRESSED) && elf_compress(scn, 0, 0) < 0)
|
|
return drgn_error_libelf();
|
|
data = elf_getdata(scn, NULL);
|
|
if (!data)
|
|
return drgn_error_libelf();
|
|
*ret = data;
|
|
return NULL;
|
|
}
|
|
|
|
struct drgn_error *elf_address_range(Elf *elf, uint64_t bias,
|
|
uint64_t *start_ret, uint64_t *end_ret)
|
|
{
|
|
uint64_t start = UINT64_MAX, end = 0;
|
|
size_t phnum, i;
|
|
|
|
/*
|
|
* Get the minimum and maximum addresses from the PT_LOAD segments. We
|
|
* ignore memory ranges that start beyond UINT64_MAX, and we truncate
|
|
* ranges that end beyond UINT64_MAX.
|
|
*/
|
|
if (elf_getphdrnum(elf, &phnum) != 0)
|
|
return drgn_error_libelf();
|
|
for (i = 0; i < phnum; i++) {
|
|
GElf_Phdr phdr_mem, *phdr;
|
|
uint64_t segment_start, segment_end;
|
|
|
|
phdr = gelf_getphdr(elf, i, &phdr_mem);
|
|
if (!phdr)
|
|
return drgn_error_libelf();
|
|
if (phdr->p_type != PT_LOAD || !phdr->p_vaddr)
|
|
continue;
|
|
if (__builtin_add_overflow(phdr->p_vaddr, bias,
|
|
&segment_start))
|
|
continue;
|
|
if (__builtin_add_overflow(segment_start, phdr->p_memsz,
|
|
&segment_end))
|
|
segment_end = UINT64_MAX;
|
|
if (segment_start < segment_end) {
|
|
if (segment_start < start)
|
|
start = segment_start;
|
|
if (segment_end > end)
|
|
end = segment_end;
|
|
}
|
|
}
|
|
if (start >= end) {
|
|
return drgn_error_create(DRGN_ERROR_OTHER,
|
|
"ELF file has no loadable segments");
|
|
}
|
|
*start_ret = start;
|
|
*end_ret = end;
|
|
return NULL;
|
|
}
|