drgn/libdrgn/linux_kernel.c
Omar Sandoval ce8540e39c libdrgn: get rid of kernel_module_iterator::notes*
These were added in commit e5874ad18a ("libdrgn: use libdwfl"), but
they have never been used. Remove them.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2020-10-12 17:23:31 -07:00

1620 lines
41 KiB
C

// Copyright (c) Facebook, Inc. and its affiliates.
// SPDX-License-Identifier: GPL-3.0+
#include <dirent.h>
#include <elf.h>
#include <gelf.h>
#include <libelf.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include "bitops.h"
#include "debug_info.h"
#include "drgn.h"
#include "error.h"
#include "hash_table.h"
#include "helpers.h"
#include "language.h"
#include "linux_kernel.h"
#include "memory_reader.h"
#include "mread.h"
#include "platform.h"
#include "program.h"
#include "type.h"
#include "util.h"
struct drgn_error *read_memory_via_pgtable(void *buf, uint64_t address,
size_t count, uint64_t offset,
void *arg, bool physical)
{
struct drgn_program *prog = arg;
return linux_helper_read_vm(prog, prog->vmcoreinfo.swapper_pg_dir,
address, buf, count);
}
static inline bool linematch(const char **line, const char *prefix)
{
size_t len = strlen(prefix);
if (strncmp(*line, prefix, len) == 0) {
*line += len;
return true;
} else {
return false;
}
}
static struct drgn_error *line_to_u64(const char *line, const char *newline,
int base, uint64_t *ret)
{
unsigned long long value;
char *end;
errno = 0;
value = strtoull(line, &end, base);
if (errno == ERANGE) {
return drgn_error_create(DRGN_ERROR_OVERFLOW,
"number in VMCOREINFO is too large");
} else if (errno || end == line || end != newline) {
return drgn_error_create(DRGN_ERROR_OVERFLOW,
"number in VMCOREINFO is invalid");
}
*ret = value;
return NULL;
}
struct drgn_error *parse_vmcoreinfo(const char *desc, size_t descsz,
struct vmcoreinfo *ret)
{
struct drgn_error *err;
const char *line = desc, *end = &desc[descsz];
ret->osrelease[0] = '\0';
ret->page_size = 0;
ret->kaslr_offset = 0;
ret->pgtable_l5_enabled = false;
while (line < end) {
const char *newline;
newline = memchr(line, '\n', end - line);
if (!newline)
break;
if (linematch(&line, "OSRELEASE=")) {
if ((size_t)(newline - line) >=
sizeof(ret->osrelease)) {
return drgn_error_create(DRGN_ERROR_OTHER,
"OSRELEASE in VMCOREINFO is too long");
}
memcpy(ret->osrelease, line, newline - line);
ret->osrelease[newline - line] = '\0';
} else if (linematch(&line, "PAGESIZE=")) {
err = line_to_u64(line, newline, 0, &ret->page_size);
if (err)
return err;
} else if (linematch(&line, "KERNELOFFSET=")) {
err = line_to_u64(line, newline, 16,
&ret->kaslr_offset);
if (err)
return err;
} else if (linematch(&line, "SYMBOL(swapper_pg_dir)=")) {
err = line_to_u64(line, newline, 16,
&ret->swapper_pg_dir);
if (err)
return err;
} else if (linematch(&line, "NUMBER(pgtable_l5_enabled)=")) {
uint64_t tmp;
err = line_to_u64(line, newline, 0, &tmp);
if (err)
return err;
ret->pgtable_l5_enabled = tmp;
}
line = newline + 1;
}
if (!ret->osrelease[0]) {
return drgn_error_create(DRGN_ERROR_OTHER,
"VMCOREINFO does not contain valid OSRELEASE");
}
if (!ret->page_size) {
return drgn_error_create(DRGN_ERROR_OTHER,
"VMCOREINFO does not contain valid PAGESIZE");
}
if (!ret->swapper_pg_dir) {
return drgn_error_create(DRGN_ERROR_OTHER,
"VMCOREINFO does not contain valid swapper_pg_dir");
}
/* KERNELOFFSET and pgtable_l5_enabled are optional. */
return NULL;
}
struct drgn_error *proc_kallsyms_symbol_addr(const char *name,
unsigned long *ret)
{
struct drgn_error *err;
FILE *file;
char *line = NULL;
size_t n = 0;
file = fopen("/proc/kallsyms", "r");
if (!file)
return drgn_error_create_os("fopen", errno, "/proc/kallsyms");
for (;;) {
char *addr_str, *sym_str, *saveptr, *end;
errno = 0;
if (getline(&line, &n, file) == -1) {
if (errno) {
err = drgn_error_create_os("getline", errno,
"/proc/kallsyms");
} else {
err = &drgn_not_found;
}
break;
}
addr_str = strtok_r(line, "\t ", &saveptr);
if (!addr_str || !*addr_str)
goto invalid;
if (!strtok_r(NULL, "\t ", &saveptr))
goto invalid;
sym_str = strtok_r(NULL, "\t\n ", &saveptr);
if (!sym_str)
goto invalid;
if (strcmp(sym_str, name) != 0)
continue;
errno = 0;
*ret = strtoul(line, &end, 16);
if (errno || *end) {
invalid:
err = drgn_error_create(DRGN_ERROR_OTHER,
"could not parse /proc/kallsyms");
break;
}
err = NULL;
break;
}
free(line);
fclose(file);
return err;
}
/*
* Before Linux kernel commit 23c85094fe18 ("proc/kcore: add vmcoreinfo note to
* /proc/kcore") (in v4.19), /proc/kcore didn't have a VMCOREINFO note. Instead,
* we can read from the physical address of the vmcoreinfo note exported in
* sysfs.
*/
struct drgn_error *read_vmcoreinfo_fallback(struct drgn_memory_reader *reader,
struct vmcoreinfo *ret)
{
struct drgn_error *err;
FILE *file;
uint64_t address;
size_t size;
char *buf;
Elf64_Nhdr *nhdr;
file = fopen("/sys/kernel/vmcoreinfo", "r");
if (!file) {
return drgn_error_create_os("fopen", errno,
"/sys/kernel/vmcoreinfo");
}
if (fscanf(file, "%" SCNx64 "%zx", &address, &size) != 2) {
fclose(file);
return drgn_error_create(DRGN_ERROR_OTHER,
"could not parse /sys/kernel/vmcoreinfo");
}
fclose(file);
buf = malloc(size);
if (!buf)
return &drgn_enomem;
err = drgn_memory_reader_read(reader, buf, address, size, true);
if (err)
goto out;
/*
* The first 12 bytes are the Elf{32,64}_Nhdr (it's the same in both
* formats). The name is padded up to 4 bytes, so the descriptor starts
* at byte 24.
*/
nhdr = (Elf64_Nhdr *)buf;
if (size < 24 || nhdr->n_namesz != 11 ||
memcmp(buf + sizeof(*nhdr), "VMCOREINFO", 10) != 0 ||
nhdr->n_descsz > size - 24) {
err = drgn_error_create(DRGN_ERROR_OTHER,
"VMCOREINFO is invalid");
goto out;
}
err = parse_vmcoreinfo(buf + 24, nhdr->n_descsz, ret);
out:
free(buf);
return err;
}
struct drgn_error *linux_kernel_get_thread_size(struct drgn_program *prog,
uint64_t *ret)
{
struct drgn_error *err;
struct drgn_qualified_type thread_union_type;
struct drgn_member_info stack_member;
if (!prog->thread_size) {
err = drgn_program_find_type(prog, "union thread_union", NULL,
&thread_union_type);
if (err)
return err;
err = drgn_program_member_info(prog, thread_union_type.type,
"stack", &stack_member);
if (err)
return err;
err = drgn_type_sizeof(stack_member.qualified_type.type,
&prog->thread_size);
if (err) {
prog->thread_size = 0;
return err;
}
}
*ret = prog->thread_size;
return NULL;
}
struct drgn_error *linux_kernel_object_find(const char *name, size_t name_len,
const char *filename,
enum drgn_find_object_flags flags,
void *arg, struct drgn_object *ret)
{
struct drgn_error *err;
struct drgn_program *prog = arg;
if (!filename && (flags & DRGN_FIND_OBJECT_CONSTANT)) {
struct drgn_qualified_type qualified_type = {};
if (name_len == strlen("PAGE_OFFSET") &&
memcmp(name, "PAGE_OFFSET", name_len) == 0) {
if (!prog->page_offset) {
if (!prog->has_platform ||
!prog->platform.arch->linux_kernel_get_page_offset)
return &drgn_not_found;
err = prog->platform.arch->linux_kernel_get_page_offset(prog,
&prog->page_offset);
if (err) {
prog->page_offset = 0;
return err;
}
}
err = drgn_program_find_primitive_type(prog,
DRGN_C_TYPE_UNSIGNED_LONG,
&qualified_type.type);
if (err)
return err;
return drgn_object_set_unsigned(ret, qualified_type,
prog->page_offset, 0);
} else if (name_len == strlen("PAGE_SHIFT") &&
memcmp(name, "PAGE_SHIFT", name_len) == 0) {
err = drgn_program_find_primitive_type(prog,
DRGN_C_TYPE_INT,
&qualified_type.type);
if (err)
return err;
return drgn_object_set_signed(ret, qualified_type,
ctz(prog->vmcoreinfo.page_size),
0);
} else if (name_len == strlen("PAGE_SIZE") &&
memcmp(name, "PAGE_SIZE", name_len) == 0) {
err = drgn_program_find_primitive_type(prog,
DRGN_C_TYPE_UNSIGNED_LONG,
&qualified_type.type);
if (err)
return err;
return drgn_object_set_unsigned(ret, qualified_type,
prog->vmcoreinfo.page_size,
0);
} else if (name_len == strlen("PAGE_MASK") &&
memcmp(name, "PAGE_MASK", name_len) == 0) {
err = drgn_program_find_primitive_type(prog,
DRGN_C_TYPE_UNSIGNED_LONG,
&qualified_type.type);
if (err)
return err;
return drgn_object_set_unsigned(ret, qualified_type,
~(prog->vmcoreinfo.page_size - 1),
0);
} else if (name_len == strlen("THREAD_SIZE") &&
memcmp(name, "THREAD_SIZE", name_len) == 0) {
uint64_t thread_size;
err = linux_kernel_get_thread_size(prog, &thread_size);
if (err)
return err;
err = drgn_program_find_primitive_type(prog,
DRGN_C_TYPE_UNSIGNED_LONG,
&qualified_type.type);
if (err)
return err;
return drgn_object_set_unsigned(ret, qualified_type,
thread_size, 0);
} else if (name_len == strlen("UTS_RELEASE") &&
memcmp(name, "UTS_RELEASE", name_len) == 0) {
size_t len;
err = drgn_program_find_primitive_type(prog,
DRGN_C_TYPE_CHAR,
&qualified_type.type);
if (err)
return err;
qualified_type.qualifiers = DRGN_QUALIFIER_CONST;
len = strlen(prog->vmcoreinfo.osrelease);
err = drgn_array_type_create(prog, qualified_type,
len + 1, &drgn_language_c,
&qualified_type.type);
if (err)
return err;
qualified_type.qualifiers = 0;
return drgn_object_set_buffer(ret, qualified_type,
prog->vmcoreinfo.osrelease,
0, 0,
DRGN_PROGRAM_ENDIAN);
} else if (name_len == strlen("vmemmap") &&
memcmp(name, "vmemmap", name_len) == 0) {
if (!prog->vmemmap) {
if (!prog->has_platform ||
!prog->platform.arch->linux_kernel_get_vmemmap)
return &drgn_not_found;
err = prog->platform.arch->linux_kernel_get_vmemmap(prog,
&prog->vmemmap);
if (err) {
prog->vmemmap = 0;
return err;
}
}
err = drgn_program_find_type(prog, "struct page *",
NULL, &qualified_type);
if (err)
return err;
return drgn_object_set_unsigned(ret, qualified_type,
prog->vmemmap, 0);
}
}
return &drgn_not_found;
}
struct kernel_module_iterator {
char *name;
FILE *file;
union {
struct {
size_t name_capacity;
uint64_t start, end;
};
struct {
struct drgn_qualified_type module_type;
struct drgn_object mod, node, tmp1, tmp2, tmp3;
uint64_t head;
};
};
};
static void kernel_module_iterator_deinit(struct kernel_module_iterator *it)
{
if (it->file) {
fclose(it->file);
} else {
drgn_object_deinit(&it->tmp3);
drgn_object_deinit(&it->tmp2);
drgn_object_deinit(&it->tmp1);
drgn_object_deinit(&it->node);
drgn_object_deinit(&it->mod);
}
free(it->name);
}
static struct drgn_error *
kernel_module_iterator_init(struct kernel_module_iterator *it,
struct drgn_program *prog)
{
struct drgn_error *err;
it->name = NULL;
if (prog->flags & DRGN_PROGRAM_IS_LIVE) {
it->file = fopen("/proc/modules", "r");
if (!it->file) {
return drgn_error_create_os("fopen", errno,
"/proc/modules");
}
it->name_capacity = 0;
} else {
it->file = NULL;
err = drgn_program_find_type(prog, "struct module", NULL,
&it->module_type);
if (err)
return err;
drgn_object_init(&it->mod, prog);
drgn_object_init(&it->node, prog);
drgn_object_init(&it->tmp1, prog);
drgn_object_init(&it->tmp2, prog);
drgn_object_init(&it->tmp3, prog);
err = drgn_program_find_object(prog, "modules", NULL,
DRGN_FIND_OBJECT_VARIABLE,
&it->node);
if (err)
goto err;
err = drgn_object_address_of(&it->node, &it->node);
if (err)
goto err;
err = drgn_object_read(&it->node, &it->node);
if (err)
goto err;
err = drgn_object_read_unsigned(&it->node, &it->head);
if (err)
goto err;
}
return NULL;
err:
kernel_module_iterator_deinit(it);
return err;
}
static struct drgn_error *
kernel_module_iterator_next_live(struct kernel_module_iterator *it)
{
ssize_t ret;
char *p;
size_t size;
errno = 0;
ret = getline(&it->name, &it->name_capacity, it->file);
if (ret == -1) {
if (errno) {
return drgn_error_create_os("getline", errno,
"/proc/modules");
} else {
return &drgn_stop;
}
}
p = strchr(it->name, ' ');
if (!p || sscanf(p + 1, "%zu %*s %*s %*s %" SCNx64, &size,
&it->start) != 2) {
return drgn_error_create(DRGN_ERROR_OTHER,
"could not parse /proc/modules");
}
*p = '\0';
it->end = it->start + size;
return NULL;
}
static struct drgn_error *
kernel_module_iterator_next_offline(struct kernel_module_iterator *it)
{
struct drgn_error *err;
uint64_t addr;
char *name;
err = drgn_object_member_dereference(&it->node, &it->node, "next");
if (err)
return err;
err = drgn_object_read(&it->node, &it->node);
if (err)
return err;
err = drgn_object_read_unsigned(&it->node, &addr);
if (err)
return err;
if (addr == it->head)
return &drgn_stop;
err = drgn_object_container_of(&it->mod, &it->node, it->module_type,
"list");
if (err)
return err;
err = drgn_object_member_dereference(&it->tmp1, &it->mod, "name");
if (err)
return err;
err = drgn_object_read_c_string(&it->tmp1, &name);
if (err)
return err;
free(it->name);
it->name = name;
return NULL;
}
/**
* Get the the next loaded kernel module.
*
* After this is called, @c it->name is set to the name of the kernel module,
* and @c it->start and @c it->end are set to the address range of the kernel
* module. These are valid until the next time this is called or the iterator is
* destroyed.
*
* @return @c NULL on success, non-@c NULL on error. In particular, when there
* are no more modules, a @ref DRGN_ERROR_STOP error is returned.
*/
static struct drgn_error *
kernel_module_iterator_next(struct kernel_module_iterator *it)
{
if (it->file)
return kernel_module_iterator_next_live(it);
else
return kernel_module_iterator_next_offline(it);
}
struct kernel_module_section_iterator {
struct kernel_module_iterator *kmod_it;
DIR *dir;
union {
int dirfd;
struct {
uint64_t i;
uint64_t nsections;
char *name;
};
};
};
static struct drgn_error *
kernel_module_section_iterator_init(struct kernel_module_section_iterator *it,
struct kernel_module_iterator *kmod_it)
{
struct drgn_error *err;
it->kmod_it = kmod_it;
if (kmod_it->file) {
char *path;
if (asprintf(&path, "/sys/module/%s/sections",
kmod_it->name) == -1)
return &drgn_enomem;
it->dir = opendir(path);
free(path);
if (!it->dir) {
err = drgn_error_format_os("opendir", errno,
"/sys/module/%s/sections",
kmod_it->name);
return err;
}
it->dirfd = dirfd(it->dir);
if (it->dirfd == -1) {
err = drgn_error_format_os("dirfd", errno,
"/sys/module/%s/sections",
kmod_it->name);
closedir(it->dir);
return err;
}
return NULL;
} else {
it->dir = NULL;
it->i = 0;
it->name = NULL;
/* it->nsections = mod->sect_attrs->nsections */
err = drgn_object_member_dereference(&kmod_it->tmp1,
&kmod_it->mod,
"sect_attrs");
if (err)
return err;
err = drgn_object_member_dereference(&kmod_it->tmp2,
&kmod_it->tmp1,
"nsections");
if (err)
return err;
err = drgn_object_read_unsigned(&kmod_it->tmp2,
&it->nsections);
if (err)
return err;
/* kmod_it->tmp1 = mod->sect_attrs->attrs */
return drgn_object_member_dereference(&kmod_it->tmp1,
&kmod_it->tmp1, "attrs");
}
}
static void
kernel_module_section_iterator_deinit(struct kernel_module_section_iterator *it)
{
if (it->dir)
closedir(it->dir);
else
free(it->name);
}
static struct drgn_error *
kernel_module_section_iterator_next_live(struct kernel_module_section_iterator *it,
const char **name_ret,
uint64_t *address_ret)
{
struct dirent *ent;
while ((errno = 0, ent = readdir(it->dir))) {
int fd;
FILE *file;
int ret;
if (ent->d_type == DT_DIR)
continue;
if (ent->d_type == DT_UNKNOWN) {
struct stat st;
if (fstatat(it->dirfd, ent->d_name, &st, 0) == -1) {
return drgn_error_format_os("fstatat", errno,
"/sys/module/%s/sections/%s",
it->kmod_it->name,
ent->d_name);
}
if (S_ISDIR(st.st_mode))
continue;
}
fd = openat(it->dirfd, ent->d_name, O_RDONLY);
if (fd == -1) {
return drgn_error_format_os("openat", errno,
"/sys/module/%s/sections/%s",
it->kmod_it->name,
ent->d_name);
}
file = fdopen(fd, "r");
if (!file) {
close(fd);
return drgn_error_create_os("fdopen", errno, NULL);
}
ret = fscanf(file, "%" SCNx64, address_ret);
fclose(file);
if (ret != 1) {
return drgn_error_format(DRGN_ERROR_OTHER,
"could not parse /sys/module/%s/sections/%s",
it->kmod_it->name,
ent->d_name);
}
*name_ret = ent->d_name;
return NULL;
}
if (errno) {
return drgn_error_format_os("readdir", errno,
"/sys/module/%s/sections",
it->kmod_it->name);
} else {
return &drgn_stop;
}
}
static struct drgn_error *
kernel_module_section_iterator_next_offline(struct kernel_module_section_iterator *it,
const char **name_ret,
uint64_t *address_ret)
{
struct drgn_error *err;
struct kernel_module_iterator *kmod_it = it->kmod_it;
char *name;
if (it->i >= it->nsections)
return &drgn_stop;
err = drgn_object_subscript(&kmod_it->tmp2, &kmod_it->tmp1, it->i++);
if (err)
return err;
err = drgn_object_member(&kmod_it->tmp3, &kmod_it->tmp2, "address");
if (err)
return err;
err = drgn_object_read_unsigned(&kmod_it->tmp3, address_ret);
if (err)
return err;
err = drgn_object_member(&kmod_it->tmp3, &kmod_it->tmp2, "name");
if (err)
return err;
err = drgn_object_read_c_string(&kmod_it->tmp3, &name);
if (err)
return err;
free(it->name);
*name_ret = it->name = name;
return NULL;
}
static struct drgn_error *
kernel_module_section_iterator_next(struct kernel_module_section_iterator *it,
const char **name_ret,
uint64_t *address_ret)
{
if (it->dir) {
return kernel_module_section_iterator_next_live(it, name_ret,
address_ret);
} else {
return kernel_module_section_iterator_next_offline(it, name_ret,
address_ret);
}
}
/*
* /lib/modules/$(uname -r)/modules.dep.bin maps all installed kernel modules to
* their filesystem path (and dependencies, which we don't care about). It is
* generated by depmod; the format is a fairly simple serialized radix tree.
*
* modules.dep(5) contains a warning: "These files are not intended for editing
* or use by any additional utilities as their format is subject to change in
* the future." But, the format hasn't changed since 2009, and pulling in
* libkmod is overkill since we only need a very small subset of its
* functionality (plus our minimal parser is more efficient). If the format
* changes in the future, we can reevaluate this.
*/
struct kmod_index {
const char *ptr, *end;
};
static struct drgn_error *kmod_index_validate(struct kmod_index *index,
const char *path)
{
const char *ptr = index->ptr;
uint32_t magic, version;
if (!mread_be32(&ptr, index->end, &magic) ||
!mread_be32(&ptr, index->end, &version)) {
return drgn_error_format(DRGN_ERROR_OTHER, "%s is too short",
path);
}
if (magic != 0xb007f457) {
return drgn_error_format(DRGN_ERROR_OTHER,
"%s has invalid magic (0x%" PRIx32 ")",
path, magic);
}
if (version != 0x00020001) {
return drgn_error_format(DRGN_ERROR_OTHER,
"%s has unknown version (0x%" PRIx32 ")",
path, version);
}
return NULL;
}
static void kmod_index_deinit(struct kmod_index *index)
{
munmap((void *)index->ptr, index->end - index->ptr);
}
static struct drgn_error *kmod_index_init(struct kmod_index *index,
const char *path)
{
struct drgn_error *err;
int fd;
struct stat st;
void *map;
fd = open(path, O_RDONLY);
if (fd == -1)
return drgn_error_create_os("open", errno, path);
if (fstat(fd, &st) == -1) {
err = drgn_error_create_os("fstat", errno, path);
goto out;
}
if (st.st_size < 0 || st.st_size > SIZE_MAX) {
err = &drgn_enomem;
goto out;
}
map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (map == MAP_FAILED) {
err = drgn_error_create_os("mmap", errno, path);
goto out;
}
index->ptr = map;
index->end = index->ptr + st.st_size;
err = kmod_index_validate(index, path);
if (err)
kmod_index_deinit(index);
out:
close(fd);
return err;
}
static const char *kmod_index_find(struct kmod_index *index, const char *key)
{
static const uint32_t INDEX_NODE_MASK = UINT32_C(0x0fffffff);
static const uint32_t INDEX_NODE_CHILDS = UINT32_C(0x20000000);
static const uint32_t INDEX_NODE_VALUES = UINT32_C(0x40000000);
static const uint32_t INDEX_NODE_PREFIX = UINT32_C(0x80000000);
/* kmod_index_validate() already checked that this is within bounds. */
const char *ptr = index->ptr + 8;
uint32_t offset;
for (;;) {
if (!mread_be32(&ptr, index->end, &offset) ||
!(ptr = mread_begin(index->ptr, index->end,
offset & INDEX_NODE_MASK)))
return NULL;
if (offset & INDEX_NODE_PREFIX) {
const char *prefix;
size_t prefix_len;
if (!mread_string(&ptr, index->end, &prefix,
&prefix_len))
return NULL;
if (strncmp(key, prefix, prefix_len) != 0)
return NULL;
key += prefix_len;
}
if (offset & INDEX_NODE_CHILDS) {
uint8_t first, last;
if (!mread_u8(&ptr, index->end, &first) ||
!mread_u8(&ptr, index->end, &last))
return NULL;
if (*key) {
uint8_t cur = *key;
if (cur < first || cur > last ||
!mread_skip(&ptr, index->end,
4 * (cur - first)))
return NULL;
key++;
continue;
} else {
if (!mread_skip(&ptr, index->end,
4 * (last - first + 1)))
return NULL;
break;
}
} else if (*key) {
return NULL;
} else {
break;
}
}
if (!(offset & INDEX_NODE_VALUES))
return NULL;
return ptr;
}
struct depmod_index {
struct kmod_index modules_dep;
};
static struct drgn_error *depmod_index_init(struct depmod_index *depmod,
const char *osrelease)
{
char path[256];
snprintf(path, sizeof(path), "/lib/modules/%s/modules.dep.bin",
osrelease);
return kmod_index_init(&depmod->modules_dep, path);
}
static void depmod_index_deinit(struct depmod_index *depmod)
{
kmod_index_deinit(&depmod->modules_dep);
}
/*
* Look up the path of the kernel module with the given name.
*
* @param[in] name Name of the kernel module.
* @param[out] path_ret Returned path of the kernel module, relative to
* /lib/modules/$(uname -r). This is @em not null-terminated.
* @param[out] len_ret Returned length of @p path_ret.
* @return Whether the module was found.
*/
static bool depmod_index_find(struct depmod_index *depmod, const char *name,
const char **path_ret, size_t *len_ret)
{
const char *ptr = kmod_index_find(&depmod->modules_dep, name);
if (!ptr)
return false;
uint32_t value_count;
if (!mread_be32(&ptr, depmod->modules_dep.end, &value_count) ||
!value_count)
return false;
/* Skip over priority. */
const char *deps;
size_t deps_len;
if (!mread_skip(&ptr, depmod->modules_dep.end, 4) ||
!mread_string(&ptr, depmod->modules_dep.end, &deps,
&deps_len))
return false;
const char *colon = strchr(deps, ':');
if (!colon)
return false;
*path_ret = deps;
*len_ret = colon - deps;
return true;
}
/*
* Identify an ELF file as a kernel module, vmlinux, or neither. A kernel module
* should have sections named .gnu.linkonce.this_module and .modinfo; we return
* those sections and classify the file as a kernel module if either is present.
*
* If neither is present, and the file has a section named .init.text, we
* classify it as vmlinux.
*/
static struct drgn_error *identify_kernel_elf(Elf *elf,
Elf_Scn **this_module_scn_ret,
Elf_Scn **modinfo_scn_ret,
bool *is_vmlinux_ret)
{
size_t shstrndx;
Elf_Scn *scn;
bool have_init_text = false;
if (elf_getshdrstrndx(elf, &shstrndx))
return drgn_error_libelf();
scn = *this_module_scn_ret = *modinfo_scn_ret = NULL;
while ((scn = elf_nextscn(elf, scn))) {
GElf_Shdr *shdr, shdr_mem;
const char *scnname;
shdr = gelf_getshdr(scn, &shdr_mem);
if (!shdr)
continue;
scnname = elf_strptr(elf, shstrndx, shdr->sh_name);
if (!scnname)
continue;
if (strcmp(scnname, ".gnu.linkonce.this_module") == 0)
*this_module_scn_ret = scn;
else if (strcmp(scnname, ".modinfo") == 0)
*modinfo_scn_ret = scn;
else if (strcmp(scnname, ".init.text") == 0)
have_init_text = true;
}
*is_vmlinux_ret = (!*this_module_scn_ret && !*modinfo_scn_ret &&
have_init_text);
return NULL;
}
/*
* Since Linux kernel commit 3e2e857f9c3a ("module: Add module name to modinfo")
* (in v4.13), we can get the module name from .modinfo.
*/
static struct drgn_error *
get_kernel_module_name_from_modinfo(Elf_Scn *modinfo_scn, const char **ret)
{
struct drgn_error *err;
Elf_Data *data;
const char *p, *end, *nul;
if (modinfo_scn) {
err = read_elf_section(modinfo_scn, &data);
if (err)
return err;
p = data->d_buf;
end = p + data->d_size;
while (p < end) {
nul = memchr(p, 0, end - p);
if (!nul)
break;
if (strstartswith(p, "name=")) {
*ret = p + 5;
return NULL;
}
p = nul + 1;
}
}
*ret = NULL;
return NULL;
}
/*
* If the module name isn't in .modinfo, we need to get it from
* .gnu.linkonce.this_module, which contains a struct module. name_offset is
* offsetof(struct module, name).
*/
static struct drgn_error *
get_kernel_module_name_from_this_module(Elf_Scn *this_module_scn,
size_t name_offset, const char **ret)
{
struct drgn_error *err;
Elf_Data *data;
const char *p, *nul;
if (this_module_scn) {
err = read_elf_section(this_module_scn, &data);
if (err)
return err;
if (name_offset < data->d_size) {
p = data->d_buf + name_offset;
nul = memchr(p, 0, data->d_size - name_offset);
if (nul && nul != p) {
*ret = p;
return NULL;
}
}
}
*ret = NULL;
return NULL;
}
DEFINE_HASH_MAP(elf_scn_name_map, const char *, Elf_Scn *,
c_string_key_hash_pair, c_string_key_eq)
static struct drgn_error *
cache_kernel_module_sections(struct kernel_module_iterator *kmod_it, Elf *elf,
uint64_t *start_ret, uint64_t *end_ret)
{
struct drgn_error *err;
size_t shstrndx;
if (elf_getshdrstrndx(elf, &shstrndx))
return drgn_error_libelf();
struct elf_scn_name_map scn_map = HASH_TABLE_INIT;
Elf_Scn *scn = NULL;
while ((scn = elf_nextscn(elf, scn))) {
GElf_Shdr shdr_mem;
GElf_Shdr *shdr = gelf_getshdr(scn, &shdr_mem);
if (!shdr) {
err = drgn_error_libelf();
goto out_scn_map;
}
if (!(shdr->sh_flags & SHF_ALLOC))
continue;
struct elf_scn_name_map_entry entry = {
.key = elf_strptr(elf, shstrndx, shdr->sh_name),
.value = scn,
};
/*
* .init sections are freed once the module is initialized, but
* they remain in the section list. Ignore them so we don't get
* confused if the address gets reused for another module.
*/
if (!entry.key || strstartswith(entry.key, ".init"))
continue;
if (elf_scn_name_map_insert(&scn_map, &entry, NULL) == -1) {
err = &drgn_enomem;
goto out_scn_map;
}
}
uint64_t start = UINT64_MAX, end = 0;
struct kernel_module_section_iterator section_it;
err = kernel_module_section_iterator_init(&section_it, kmod_it);
if (err)
goto out_scn_map;
const char *name;
uint64_t address;
while (!(err = kernel_module_section_iterator_next(&section_it, &name,
&address))) {
struct elf_scn_name_map_iterator it =
elf_scn_name_map_search(&scn_map, &name);
if (it.entry) {
GElf_Shdr shdr_mem;
GElf_Shdr *shdr = gelf_getshdr(it.entry->value,
&shdr_mem);
if (!shdr) {
err = drgn_error_libelf();
break;
}
shdr->sh_addr = address;
if (!gelf_update_shdr(it.entry->value, shdr)) {
err = drgn_error_libelf();
break;
}
uint64_t section_end;
if (__builtin_add_overflow(address, shdr->sh_size,
&section_end))
section_end = UINT64_MAX;
if (address < section_end) {
if (address < start)
start = address;
if (section_end > end)
end = section_end;
}
}
}
if (err && err->code != DRGN_ERROR_STOP)
goto out_section_it;
err = NULL;
if (start >= end)
start = end = 0;
*start_ret = start;
*end_ret = end;
out_section_it:
kernel_module_section_iterator_deinit(&section_it);
out_scn_map:
elf_scn_name_map_deinit(&scn_map);
return err;
}
struct kernel_module_file {
const char *path;
int fd;
Elf *elf;
/*
* Kernel module name. This is owned by the Elf handle. Because we use
* this as the key in the kernel_module_table, the file must always be
* removed from the table before it is reported to the DWARF index
* (which takes ownership of the Elf handle).
*/
const char *name;
Elf_Scn *this_module_scn;
/* Next file with the same name. */
struct kernel_module_file *next;
};
static const char *
kernel_module_table_key(struct kernel_module_file * const *entry)
{
return (*entry)->name;
}
DEFINE_HASH_TABLE(kernel_module_table, struct kernel_module_file *,
kernel_module_table_key, c_string_key_hash_pair,
c_string_key_eq)
static struct drgn_error *
report_loaded_kernel_module(struct drgn_debug_info_load_state *load,
struct kernel_module_iterator *kmod_it,
struct kernel_module_table *kmod_table)
{
struct drgn_error *err;
const char *name = kmod_it->name;
struct hash_pair hp = kernel_module_table_hash(&name);
struct kernel_module_table_iterator it =
kernel_module_table_search_hashed(kmod_table, &name, hp);
if (!it.entry)
return &drgn_not_found;
struct kernel_module_file *kmod = *it.entry;
kernel_module_table_delete_iterator_hashed(kmod_table, it, hp);
do {
uint64_t start, end;
err = cache_kernel_module_sections(kmod_it, kmod->elf, &start,
&end);
if (err) {
err = drgn_debug_info_report_error(load, kmod->path,
"could not get section addresses",
err);
if (err)
return err;
continue;
}
err = drgn_debug_info_report_elf(load, kmod->path, kmod->fd,
kmod->elf, start, end,
kmod->name, NULL);
kmod->elf = NULL;
kmod->fd = -1;
if (err)
return err;
kmod = kmod->next;
} while (kmod);
return NULL;
}
static struct drgn_error *
report_default_kernel_module(struct drgn_debug_info_load_state *load,
struct kernel_module_iterator *kmod_it,
struct depmod_index *depmod)
{
static const char * const module_paths[] = {
"/usr/lib/debug/lib/modules/%s/%.*s",
"/usr/lib/debug/lib/modules/%s/%.*s.debug",
"/lib/modules/%s/%.*s%.*s",
NULL,
};
struct drgn_error *err;
const char *depmod_path;
size_t depmod_path_len;
if (!depmod_index_find(depmod, kmod_it->name, &depmod_path,
&depmod_path_len)) {
return drgn_debug_info_report_error(load, kmod_it->name,
"could not find module in depmod",
NULL);
}
size_t extension_len;
if (depmod_path_len >= 3 &&
(memcmp(depmod_path + depmod_path_len - 3, ".gz", 3) == 0 ||
memcmp(depmod_path + depmod_path_len - 3, ".xz", 3) == 0))
extension_len = 3;
else
extension_len = 0;
char *path;
int fd;
Elf *elf;
err = find_elf_file(&path, &fd, &elf, module_paths,
load->dbinfo->prog->vmcoreinfo.osrelease,
depmod_path_len - extension_len, depmod_path,
extension_len,
depmod_path + depmod_path_len - extension_len);
if (err)
return drgn_debug_info_report_error(load, NULL, NULL, err);
if (!elf) {
return drgn_debug_info_report_error(load, kmod_it->name,
"could not find .ko",
NULL);
}
uint64_t start, end;
err = cache_kernel_module_sections(kmod_it, elf, &start, &end);
if (err) {
elf_end(elf);
close(fd);
free(path);
return drgn_debug_info_report_error(load, path,
"could not get section addresses",
err);
}
err = drgn_debug_info_report_elf(load, path, fd, elf, start, end,
kmod_it->name, NULL);
free(path);
return err;
}
static struct drgn_error *
report_loaded_kernel_modules(struct drgn_debug_info_load_state *load,
struct kernel_module_table *kmod_table,
struct depmod_index *depmod)
{
struct drgn_program *prog = load->dbinfo->prog;
struct drgn_error *err;
struct kernel_module_iterator kmod_it;
err = kernel_module_iterator_init(&kmod_it, prog);
if (err) {
kernel_module_iterator_error:
return drgn_debug_info_report_error(load, "kernel modules",
"could not find loaded kernel modules",
err);
}
for (;;) {
err = kernel_module_iterator_next(&kmod_it);
if (err && err->code == DRGN_ERROR_STOP) {
err = NULL;
break;
} else if (err) {
kernel_module_iterator_deinit(&kmod_it);
goto kernel_module_iterator_error;
}
/* Look for an explicitly-reported file first. */
if (kmod_table) {
err = report_loaded_kernel_module(load, &kmod_it,
kmod_table);
if (!err)
continue;
else if (err != &drgn_not_found)
break;
}
/*
* If it was not reported explicitly and we're also reporting the
* defaults, look for the module at the standard locations unless we've
* already indexed that module.
*/
if (depmod &&
!drgn_debug_info_is_indexed(load->dbinfo, kmod_it.name)) {
if (!depmod->modules_dep.ptr) {
err = depmod_index_init(depmod,
prog->vmcoreinfo.osrelease);
if (err) {
depmod->modules_dep.ptr = NULL;
err = drgn_debug_info_report_error(load,
"kernel modules",
"could not read depmod",
err);
if (err)
break;
depmod = NULL;
continue;
}
}
err = report_default_kernel_module(load, &kmod_it,
depmod);
if (err)
break;
}
}
kernel_module_iterator_deinit(&kmod_it);
return err;
}
static struct drgn_error *
report_kernel_modules(struct drgn_debug_info_load_state *load,
struct kernel_module_file *kmods, size_t num_kmods,
bool need_module_definition, bool vmlinux_is_pending)
{
struct drgn_program *prog = load->dbinfo->prog;
struct drgn_error *err;
if (!num_kmods && !load->load_default)
return NULL;
/*
* If we're not debugging the running kernel, then we need to index
* vmlinux now so that we can walk the list of modules in the kernel.
*
* If we need the definition of struct module to get the name of any
* kernel modules, then we also need to index vmlinux now.
*/
if (vmlinux_is_pending &&
(!(prog->flags & DRGN_PROGRAM_IS_LIVE) || need_module_definition)) {
err = drgn_debug_info_report_flush(load);
if (err)
return err;
}
size_t module_name_offset = 0;
if (need_module_definition) {
struct drgn_qualified_type module_type;
struct drgn_member_info name_member;
err = drgn_program_find_type(prog, "struct module", NULL,
&module_type);
if (!err) {
err = drgn_program_member_info(prog, module_type.type,
"name", &name_member);
}
if (err) {
return drgn_debug_info_report_error(load,
"kernel modules",
"could not get kernel module names",
err);
}
module_name_offset = name_member.bit_offset / 8;
}
struct kernel_module_table kmod_table = HASH_TABLE_INIT;
struct depmod_index depmod;
struct kernel_module_table_iterator it;
depmod.modules_dep.ptr = NULL;
for (size_t i = 0; i < num_kmods; i++) {
struct kernel_module_file *kmod = &kmods[i];
if (!kmod->name) {
err = get_kernel_module_name_from_this_module(kmod->this_module_scn,
module_name_offset,
&kmod->name);
if (err) {
err = drgn_debug_info_report_error(load,
kmod->path,
NULL, err);
if (err)
goto out;
continue;
}
if (!kmod->name) {
err = drgn_debug_info_report_error(load,
kmod->path,
"could not find kernel module name",
NULL);
if (err)
goto out;
continue;
}
}
struct hash_pair hp = kernel_module_table_hash(&kmod->name);
it = kernel_module_table_search_hashed(&kmod_table, &kmod->name,
hp);
if (it.entry) {
kmod->next = *it.entry;
*it.entry = kmod;
} else {
if (kernel_module_table_insert_searched(&kmod_table,
&kmod, hp,
NULL) == -1) {
err = &drgn_enomem;
goto out;
}
kmod->next = NULL;
}
}
err = report_loaded_kernel_modules(load,
num_kmods ? &kmod_table : NULL,
load->load_default ? &depmod : NULL);
if (err)
goto out;
/* Anything left over was not loaded. */
for (it = kernel_module_table_first(&kmod_table); it.entry; ) {
struct kernel_module_file *kmod = *it.entry;
it = kernel_module_table_delete_iterator(&kmod_table, it);
do {
err = drgn_debug_info_report_elf(load, kmod->path,
kmod->fd, kmod->elf, 0,
0, kmod->name, NULL);
kmod->elf = NULL;
kmod->fd = -1;
if (err)
goto out;
kmod = kmod->next;
} while (kmod);
}
err = NULL;
out:
if (depmod.modules_dep.ptr)
depmod_index_deinit(&depmod);
kernel_module_table_deinit(&kmod_table);
return err;
}
static struct drgn_error *
report_vmlinux(struct drgn_debug_info_load_state *load,
bool *vmlinux_is_pending)
{
static const char * const vmlinux_paths[] = {
/*
* The files under /usr/lib/debug should always have debug
* information, so check for those first.
*/
"/usr/lib/debug/boot/vmlinux-%s",
"/usr/lib/debug/lib/modules/%s/vmlinux",
"/boot/vmlinux-%s",
"/lib/modules/%s/build/vmlinux",
"/lib/modules/%s/vmlinux",
NULL,
};
struct drgn_program *prog = load->dbinfo->prog;
struct drgn_error *err;
char *path;
int fd;
Elf *elf;
err = find_elf_file(&path, &fd, &elf, vmlinux_paths,
prog->vmcoreinfo.osrelease);
if (err)
return drgn_debug_info_report_error(load, NULL, NULL, err);
if (!elf) {
err = drgn_error_format(DRGN_ERROR_OTHER,
"could not find vmlinux for %s",
prog->vmcoreinfo.osrelease);
return drgn_debug_info_report_error(load, "kernel", NULL, err);
}
uint64_t start, end;
err = elf_address_range(elf, prog->vmcoreinfo.kaslr_offset, &start,
&end);
if (err) {
err = drgn_debug_info_report_error(load, path, NULL, err);
elf_end(elf);
close(fd);
free(path);
return err;
}
err = drgn_debug_info_report_elf(load, path, fd, elf, start, end,
"kernel", vmlinux_is_pending);
free(path);
return err;
}
struct drgn_error *
linux_kernel_report_debug_info(struct drgn_debug_info_load_state *load)
{
struct drgn_program *prog = load->dbinfo->prog;
struct drgn_error *err;
struct kernel_module_file *kmods;
if (load->num_paths) {
kmods = malloc_array(load->num_paths, sizeof(*kmods));
if (!kmods)
return &drgn_enomem;
} else {
kmods = NULL;
}
/*
* We may need to index vmlinux before we can properly report kernel
* modules. So, this sets aside kernel modules and reports everything
* else.
*/
size_t num_kmods = 0;
bool need_module_definition = false;
bool vmlinux_is_pending = false;
for (size_t i = 0; i < load->num_paths; i++) {
const char *path = load->paths[i];
int fd;
Elf *elf;
err = open_elf_file(path, &fd, &elf);
if (err) {
err = drgn_debug_info_report_error(load, path, NULL,
err);
if (err)
goto out;
continue;
}
Elf_Scn *this_module_scn, *modinfo_scn;
bool is_vmlinux;
err = identify_kernel_elf(elf, &this_module_scn, &modinfo_scn,
&is_vmlinux);
if (err) {
err = drgn_debug_info_report_error(load, path, NULL,
err);
elf_end(elf);
close(fd);
if (err)
goto out;
continue;
}
if (this_module_scn || modinfo_scn) {
struct kernel_module_file *kmod = &kmods[num_kmods++];
kmod->path = path;
kmod->fd = fd;
kmod->elf = elf;
err = get_kernel_module_name_from_modinfo(modinfo_scn,
&kmod->name);
if (err) {
err = drgn_debug_info_report_error(load, path,
NULL, err);
if (err)
goto out;
continue;
}
if (!kmod->name) {
kmod->this_module_scn = this_module_scn;
need_module_definition = true;
}
} else if (is_vmlinux) {
uint64_t start, end;
err = elf_address_range(elf,
prog->vmcoreinfo.kaslr_offset,
&start, &end);
if (err) {
elf_end(elf);
close(fd);
err = drgn_debug_info_report_error(load, path,
NULL, err);
if (err)
goto out;
continue;
}
bool is_new;
err = drgn_debug_info_report_elf(load, path, fd, elf,
start, end, "kernel",
&is_new);
if (err)
goto out;
if (is_new)
vmlinux_is_pending = true;
} else {
err = drgn_debug_info_report_elf(load, path, fd, elf, 0,
0, NULL, NULL);
if (err)
goto out;
}
}
if (load->load_main && !vmlinux_is_pending &&
!drgn_debug_info_is_indexed(load->dbinfo, "kernel")) {
err = report_vmlinux(load, &vmlinux_is_pending);
if (err)
goto out;
}
err = report_kernel_modules(load, kmods, num_kmods,
need_module_definition, vmlinux_is_pending);
out:
for (size_t i = 0; i < num_kmods; i++) {
elf_end(kmods[i].elf);
if (kmods[i].fd != -1)
close(kmods[i].fd);
}
free(kmods);
return err;
}