/*
 * Copyright (c) 2013 Plausible Labs Cooperative, Inc.
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef PLCRASH_ASYNC_COMPACT_UNWIND_ENCODING_H
#define PLCRASH_ASYNC_COMPACT_UNWIND_ENCODING_H 1

#include "PLCrashAsync.h"
#include "PLCrashAsyncDynamicLoader.h"
#include "PLCrashAsyncThread.h"
#include "PLCrashAsyncMObject.h"

#include "PLCrashFeatureConfig.h"

#include <mach-o/compact_unwind_encoding.h>

#if PLCRASH_FEATURE_UNWIND_COMPACT

/**
 * @internal
 * @ingroup plcrash_async_cfe
 * @{
 */

/**
 * @internal
 * A CFE reader instance. Performs CFE data parsing from a backing memory object.
 */
typedef struct plcrash_async_cfe_reader {
    /** A memory object containing the CFE data at the starting address. */
    plcrash_async_mobject_t *mobj;

    /** The target CPU type. */
    cpu_type_t cpu_type;

    /** The unwind info header. Note that the header values may require byte-swapping for the local process' use. */
    struct unwind_info_section_header header;

    /** The byte order of the encoded data (including the header). */
    const plcrash_async_byteorder_t *byteorder;
} plcrash_async_cfe_reader_t;

/**
 * Supported CFE entry formats.
 */
typedef enum {
    /**
     * The frame pointer (fp) is valid. To walk the stack, the previous frame pointer may be popped from
     * the current frame pointer, followed by the return address.
     *
     * All non-volatile registers that need to be restored will be saved on the stack, ranging from fp±regsize through
     * fp±1020. The actual direction depends on the stack growth direction of the target platform.
     */
    PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAME_PTR = 1,

    /**
     * The frame pointer (eg, ebp/rbp) is invalid, but the stack size is constant and is small enough (<= 1024) that it
     * may be encoded in the CFE entry itself.
     *
     * The return address may be found at the provided ± offset from the stack pointer, followed all non-volatile
     * registers that need to be restored. The actual direction of the offset depends on the stack growth direction of
     * the target platform.
     */
    PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD = 2,

    /**
     * The frame pointer (eg, ebp/rbp) is invalid, but the stack size is constant and is too large (>= 1024) to be
     * encoded in the CFE entry itself. Instead, the fixed stack size value must be extracted from an actual instruction
     * (eg, subl) within the target function, and used as the constant stack size. The decoded stack offset may be
     * added to the start address of the function to determine the location of the actual stack size.
     *
     * The return address may be found at the derived ± offset from the stack pointer, followed all non-volatile
     * registers that need to be restored. The actual direction of the offset epends on the stack growth direction of
     * the target platform.
     */
    PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT = 3,

    /**
     * The unwinding information for the target address could not be encoded using the CFE format. Instead, DWARF
     * frame information must be used.
     *
     * An offset to the DWARF FDE in the __eh_frame section is be provided.
     */
    PLCRASH_ASYNC_CFE_ENTRY_TYPE_DWARF = 4,
    
    
    /**
     * No unwind information is available for the target address. This value is only returned in the case where an
     * unwind table entry exists for the given address, but the entry is empty.
     */
    PLCRASH_ASYNC_CFE_ENTRY_TYPE_NONE = 5
} plcrash_async_cfe_entry_type_t;

/** Maximum number of registers supported by the permutation register encoding. @sa plcrash_async_cfe_register_decode and plcrash_async_cfe_register_encode. */
#define PLCRASH_ASYNC_CFE_PERMUTATION_REGISTER_MAX 6

/** Maximum number of saved non-volatile registers that may be represented in an i386 or x86-64 CFE entry */
#define PLCRASH_ASYNC_CFE_SAVED_REGISTER_X86_MAX 6

/** Maximum number of saved non-volatile registers that may be represented in an ARM64 CFE entry */
#define PLCRASH_ASYNC_CFE_SAVED_REGISTER_ARM64_MAX 10

#define _PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX(a, b) (((a) > (b)) ? (a) : (b))

/** Maximum number of saved non-volatile registers that may be represented in a CFE entry */
#define PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX _PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX(PLCRASH_ASYNC_CFE_SAVED_REGISTER_X86_MAX, PLCRASH_ASYNC_CFE_SAVED_REGISTER_ARM64_MAX)

/**
 * @internal
 *
 * A decoded CFE entry. The entry represents the data necessary to unwind the stack frame at a given PC, including
 * restoration of saved registers.
 */
