// 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_internal.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

struct drgn_error *
drgn_orc_to_cfi_x86_64(const struct drgn_orc_entry *orc,
		       struct drgn_cfi_row **row_ret, bool *interrupted_ret,
		       drgn_register_number *ret_addr_regno_ret);

/** 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
 *   `libdrgnimpl_la_SOURCES` in `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 scalar_alignment
 *     - @ref register_by_name = @ref drgn_register_by_name_unknown
 * - 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().
 * - Update `docs/support_matrix.rst`.
 *
 * To support Linux kernel loadable modules:
 *
 * - Define @ref apply_elf_reloc.
 * - Update `docs/support_matrix.rst`.
 *
 * To support stack unwinding:
 *
 * - Create a new `libdrgn/arch_foo_defs.py` file. See
 *   `libdrgn/build-aux/gen_arch_inc_strswitch.py`.
 * - Add `arch_foo_defs.py` to `ARCH_DEFS_PYS` and remove `libdrgn/arch_foo.c`
 *   from `libdrgnimpl_la_SOURCES` in `libdrgn/Makefile.am`.
 * - 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)
 * - Implement `drgn_test_get_pt_regs()` in
 *   `tests/linux_kernel/kmod/drgn_test.c` (usually by copying
 *   `crash_setup_regs()` from the Linux kernel).
 * - Add the architecture name to `skip_unless_have_stack_tracing()` in
 *   `tests/linux_kernel/__init__.py`.
 * - Update `docs/support_matrix.rst`.
 *
 * 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
 * - Add the architecture name to `HAVE_FULL_MM_SUPPORT` in
 *   `tests/linux_kernel/__init__.py`.
 * - Update `docs/support_matrix.rst`.
 *
 * 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;
	/**
	 * Default alignment of scalar types by size.
	 *
	 * `scalar_alignment[i]` is the default alignment of a scalar type with
	 * 2<sup>i</sup> &le; `sizeof(type)` < 2<sup>i+1</sup>.
	 *
	 * This may not be enough to get the correct alignment of some
	 * implementation-defined extended types, but for now it's good enough.
	 *
	 * You can generate this for a new architecture using
	 * `scripts/scalar_alignment.c`.
	 */
	unsigned char scalar_alignment[5];
	/**
	 * 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;
	/**
	 * 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` 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.
	 *
	 * Refer to this architecture's implementation of `switch_to()` in the
	 * Linux kernel, which usually saves the context to one of these places:
	 *
	 * - `struct thread_struct` (`struct task_struct::thread`).
	 * - The thread stack (`struct task_struct::stack`).
	 * - `struct thread_info` (either `struct task_struct::thread_info` or
	 *   on the stack, see `task_thread_info()` 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;
	/**
	 * Return the canonical form of a virtual address, i.e. apply any
	 * transformations that the CPU applies to the address before page
	 * table walking.
	 */
	uint64_t (*untagged_addr)(uint64_t addr);
};

/**
 * 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 */
