// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)

#ifndef DWARF_UNWIND_H
#define DWARF_UNWIND_H

typedef struct DW_UnwindResult
{
  B32 is_invalid;
  B32 missed_read;
  U64 missed_read_addr;
  U64 stack_pointer;
} DW_UnwindResult;

// EH: Exception Frames

typedef U8 DW_EhPtrEnc;
enum
{
  DW_EhPtrEnc_TypeMask = 0x0F,
  DW_EhPtrEnc_Ptr       = 0x00, // Pointer sized unsigned value
  DW_EhPtrEnc_ULEB128   = 0x01, // Unsigned LE base-128 value
  DW_EhPtrEnc_UData2    = 0x02, // Unsigned 16-bit value
  DW_EhPtrEnc_UData4    = 0x03, // Unsigned 32-bit value
  DW_EhPtrEnc_UData8    = 0x04, // Unsigned 64-bit value
  DW_EhPtrEnc_Signed    = 0x08, // Signed pointer
  DW_EhPtrEnc_SLEB128   = 0x09, // Signed LE base-128 value
  DW_EhPtrEnc_SData2    = 0x0A, // Signed 16-bit value
  DW_EhPtrEnc_SData4    = 0x0B, // Signed 32-bit value
  DW_EhPtrEnc_SData8    = 0x0C, // Signed 64-bit value
};

enum
{
  DW_EhPtrEnc_ModifyMask = 0x70,
  DW_EhPtrEnc_PcRel      = 0x10, // Value is relative to the current program counter.
  DW_EhPtrEnc_TextRel    = 0x20, // Value is relative to the .text section.
  DW_EhPtrEnc_DataRel    = 0x30, // Value is relative to the .got or .eh_frame_hdr section.
  DW_EhPtrEnc_FuncRel    = 0x40, // Value is relative to the function.
  DW_EhPtrEnc_Aligned    = 0x50, // Value is aligned to an address unit sized boundary.
};

enum
{
  DW_EhPtrEnc_Indirect = 0x80, // This flag indicates that value is stored in virtual memory.
  DW_EhPtrEnc_Omit     = 0xFF,
};

typedef struct DW_EhPtrCtx
{
  U64 raw_base_vaddr; // address where pointer is being read
  U64 text_vaddr;     // base address of section with instructions (used for encoding pointer on SH and IA64)
  U64 data_vaddr;     // base address of data section (used for encoding pointer on x86-64)
  U64 func_vaddr;     // base address of function where IP is located
} DW_EhPtrCtx;

// CIE: Common Information Entry
typedef struct DW_CIEUnpacked
{
  U8          version;
  DW_EhPtrEnc lsda_encoding;
  DW_EhPtrEnc addr_encoding;
  
  B32     has_augmentation_size;
  U64     augmentation_size;
  String8 augmentation;
  
  U64 code_align_factor;
  S64 data_align_factor;
  U64 ret_addr_reg;
  
  U64 handler_ip;
  
  Rng1U64 cfi_range;
} DW_CIEUnpacked;

typedef struct DW_CIEUnpackedNode
{
  struct DW_CIEUnpackedNode *next;
  DW_CIEUnpacked             cie;
  U64                        offset;
} DW_CIEUnpackedNode;

// FDE: Frame Description Entry
typedef struct DW_FDEUnpacked
{
  Rng1U64 ip_voff_range;
  U64     lsda_ip;
  Rng1U64 cfi_range;
} DW_FDEUnpacked;

// CFI: Call Frame Information
typedef struct DW_CFIRecords
{
  B32            valid;
  DW_CIEUnpacked cie;
  DW_FDEUnpacked fde;
} DW_CFIRecords;

typedef enum DW_CFICFARule{
  DW_CFI_CFA_Rule_RegOff,
  DW_CFI_CFA_Rule_Expr,
} DW_CFICFARule;

typedef struct DW_CFICFACell
{
  DW_CFICFARule rule;
  union {
    struct {
      U64 reg_idx;
      S64 offset;
    };
    Rng1U64 expr;
  };
} DW_CFICFACell;

typedef enum DW_CFIRegisterRule
{
  DW_CFIRegisterRule_SameValue,
  DW_CFIRegisterRule_Undefined,
  DW_CFIRegisterRule_Offset,
  DW_CFIRegisterRule_ValOffset,
  DW_CFIRegisterRule_Register,
  DW_CFIRegisterRule_Expression,
  DW_CFIRegisterRule_ValExpression,
} DW_CFIRegisterRule;

typedef struct DW_CFICell
{
  DW_CFIRegisterRule rule;
  union {
    S64 n;
    Rng1U64 expr;
  };
} DW_CFICell;

