mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-22 17:23:06 +00:00
2f97cc0f5f
There are a lot of details about how the page table iterator functions
should be used/implemented that commit 174b797ae3
("libdrgn: platform:
add documentation (especially for drgn_architecture_info)") didn't
cover. Add an example and expand/clarify the documentation for the
callbacks.
Signed-off-by: Omar Sandoval <osandov@osandov.com>
500 lines
17 KiB
C
500 lines
17 KiB
C
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
/**
|
|
* @file
|
|
*
|
|
* Platform internals
|
|
*
|
|
* See @ref PlatformInternals.
|
|
*/
|
|
|
|
#ifndef DRGN_PLATFORM_H
|
|
#define DRGN_PLATFORM_H
|
|
|
|
#include <gelf.h>
|
|
#include <inttypes.h>
|
|
|
|
#include "cfi.h"
|
|
#include "drgn.h"
|
|
#include "util.h"
|
|
|
|
struct drgn_orc_entry;
|
|
struct drgn_register_state;
|
|
|
|
/**
|
|
* @ingroup Internals
|
|
*
|
|
* @defgroup PlatformInternals Platforms
|
|
*
|
|
* Platform internals.
|
|
*
|
|
* drgn's external representation of a platform is @ref drgn_platform.
|
|
* Internally, architecture-specific handling is mainly in @ref
|
|
* drgn_architecture_info. See @ref drgn_architecture_info for instructions on
|
|
* adding support for a new architecture.
|
|
*
|
|
* @{
|
|
*/
|
|
|
|
struct drgn_register {
|
|
/** Human-readable names of this register. */
|
|
const char * const *names;
|
|
/** Number of names in @ref names. */
|
|
size_t num_names;
|
|
/** Internal register number. */
|
|
drgn_register_number regno;
|
|
};
|
|
|
|
/** Offset and size of a register in @ref drgn_register_state::buf. */
|
|
struct drgn_register_layout {
|
|
uint32_t offset;
|
|
uint32_t size;
|
|
};
|
|
|
|
// This is an ugly layering violation needed for DW_CFA_AARCH64_negate_ra_state.
|
|
// We enforce that it stays up to date with a static_assert() in arch_aarch64.c.
|
|
#define DRGN_AARCH64_RA_SIGN_STATE_REGNO 0
|
|
|
|
/** ELF section to apply relocations to. */
|
|
struct drgn_relocating_section {
|
|
char *buf;
|
|
size_t buf_size;
|
|
uint64_t addr;
|
|
bool bswap;
|
|
};
|
|
|
|
extern struct drgn_error drgn_invalid_relocation_offset;
|
|
|
|
#ifdef DOXYGEN
|
|
/**
|
|
* Apply an ELF relocation as:
|
|
*
|
|
* - `*dst = addend + *r_addend` if `r_addend` is not `NULL` (for `ElfN_Rela`)
|
|
* - `*dst += addend` if `r_addend` is `NULL` (for `ElfN_Rel`)
|
|
*
|
|
* Where `dst = (uintN_t *)(relocating->buf + r_offset)`.
|
|
*
|
|
* This checks bounds and handles unaligned destinations and byte swapping. It
|
|
* does not check for overflow.
|
|
*
|
|
* This is defined for N of 8, 16, 32, and 64.
|
|
*/
|
|
struct drgn_error *
|
|
drgn_reloc_addN(const struct drgn_relocating_section *relocating,
|
|
uint64_t r_offset, const int64_t *r_addend, uintN_t addend);
|
|
#endif
|
|
|
|
struct drgn_error *
|
|
drgn_reloc_add64(const struct drgn_relocating_section *relocating,
|
|
uint64_t r_offset, const int64_t *r_addend, uint64_t addend);
|
|
struct drgn_error *
|
|
drgn_reloc_add32(const struct drgn_relocating_section *relocating,
|
|
uint64_t r_offset, const int64_t *r_addend, uint32_t addend);
|
|
struct drgn_error *
|
|
drgn_reloc_add16(const struct drgn_relocating_section *relocating,
|
|
uint64_t r_offset, const int64_t *r_addend, uint16_t addend);
|
|
struct drgn_error *
|
|
drgn_reloc_add8(const struct drgn_relocating_section *relocating,
|
|
uint64_t r_offset, const int64_t *r_addend, uint8_t addend);
|
|
|
|
/** Create an error for an unknown ELF relocation type. */
|
|
#define DRGN_UNKNOWN_RELOCATION_TYPE(r_type) \
|
|
drgn_error_format(DRGN_ERROR_OTHER, \
|
|
"unknown relocation type %" PRIu32 " in %s; " \
|
|
"please report this to %s", \
|
|
(r_type), __func__, PACKAGE_BUGREPORT)
|
|
|
|
/**
|
|
* Apply an ELF relocation. If @p r_addend is `NULL`, then this is an `ElfN_Rel`
|
|
* relocation. Otherwise, this is an `ElfN_Rela` relocation.
|
|
*/
|
|
typedef struct drgn_error *
|
|
apply_elf_reloc_fn(const struct drgn_relocating_section *relocating,
|
|
uint64_t r_offset, uint32_t r_type, const int64_t *r_addend,
|
|
uint64_t sym_value);
|
|
|
|
/** Page table iterator. */
|
|
struct pgtable_iterator {
|
|
/** Address of the top-level page table to iterate. */
|
|
uint64_t pgtable;
|
|
/** Current virtual address to translate. */
|
|
uint64_t virt_addr;
|
|
};
|
|
|
|
/**
|
|
* Translate the current virtual address from a page table iterator.
|
|
*
|
|
* Abstractly, a virtual address lies in a range of addresses in the address
|
|
* space. A range may be a mapped page, a page table gap, or a range of invalid
|
|
* addresses (e.g., non-canonical addresses on x86-64). This finds the range
|
|
* containing the current virtual address (`it->virt_addr`), returns the first
|
|
* virtual address of that range and the physical address it maps to (if any),
|
|
* and updates `it->virt_addr` to the end of the range.
|
|
*
|
|
* This does not merge contiguous ranges. For example, if two adjacent mapped
|
|
* pages have adjacent physical addresses, this returns each page separately.
|
|
* This makes it possible to distinguish between contiguous pages and "huge
|
|
* pages" on architectures that support different page sizes. Similarly, if two
|
|
* adjacent entries at level 2 of the page table are empty, this returns each
|
|
* gap separately.
|
|
*
|
|
* @param[in] it Iterator.
|
|
* @param[out] virt_addr_ret Returned first virtual address in the range
|
|
* containing the current virtual address.
|
|
* @param[out] phys_addr_ret Returned physical address that @p virt_addr_ret
|
|
* maps to, or @c UINT64_MAX if it is not mapped.
|
|
*/
|
|
typedef struct drgn_error *
|
|
(pgtable_iterator_next_fn)(struct drgn_program *prog,
|
|
struct pgtable_iterator *it, uint64_t *virt_addr_ret,
|
|
uint64_t *phys_addr_ret);
|
|
|
|
/**
|
|
* Architecture-specific information and callbacks.
|
|
*
|
|
* To add the bare minimum support for recognizing a new architecture:
|
|
*
|
|
* - Add a `DRGN_ARCH_FOO` enumerator to @ref drgn_architecture.
|
|
* - Add the constant to `class Architecture` in `_drgn.pyi`.
|
|
* - Create a new `libdrgn/arch_foo.c` file and add it to `libdrgn/Makefile.am`.
|
|
* - Define `struct drgn_architecture_info arch_info_foo` in
|
|
* `libdrgn/arch_foo.c` with the following members:
|
|
* - @ref name
|
|
* - @ref arch
|
|
* - @ref default_flags
|
|
* - @ref register_by_name
|
|
* - Add an `extern` declaration of `arch_info_foo` to `libdrgn/platform.h`.
|
|
* - Handle the architecture in @ref drgn_platform_from_kdump(), @ref
|
|
* drgn_host_platform, @ref drgn_platform_create(), and @ref
|
|
* drgn_platform_from_elf().
|
|
*
|
|
* To support Linux kernel loadable modules:
|
|
*
|
|
* - Define @ref apply_elf_reloc.
|
|
*
|
|
* To support stack unwinding:
|
|
*
|
|
* - Create a new `libdrgn/arch_foo_defs.py` file. See
|
|
* `libdrgn/build-aux/gen_arch_inc_strswitch.py`.
|
|
* - Add `#include "arch_foo_defs.inc"` to `libdrgn/arch_foo.c`.
|
|
* - Add `DRGN_ARCHITECTURE_REGISTERS` to `arch_info_foo` (and remove @ref
|
|
* register_by_name).
|
|
* - Define the following @ref drgn_architecture_info members:
|
|
* - @ref default_dwarf_cfi_row (use @ref DRGN_CFI_ROW)
|
|
* - @ref fallback_unwind
|
|
* - @ref pt_regs_get_initial_registers
|
|
* - @ref prstatus_get_initial_registers
|
|
* - @ref linux_kernel_get_initial_registers
|
|
* - @ref demangle_cfi_registers (only if needed)
|
|
*
|
|
* To support virtual address translation:
|
|
*
|
|
* - Define the @ref drgn_architecture_info page table iterator members:
|
|
* - @ref linux_kernel_pgtable_iterator_create
|
|
* - @ref linux_kernel_pgtable_iterator_destroy
|
|
* - @ref linux_kernel_pgtable_iterator_init
|
|
* - @ref linux_kernel_pgtable_iterator_next
|
|
*
|
|
* This is an example of how the page table iterator members may be used
|
|
* (ignoring error handling):
|
|
*
|
|
* ```
|
|
* // Create the iterator.
|
|
* struct pgtable_iterator *it;
|
|
* arch->linux_kernel_pgtable_iterator_create(prog, &it);
|
|
*
|
|
* // Initialize the iterator to translate virtual address 0x80000000 using
|
|
* // the page table "pgtable".
|
|
* it->pgtable = pgtable;
|
|
* it->virt_addr = 0x80000000;
|
|
* arch->linux_kernel_pgtable_iterator_init(prog, it);
|
|
* // Iterate up to virtual address 0x90000000.
|
|
* while (it->virt_addr < 0x90000000) {
|
|
* uint64_t virt_addr, phys_addr;
|
|
* arch->linux_kernel_pgtable_iterator_next(prog, it, &virt_addr,
|
|
* &phys_addr);
|
|
* if (phys_addr == UINT64_MAX) {
|
|
* printf("Virtual address range 0x%" PRIx64 "-0x%" PRIx64
|
|
* " is not mapped\n",
|
|
* virt_addr, it->virt_addr);
|
|
* } else {
|
|
* printf("Virtual address range 0x%" PRIx64 "-0x%" PRIx64
|
|
* " maps to physical address 0x%" PRIx64 "\n",
|
|
* virt_addr, phys_addr);
|
|
* }
|
|
* }
|
|
*
|
|
* // Reuse the iterator to translate a different address using a different page
|
|
* // table.
|
|
* it->pgtable = another_pgtable;
|
|
* it->virt_addr = 0x11110000;
|
|
* uint64_t virt_addr, phys_addr;
|
|
* arch->linux_kernel_pgtable_iterator_next(prog, it, &virt_addr, &phys_addr);
|
|
* if (phys_addr != UINT64_MAX) {
|
|
* printf("Virtual address 0x11110000 maps to physical address 0x%" PRIx64 "\n",
|
|
* phys_addr + (0x11110000 - virt_addr));
|
|
* }
|
|
*
|
|
* // Free the iterator now that we're done with it.
|
|
* arch->linux_kernel_pgtable_iterator_destroy(prog, &it);
|
|
* ```
|
|
*/
|
|
struct drgn_architecture_info {
|
|
/** Human-readable name of this architecture. */
|
|
const char *name;
|
|
/** Architecture identifier. */
|
|
enum drgn_architecture arch;
|
|
/**
|
|
* Flags to set for the platform if we're not getting them from
|
|
* elsewhere (like from an ELF file).
|
|
*/
|
|
enum drgn_platform_flags default_flags;
|
|
/**
|
|
* Registers visible to the public API.
|
|
*
|
|
* This is set by `DRGN_ARCHITECTURE_REGISTERS`.
|
|
*/
|
|
const struct drgn_register *registers;
|
|
/**
|
|
* Number of registers in @ref registers.
|
|
*
|
|
* This is set by `DRGN_ARCHITECTURE_REGISTERS`.
|
|
*/
|
|
size_t num_registers;
|
|
/**
|
|
* Internal register number of stack pointer.
|
|
*
|
|
* This is set by `DRGN_ARCHITECTURE_REGISTERS`.
|
|
*/
|
|
drgn_register_number stack_pointer_regno;
|
|
/**
|
|
* Return the API-visible register with the given name, or @c NULL if it
|
|
* is not recognized.
|
|
*
|
|
* This is set by `DRGN_ARCHITECTURE_REGISTERS`. It cannot be `NULL`.
|
|
* Set it to @ref drgn_register_by_name_unknown if not using
|
|
* `DRGN_ARCHITECTURE_REGISTERS`.
|
|
*/
|
|
const struct drgn_register *(*register_by_name)(const char *name);
|
|
/**
|
|
* Internal register layouts indexed by internal register number.
|
|
*
|
|
* This is set by `DRGN_ARCHITECTURE_REGISTERS`.
|
|
*/
|
|
const struct drgn_register_layout *register_layout;
|
|
/**
|
|
* Return the internal register number for the given DWARF register
|
|
* number, or @ref DRGN_REGISTER_NUMBER_UNKNOWN if it is not recognized.
|
|
*
|
|
* This is set by `DRGN_ARCHITECTURE_REGISTERS`.
|
|
*/
|
|
drgn_register_number (*dwarf_regno_to_internal)(uint64_t);
|
|
/** CFI row containing default rules for DWARF CFI. */
|
|
const struct drgn_cfi_row *default_dwarf_cfi_row;
|
|
/**
|
|
* Translate an ORC entry to a @ref drgn_cfi_row.
|
|
*
|
|
* This should be `NULL` if the architecture doesn't use ORC.
|
|
*/
|
|
struct drgn_error *(*orc_to_cfi)(const struct drgn_orc_entry *,
|
|
struct drgn_cfi_row **, bool *,
|
|
drgn_register_number *);
|
|
/**
|
|
* Replace mangled registers unwound by CFI with their actual values.
|
|
*
|
|
* This should be `NULL` if not needed.
|
|
*/
|
|
void (*demangle_cfi_registers)(struct drgn_program *,
|
|
struct drgn_register_state *);
|
|
/**
|
|
* Try to unwind a stack frame if CFI wasn't found. Returns &@ref
|
|
* drgn_stop if we couldn't.
|
|
*
|
|
* This typically uses something like frame pointers. If this has to
|
|
* read memory, translate @ref DRGN_ERROR_FAULT errors to &@ref
|
|
* drgn_stop.
|
|
*/
|
|
struct drgn_error *(*fallback_unwind)(struct drgn_program *,
|
|
struct drgn_register_state *,
|
|
struct drgn_register_state **);
|
|
/**
|
|
* Create a @ref drgn_register_state from a Linux `struct pt_regs`.
|
|
*
|
|
* This should check that the object is sufficiently large with @ref
|
|
* drgn_object_size(), call @ref drgn_register_state_create() with
|
|
* `interrupted = true`, and initialize it from the contents of @ref
|
|
* drgn_object_buffer().
|
|
*
|
|
* @param[in] obj `struct pt_regs` as a value buffer object.
|
|
* @param[out] ret Returned registers.
|
|
*/
|
|
struct drgn_error *(*pt_regs_get_initial_registers)(const struct drgn_object *obj,
|
|
struct drgn_register_state **ret);
|
|
/**
|
|
* Create a @ref drgn_register_state from the contents of an ELF
|
|
* `NT_PRSTATUS` note.
|
|
*
|
|
* This should check that @p size is sufficiently large, call @ref
|
|
* drgn_register_state_create() with `interrupted = true`, and
|
|
* initialize it from @p prstatus.
|
|
*
|
|
* Refer to `struct elf_prstatus_common` in the Linux kernel source for
|
|
* the format, and in particular, the `elf_gregset_t pr_reg` member.
|
|
* `elf_gregset_t` has an architecture-specific layout; on many
|
|
* architectures, it is identical to or a prefix of `struct pt_regs`.
|
|
* `pr_reg` is typically at offset 112 on 64-bit platforms and 72 on
|
|
* 32-bit platforms.
|
|
*
|
|
* @param[in] prstatus Buffer of `NT_PRSTATUS` contents.
|
|
* @param[in] size Size of @p prstatus in bytes.
|
|
* @param[out] ret Returned registers.
|
|
*/
|
|
struct drgn_error *(*prstatus_get_initial_registers)(struct drgn_program *prog,
|
|
const void *prstatus,
|
|
size_t size,
|
|
struct drgn_register_state **ret);
|
|
/**
|
|
* Create a @ref drgn_register_state from the `struct task_struct` of a
|
|
* scheduled-out Linux kernel thread.
|
|
*
|
|
* This should should call @ref drgn_register_state_create() with
|
|
* `interrupted = false` and initialize it from the saved thread
|
|
* context.
|
|
*
|
|
* The context can usually be found in `struct task_struct::thread`
|
|
* and/or the thread stack. Refer to this architecture's implementation
|
|
* of `switch_to()` in the Linux kernel.
|
|
*
|
|
* @param[in] task_obj `struct task_struct` object.
|
|
* @param[out] ret Returned registers.
|
|
*/
|
|
struct drgn_error *(*linux_kernel_get_initial_registers)(const struct drgn_object *task_obj,
|
|
struct drgn_register_state **ret);
|
|
/**
|
|
* Apply an ELF relocation.
|
|
*
|
|
* This should use the pre-defined @ref drgn_reloc_addN() functions
|
|
* whenever possible. Note that this is only used to relocate debugging
|
|
* information sections, so typically only simple absolute and
|
|
* PC-relative relocations need to be implemented.
|
|
*/
|
|
apply_elf_reloc_fn *apply_elf_reloc;
|
|
/**
|
|
* Return the address and size of the direct mapping virtual address
|
|
* range.
|
|
*
|
|
* This is a hack which is only called when debugging a live Linux
|
|
* kernel older than v4.11.
|
|
*/
|
|
struct drgn_error *(*linux_kernel_live_direct_mapping_fallback)(struct drgn_program *prog,
|
|
uint64_t *address_ret,
|
|
uint64_t *size_ret);
|
|
/** Allocate a Linux kernel page table iterator. */
|
|
struct drgn_error *(*linux_kernel_pgtable_iterator_create)(struct drgn_program *,
|
|
struct pgtable_iterator **);
|
|
/** Free a Linux kernel page table iterator. */
|
|
void (*linux_kernel_pgtable_iterator_destroy)(struct pgtable_iterator *);
|
|
/**
|
|
* (Re)initialize a Linux kernel page table iterator.
|
|
*
|
|
* This is called each time that the iterator will be used to translate
|
|
* a contiguous range of virtual addresses from a single page table. It
|
|
* is called with @ref pgtable_iterator::pgtable set to the address of
|
|
* the page table to use and @ref pgtable_iterator::virt_addr set to the
|
|
* starting virtual address to translate.
|
|
*/
|
|
void (*linux_kernel_pgtable_iterator_init)(struct drgn_program *,
|
|
struct pgtable_iterator *);
|
|
/**
|
|
* Iterate a (user or kernel) page table in the Linux kernel.
|
|
*
|
|
* This is called after @ref linux_kernel_pgtable_iterator_init() to
|
|
* translate the starting address and may be called again without
|
|
* reinitializing the iterator to translate subsequent adjacent
|
|
* addresses in the same page table.
|
|
*
|
|
* If the caller needs to translate from a different page table or
|
|
* virtual address, it will call @ref
|
|
* linux_kernel_pgtable_iterator_init() before calling this function
|
|
* again.
|
|
*
|
|
* @see pgtable_iterator_next_fn
|
|
*/
|
|
pgtable_iterator_next_fn *linux_kernel_pgtable_iterator_next;
|
|
};
|
|
|
|
/**
|
|
* Implementation of @ref drgn_architecture_info::register_by_name that always
|
|
* returns `NULL`.
|
|
*/
|
|
const struct drgn_register *drgn_register_by_name_unknown(const char *name);
|
|
|
|
extern const struct drgn_architecture_info arch_info_unknown;
|
|
extern const struct drgn_architecture_info arch_info_x86_64;
|
|
extern const struct drgn_architecture_info arch_info_i386;
|
|
extern const struct drgn_architecture_info arch_info_aarch64;
|
|
extern const struct drgn_architecture_info arch_info_arm;
|
|
extern const struct drgn_architecture_info arch_info_ppc64;
|
|
extern const struct drgn_architecture_info arch_info_riscv64;
|
|
extern const struct drgn_architecture_info arch_info_riscv32;
|
|
extern const struct drgn_architecture_info arch_info_s390x;
|
|
extern const struct drgn_architecture_info arch_info_s390;
|
|
|
|
struct drgn_platform {
|
|
const struct drgn_architecture_info *arch;
|
|
enum drgn_platform_flags flags;
|
|
};
|
|
|
|
static inline bool drgn_platforms_equal(const struct drgn_platform *a,
|
|
const struct drgn_platform *b)
|
|
{
|
|
return a->arch == b->arch && a->flags == b->flags;
|
|
}
|
|
|
|
static inline bool
|
|
drgn_platform_is_little_endian(const struct drgn_platform *platform)
|
|
{
|
|
return platform->flags & DRGN_PLATFORM_IS_LITTLE_ENDIAN;
|
|
}
|
|
|
|
static inline bool drgn_platform_bswap(const struct drgn_platform *platform)
|
|
{
|
|
return drgn_platform_is_little_endian(platform) != HOST_LITTLE_ENDIAN;
|
|
}
|
|
|
|
static inline bool drgn_platform_is_64_bit(const struct drgn_platform *platform)
|
|
{
|
|
return platform->flags & DRGN_PLATFORM_IS_64_BIT;
|
|
}
|
|
|
|
static inline uint8_t
|
|
drgn_platform_address_size(const struct drgn_platform *platform)
|
|
{
|
|
return drgn_platform_is_64_bit(platform) ? 8 : 4;
|
|
}
|
|
|
|
static inline uint64_t
|
|
drgn_platform_address_mask(const struct drgn_platform *platform)
|
|
{
|
|
return drgn_platform_is_64_bit(platform) ? UINT64_MAX : UINT32_MAX;
|
|
}
|
|
|
|
/**
|
|
* Initialize a @ref drgn_platform from an architecture, word size, and
|
|
* endianness.
|
|
*
|
|
* The default flags for the architecture are used other than the word size and
|
|
* endianness.
|
|
*/
|
|
void drgn_platform_from_arch(const struct drgn_architecture_info *arch,
|
|
bool is_64_bit, bool is_little_endian,
|
|
struct drgn_platform *ret);
|
|
|
|
/** Initialize a @ref drgn_platform from an ELF header. */
|
|
void drgn_platform_from_elf(GElf_Ehdr *ehdr, struct drgn_platform *ret);
|
|
|
|
/** @} */
|
|
|
|
#endif /* DRGN_PLATFORM_H */
|