/***************************************************************************
 * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk
 *
 * This program is PUBLIC DOMAIN.
 * This means that there is no copyright and anyone is able to take a copy
 * for free and use it as they wish, with or without modifications, and in
 * any context, commerically or otherwise. The only limitation is that I
 * don't guarantee that the software is fit for any purpose or accept any
 * liability for its use or misuse - this software is without warranty.
 ***************************************************************************
 * File Description: Internal interface between the ARM unwinding sub-modules.
 **************************************************************************/

#pragma once

#include "unwinder.h"

/** The maximum number of instructions to interpet in a function.
 * Unwinding will be unconditionally stopped and UNWIND_EXHAUSTED returned
 * if more than this number of instructions are interpreted in a single
 * function without unwinding a stack frame.  This prevents infinite loops
 * or corrupted program memory from preventing unwinding from progressing.
 */
#define UNW_MAX_INSTR_COUNT 500

/** The size of the hash used to track reads and writes to memory.
 * This should be a prime value for efficiency.
 */
#define MEM_HASH_SIZE       31

/***************************************************************************
 * Type Definitions
 **************************************************************************/

typedef enum {
  /** Invalid value. */
  REG_VAL_INVALID     = 0x00,
  REG_VAL_FROM_STACK  = 0x01,
  REG_VAL_FROM_MEMORY = 0x02,
  REG_VAL_FROM_CONST  = 0x04,
  REG_VAL_ARITHMETIC  = 0x80
} RegValOrigin;


/** Type for tracking information about a register.
 * This stores the register value, as well as other data that helps unwinding.
 */
typedef struct {

  /** The value held in the register. */
  uint32_t v;

  /** The origin of the register value.
   * This is used to track how the value in the register was loaded.
   */
  int o; /* (RegValOrigin) */
} RegData;


/** Structure used to track reads and writes to memory.
 * This structure is used as a hash to store a small number of writes
 * to memory.
 */
typedef struct {
  /** Memory contents. */
  uint32_t v[MEM_HASH_SIZE];

  /** Address at which v[n] represents. */
  uint32_t a[MEM_HASH_SIZE];

  /** Indicates whether the data in v[n] and a[n] is occupied.
   * Each bit represents one hash value.
   */
  uint8_t used[(MEM_HASH_SIZE + 7) / 8];

  /** Indicates whether the data in v[n] is valid.
   * This allows a[n] to be set, but for v[n] to be marked as invalid.
   * Specifically this is needed for when an untracked register value
   * is written to memory.
   */
  uint8_t tracked[(MEM_HASH_SIZE + 7) / 8];
} MemData;


/** Structure that is used to keep track of unwinding meta-data.
 * This data is passed between all the unwinding functions.
 */
typedef struct {
  /** The register values and meta-data. */
  RegData regData[16];

  /** Memory tracking data. */
  MemData memData;

  /** Pointer to the callback functions */
  const UnwindCallbacks *cb;

  /** Pointer to pass to the report function. */
  const void *reportData;
} UnwState;

/***************************************************************************
 *  Macros
 **************************************************************************/

#define M_IsOriginValid(v) !!((v) & 0x7F)
#define M_Origin2Str(v)    ((v) ? "VALID" : "INVALID")

#ifdef UNW_DEBUG
#define UnwPrintd1(a)               state->cb->printf(a)
#define UnwPrintd2(a,b)             state->cb->printf(a,b)
#define UnwPrintd3(a,b,c)           state->cb->printf(a,b,c)
#define UnwPrintd4(a,b,c,d)         state->cb->printf(a,b,c,d)
#define UnwPrintd5(a,b,c,d,e)       state->cb->printf(a,b,c,d,e)
#define UnwPrintd6(a,b,c,d,e,f)     state->cb->printf(a,b,c,d,e,f)
#define UnwPrintd7(a,b,c,d,e,f,g)   state->cb->printf(a,b,c,d,e,f,g)
#define UnwPrintd8(a,b,c,d,e,f,g,h) state->cb->printf(a,b,c,d,e,f,g,h)
#else
#define UnwPrintd1(a)
#define UnwPrintd2(a,b)
#define UnwPrintd3(a,b,c)
#define UnwPrintd4(a,b,c,d)
#define UnwPrintd5(a,b,c,d,e)
#define UnwPrintd6(a,b,c,d,e,f)
#define UnwPrintd7(a,b,c,d,e,f,g)
#define UnwPrintd8(a,b,c,d,e,f,g,h)
#endif

/***************************************************************************
 *  Function Prototypes
 **************************************************************************/

UnwResult UnwStartArm(UnwState * const state);
UnwResult UnwStartThumb(UnwState * const state);
void UnwInvalidateRegisterFile(RegData *regFile);
void UnwInitState(UnwState * const state, const UnwindCallbacks *cb, void *rptData, uint32_t pcValue, uint32_t spValue);
bool UnwReportRetAddr(UnwState * const state, uint32_t addr);
bool UnwMemWriteRegister(UnwState * const state, const uint32_t addr, const RegData * const reg);
bool UnwMemReadRegister(UnwState * const state, const uint32_t addr, RegData * const reg);
void UnwMemHashGC(UnwState * const state);