typedef struct plcrash_async_cfe_entry {
    /** The CFE entry type. */
    plcrash_async_cfe_entry_type_t type;

    /** The target CPU type. */
    cpu_type_t cpu_type;

    /**
     * Encoded stack offset. Interpretation of this value depends on the CFE type:
     * - PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAME_PTR: Saved non-volatile registers may be found at ± offset from the frame
     *   pointer (eg, ebp/rbp).
     * - PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD: The return address may be found at ± offset from the stack
     *   pointer (eg, esp/rsp), and is followed all non-volatile registers that need to be restored.
     * - PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT: The actual offset may be loaded from the target function's
     *   instruction prologue. The offset given here must be added to the start address of the function to determine
     *   the location of the actual stack size as encoded in the prologue.
     *
     *   The return address may be found at ± offset from the stack pointer (eg, esp/rsp), and is followed all
     *   non-volatile registers that need to be restored.
     *
     *   TODO: Need a mechanism to define the actual size of the offset. For x86-32/x86-64, it is defined as being
     *   encoded in a subl instruction.
     * - PLCRASH_ASYNC_CFE_ENTRY_TYPE_DWARF: The offset to the DWARF FDE in the __eh_frame section.
     * - PLCRASH_ASYNC_CFE_ENTRY_TYPE_NONE: Unused.
     */
    intptr_t stack_offset;

    /**
     * Stack adjustment offset. This is an offset to be applied to the final stack value read via
     * PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT.
     *
     * This value is unused for all other CFE types.
     */
    uint32_t stack_adjust;
    
    /**
     * The link register to be used for the return address (eg, such as in a ARM leaf frame), or PLCRASH_REG_INVALID if the return address
     * is found on the stack. This value is only supported for the following CFE types:
     * - PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD and
     * - PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT
     */
    plcrash_regnum_t return_address_register;

    /**
     * The number of non-volatile registers that need to be restored from the stack.
     */
    uint32_t register_count;

    /**
     * The ordered list of register_count non-volatile registers that must be restored from the stack. These values are
     * specific to the target platform, and are defined in the @a plcrash_async_thread API. @sa plcrash_x86_regnum_t
     * and @sa plcrash_x86_64_regnum_t. Note that the list may be sparse; some entries may be set to a value of
     * PLCRASH_REG_INVALID.
     */
    plcrash_regnum_t register_list[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX];
} plcrash_async_cfe_entry_t;

plcrash_error_t plcrash_async_cfe_reader_init (plcrash_async_cfe_reader_t *reader, plcrash_async_mobject_t *mobj, cpu_type_t cputype);

plcrash_error_t plcrash_async_cfe_reader_find_pc (plcrash_async_cfe_reader_t *reader, pl_vm_address_t pc, pl_vm_address_t *function_base, uint32_t *encoding);

void plcrash_async_cfe_reader_free (plcrash_async_cfe_reader_t *reader);


plcrash_error_t plcrash_async_cfe_entry_init (plcrash_async_cfe_entry_t *entry, cpu_type_t cpu_type, uint32_t encoding);

plcrash_async_cfe_entry_type_t plcrash_async_cfe_entry_type (plcrash_async_cfe_entry_t *entry);
intptr_t plcrash_async_cfe_entry_stack_offset (plcrash_async_cfe_entry_t *entry);
uint32_t plcrash_async_cfe_entry_stack_adjustment (plcrash_async_cfe_entry_t *entry);
plcrash_regnum_t plcrash_async_cfe_entry_return_address_register (plcrash_async_cfe_entry_t *entry);
uint32_t plcrash_async_cfe_entry_register_count (plcrash_async_cfe_entry_t *entry);
void plcrash_async_cfe_entry_register_list (plcrash_async_cfe_entry_t *entry, plcrash_regnum_t register_list[]);

plcrash_error_t plcrash_async_cfe_entry_apply (task_t task,
                                               pl_vm_address_t function_address,
                                               const plcrash_async_thread_state_t *thread_state,
                                               plcrash_async_cfe_entry_t *entry,
                                               plcrash_async_thread_state_t *new_thread_state);

void plcrash_async_cfe_entry_free (plcrash_async_cfe_entry_t *entry);

uint32_t plcrash_async_cfe_register_encode (const uint32_t registers[], uint32_t count);
plcrash_error_t plcrash_async_cfe_register_decode (uint32_t permutation, uint32_t count, uint32_t registers[]);

/**
 * @} plcrash_async_cfe
 */

#endif /* PLCRASH_FEATURE_UNWIND_COMPACT */

#endif /* PLCRASH_ASYNC_COMPACT_UNWIND_ENCODING_H */