typedef struct DW_CFIRow
{
  struct DW_CFIRow *next;
  DW_CFICell       *cells;
  DW_CFICFACell     cfa_cell;
} DW_CFIRow;

typedef struct DW_CFIMachine
{
  U64             cells_per_row;
  DW_CIEUnpacked *cie;
  DW_EhPtrCtx    *ptr_ctx;
  DW_CFIRow      *initial_row;
  U64             fde_ip;
} DW_CFIMachine;

typedef U8 DW_CFADecode;
enum
{
  DW_CFADecode_Nop     = 0x0,
  // 1,2,4,8 reserved for literal byte sizes
  DW_CFADecode_Address = 0x9,
  DW_CFADecode_ULEB128 = 0xA,
  DW_CFADecode_SLEB128 = 0xB,
};

typedef U16 DW_CFAControlBits;
enum
{
  DW_CFAControlBits_Dec1Mask = 0x00F,
  DW_CFAControlBits_Dec2Mask = 0x0F0,
  DW_CFAControlBits_IsReg0   = 0x100,
  DW_CFAControlBits_IsReg1   = 0x200,
  DW_CFAControlBits_IsReg2   = 0x400,
  DW_CFAControlBits_NewRow   = 0x800,
};

global read_only DW_CFAControlBits dw_unwind__cfa_control_bits_kind1[DW_CFA_OplKind1 + 1];
global read_only DW_CFAControlBits dw_unwind__cfa_control_bits_kind2[DW_CFA_OplKind2 + 1];

// register codes for unwinding match the DW_RegX64 register codes
#define DW_UNWIND_X64__REG_SLOT_COUNT 17

////////////////////////////////
// x64 Unwind Function

internal DW_UnwindResult
dw_unwind_x64(String8           raw_text,
              String8           raw_eh_frame,
              String8           raw_eh_frame_header,
              Rng1U64           text_vrange,
              Rng1U64           eh_frame_vrange,
              Rng1U64           eh_frame_header_vrange,
              U64               default_image_base,
              U64               image_base,
              U64               stack_pointer,
              DW_RegsX64       *regs,
              DW_ReadMemorySig *read_memory,
              void             *read_memory_ud);

internal DW_UnwindResult dw_unwind_x64__apply_frame_rules(String8 raw_eh_frame, DW_CFIRow *row, U64 text_base_vaddr, DW_ReadMemorySig *read_memory, void *read_memory_ud, U64 stack_pointer, DW_RegsX64 *regs);

////////////////////////////////
// x64 Unwind Helper Functions

internal void dw_unwind_init_x64(void);
internal U64  dw_unwind_parse_pointer_x64(void *base, Rng1U64 range, DW_EhPtrCtx *ptr_ctx, DW_EhPtrEnc ptr_enc, U64 off, U64 *ptr_out);

//- eh_frame parsing
internal void dw_unwind_parse_cie_x64(void *base,Rng1U64 range,DW_EhPtrCtx *ptr_ctx, U64 off, DW_CIEUnpacked *cie_out);
internal void dw_unwind_parse_fde_x64(void *base,Rng1U64 range,DW_EhPtrCtx *ptr_ctx, DW_CIEUnpacked *parent_cie, U64 off, DW_FDEUnpacked *fde_out);
internal DW_CFIRecords dw_unwind_eh_frame_cfi_from_ip_slow_x64(String8 raw_eh_frame, DW_EhPtrCtx *ptr_ctx, U64 ip_voff);
internal DW_CFIRecords dw_unwind_eh_frame_hdr_from_ip_fast_x64(String8 raw_eh_frame, String8 raw_eh_frame_hdr, DW_EhPtrCtx *ptr_ctx, U64 ip_voff);

//- cfi machine

internal DW_CFIMachine dw_unwind_make_machine_x64(U64 cells_per_row, DW_CIEUnpacked *cie, DW_EhPtrCtx *ptr_ctx);
internal void          dw_unwind_machine_equip_initial_row_x64(DW_CFIMachine *machine, DW_CFIRow *initial_row);
internal void          dw_unwind_machine_equip_fde_ip_x64(DW_CFIMachine *machine, U64 fde_ip);

internal DW_CFIRow* dw_unwind_row_alloc_x64(Arena *arena, U64 cells_per_row);
internal void       dw_unwind_row_zero_x64(DW_CFIRow *row, U64 cells_per_row);
internal void       dw_unwind_row_copy_x64(DW_CFIRow *dst, DW_CFIRow *src, U64 cells_per_row);

internal B32 dw_unwind_machine_run_to_ip_x64(void *base, Rng1U64 range, DW_CFIMachine *machine, U64 target_ip, DW_CFIRow *row_out);

#endif // DWARF_UNWIND_H

