mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-22 17:23:06 +00:00
libdrgn: report better errors when parsing DWARF/kmod index
If the DWARF index encounters any error while parsing, it returns an error saying only "debug information is truncated", which makes it hard to track down parsing errors. The kmod index parser silently swallows errors. For both, replace the mread functions with a higher-level binary_buffer interface that can include more information including the location of the error. For example: /tmp/mybinary: .debug_info+0x4: expected at least 56 bytes, have 55 Signed-off-by: Omar Sandoval <osandov@osandov.com>
This commit is contained in:
parent
756e5d27ad
commit
5975d19580
@ -18,6 +18,8 @@ noinst_LTLIBRARIES = libdrgnimpl.la
|
||||
ARCH_INS = arch_x86_64.c.in
|
||||
|
||||
libdrgnimpl_la_SOURCES = $(ARCH_INS:.c.in=.c) \
|
||||
binary_buffer.c \
|
||||
binary_buffer.h \
|
||||
binary_search_tree.h \
|
||||
bitops.h \
|
||||
cityhash.h \
|
||||
@ -40,7 +42,6 @@ libdrgnimpl_la_SOURCES = $(ARCH_INS:.c.in=.c) \
|
||||
memory_reader.c \
|
||||
memory_reader.h \
|
||||
minmax.h \
|
||||
mread.h \
|
||||
object.c \
|
||||
object.h \
|
||||
object_index.c \
|
||||
|
43
libdrgn/binary_buffer.c
Normal file
43
libdrgn/binary_buffer.c
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "binary_buffer.h"
|
||||
#include "drgn.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
static struct drgn_error *binary_buffer_error_vat(struct binary_buffer *bb,
|
||||
const char *pos,
|
||||
const char *format,
|
||||
va_list ap)
|
||||
{
|
||||
char *message;
|
||||
int ret = vasprintf(&message, format, ap);
|
||||
if (ret == -1)
|
||||
return &drgn_enomem;
|
||||
struct drgn_error *err = bb->error_fn(bb, pos, message);
|
||||
free(message);
|
||||
return err;
|
||||
}
|
||||
|
||||
struct drgn_error *binary_buffer_error(struct binary_buffer *bb,
|
||||
const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
struct drgn_error *err = binary_buffer_error_vat(bb, bb->prev, format,
|
||||
ap);
|
||||
va_end(ap);
|
||||
return err;
|
||||
}
|
||||
|
||||
struct drgn_error *binary_buffer_error_at(struct binary_buffer *bb,
|
||||
const char *pos, const char *format,
|
||||
...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
struct drgn_error *err = binary_buffer_error_vat(bb, pos, format, ap);
|
||||
va_end(ap);
|
||||
return err;
|
||||
}
|
302
libdrgn/binary_buffer.h
Normal file
302
libdrgn/binary_buffer.h
Normal file
@ -0,0 +1,302 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Binary format parsing.
|
||||
*
|
||||
* See @ref BinaryBuffer.
|
||||
*/
|
||||
|
||||
#ifndef DRGN_BINARY_BUFFER_H
|
||||
#define DRGN_BINARY_BUFFER_H
|
||||
|
||||
#include <byteswap.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
/**
|
||||
* @ingroup Internals
|
||||
*
|
||||
* @defgroup BinaryBuffer Binary buffer
|
||||
*
|
||||
* Binary format parsing.
|
||||
*
|
||||
* A @ref binary_buffer is a buffer for parsing binary data safely. It has a
|
||||
* position (@ref binary_buffer::pos) and various functions to read from the
|
||||
* current position and advance it.
|
||||
*
|
||||
* The `binary_buffer_next*` functions read a value from the buffer and advance
|
||||
* the position past the read value. They return an error if the desired value
|
||||
* is out of bounds of the buffer. They also save the previous position for
|
||||
* error reporting (@ref binary_buffer::prev). On error, they do not advance the
|
||||
* position or change the previous position.
|
||||
*
|
||||
* The `binary_buffer_skip*` functions are similar, except that they skip past
|
||||
* unneeded data in the buffer and don't change the previous position.
|
||||
*
|
||||
* Errors are formatted through a callback (@ref binary_buffer_error_fn) which
|
||||
* can provide information about, e.g., what file contained the bad data. The
|
||||
* @ref binary_buffer can be embedded in a structure containing additional
|
||||
* context.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
struct binary_buffer;
|
||||
|
||||
/**
|
||||
* Binary buffer error formatting function.
|
||||
*
|
||||
* @param[in] bb Buffer.
|
||||
* @param[in] pos Position in the buffer where the error occurred.
|
||||
* @param[in] message Error message.
|
||||
*/
|
||||
typedef struct drgn_error *(*binary_buffer_error_fn)(struct binary_buffer *bb,
|
||||
const char *pos,
|
||||
const char *message);
|
||||
|
||||
/**
|
||||
* Buffer of binary data to parse.
|
||||
*
|
||||
* In addition to the functions defined here, `pos`, `prev`, and `end` may be
|
||||
* modified directly so long as `pos <= end && prev <= end` remains true.
|
||||
*/
|
||||
struct binary_buffer {
|
||||
/**
|
||||
* Current position in the buffer.
|
||||
*
|
||||
* This is advanced by the `binary_buffer_next*` functions.
|
||||
*/
|
||||
const char *pos;
|
||||
/** Pointer to one byte after the last valid byte in the buffer. */
|
||||
const char *end;
|
||||
/**
|
||||
* Position of the last accessed value.
|
||||
*
|
||||
* On success, the `binary_buffer_next*` functions set this to the
|
||||
* position of the returned value (i.e., the position on entry). This is
|
||||
* useful for reporting errors after validating a value that was just
|
||||
* read.
|
||||
*
|
||||
* This is not updated by the `binary_buffer_skip*` functions.
|
||||
*/
|
||||
const char *prev;
|
||||
/** Whether the data is in the opposite byte order from the host. */
|
||||
bool bswap;
|
||||
/** Error formatting callback. */
|
||||
binary_buffer_error_fn error_fn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize a @ref binary_buffer.
|
||||
*
|
||||
* @param[in] buf Pointer to data.
|
||||
* @param[in] len Length of data in bytes.
|
||||
* @param[in] little_endian Whether the data is little endian.
|
||||
* @param[in] error_fn Error formatting callback.
|
||||
*/
|
||||
static inline void binary_buffer_init(struct binary_buffer *bb, const void *buf,
|
||||
size_t len, bool little_endian,
|
||||
binary_buffer_error_fn error_fn)
|
||||
{
|
||||
bb->pos = buf;
|
||||
bb->end = (const char *)buf + len;
|
||||
bb->prev = NULL;
|
||||
bb->bswap = little_endian != (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__);
|
||||
bb->error_fn = error_fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report an error at the previous buffer position (@ref binary_buffer::prev).
|
||||
*/
|
||||
struct drgn_error *binary_buffer_error(struct binary_buffer *bb,
|
||||
const char *format, ...)
|
||||
__attribute__((__returns_nonnull__, __format__(__printf__, 2, 3)));
|
||||
|
||||
/** Report an error at a given position in the buffer. */
|
||||
struct drgn_error *binary_buffer_error_at(struct binary_buffer *bb,
|
||||
const char *pos, const char *format,
|
||||
...)
|
||||
__attribute__((__returns_nonnull__, __format__(__printf__, 3, 4)));
|
||||
|
||||
/**
|
||||
* Return whether there are any bytes in the buffer after the current position.
|
||||
*
|
||||
* @return @c true if there bytes remaining, @c false if the position is at the
|
||||
* end of the buffer.
|
||||
*/
|
||||
static inline bool binary_buffer_has_next(struct binary_buffer *bb)
|
||||
{
|
||||
return bb->pos < bb->end;
|
||||
}
|
||||
|
||||
static inline struct drgn_error *
|
||||
binary_buffer_check_bounds(struct binary_buffer *bb, size_t n)
|
||||
{
|
||||
if (unlikely(bb->end - bb->pos < n)) {
|
||||
return binary_buffer_error_at(bb, bb->pos,
|
||||
"expected at least %zu byte%s, have %td",
|
||||
n, n == 1 ? "" : "s",
|
||||
bb->end - bb->pos);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Advance the current buffer position by @p n bytes. */
|
||||
static inline struct drgn_error *binary_buffer_skip(struct binary_buffer *bb,
|
||||
size_t n)
|
||||
{
|
||||
struct drgn_error *err;
|
||||
if ((err = binary_buffer_check_bounds(bb, n)))
|
||||
return err;
|
||||
bb->pos += n;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef DOXYGEN
|
||||
/**
|
||||
* Get an unsigned N-bit integer at the current buffer position and advance the
|
||||
* position.
|
||||
*
|
||||
* This is defined for N of 16, 32, and 64.
|
||||
*
|
||||
* The byte order is determined by the @p little_endian parameter that was
|
||||
* passed to @ref binary_buffer_init().
|
||||
*
|
||||
* @param[out] ret Returned value.
|
||||
*/
|
||||
struct drgn_error *binary_buffer_next_uN(struct binary_buffer *bb,
|
||||
uintN_t *ret);
|
||||
|
||||
/** Like @ref binary_buffer_next_uN(), but return the value as a @c uint64_t. */
|
||||
struct drgn_error *binary_buffer_next_uN_into_u64(struct binary_buffer *bb,
|
||||
uint64_t *ret);
|
||||
#endif
|
||||
|
||||
#define DEFINE_NEXT_UINT(bits) \
|
||||
static inline struct drgn_error * \
|
||||
binary_buffer_next_u##bits(struct binary_buffer *bb, uint##bits##_t *ret) \
|
||||
{ \
|
||||
struct drgn_error *err; \
|
||||
uint##bits##_t tmp; \
|
||||
if ((err = binary_buffer_check_bounds(bb, sizeof(tmp)))) \
|
||||
return err; \
|
||||
bb->prev = bb->pos; \
|
||||
memcpy(&tmp, bb->pos, sizeof(tmp)); \
|
||||
bb->pos += sizeof(tmp); \
|
||||
*ret = bb->bswap ? bswap_##bits(tmp) : tmp; \
|
||||
return NULL; \
|
||||
} \
|
||||
\
|
||||
static inline struct drgn_error * \
|
||||
binary_buffer_next_u##bits##_into_u64(struct binary_buffer *bb, uint64_t *ret) \
|
||||
{ \
|
||||
struct drgn_error *err; \
|
||||
uint##bits##_t tmp; \
|
||||
if ((err = binary_buffer_next_u##bits(bb, &tmp))) \
|
||||
return err; \
|
||||
*ret = tmp; \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
#define bswap_8(x) (x)
|
||||
DEFINE_NEXT_UINT(8)
|
||||
#undef bswap_8
|
||||
DEFINE_NEXT_UINT(16)
|
||||
DEFINE_NEXT_UINT(32)
|
||||
DEFINE_NEXT_UINT(64)
|
||||
|
||||
#undef DEFINE_NEXT_UINT
|
||||
|
||||
/**
|
||||
* Decode an Unsigned Little-Endian Base 128 (ULEB128) number at the current
|
||||
* buffer position and advance the position.
|
||||
*
|
||||
* If the number does not fit in a @c uint64_t, an error is returned.
|
||||
*
|
||||
* @param[out] ret Returned value.
|
||||
*/
|
||||
static inline struct drgn_error *
|
||||
binary_buffer_next_uleb128(struct binary_buffer *bb, uint64_t *ret)
|
||||
{
|
||||
int shift = 0;
|
||||
uint64_t value = 0;
|
||||
const char *pos = bb->pos;
|
||||
while (likely(pos < bb->end)) {
|
||||
uint8_t byte = *(uint8_t *)(pos++);
|
||||
if (unlikely(shift == 63 && byte > 1)) {
|
||||
return binary_buffer_error_at(bb, bb->pos,
|
||||
"ULEB128 number overflows unsigned 64-bit integer");
|
||||
}
|
||||
value |= (uint64_t)(byte & 0x7f) << shift;
|
||||
shift += 7;
|
||||
if (!(byte & 0x80)) {
|
||||
bb->prev = bb->pos;
|
||||
bb->pos = pos;
|
||||
*ret = value;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return binary_buffer_error_at(bb, bb->pos, "expected ULEB128 number");
|
||||
}
|
||||
|
||||
/** Skip past a LEB128 number at the current buffer position. */
|
||||
static inline struct drgn_error *
|
||||
binary_buffer_skip_leb128(struct binary_buffer *bb)
|
||||
{
|
||||
const char *pos = bb->pos;
|
||||
while (likely(pos < bb->end)) {
|
||||
if (!(*(uint8_t *)(pos++) & 0x80)) {
|
||||
bb->pos = pos;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return binary_buffer_error_at(bb, bb->pos, "expected LEB128 number");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a null-terminated string at the current buffer position and advance the
|
||||
* position.
|
||||
*
|
||||
* @param[out] str_ret Returned string (i.e., the buffer position on entry).
|
||||
* @param[out] len_ret Returned string length not including the null byte.
|
||||
*/
|
||||
static inline struct drgn_error *
|
||||
binary_buffer_next_string(struct binary_buffer *bb, const char **str_ret,
|
||||
size_t *len_ret)
|
||||
{
|
||||
size_t len = strnlen(bb->pos, bb->end - bb->pos);
|
||||
if (unlikely(len == bb->end - bb->pos)) {
|
||||
return binary_buffer_error_at(bb, bb->pos,
|
||||
"expected null-terminated string");
|
||||
}
|
||||
*str_ret = bb->prev = bb->pos;
|
||||
*len_ret = len;
|
||||
bb->pos += len + 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Skip past a null-terminated string at the current buffer position. */
|
||||
static inline struct drgn_error *
|
||||
binary_buffer_skip_string(struct binary_buffer *bb)
|
||||
{
|
||||
size_t len = strnlen(bb->pos, bb->end - bb->pos);
|
||||
if (unlikely(len == bb->end - bb->pos)) {
|
||||
return binary_buffer_error_at(bb, bb->pos,
|
||||
"expected null-terminated string");
|
||||
}
|
||||
bb->pos += len + 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif /* DRGN_BINARY_BUFFER_H */
|
@ -63,6 +63,28 @@ static const char * const drgn_debug_scn_names[] = {
|
||||
[DRGN_SCN_DEBUG_LINE] = ".debug_line",
|
||||
};
|
||||
|
||||
|
||||
struct drgn_error *drgn_error_debug_info(struct drgn_debug_info_module *module,
|
||||
enum drgn_debug_info_scn scn,
|
||||
const char *ptr, const char *message)
|
||||
{
|
||||
const char *name = dwfl_module_info(module->dwfl_module, NULL, NULL,
|
||||
NULL, NULL, NULL, NULL, NULL);
|
||||
return drgn_error_format(DRGN_ERROR_OTHER, "%s: %s+%#tx: %s",
|
||||
name, drgn_debug_scn_names[scn],
|
||||
ptr - (const char *)module->scns[scn]->d_buf,
|
||||
message);
|
||||
}
|
||||
|
||||
struct drgn_error *drgn_debug_info_buffer_error(struct binary_buffer *bb,
|
||||
const char *pos,
|
||||
const char *message)
|
||||
{
|
||||
struct drgn_debug_info_buffer *buffer =
|
||||
container_of(bb, struct drgn_debug_info_buffer, bb);
|
||||
return drgn_error_debug_info(buffer->module, buffer->scn, pos, message);
|
||||
}
|
||||
|
||||
DEFINE_VECTOR_FUNCTIONS(drgn_debug_info_module_vector)
|
||||
|
||||
static inline struct hash_pair
|
||||
@ -819,9 +841,7 @@ drgn_get_debug_sections(struct drgn_debug_info_module *module)
|
||||
if (!elf)
|
||||
return drgn_error_libdw();
|
||||
|
||||
module->bswap = (elf_getident(elf, NULL)[EI_DATA] !=
|
||||
(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ ?
|
||||
ELFDATA2LSB : ELFDATA2MSB));
|
||||
module->little_endian = elf_getident(elf, NULL)[EI_DATA] == ELFDATA2LSB;
|
||||
|
||||
size_t shstrndx;
|
||||
if (elf_getshdrstrndx(elf, &shstrndx))
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <elfutils/libdwfl.h>
|
||||
#include <libelf.h>
|
||||
|
||||
#include "binary_buffer.h"
|
||||
#include "drgn.h"
|
||||
#include "dwarf_index.h"
|
||||
#include "hash_table.h"
|
||||
@ -83,7 +84,7 @@ struct drgn_debug_info_module {
|
||||
Elf *elf;
|
||||
int fd;
|
||||
enum drgn_debug_info_module_state state;
|
||||
bool bswap;
|
||||
bool little_endian;
|
||||
/** Error while loading. */
|
||||
struct drgn_error *err;
|
||||
/**
|
||||
@ -97,6 +98,32 @@ struct drgn_debug_info_module {
|
||||
struct drgn_debug_info_module *next;
|
||||
};
|
||||
|
||||
struct drgn_error *drgn_error_debug_info(struct drgn_debug_info_module *module,
|
||||
enum drgn_debug_info_scn scn,
|
||||
const char *ptr, const char *message);
|
||||
|
||||
struct drgn_debug_info_buffer {
|
||||
struct binary_buffer bb;
|
||||
struct drgn_debug_info_module *module;
|
||||
enum drgn_debug_info_scn scn;
|
||||
};
|
||||
|
||||
struct drgn_error *drgn_debug_info_buffer_error(struct binary_buffer *bb,
|
||||
const char *pos,
|
||||
const char *message);
|
||||
|
||||
static inline void
|
||||
drgn_debug_info_buffer_init(struct drgn_debug_info_buffer *buffer,
|
||||
struct drgn_debug_info_module *module,
|
||||
enum drgn_debug_info_scn scn)
|
||||
{
|
||||
binary_buffer_init(&buffer->bb, module->scns[scn]->d_buf,
|
||||
module->scns[scn]->d_size, module->little_endian,
|
||||
drgn_debug_info_buffer_error);
|
||||
buffer->module = module;
|
||||
buffer->scn = scn;
|
||||
}
|
||||
|
||||
struct drgn_debug_info_module_key {
|
||||
const void *build_id;
|
||||
size_t build_id_len;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -15,6 +15,7 @@
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "binary_buffer.h"
|
||||
#include "bitops.h"
|
||||
#include "debug_info.h"
|
||||
#include "drgn.h"
|
||||
@ -24,7 +25,6 @@
|
||||
#include "language.h"
|
||||
#include "linux_kernel.h"
|
||||
#include "memory_reader.h"
|
||||
#include "mread.h"
|
||||
#include "platform.h"
|
||||
#include "program.h"
|
||||
#include "type.h"
|
||||
@ -738,52 +738,80 @@ kernel_module_section_iterator_next(struct kernel_module_section_iterator *it,
|
||||
* changes in the future, we can reevaluate this.
|
||||
*/
|
||||
|
||||
struct kmod_index {
|
||||
const char *ptr, *end;
|
||||
struct depmod_index {
|
||||
void *addr;
|
||||
size_t len;
|
||||
char path[256];
|
||||
};
|
||||
|
||||
static struct drgn_error *kmod_index_validate(struct kmod_index *index,
|
||||
const char *path)
|
||||
static void depmod_index_deinit(struct depmod_index *depmod)
|
||||
{
|
||||
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);
|
||||
}
|
||||
munmap(depmod->addr, depmod->len);
|
||||
}
|
||||
|
||||
struct depmod_index_buffer {
|
||||
struct binary_buffer bb;
|
||||
struct depmod_index *depmod;
|
||||
};
|
||||
|
||||
static struct drgn_error *depmod_index_buffer_error(struct binary_buffer *bb,
|
||||
const char *pos,
|
||||
const char *message)
|
||||
{
|
||||
struct depmod_index_buffer *buffer =
|
||||
container_of(bb, struct depmod_index_buffer, bb);
|
||||
return drgn_error_format(DRGN_ERROR_OTHER, "%s: %#tx: %s",
|
||||
buffer->depmod->path,
|
||||
pos - (const char *)buffer->depmod->addr,
|
||||
message);
|
||||
}
|
||||
|
||||
static void depmod_index_buffer_init(struct depmod_index_buffer *buffer,
|
||||
struct depmod_index *depmod)
|
||||
{
|
||||
binary_buffer_init(&buffer->bb, depmod->addr, depmod->len, false,
|
||||
depmod_index_buffer_error);
|
||||
buffer->depmod = depmod;
|
||||
}
|
||||
|
||||
static struct drgn_error *depmod_index_validate(struct depmod_index *depmod)
|
||||
{
|
||||
struct drgn_error *err;
|
||||
struct depmod_index_buffer buffer;
|
||||
depmod_index_buffer_init(&buffer, depmod);
|
||||
uint32_t magic;
|
||||
if ((err = binary_buffer_next_u32(&buffer.bb, &magic)))
|
||||
return err;
|
||||
if (magic != 0xb007f457) {
|
||||
return drgn_error_format(DRGN_ERROR_OTHER,
|
||||
"%s has invalid magic (0x%" PRIx32 ")",
|
||||
path, magic);
|
||||
return binary_buffer_error(&buffer.bb,
|
||||
"invalid magic 0x%" PRIx32, magic);
|
||||
}
|
||||
uint32_t version;
|
||||
if ((err = binary_buffer_next_u32(&buffer.bb, &version)))
|
||||
return err;
|
||||
if (version != 0x00020001) {
|
||||
return drgn_error_format(DRGN_ERROR_OTHER,
|
||||
"%s has unknown version (0x%" PRIx32 ")",
|
||||
path, version);
|
||||
return binary_buffer_error(&buffer.bb,
|
||||
"unknown version 0x%" PRIx32,
|
||||
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)
|
||||
static struct drgn_error *depmod_index_init(struct depmod_index *depmod,
|
||||
const char *osrelease)
|
||||
{
|
||||
struct drgn_error *err;
|
||||
int fd;
|
||||
struct stat st;
|
||||
void *map;
|
||||
|
||||
fd = open(path, O_RDONLY);
|
||||
snprintf(depmod->path, sizeof(depmod->path),
|
||||
"/lib/modules/%s/modules.dep.bin", osrelease);
|
||||
|
||||
int fd = open(depmod->path, O_RDONLY);
|
||||
if (fd == -1)
|
||||
return drgn_error_create_os("open", errno, path);
|
||||
return drgn_error_create_os("open", errno, depmod->path);
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == -1) {
|
||||
err = drgn_error_create_os("fstat", errno, path);
|
||||
err = drgn_error_create_os("fstat", errno, depmod->path);
|
||||
goto out;
|
||||
}
|
||||
|
||||
@ -792,134 +820,122 @@ static struct drgn_error *kmod_index_init(struct kmod_index *index,
|
||||
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);
|
||||
void *addr = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (addr == MAP_FAILED) {
|
||||
err = drgn_error_create_os("mmap", errno, depmod->path);
|
||||
goto out;
|
||||
}
|
||||
index->ptr = map;
|
||||
index->end = index->ptr + st.st_size;
|
||||
|
||||
err = kmod_index_validate(index, path);
|
||||
depmod->addr = addr;
|
||||
depmod->len = st.st_size;
|
||||
|
||||
err = depmod_index_validate(depmod);
|
||||
if (err)
|
||||
kmod_index_deinit(index);
|
||||
depmod_index_deinit(depmod);
|
||||
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.
|
||||
* /lib/modules/$(uname -r). This is @em not null-terminated. @c NULL if not
|
||||
* found.
|
||||
* @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)
|
||||
static struct drgn_error *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;
|
||||
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);
|
||||
|
||||
struct drgn_error *err;
|
||||
struct depmod_index_buffer buffer;
|
||||
depmod_index_buffer_init(&buffer, depmod);
|
||||
|
||||
/* depmod_index_validate() already checked that this is within bounds. */
|
||||
buffer.bb.pos += 8;
|
||||
uint32_t offset;
|
||||
for (;;) {
|
||||
if ((err = binary_buffer_next_u32(&buffer.bb, &offset)))
|
||||
return err;
|
||||
if ((offset & INDEX_NODE_MASK) > depmod->len) {
|
||||
return binary_buffer_error(&buffer.bb,
|
||||
"offset is out of bounds");
|
||||
}
|
||||
buffer.bb.pos = (const char *)depmod->addr + (offset & INDEX_NODE_MASK);
|
||||
|
||||
if (offset & INDEX_NODE_PREFIX) {
|
||||
const char *prefix;
|
||||
size_t prefix_len;
|
||||
if ((err = binary_buffer_next_string(&buffer.bb,
|
||||
&prefix,
|
||||
&prefix_len)))
|
||||
return err;
|
||||
if (strncmp(name, prefix, prefix_len) != 0)
|
||||
goto not_found;
|
||||
name += prefix_len;
|
||||
}
|
||||
|
||||
if (offset & INDEX_NODE_CHILDS) {
|
||||
uint8_t first, last;
|
||||
if ((err = binary_buffer_next_u8(&buffer.bb, &first)) ||
|
||||
(err = binary_buffer_next_u8(&buffer.bb, &last)))
|
||||
return err;
|
||||
if (*name) {
|
||||
uint8_t cur = *name;
|
||||
if (cur < first || cur > last)
|
||||
goto not_found;
|
||||
if ((err = binary_buffer_skip(&buffer.bb,
|
||||
4 * (cur - first))))
|
||||
return err;
|
||||
name++;
|
||||
continue;
|
||||
} else {
|
||||
if ((err = binary_buffer_skip(&buffer.bb,
|
||||
4 * (last - first + 1))))
|
||||
return err;
|
||||
break;
|
||||
}
|
||||
} else if (*name) {
|
||||
goto not_found;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!(offset & INDEX_NODE_VALUES))
|
||||
goto not_found;
|
||||
|
||||
uint32_t value_count;
|
||||
if (!mread_be32(&ptr, depmod->modules_dep.end, &value_count) ||
|
||||
!value_count)
|
||||
return false;
|
||||
if ((err = binary_buffer_next_u32(&buffer.bb, &value_count)))
|
||||
return err;
|
||||
if (!value_count)
|
||||
goto not_found; /* Or is this malformed? */
|
||||
|
||||
/* 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;
|
||||
if ((err = binary_buffer_skip(&buffer.bb, 4)))
|
||||
return err;
|
||||
|
||||
const char *colon = strchr(deps, ':');
|
||||
if (!colon)
|
||||
return false;
|
||||
const char *colon = memchr(buffer.bb.pos, ':',
|
||||
buffer.bb.end - buffer.bb.pos);
|
||||
if (!colon) {
|
||||
return binary_buffer_error(&buffer.bb,
|
||||
"expected string containing ':'");
|
||||
}
|
||||
*path_ret = buffer.bb.pos;
|
||||
*len_ret = colon - buffer.bb.pos;
|
||||
return NULL;
|
||||
|
||||
*path_ret = deps;
|
||||
*len_ret = colon - deps;
|
||||
return true;
|
||||
not_found:
|
||||
*path_ret = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1204,8 +1220,14 @@ report_default_kernel_module(struct drgn_debug_info_load_state *load,
|
||||
|
||||
const char *depmod_path;
|
||||
size_t depmod_path_len;
|
||||
if (!depmod_index_find(depmod, kmod_it->name, &depmod_path,
|
||||
&depmod_path_len)) {
|
||||
err = depmod_index_find(depmod, kmod_it->name, &depmod_path,
|
||||
&depmod_path_len);
|
||||
if (err) {
|
||||
return drgn_debug_info_report_error(load,
|
||||
"kernel modules",
|
||||
"could not parse depmod",
|
||||
err);
|
||||
} else if (!depmod_path) {
|
||||
return drgn_debug_info_report_error(load, kmod_it->name,
|
||||
"could not find module in depmod",
|
||||
NULL);
|
||||
@ -1296,11 +1318,11 @@ kernel_module_iterator_error:
|
||||
*/
|
||||
if (depmod &&
|
||||
!drgn_debug_info_is_indexed(load->dbinfo, kmod_it.name)) {
|
||||
if (!depmod->modules_dep.ptr) {
|
||||
if (!depmod->addr) {
|
||||
err = depmod_index_init(depmod,
|
||||
prog->vmcoreinfo.osrelease);
|
||||
if (err) {
|
||||
depmod->modules_dep.ptr = NULL;
|
||||
depmod->addr = NULL;
|
||||
err = drgn_debug_info_report_error(load,
|
||||
"kernel modules",
|
||||
"could not read depmod",
|
||||
@ -1377,8 +1399,8 @@ report_kernel_modules(struct drgn_debug_info_load_state *load,
|
||||
|
||||
struct kernel_module_table kmod_table = HASH_TABLE_INIT;
|
||||
struct depmod_index depmod;
|
||||
depmod.addr = NULL;
|
||||
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) {
|
||||
@ -1445,7 +1467,7 @@ report_kernel_modules(struct drgn_debug_info_load_state *load,
|
||||
}
|
||||
err = NULL;
|
||||
out:
|
||||
if (depmod.modules_dep.ptr)
|
||||
if (depmod.addr)
|
||||
depmod_index_deinit(&depmod);
|
||||
kernel_module_table_deinit(&kmod_table);
|
||||
return err;
|
||||
|
258
libdrgn/mread.h
258
libdrgn/mread.h
@ -1,258 +0,0 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Helpers for parsing values in memory.
|
||||
*
|
||||
* See @ref MemoryParsing.
|
||||
*/
|
||||
|
||||
#ifndef DRGN_MREAD_H
|
||||
#define DRGN_MREAD_H
|
||||
|
||||
#include <byteswap.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @ingroup Internals
|
||||
*
|
||||
* @defgroup MemoryParsing Memory parsing
|
||||
*
|
||||
* Helpers for reading values in memory.
|
||||
*
|
||||
* This provides helpers for reading values in memory (e.g., from an mmap'd
|
||||
* file) with safe bounds checking.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return whether <tt>ptr + offset</tt> is within @p end.
|
||||
*
|
||||
* @param[in] ptr Pointer to check.
|
||||
* @param[in] end Pointer to one byte after the last valid byte.
|
||||
* @param[in] offset Offset to check.
|
||||
* @return @c true if the result would be in bounds, @c false if not.
|
||||
*/
|
||||
static inline bool mread_in_bounds(const char *ptr, const char *end,
|
||||
size_t offset)
|
||||
{
|
||||
return end - ptr >= offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return <tt>start + offset</tt>, checking bounds.
|
||||
*
|
||||
* @param[in] start Pointer to first valid byte.
|
||||
* @param[in] end Pointer to one byte after the last valid byte.
|
||||
* @param[in] offset Offset from @p start.
|
||||
* @return <tt>start + offset</tt> if it is within @p end, @c NULL if not.
|
||||
*/
|
||||
static inline const char *mread_begin(const char *start, const char *end,
|
||||
size_t offset)
|
||||
{
|
||||
return mread_in_bounds(start, end, offset) ? start + offset : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance @p ptr by @p offset, checking bounds.
|
||||
*
|
||||
* @param[in,out] ptr Pointer to check and advance.
|
||||
* @param[in] end Pointer to one byte after the last valid byte.
|
||||
* @param[in] offset Offset to advance by.
|
||||
* @return @c true if the pointer was advanced, @c false if it was not advanced
|
||||
* because the result would be out of bounds.
|
||||
*/
|
||||
static inline bool mread_skip(const char **ptr, const char *end, size_t offset)
|
||||
{
|
||||
if (!mread_in_bounds(*ptr, end, offset))
|
||||
return false;
|
||||
*ptr += offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an unsigned 8-bit integer in memory and advance @p ptr.
|
||||
*
|
||||
* @param[in,out] ptr Pointer to read from and advance.
|
||||
* @param[in] end Pointer to one byte after the last valid byte.
|
||||
* @param[out] ret Returned value.
|
||||
* @return @c true on success, @c false if the read was out of bounds.
|
||||
*/
|
||||
static inline bool mread_u8(const char **ptr, const char *end, uint8_t *ret)
|
||||
{
|
||||
if (!mread_in_bounds(*ptr, end, sizeof(uint8_t)))
|
||||
return false;
|
||||
*ret = *(const uint8_t *)*ptr;
|
||||
*ptr += sizeof(uint8_t);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an unsigned 8-bit integer in memory into a @c size_t and advance @p ptr.
|
||||
*
|
||||
* @sa mread_u8()
|
||||
*/
|
||||
static inline bool mread_u8_into_size_t(const char **ptr, const char *end,
|
||||
size_t *ret)
|
||||
{
|
||||
uint8_t tmp;
|
||||
if (!mread_u8(ptr, end, &tmp))
|
||||
return false;
|
||||
/* SIZE_MAX is required to be at least 65535, so this won't overflow. */
|
||||
*ret = tmp;
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef DOXYGEN
|
||||
/**
|
||||
* Read an unsigned N-bit integer in memory and advance @p ptr.
|
||||
*
|
||||
* This is defined for N of 16, 32, and 64.
|
||||
*
|
||||
* @param[in,out] ptr Pointer to read from and advance.
|
||||
* @param[in] end Pointer to one byte after the last valid byte.
|
||||
* @param[in] bswap Whether to swap the byte order of the read value.
|
||||
* @param[out] ret Returned value.
|
||||
* @return @c true on success, @c false if the read was out of bounds.
|
||||
*/
|
||||
bool mread_uN(const char **ptr, const char *end, bool bswap, uintN_t *ret);
|
||||
|
||||
/**
|
||||
* Read an unsigned N-bit little-endian integer in memory and advance @p ptr.
|
||||
*
|
||||
* @sa mread_uN()
|
||||
*/
|
||||
bool mread_leN(const char **ptr, const char *end, uintN_t *ret);
|
||||
|
||||
/**
|
||||
* Read an unsigned N-bit big-endian integer in memory and advance @p ptr.
|
||||
*
|
||||
* @sa mread_uN()
|
||||
*/
|
||||
bool mread_beN(const char **ptr, const char *end, uintN_t *ret);
|
||||
|
||||
/**
|
||||
* Read an unsigned N-bit integer in memory into a @c uint64_t and advance @p
|
||||
* ptr.
|
||||
*
|
||||
* @sa mread_uN()
|
||||
*/
|
||||
bool mread_uN_into_u64(const char **ptr, const char *end, bool bswap,
|
||||
uint64_t *ret);
|
||||
|
||||
/**
|
||||
* Read an unsigned N-bit integer in memory into a @c size_t and advance @p
|
||||
* ptr.
|
||||
*
|
||||
* @sa mread_uN()
|
||||
*
|
||||
* @return @c true on success, @c false if the read was out of bounds or the
|
||||
* result is too large for a @c size_t
|
||||
*/
|
||||
bool mread_uN_into_size_t(const char **ptr, const char *end, bool bswap,
|
||||
uint64_t *ret);
|
||||
#endif
|
||||
|
||||
#define DEFINE_READ(size) \
|
||||
static inline bool mread_u##size(const char **ptr, const char *end, bool bswap, \
|
||||
uint##size##_t *ret) \
|
||||
{ \
|
||||
if (!mread_in_bounds(*ptr, end, sizeof(uint##size##_t))) \
|
||||
return false; \
|
||||
uint##size##_t tmp; \
|
||||
memcpy(&tmp, *ptr, sizeof(tmp)); \
|
||||
if (bswap) \
|
||||
tmp = bswap_##size(tmp); \
|
||||
*ret = tmp; \
|
||||
*ptr += sizeof(uint##size##_t); \
|
||||
return true; \
|
||||
} \
|
||||
\
|
||||
static inline bool mread_le##size(const char **ptr, const char *end, \
|
||||
uint##size##_t *ret) \
|
||||
{ \
|
||||
return mread_u##size(ptr, end, \
|
||||
__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__, ret); \
|
||||
} \
|
||||
\
|
||||
static inline bool mread_be##size(const char **ptr, const char *end, \
|
||||
uint##size##_t *ret) \
|
||||
{ \
|
||||
return mread_u##size(ptr, end, __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__, \
|
||||
ret); \
|
||||
} \
|
||||
\
|
||||
static inline bool mread_u##size##_into_u64(const char **ptr, const char *end, \
|
||||
bool bswap, uint64_t *ret) \
|
||||
{ \
|
||||
uint##size##_t tmp; \
|
||||
if (!mread_u##size(ptr, end, bswap, &tmp)) \
|
||||
return false; \
|
||||
*ret = tmp; \
|
||||
return true; \
|
||||
} \
|
||||
\
|
||||
static inline bool mread_u##size##_into_size_t(const char **ptr, \
|
||||
const char *end, bool bswap, \
|
||||
size_t *ret) \
|
||||
{ \
|
||||
uint##size##_t tmp; \
|
||||
if (!mread_u##size(ptr, end, bswap, &tmp)) \
|
||||
return false; \
|
||||
if (tmp > SIZE_MAX) \
|
||||
return false; \
|
||||
*ret = tmp; \
|
||||
return true; \
|
||||
}
|
||||
|
||||
DEFINE_READ(16)
|
||||
DEFINE_READ(32)
|
||||
DEFINE_READ(64)
|
||||
|
||||
/**
|
||||
* Advance @p ptr to the byte after the next null byte.
|
||||
*
|
||||
* @param[in,out] ptr Pointer to advance.
|
||||
* @param[in] end Pointer to one byte after the last valid byte.
|
||||
* @return @c true if the pointer was advanced, @c false if no null byte was
|
||||
* found.
|
||||
*/
|
||||
static inline bool mread_skip_string(const char **ptr, const char *end)
|
||||
{
|
||||
const char *nul = memchr(*ptr, 0, end - *ptr);
|
||||
if (!nul)
|
||||
return false;
|
||||
*ptr = nul + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a null-terminated string in memory and advance @p ptr.
|
||||
*
|
||||
* @param[in,out] ptr Pointer to read from and advance.
|
||||
* @param[in] end Pointer to one byte after the last valid byte.
|
||||
* @param[out] str_ret Returned string. Equal to the initial value of
|
||||
* <tt>*ptr</tt>.
|
||||
* @param[out] len_ret Returned string length not including the null byte.
|
||||
* @return @c true on success, @c false if no null byte was found.
|
||||
*/
|
||||
static inline bool mread_string(const char **ptr, const char *end,
|
||||
const char **str_ret, size_t *len_ret)
|
||||
{
|
||||
const char *nul = memchr(*ptr, 0, end - *ptr);
|
||||
if (!nul)
|
||||
return false;
|
||||
*str_ret = *ptr;
|
||||
*len_ret = nul - *ptr;
|
||||
*ptr = nul + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif /* DRGN_MREAD_H */
|
Loading…
Reference in New Issue
Block a user