// Copyright (c) Meta Platforms, Inc. and affiliates.
// SPDX-License-Identifier: LGPL-2.1-or-later

/**
 * @file
 *
 * Call frame information
 *
 * See @ref CallFrameInformation.
 */

#ifndef DRGN_CFI_H
#define DRGN_CFI_H

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

/**
 * @ingroup Internals
 *
 * @defgroup CallFrameInformation Call frame information
 *
 * Call frame information for stack unwinding.
 *
 * This defines a generic representation for Call Frame Information (CFI), which
 * describes how to determine the Canonical Frame Address (CFA) and previous
 * register values while unwinding a stack trace.
 *
 * @{
 */

/**
 * Numeric identifier for a register.
 *
 * These are only unique within an architecture, and they are not necessarily
 * the same as the register numbers used by DWARF.
 */
typedef uint16_t drgn_register_number;

/** Maximum valid register number. */
#define DRGN_MAX_REGISTER_NUMBER ((drgn_register_number)-3)
/** Placeholder number for unknown register. */
#define DRGN_REGISTER_NUMBER_UNKNOWN ((drgn_register_number)-1)

/** Kinds of CFI rules. */
enum drgn_cfi_rule_kind {
	/** Register value in the caller is not known. */
	DRGN_CFI_RULE_UNDEFINED,
	/**
	 * Register value in the caller is stored at the CFA in the current
	 * frame plus an offset: `*(cfa + offset)`.
	 */
	DRGN_CFI_RULE_AT_CFA_PLUS_OFFSET,
	/**
	 * Register value in the caller is the CFA in the current frame plus an
	 * offset: `cfa + offset`.
	 */
	DRGN_CFI_RULE_CFA_PLUS_OFFSET,
	/**
	 * Register value in the caller is stored at the value of a register in
	 * the current frame plus an offset: `*(reg + offset)`.
	 */
	DRGN_CFI_RULE_AT_REGISTER_PLUS_OFFSET,
	/**
	 * Register value in the caller is an offset plus the value stored at
	 * the value of a register in the current frame: `(*reg) + offset`.
	 */
	DRGN_CFI_RULE_AT_REGISTER_ADD_OFFSET,
	/**
	 * Register value in the caller is the value of a register in the
	 * current frame plus an offset: `reg + offset`.
	 *
	 * Note that this can also be used to represent DWARF's "same value"
	 * rule by using the same register with an offset of 0.
	 */
	DRGN_CFI_RULE_REGISTER_PLUS_OFFSET,
	/**
	 * Register value in the caller is stored at the address given by a
	 * DWARF expression.
	 */
	DRGN_CFI_RULE_AT_DWARF_EXPRESSION,
	/** Register value in the caller is given by a DWARF expression. */
	DRGN_CFI_RULE_DWARF_EXPRESSION,
	/** Register value in the caller has a constant value. */
	DRGN_CFI_RULE_CONSTANT,
} __attribute__((__packed__));

/** Rule for determining a single register value or CFA. */
struct drgn_cfi_rule {
	/** Rule kind. */
	enum drgn_cfi_rule_kind kind;
	/**
	 * Whether to push the CFA before evaluating the DWARF
	 * expression for @ref DRGN_CFI_RULE_AT_DWARF_EXPRESSION or @ref
	 * DRGN_CFI_RULE_DWARF_EXPRESSION.
	 */
	bool push_cfa;
	/** Register number for @ref DRGN_CFI_RULE_REGISTER_PLUS_OFFSET. */
	drgn_register_number regno;
	union {
		/**
		 * Offset for @ref DRGN_CFI_RULE_AT_CFA_PLUS_OFFSET, @ref
		 * DRGN_CFI_RULE_CFA_PLUS_OFFSET, @ref
		 * DRGN_CFI_RULE_AT_REGISTER_PLUS_OFFSET, @ref
		 * DRGN_CFI_RULE_AT_REGISTER_ADD_OFFSET, and @ref
		 * DRGN_CFI_RULE_REGISTER_PLUS_OFFSET.
		 */
		int64_t offset;
		/** Constant for @ref DRGN_CFI_RULE_CONSTANT. */
		uint64_t constant;
		/**
		 * DWARF expression for @ref DRGN_CFI_RULE_AT_DWARF_EXPRESSION
		 * and @ref DRGN_CFI_RULE_DWARF_EXPRESSION.
		 */
		struct {
			/** Pointer to expression data. */
			const char *expr;
			/** Size of @ref drgn_cfi_rule::expr. */
			size_t expr_size;
		};
	};
};

/**
 * "Row" of call frame information, i.e., how to get the CFA and the previous
 * value of each register at a single location in the program.
 *
 * A row may be allocated statically or on the heap. Static rows are created
 * with @ref DRGN_CFI_ROW(). The first time a static row would be modified (with
 * @ref drgn_cfi_row_copy(), @ref drgn_cfi_row_set_cfa(), or @ref
 * drgn_cfi_row_set_register()), it is first copied to the heap. Subsequent
 * modifications reuse the heap allocation, growing it if necessary. The
 * allocation must be freed with @ref drgn_cfi_row_destroy().
 */
struct drgn_cfi_row {
	/**
	 * Number of rules allocated, including the CFA rule.
	 *
	 * If the row is statically allocated, then this is zero, even if
	 * `num_regs` is non-zero. Otherwise, it is at least `num_regs + 1`.
	 */
	uint16_t allocated_rules;
	/** Number of initialized elements in `reg_rules`. */
	uint16_t num_regs;
	/** Canonical Frame Address rule. */
	struct drgn_cfi_rule cfa_rule;
	/** Register rules. */
	struct drgn_cfi_rule reg_rules[];
};

/**
 * Initializer for a static @ref drgn_cfi_row given initializers for @ref
 * drgn_cfi_row::reg_rules.
 */
#define DRGN_CFI_ROW(...) {						\
	.num_regs = (sizeof((struct drgn_cfi_rule []){ __VA_ARGS__ })	\
		     / sizeof(struct drgn_cfi_rule)),			\
	.reg_rules = { __VA_ARGS__ },					\
}

/**
 * Initializer for a rule in @ref drgn_cfi_row::reg_rules specifying that the
 * register with the given number has the same value in the caller.
 */
#define DRGN_CFI_SAME_VALUE_INIT(number)			\
	[(number)] = {						\
		.kind = DRGN_CFI_RULE_REGISTER_PLUS_OFFSET,	\
		.regno = (number),				\
	}

extern const struct drgn_cfi_row drgn_empty_cfi_row_impl;
/**
 * Static @ref drgn_cfi_row with all rules set to @ref DRGN_CFI_RULE_UNDEFINED.
 */
#define drgn_empty_cfi_row ((struct drgn_cfi_row *)&drgn_empty_cfi_row_impl)

/** Free a @ref drgn_cfi_row. */
static inline void drgn_cfi_row_destroy(struct drgn_cfi_row *row)
{
	if (row->allocated_rules > 0)
		free(row);
}

/** Copy the rules from one @ref drgn_cfi_row to another. */
bool drgn_cfi_row_copy(struct drgn_cfi_row **dst,
		       const struct drgn_cfi_row *src);

/**
 * Get the rule for the Canonical Frame Address in a @ref drgn_cfi_row.
 *
 * @param[out] ret Returned rule.
 */
static inline void drgn_cfi_row_get_cfa(const struct drgn_cfi_row *row,
					struct drgn_cfi_rule *ret)
{
	*ret = row->cfa_rule;
}

/**
 * Set the rule for the Canonical Frame Address in a @ref drgn_cfi_row.
 *
 * @param[in] rule Rule to set to.
 * @return @c true on success, @c false on failure to allocate memory.
 */
bool drgn_cfi_row_set_cfa(struct drgn_cfi_row **row,
			  const struct drgn_cfi_rule *rule);

/**
 * Get the rule for a register in a @ref drgn_cfi_row.
 *
 * @param[in] regno Register number.
 */
void drgn_cfi_row_get_register(const struct drgn_cfi_row *row,
			       drgn_register_number regno,
			       struct drgn_cfi_rule *ret);

/**
 * Set the rule for a register in a @ref drgn_cfi_row.
 *
 * @param[in] regno Register number.
 * @param[in] rule Rule to set to.
 * @return @c true on success, @c false on failure to allocate memory.
 */
bool drgn_cfi_row_set_register(struct drgn_cfi_row **row,
			       drgn_register_number regno,
			       const struct drgn_cfi_rule *rule);

/** @} */

#endif /* DRGN_CFI_H */
