mirror of
https://github.com/Ed94/raddebugger.git
synced 2026-06-26 21:44:59 -07:00
1166 lines
38 KiB
C
1166 lines
38 KiB
C
// Copyright (c) Epic Games Tools
|
|
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
|
|
|
internal U64 dw_based_range_read(void *base, Rng1U64 range, U64 off, U64 size, void *out) { return 0; }
|
|
internal U64 dw_based_range_read_uleb128(void *base, Rng1U64 range, U64 off, U64 *out) { return 0; }
|
|
internal U64 dw_based_range_read_sleb128(void *base, Rng1U64 range, U64 off, S64 *out) { return 0; }
|
|
internal U64 dw_based_range_read_length(void *base, Rng1U64 range, U64 off, U64 *out) { return 0; }
|
|
|
|
////////////////////////////////
|
|
// x64 Unwind Function
|
|
|
|
internal DW_UnwindResult
|
|
dw_unwind_x64(String8 raw_text,
|
|
String8 raw_eh_frame,
|
|
String8 raw_eh_frame_hdr,
|
|
Rng1U64 text_vrange,
|
|
Rng1U64 eh_frame_vrange,
|
|
Rng1U64 eh_frame_hdr_vrange,
|
|
U64 default_image_base,
|
|
U64 image_base,
|
|
U64 stack_pointer,
|
|
DW_RegsX64 *regs,
|
|
DW_ReadMemorySig *read_memory,
|
|
void *read_memory_ud)
|
|
{
|
|
// TODO: What if ELF has two sections with instructions and pointer is ecnoded relative to .text2?
|
|
Temp scratch = scratch_begin(0, 0);
|
|
|
|
DW_UnwindResult result = {0};
|
|
|
|
dw_unwind_init_x64();
|
|
|
|
// rebase
|
|
U64 rebase_voff_to_vaddr = (image_base - default_image_base);
|
|
|
|
// get ip register values
|
|
U64 ip_value = regs->rip;
|
|
U64 ip_voff = ip_value - rebase_voff_to_vaddr;
|
|
|
|
// check sections
|
|
B32 has_needed_sections = (raw_text.size > 0 && raw_eh_frame.size > 0);
|
|
if (!has_needed_sections) {
|
|
result.is_invalid = 1;
|
|
}
|
|
|
|
//- get frame info range
|
|
void *frame_base = raw_eh_frame.str;
|
|
Rng1U64 frame_range = rng_1u64(0, raw_eh_frame.size);
|
|
|
|
//- section vaddrs
|
|
U64 text_base_vaddr = text_vrange.min + rebase_voff_to_vaddr;
|
|
U64 frame_base_voff = text_vrange.min;
|
|
U64 data_base_vaddr = eh_frame_hdr_vrange.min + rebase_voff_to_vaddr;
|
|
|
|
//- find cfi records
|
|
DW_CFIRecords cfi_recs = {0};
|
|
if (has_needed_sections) {
|
|
DW_EhPtrCtx ptr_ctx = {0};
|
|
ptr_ctx.raw_base_vaddr = frame_base_voff;
|
|
ptr_ctx.text_vaddr = text_base_vaddr;
|
|
ptr_ctx.data_vaddr = data_base_vaddr;
|
|
ptr_ctx.func_vaddr = 0;
|
|
if (raw_eh_frame_hdr.size) {
|
|
cfi_recs = dw_unwind_eh_frame_hdr_from_ip_fast_x64(raw_eh_frame, raw_eh_frame_hdr, &ptr_ctx, ip_voff);
|
|
} else {
|
|
cfi_recs = dw_unwind_eh_frame_cfi_from_ip_slow_x64(raw_eh_frame, &ptr_ctx, ip_voff);
|
|
}
|
|
}
|
|
|
|
//- check cfi records
|
|
if (!cfi_recs.valid) {
|
|
result.is_invalid = 1;
|
|
}
|
|
|
|
//- cfi machine setup
|
|
DW_CFIMachine machine = {0};
|
|
if (cfi_recs.valid) {
|
|
DW_EhPtrCtx ptr_ctx = {0};
|
|
ptr_ctx.raw_base_vaddr = frame_base_voff;
|
|
ptr_ctx.text_vaddr = text_base_vaddr;
|
|
ptr_ctx.data_vaddr = data_base_vaddr;
|
|
ptr_ctx.func_vaddr = cfi_recs.fde.ip_voff_range.min + rebase_voff_to_vaddr; // TODO: it's not super clear how to set up this member, need more test cases
|
|
machine = dw_unwind_make_machine_x64(DW_UNWIND_X64__REG_SLOT_COUNT, &cfi_recs.cie, &ptr_ctx);
|
|
}
|
|
|
|
// initial row
|
|
DW_CFIRow *init_row = 0;
|
|
if (cfi_recs.valid) {
|
|
Rng1U64 init_cfi_range = cfi_recs.cie.cfi_range;
|
|
DW_CFIRow *row = dw_unwind_row_alloc_x64(scratch.arena, machine.cells_per_row);
|
|
if (dw_unwind_machine_run_to_ip_x64(frame_base, init_cfi_range, &machine, max_U64, row)) {
|
|
init_row = row;
|
|
}
|
|
if (init_row == 0) {
|
|
result.is_invalid = 1;
|
|
}
|
|
}
|
|
|
|
// main row
|
|
DW_CFIRow *main_row = 0;
|
|
if (init_row != 0) {
|
|
// upgrade machine with new equipment
|
|
dw_unwind_machine_equip_initial_row_x64(&machine, init_row);
|
|
dw_unwind_machine_equip_fde_ip_x64(&machine, cfi_recs.fde.ip_voff_range.min);
|
|
|
|
// decode main row
|
|
Rng1U64 main_cfi_range = cfi_recs.fde.cfi_range;
|
|
DW_CFIRow *row = dw_unwind_row_alloc_x64(scratch.arena, machine.cells_per_row);
|
|
if (dw_unwind_machine_run_to_ip_x64(frame_base, main_cfi_range, &machine, ip_value, row)) {
|
|
main_row = row;
|
|
}
|
|
if (main_row == 0) {
|
|
result.is_invalid = 1;
|
|
}
|
|
}
|
|
|
|
// apply main row to modify the registers
|
|
if (main_row != 0) {
|
|
result = dw_unwind_x64__apply_frame_rules(raw_eh_frame, main_row, text_base_vaddr, read_memory, read_memory_ud, stack_pointer, regs);
|
|
}
|
|
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|
|
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)
|
|
{
|
|
DW_UnwindResult result = {0};
|
|
|
|
U64 missed_read_addr = 0;
|
|
|
|
//- setup a dwarf expression machine
|
|
DW_ExprMachineConfig dwexpr_config = {0};
|
|
dwexpr_config.max_step_count = 0xFFFF;
|
|
dwexpr_config.read_memory = read_memory;
|
|
dwexpr_config.read_memory_ud = read_memory_ud;
|
|
dwexpr_config.regs = regs;
|
|
dwexpr_config.text_section_base = &text_base_vaddr;
|
|
|
|
//- compute cfa
|
|
U64 cfa = 0;
|
|
switch (row->cfa_cell.rule) {
|
|
case DW_CFI_CFA_Rule_RegOff: {
|
|
// TODO: have we done anything to gaurantee reg_idx here?
|
|
U64 reg_idx = row->cfa_cell.reg_idx;
|
|
|
|
// is this a roll-over CFA?
|
|
B32 is_roll_over_cfa = 0;
|
|
if (reg_idx == DW_RegX64_Rsp) {
|
|
DW_CFIRegisterRule rule = row->cells[reg_idx].rule;
|
|
if (rule == DW_CFIRegisterRule_Undefined || rule == DW_CFIRegisterRule_SameValue) {
|
|
is_roll_over_cfa = 1;
|
|
}
|
|
}
|
|
|
|
// compute cfa
|
|
if (is_roll_over_cfa) {
|
|
cfa = stack_pointer + row->cfa_cell.offset;
|
|
} else {
|
|
cfa = regs->r[reg_idx] + row->cfa_cell.offset;
|
|
}
|
|
} break;
|
|
|
|
case DW_CFI_CFA_Rule_Expr: {
|
|
Rng1U64 expr_range = row->cfa_cell.expr;
|
|
DW_Location location = dw_expr__eval(0, raw_eh_frame.str, expr_range, &dwexpr_config);
|
|
if (location.non_piece_loc.kind == DW_SimpleLocKind_Fail && location.non_piece_loc.fail_kind == DW_LocFailKind_MissingMemory) {
|
|
missed_read_addr = location.non_piece_loc.fail_data;
|
|
goto error_out;
|
|
}
|
|
if (location.non_piece_loc.kind == DW_SimpleLocKind_Address) {
|
|
cfa = location.non_piece_loc.addr;
|
|
}
|
|
} break;
|
|
}
|
|
|
|
// compute registers
|
|
{
|
|
DW_CFICell *cell = row->cells;
|
|
DW_RegsX64 new_regs = {0};
|
|
for (U64 i = 0; i < DW_UNWIND_X64__REG_SLOT_COUNT; ++i, ++cell) {
|
|
// compute value
|
|
U64 v = 0;
|
|
switch (cell->rule) {
|
|
default:
|
|
{
|
|
Assert(!"UNEXPECTED-RULE");
|
|
} break;
|
|
|
|
case DW_CFIRegisterRule_Undefined:
|
|
{
|
|
Assert(!"UNDEFINED");
|
|
} break;
|
|
|
|
case DW_CFIRegisterRule_SameValue:
|
|
{
|
|
v = regs->r[i];
|
|
} break;
|
|
|
|
case DW_CFIRegisterRule_Offset:
|
|
{
|
|
U64 addr = cfa + cell->n;
|
|
U64 read_size = read_memory(addr, sizeof(v), &v, read_memory_ud);
|
|
if (read_size != sizeof(v)) {
|
|
missed_read_addr = addr;
|
|
goto error_out;
|
|
}
|
|
} break;
|
|
|
|
case DW_CFIRegisterRule_ValOffset:
|
|
{
|
|
v = cfa + cell->n;
|
|
} break;
|
|
|
|
case DW_CFIRegisterRule_Register:
|
|
{
|
|
v = regs->r[i];
|
|
} break;
|
|
|
|
case DW_CFIRegisterRule_Expression:
|
|
{
|
|
Rng1U64 expr_range = cell->expr;
|
|
U64 addr = 0;
|
|
DW_Location location = dw_expr__eval(0, raw_eh_frame.str, expr_range, &dwexpr_config);
|
|
if (location.non_piece_loc.kind == DW_SimpleLocKind_Fail && location.non_piece_loc.fail_kind == DW_LocFailKind_MissingMemory) {
|
|
missed_read_addr = location.non_piece_loc.fail_data;
|
|
goto error_out;
|
|
}
|
|
if (location.non_piece_loc.kind == DW_SimpleLocKind_Address) {
|
|
addr = location.non_piece_loc.addr;
|
|
}
|
|
U64 read_size = read_memory(addr, sizeof(v), &v, read_memory_ud);
|
|
if (read_size != sizeof(v)) {
|
|
missed_read_addr = addr;
|
|
goto error_out;
|
|
}
|
|
} break;
|
|
|
|
case DW_CFIRegisterRule_ValExpression:
|
|
{
|
|
Rng1U64 expr_range = cell->expr;
|
|
DW_Location location = dw_expr__eval(0, raw_eh_frame.str, expr_range, &dwexpr_config);
|
|
if (location.non_piece_loc.kind == DW_SimpleLocKind_Fail && location.non_piece_loc.fail_kind == DW_LocFailKind_MissingMemory) {
|
|
missed_read_addr = location.non_piece_loc.fail_data;
|
|
goto error_out;
|
|
}
|
|
if (location.non_piece_loc.kind == DW_SimpleLocKind_Address) {
|
|
v = location.non_piece_loc.addr;
|
|
}
|
|
} break;
|
|
}
|
|
|
|
// commit value to output slot
|
|
new_regs.r[i] = v;
|
|
}
|
|
|
|
// commit all new regs
|
|
MemoryCopy(regs, &new_regs, sizeof(new_regs));
|
|
}
|
|
|
|
//- save new stack pointer
|
|
result.stack_pointer = cfa;
|
|
|
|
error_out:;
|
|
if (missed_read_addr) {
|
|
result.is_invalid = 1;
|
|
result.missed_read = 1;
|
|
result.missed_read_addr = missed_read_addr;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////
|
|
// Helper Functions
|
|
|
|
internal void
|
|
dw_unwind_init_x64(void)
|
|
{
|
|
local_persist B32 did_init = 0;
|
|
|
|
if (!did_init) {
|
|
did_init = 1;
|
|
|
|
// control bits tables
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_Nop ] = 0x000;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_SetLoc ] = 0x809;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_AdvanceLoc1 ] = 0x801;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_AdvanceLoc2 ] = 0x802;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_AdvanceLoc4 ] = 0x804;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_OffsetExt ] = 0x2AA;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_RestoreExt ] = 0x20A;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_Undefined ] = 0x20A;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_SameValue ] = 0x20A;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_Register ] = 0x6AA;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_RememberState ] = 0x000;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_RestoreState ] = 0x000;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_DefCfa ] = 0x2AA;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_DefCfaRegister ] = 0x20A;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_DefCfaOffset ] = 0x00A;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_DefCfaExpr ] = 0x00A;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_Expr ] = 0x2AA;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_OffsetExtSf ] = 0x2BA;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_DefCfaSf ] = 0x2BA;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_DefCfaOffsetSf ] = 0x00B;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_ValOffset ] = 0x2AA;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_ValOffsetSf ] = 0x2BA;
|
|
dw_unwind__cfa_control_bits_kind1[DW_CFA_ValExpr ] = 0x2AA;
|
|
|
|
dw_unwind__cfa_control_bits_kind2[DW_CFA_AdvanceLoc >> 6] = 0x800;
|
|
dw_unwind__cfa_control_bits_kind2[DW_CFA_Offset >> 6] = 0x10A;
|
|
dw_unwind__cfa_control_bits_kind2[DW_CFA_Restore >> 6] = 0x100;
|
|
}
|
|
}
|
|
|
|
internal U64
|
|
dw_unwind_parse_pointer_x64(void *frame_base, Rng1U64 frame_range, DW_EhPtrCtx *ptr_ctx, DW_EhPtrEnc encoding, U64 off, U64 *ptr_out)
|
|
{
|
|
// aligned offset
|
|
U64 pointer_off = off;
|
|
if (encoding == DW_EhPtrEnc_Aligned) {
|
|
pointer_off = AlignPow2(off, 8); // TODO: align to 4 bytes when we parse x86 ELF binary
|
|
encoding = DW_EhPtrEnc_Ptr;
|
|
}
|
|
|
|
// decode pointer value
|
|
U64 size_param = 0;
|
|
U64 after_pointer_off = 0;
|
|
U64 raw_pointer = 0;
|
|
switch (encoding & DW_EhPtrEnc_TypeMask) {
|
|
default:break;
|
|
|
|
case DW_EhPtrEnc_Ptr : size_param = 8; goto ufixed;
|
|
case DW_EhPtrEnc_UData2: size_param = 2; goto ufixed;
|
|
case DW_EhPtrEnc_UData4: size_param = 4; goto ufixed;
|
|
case DW_EhPtrEnc_UData8: size_param = 8; goto ufixed;
|
|
ufixed:
|
|
{
|
|
dw_based_range_read(frame_base, frame_range, pointer_off, size_param, &raw_pointer);
|
|
after_pointer_off = pointer_off + size_param;
|
|
} break;
|
|
|
|
// TODO: Signed is actually just a flag that indicates this int is negavite.
|
|
// There shouldn't be a read for Signed.
|
|
// For instance, (DW_EhPtrEnc_UData2 | DW_EhPtrEnc_Signed) == DW_EhPtrEnc_SData etc.
|
|
case DW_EhPtrEnc_Signed:size_param = 8; goto sfixed;
|
|
|
|
case DW_EhPtrEnc_SData2:size_param = 2; goto sfixed;
|
|
case DW_EhPtrEnc_SData4:size_param = 4; goto sfixed;
|
|
case DW_EhPtrEnc_SData8:size_param = 8; goto sfixed;
|
|
sfixed:
|
|
{
|
|
dw_based_range_read(frame_base, frame_range, pointer_off, size_param, &raw_pointer);
|
|
after_pointer_off = pointer_off + size_param;
|
|
// sign extension
|
|
U64 sign_bit = size_param*8 - 1;
|
|
if ((raw_pointer >> sign_bit) != 0) {
|
|
raw_pointer |= (~(1 << sign_bit)) + 1;
|
|
}
|
|
} break;
|
|
|
|
case DW_EhPtrEnc_ULEB128:
|
|
{
|
|
U64 size = dw_based_range_read_uleb128(frame_base, frame_range, pointer_off, &raw_pointer);
|
|
after_pointer_off = pointer_off + size;
|
|
} break;
|
|
|
|
case DW_EhPtrEnc_SLEB128:
|
|
{
|
|
U64 size = dw_based_range_read_sleb128(frame_base, frame_range, pointer_off,
|
|
(S64*)&raw_pointer);
|
|
after_pointer_off = pointer_off + size;
|
|
} break;
|
|
}
|
|
|
|
// apply relative bases
|
|
U64 pointer = raw_pointer;
|
|
if (pointer != 0) {
|
|
switch (encoding & DW_EhPtrEnc_ModifyMask) {
|
|
case DW_EhPtrEnc_PcRel:
|
|
{
|
|
pointer = ptr_ctx->raw_base_vaddr + frame_range.min + off + raw_pointer;
|
|
} break;
|
|
case DW_EhPtrEnc_TextRel:
|
|
{
|
|
pointer = ptr_ctx->text_vaddr + raw_pointer;
|
|
} break;
|
|
case DW_EhPtrEnc_DataRel:
|
|
{
|
|
pointer = ptr_ctx->data_vaddr + raw_pointer;
|
|
} break;
|
|
case DW_EhPtrEnc_FuncRel:
|
|
{
|
|
Assert(!"TODO: need a sample to verify implementation");
|
|
pointer = ptr_ctx->func_vaddr + raw_pointer;
|
|
} break;
|
|
}
|
|
}
|
|
|
|
// return
|
|
*ptr_out = pointer;
|
|
U64 result = after_pointer_off - off;
|
|
return(result);
|
|
}
|
|
|
|
//- eh_frame parsing
|
|
|
|
internal void
|
|
dw_unwind_parse_cie_x64(void *base, Rng1U64 range, DW_EhPtrCtx *ptr_ctx, U64 off, DW_CIEUnpacked *cie_out)
|
|
{
|
|
NotImplemented;
|
|
#if 0
|
|
MemoryZeroStruct(cie_out);
|
|
|
|
// get version
|
|
U64 version_off = off;
|
|
U8 version = 0;
|
|
dw_based_range_read(base, range, version_off, 1, &version);
|
|
|
|
// check version
|
|
if (version == 1 || version == 3) {
|
|
|
|
// read augmentation
|
|
U64 augmentation_off = version_off + 1;
|
|
String8 augmentation = dw_based_range_read_string(base, range, augmentation_off);
|
|
|
|
// read code align
|
|
U64 code_align_factor_off = augmentation_off + augmentation.size + 1;
|
|
U64 code_align_factor = 0;
|
|
U64 code_align_factor_size = dw_based_range_read_uleb128(base, range, code_align_factor_off, &code_align_factor);
|
|
|
|
// read data align
|
|
U64 data_align_factor_off = code_align_factor_off + code_align_factor_size;
|
|
S64 data_align_factor = 0;
|
|
U64 data_align_factor_size = dw_based_range_read_sleb128(base, range, data_align_factor_off, &data_align_factor);
|
|
|
|
// return address register
|
|
U64 ret_addr_reg_off = data_align_factor_off + data_align_factor_size;
|
|
U64 after_ret_addr_reg_off = 0;
|
|
U64 ret_addr_reg = 0;
|
|
if (version == 1) {
|
|
dw_based_range_read(base, range, ret_addr_reg_off, 1, &ret_addr_reg);
|
|
after_ret_addr_reg_off = ret_addr_reg_off + 1;
|
|
} else {
|
|
U64 ret_addr_reg_size = dw_based_range_read_uleb128(base, range, ret_addr_reg_off, &ret_addr_reg);
|
|
after_ret_addr_reg_off = ret_addr_reg_off + ret_addr_reg_size;
|
|
}
|
|
|
|
// TODO:
|
|
// Handle "eh" param, it indicates presence of EH Data field.
|
|
// On 32bit arch it is a 4-byte and on 64-bit 8-byte value.
|
|
// Reference: https://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-PDA/LSB-PDA/ehframechpt.html
|
|
// Reference doc doesn't clarify structure for EH Data though
|
|
|
|
// check for augmentation data
|
|
U64 aug_size_off = after_ret_addr_reg_off;
|
|
U64 after_aug_size_off = after_ret_addr_reg_off;
|
|
B32 has_augmentation_size = 0;
|
|
U64 augmentation_size = 0;
|
|
if (augmentation.size > 0 && augmentation.str[0] == 'z') {
|
|
has_augmentation_size = 1;
|
|
U64 aug_size_size = dw_based_range_read_uleb128(base, range, aug_size_off, &augmentation_size);
|
|
after_aug_size_off += aug_size_size;
|
|
}
|
|
|
|
// read augmentation data
|
|
U64 aug_data_off = after_aug_size_off;
|
|
U64 after_aug_data_off = after_aug_size_off;
|
|
|
|
DW_EhPtrEnc lsda_encoding = DW_EhPtrEnc_Omit;
|
|
U64 handler_ip = 0;
|
|
DW_EhPtrEnc addr_encoding = DW_EhPtrEnc_UData8;
|
|
|
|
if (has_augmentation_size > 0) {
|
|
U64 aug_data_cursor = aug_data_off;
|
|
for (U8 *ptr = augmentation.str + 1, *opl = augmentation.str + augmentation.size; ptr < opl; ++ptr) {
|
|
switch (*ptr) {
|
|
case 'L': {
|
|
dw_based_range_read_struct(base, range, aug_data_cursor, &lsda_encoding);
|
|
aug_data_cursor += sizeof(lsda_encoding);
|
|
} break;
|
|
case 'P': {
|
|
DW_EhPtrEnc handler_encoding = DW_EhPtrEnc_Omit;
|
|
dw_based_range_read_struct(base, range, aug_data_cursor, &handler_encoding);
|
|
|
|
U64 ptr_off = aug_data_cursor + sizeof(handler_encoding);
|
|
U64 ptr_size = dw_unwind_parse_pointer_x64(base, range, ptr_ctx, handler_encoding, ptr_off, &handler_ip);
|
|
aug_data_cursor = ptr_off + ptr_size;
|
|
} break;
|
|
case 'R': {
|
|
dw_based_range_read_struct(base, range, aug_data_cursor, &addr_encoding);
|
|
aug_data_cursor += sizeof(addr_encoding);
|
|
} break;
|
|
default: {
|
|
goto dbl_break_aug;
|
|
} break;
|
|
}
|
|
}
|
|
dbl_break_aug:;
|
|
after_aug_data_off = aug_data_cursor;
|
|
}
|
|
|
|
// cfi range
|
|
U64 cfi_off = range.min + after_aug_data_off;
|
|
U64 cfi_size = 0;
|
|
if (range.max > cfi_off) {
|
|
cfi_size = range.max - cfi_off;
|
|
}
|
|
|
|
// commit values to out
|
|
cie_out->version = version;
|
|
cie_out->lsda_encoding = lsda_encoding;
|
|
cie_out->addr_encoding = addr_encoding;
|
|
cie_out->has_augmentation_size = has_augmentation_size;
|
|
cie_out->augmentation_size = augmentation_size;
|
|
cie_out->augmentation = augmentation;
|
|
cie_out->code_align_factor = code_align_factor;
|
|
cie_out->data_align_factor = data_align_factor;
|
|
cie_out->ret_addr_reg = ret_addr_reg;
|
|
cie_out->handler_ip = handler_ip;
|
|
cie_out->cfi_range.min = cfi_off;
|
|
cie_out->cfi_range.max = cfi_off + cfi_size;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
internal void
|
|
dw_unwind_parse_fde_x64(void *base, Rng1U64 range, DW_EhPtrCtx *ptr_ctx, DW_CIEUnpacked *cie, U64 off, DW_FDEUnpacked *fde_out)
|
|
{
|
|
// pull out pointer encoding field
|
|
DW_EhPtrEnc ptr_enc = cie->addr_encoding;
|
|
|
|
// ip first
|
|
U64 ip_first_off = off;
|
|
U64 ip_first = 0;
|
|
U64 ip_first_size = dw_unwind_parse_pointer_x64(base, range, ptr_ctx, ptr_enc, ip_first_off, &ip_first);
|
|
|
|
// ip range size
|
|
U64 ip_range_size_off = ip_first_off + ip_first_size;
|
|
U64 ip_range_size = 0;
|
|
U64 ip_range_size_size = dw_unwind_parse_pointer_x64(base, range, ptr_ctx, ptr_enc & DW_EhPtrEnc_TypeMask, ip_range_size_off, &ip_range_size);
|
|
|
|
// augmentation data
|
|
U64 aug_data_off = ip_range_size_off + ip_range_size_size;
|
|
U64 after_aug_data_off = aug_data_off;
|
|
U64 lsda_ip = 0;
|
|
|
|
if (cie->has_augmentation_size) {
|
|
// augmentation size
|
|
U64 augmentation_size = 0;
|
|
U64 aug_size_size = dw_based_range_read_uleb128(base, range, aug_data_off, &augmentation_size);
|
|
U64 after_aug_size_off = aug_data_off + aug_size_size;
|
|
|
|
// extract lsda (only thing that can actually be in FDE's augmentation data as far as we know)
|
|
DW_EhPtrEnc lsda_encoding = cie->lsda_encoding;
|
|
if (lsda_encoding != DW_EhPtrEnc_Omit) {
|
|
U64 lsda_off = after_aug_size_off;
|
|
dw_unwind_parse_pointer_x64(base, range, ptr_ctx, lsda_encoding, lsda_off, &lsda_ip);
|
|
}
|
|
|
|
// set offset at end of augmentation data
|
|
after_aug_data_off = after_aug_size_off + augmentation_size;
|
|
}
|
|
|
|
// cfi range
|
|
U64 cfi_off = range.min + after_aug_data_off;
|
|
U64 cfi_size = 0;
|
|
if (range.max > cfi_off) {
|
|
cfi_size = range.max - cfi_off;
|
|
}
|
|
|
|
// commit values to out
|
|
fde_out->ip_voff_range.min = ip_first;
|
|
fde_out->ip_voff_range.max = ip_first + ip_range_size;
|
|
fde_out->lsda_ip = lsda_ip;
|
|
fde_out->cfi_range.min = cfi_off;
|
|
fde_out->cfi_range.max = cfi_off + cfi_size;
|
|
}
|
|
|
|
internal DW_CFIRecords
|
|
dw_unwind_eh_frame_cfi_from_ip_slow_x64(String8 raw_eh_frame, DW_EhPtrCtx *ptr_ctx, U64 ip_voff)
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
|
|
DW_CFIRecords result = {0};
|
|
|
|
DW_CIEUnpackedNode *cie_first = 0;
|
|
DW_CIEUnpackedNode *cie_last = 0;
|
|
|
|
U64 cursor = 0;
|
|
for (;;) {
|
|
// CIE/FDE size
|
|
U64 rec_off = cursor;
|
|
U64 after_rec_size_off = 0;
|
|
U64 rec_size = 0;
|
|
|
|
{
|
|
str8_deserial_read(raw_eh_frame, rec_off, &rec_size, 4, 1);
|
|
after_rec_size_off = 4;
|
|
if (rec_size == max_U32) {
|
|
str8_deserial_read(raw_eh_frame, rec_off + 4, &rec_size, 8, 1);
|
|
after_rec_size_off = 12;
|
|
}
|
|
}
|
|
|
|
// zero size is the end of the loop
|
|
if (rec_size == 0) {
|
|
break;
|
|
}
|
|
|
|
// compute end offset
|
|
U64 rec_opl = rec_off + after_rec_size_off + rec_size;
|
|
|
|
// sub-range the rest of the reads
|
|
Rng1U64 rec_range = rng_1u64(rec_off, rec_opl);
|
|
String8 raw_rec = str8_substr(raw_eh_frame, rec_range);
|
|
|
|
|
|
// discriminator
|
|
U64 discrim_off = after_rec_size_off;
|
|
U32 discrim = 0;
|
|
str8_deserial_read(raw_rec, discrim_off, &discrim, 4, 1);
|
|
|
|
U64 after_discrim_off = discrim_off + 4;
|
|
|
|
// CIE
|
|
if (discrim == 0) {
|
|
DW_CIEUnpacked cie = {0};
|
|
dw_unwind_parse_cie_x64(raw_rec.str, rng_1u64(0, raw_rec.size), ptr_ctx, after_discrim_off, &cie);
|
|
if (cie.version != 0) {
|
|
DW_CIEUnpackedNode *node = push_array(scratch.arena, DW_CIEUnpackedNode, 1);
|
|
node->cie = cie;
|
|
node->offset = rec_off;
|
|
SLLQueuePush(cie_first, cie_last, node);
|
|
}
|
|
}
|
|
// FDE
|
|
else {
|
|
// compute cie offset
|
|
U64 cie_offset = rec_range.min + discrim_off - discrim;
|
|
|
|
// get cie node
|
|
DW_CIEUnpackedNode *cie_node = 0;
|
|
for (DW_CIEUnpackedNode *node = cie_first; node != 0; node = node->next) {
|
|
if (node->offset == cie_offset) {
|
|
cie_node = node;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// parse fde
|
|
DW_FDEUnpacked fde = {0};
|
|
if (cie_node != 0) {
|
|
dw_unwind_parse_fde_x64(raw_rec.str, rng_1u64(0,raw_rec.size), ptr_ctx, &cie_node->cie, after_discrim_off, &fde);
|
|
}
|
|
|
|
if (contains_1u64(fde.ip_voff_range, ip_voff)) {
|
|
result.valid = 1;
|
|
result.cie = cie_node->cie;
|
|
result.fde = fde;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// advance cursor
|
|
cursor = rec_opl;
|
|
}
|
|
|
|
scratch_end(scratch);
|
|
|
|
return(result);
|
|
}
|
|
|
|
internal U64
|
|
dw_search_eh_frame_hdr_linear_x64(String8 raw_eh_frame_hdr, DW_EhPtrCtx *ptr_ctx, U64 location)
|
|
{
|
|
// Table contains only addresses for first instruction in a function and we cannot
|
|
// guarantee that result is FDE that corresponds to the input location.
|
|
// So input location must be cheked against range from FDE header again.
|
|
|
|
U64 closest_location = max_U64;
|
|
U64 closest_address = max_U64;
|
|
|
|
U64 cursor = 0;
|
|
|
|
U8 version = 0;
|
|
cursor += str8_deserial_read_struct(raw_eh_frame_hdr, cursor, &version);
|
|
|
|
if (version == 1) {
|
|
#if 0
|
|
DW_EhPtrCtx ptr_ctx = {0};
|
|
// Set this to base address of .eh_frame_hdr. Entries are relative
|
|
// to this section for some reason.
|
|
ptr_ctx.data_vaddr = range.min;
|
|
// If input location is VMA then set this to address of .text.
|
|
// Pointer parsing function will adjust "init_location" to correct VMA.
|
|
ptr_ctx.text_vaddr = 0;
|
|
#endif
|
|
|
|
DW_EhPtrEnc eh_frame_ptr_enc = 0, fde_count_enc = 0, table_enc = 0;
|
|
cursor += str8_deserial_read_struct(raw_eh_frame_hdr, cursor, &eh_frame_ptr_enc);
|
|
cursor += str8_deserial_read_struct(raw_eh_frame_hdr, cursor, &fde_count_enc);
|
|
cursor += str8_deserial_read_struct(raw_eh_frame_hdr, cursor, &table_enc);
|
|
|
|
U64 eh_frame_ptr = 0, fde_count = 0;
|
|
cursor += dw_unwind_parse_pointer_x64(raw_eh_frame_hdr.str, rng_1u64(0, raw_eh_frame_hdr.size), ptr_ctx, eh_frame_ptr_enc, cursor, &eh_frame_ptr);
|
|
cursor += dw_unwind_parse_pointer_x64(raw_eh_frame_hdr.str, rng_1u64(0, raw_eh_frame_hdr.size), ptr_ctx, fde_count_enc, cursor, &fde_count);
|
|
|
|
for (U64 fde_idx = 0; fde_idx < fde_count; ++fde_idx) {
|
|
U64 init_location = 0, address = 0;
|
|
cursor += dw_unwind_parse_pointer_x64(raw_eh_frame_hdr.str, rng_1u64(0, raw_eh_frame_hdr.size), ptr_ctx, table_enc, cursor, &init_location);
|
|
cursor += dw_unwind_parse_pointer_x64(raw_eh_frame_hdr.str, rng_1u64(0, raw_eh_frame_hdr.size), ptr_ctx, table_enc, cursor, &address);
|
|
|
|
S64 current_delta = (S64)(location - init_location);
|
|
S64 closest_delta = (S64)(location - closest_location);
|
|
if (0 <= current_delta && current_delta < closest_delta) {
|
|
closest_location = init_location;
|
|
closest_address = address;
|
|
}
|
|
}
|
|
}
|
|
|
|
// address where to find corresponding FDE, this is an absolute offset
|
|
// into the image file.
|
|
return closest_address;
|
|
}
|
|
|
|
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)
|
|
{
|
|
DW_CFIRecords result = {0};
|
|
|
|
// find FDE offset
|
|
void *eh_frame_hdr = raw_eh_frame.str;
|
|
U64 fde_offset = dw_search_eh_frame_hdr_linear_x64(raw_eh_frame_hdr, ptr_ctx, ip_voff);
|
|
|
|
B32 is_fde_offset_valid = (fde_offset != max_U64);
|
|
if (is_fde_offset_valid) {
|
|
U64 fde_read_offset = (fde_offset - ptr_ctx->raw_base_vaddr);
|
|
|
|
// read FDE size
|
|
U64 fde_size = 0;
|
|
fde_read_offset += dw_based_range_read_length(raw_eh_frame.str, rng_1u64(0,raw_eh_frame.size), fde_read_offset, &fde_size);
|
|
|
|
// read FDE discriminator
|
|
U32 fde_discrim = 0;
|
|
fde_read_offset += str8_deserial_read_struct(raw_eh_frame, fde_read_offset, &fde_discrim);
|
|
|
|
// compute parent CIE offset
|
|
U64 cie_read_offset = fde_read_offset - (fde_discrim + sizeof(fde_discrim));
|
|
|
|
// read CIE size
|
|
U64 cie_size = 0;
|
|
cie_read_offset += dw_based_range_read_length(raw_eh_frame.str, rng_1u64(0,raw_eh_frame.size), cie_read_offset, &cie_size);
|
|
|
|
// read CIE discriminator
|
|
U32 cie_discrim = max_U32;
|
|
cie_read_offset += str8_deserial_read_struct(raw_eh_frame, cie_read_offset, &cie_discrim);
|
|
|
|
B32 is_fde = (fde_discrim != 0);
|
|
B32 is_cie = (cie_discrim == 0);
|
|
if (is_fde && is_cie) {
|
|
Rng1U64 cie_range = rng_1u64(0, cie_read_offset + (cie_size - sizeof(cie_discrim)));
|
|
Rng1U64 fde_range = rng_1u64(0, fde_read_offset + (fde_size - sizeof(fde_discrim)));
|
|
|
|
// parse CIE
|
|
DW_CIEUnpacked cie = {0};
|
|
dw_unwind_parse_cie_x64(raw_eh_frame.str, cie_range, ptr_ctx, cie_read_offset, &cie);
|
|
|
|
// parse FDE
|
|
DW_FDEUnpacked fde = {0};
|
|
dw_unwind_parse_fde_x64(raw_eh_frame.str, fde_range, ptr_ctx, &cie, fde_read_offset, &fde);
|
|
|
|
// range check instruction pointer
|
|
if (contains_1u64(fde.ip_voff_range, ip_voff)) {
|
|
result.valid = 1;
|
|
result.cie = cie;
|
|
result.fde = fde;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//- cfi machine
|
|
|
|
internal DW_CFIMachine
|
|
dw_unwind_make_machine_x64(U64 cells_per_row, DW_CIEUnpacked *cie, DW_EhPtrCtx *ptr_ctx)
|
|
{
|
|
DW_CFIMachine result = {0};
|
|
result.cells_per_row = cells_per_row;
|
|
result.cie = cie;
|
|
result.ptr_ctx = ptr_ctx;
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
dw_unwind_machine_equip_initial_row_x64(DW_CFIMachine *machine, DW_CFIRow *initial_row)
|
|
{
|
|
machine->initial_row = initial_row;
|
|
}
|
|
|
|
internal void
|
|
dw_unwind_machine_equip_fde_ip_x64(DW_CFIMachine *machine, U64 fde_ip)
|
|
{
|
|
machine->fde_ip = fde_ip;
|
|
}
|
|
|
|
internal DW_CFIRow*
|
|
dw_unwind_row_alloc_x64(Arena *arena, U64 cells_per_row)
|
|
{
|
|
DW_CFIRow *result = push_array(arena, DW_CFIRow, 1);
|
|
result->cells = push_array(arena, DW_CFICell, cells_per_row);
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
dw_unwind_row_zero_x64(DW_CFIRow *row, U64 cells_per_row) {
|
|
MemorySet(row->cells, 0, sizeof(*row->cells)*cells_per_row);
|
|
MemoryZeroStruct(&row->cfa_cell);
|
|
}
|
|
|
|
internal void
|
|
dw_unwind_row_copy_x64(DW_CFIRow *dst, DW_CFIRow *src, U64 cells_per_row)
|
|
{
|
|
MemoryCopy(dst->cells, src->cells, sizeof(*src->cells)*cells_per_row);
|
|
dst->cfa_cell = src->cfa_cell;
|
|
}
|
|
|
|
internal B32
|
|
dw_unwind_machine_run_to_ip_x64(void *base, Rng1U64 range, DW_CFIMachine *machine, U64 target_ip, DW_CFIRow *row)
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
|
|
B32 result = 0;
|
|
|
|
// pull out machine's equipment
|
|
DW_CIEUnpacked *cie = machine->cie;
|
|
DW_EhPtrCtx *ptr_ctx = machine->ptr_ctx;
|
|
U64 cells_per_row = machine->cells_per_row;
|
|
DW_CFIRow *initial_row = machine->initial_row;
|
|
|
|
// start with an empty stack
|
|
DW_CFIRow *stack = 0;
|
|
DW_CFIRow *free_rows = 0;
|
|
|
|
// initialize the row
|
|
if (initial_row != 0) {
|
|
dw_unwind_row_copy_x64(row, initial_row, cells_per_row);
|
|
} else {
|
|
dw_unwind_row_zero_x64(row, cells_per_row);
|
|
}
|
|
U64 table_ip = machine->fde_ip;
|
|
|
|
// loop
|
|
U64 cfi_off = 0;
|
|
for (;;) {
|
|
// op variables
|
|
DW_CFA opcode = 0;
|
|
U64 operand0 = 0;
|
|
U64 operand1 = 0;
|
|
U64 operand2 = 0;
|
|
DW_CFAControlBits control_bits = 0;
|
|
|
|
// decode opcode/operand0
|
|
if (!dw_based_range_read(base, range, cfi_off, 1, &opcode)) {
|
|
result = 1;
|
|
goto done;
|
|
}
|
|
if ((opcode & DW_CFAMask_OpcodeHi) != 0) {
|
|
operand0 = (opcode & DW_CFAMask_Operand);
|
|
opcode = (opcode & DW_CFAMask_OpcodeHi);
|
|
control_bits = dw_unwind__cfa_control_bits_kind2[opcode >> 6];
|
|
} else {
|
|
if (opcode < DW_CFA_OplKind1) {
|
|
control_bits = dw_unwind__cfa_control_bits_kind1[opcode];
|
|
}
|
|
}
|
|
|
|
// decode operand1/operand2
|
|
U64 decode_cursor = cfi_off + 1;
|
|
{
|
|
// setup loop ins/outs
|
|
U64 o[2];
|
|
DW_CFADecode dec[2] = {0};
|
|
dec[0] = (control_bits & 0xF);
|
|
dec[1] = ((control_bits >> 4) & 0xF);
|
|
|
|
// loop
|
|
U64 *out = o;
|
|
for (U64 i = 0; i < 2; i += 1, out += 1) {
|
|
DW_CFADecode d = dec[i];
|
|
U64 o_size = 0;
|
|
switch (d) {
|
|
case 0: {
|
|
*out = 0;
|
|
} break;
|
|
default: {
|
|
if (d <= 8) {
|
|
dw_based_range_read(base, range, decode_cursor, d, out);
|
|
o_size = d;
|
|
}
|
|
} break;
|
|
case DW_CFADecode_Address: {
|
|
o_size = dw_unwind_parse_pointer_x64(base, range, ptr_ctx, cie->addr_encoding, decode_cursor, out);
|
|
} break;
|
|
case DW_CFADecode_ULEB128: {
|
|
o_size = dw_based_range_read_uleb128(base, range, decode_cursor, out);
|
|
} break;
|
|
case DW_CFADecode_SLEB128: {
|
|
o_size = dw_based_range_read_sleb128(base, range, decode_cursor, (S64*)out);
|
|
} break;
|
|
}
|
|
decode_cursor += o_size;
|
|
}
|
|
|
|
// commit out values
|
|
operand1 = o[0];
|
|
operand2 = o[1];
|
|
}
|
|
U64 after_decode_off = decode_cursor;
|
|
|
|
// register checks
|
|
if (control_bits & DW_CFAControlBits_IsReg0) {
|
|
if (operand0 >= cells_per_row) {
|
|
goto done;
|
|
}
|
|
}
|
|
if (control_bits & DW_CFAControlBits_IsReg1) {
|
|
if (operand1 >= cells_per_row) {
|
|
goto done;
|
|
}
|
|
}
|
|
if (control_bits & DW_CFAControlBits_IsReg2) {
|
|
if (operand2 >= cells_per_row) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
// values for deferred work
|
|
U64 new_table_ip = table_ip;
|
|
|
|
// step
|
|
U64 step_cursor = after_decode_off;
|
|
switch (opcode) {
|
|
default: goto done;
|
|
case DW_CFA_Nop:break;
|
|
|
|
//// new row/IP opcodes ////
|
|
|
|
case DW_CFA_SetLoc: {
|
|
new_table_ip = operand1;
|
|
} break;
|
|
case DW_CFA_AdvanceLoc: {
|
|
new_table_ip = table_ip + operand0*cie->code_align_factor;
|
|
} break;
|
|
case DW_CFA_AdvanceLoc1:
|
|
case DW_CFA_AdvanceLoc2:
|
|
case DW_CFA_AdvanceLoc4: {
|
|
U64 advance = operand1*cie->code_align_factor;
|
|
new_table_ip = table_ip + advance;
|
|
} break;
|
|
|
|
//// change CFA (canonical frame address) opcodes ////
|
|
|
|
case DW_CFA_DefCfa: {
|
|
row->cfa_cell.rule = DW_CFI_CFA_Rule_RegOff;
|
|
row->cfa_cell.reg_idx = operand1;
|
|
row->cfa_cell.offset = operand2;
|
|
} break;
|
|
|
|
case DW_CFA_DefCfaSf: {
|
|
row->cfa_cell.rule = DW_CFI_CFA_Rule_RegOff;
|
|
row->cfa_cell.reg_idx = operand1;
|
|
row->cfa_cell.offset = ((S64)operand2)*cie->data_align_factor;
|
|
} break;
|
|
|
|
case DW_CFA_DefCfaRegister: {
|
|
// check rule
|
|
if (row->cfa_cell.rule != DW_CFI_CFA_Rule_RegOff) {
|
|
goto done;
|
|
}
|
|
// commit new cfa
|
|
row->cfa_cell.reg_idx = operand1;
|
|
} break;
|
|
|
|
case DW_CFA_DefCfaOffset: {
|
|
// check rule
|
|
if (row->cfa_cell.rule != DW_CFI_CFA_Rule_RegOff) {
|
|
goto done;
|
|
}
|
|
// commit new cfa
|
|
row->cfa_cell.offset = operand1;
|
|
} break;
|
|
|
|
case DW_CFA_DefCfaOffsetSf: {
|
|
// check rule
|
|
if (row->cfa_cell.rule != DW_CFI_CFA_Rule_RegOff) {
|
|
goto done;
|
|
}
|
|
// commit new cfa
|
|
row->cfa_cell.offset = ((S64)operand1)*cie->data_align_factor;
|
|
} break;
|
|
|
|
case DW_CFA_DefCfaExpr: {
|
|
// setup expr range
|
|
U64 expr_first = range.min + after_decode_off;
|
|
U64 expr_size = operand1;
|
|
step_cursor += expr_size;
|
|
|
|
// commit new cfa
|
|
row->cfa_cell.rule = DW_CFI_CFA_Rule_Expr;
|
|
row->cfa_cell.expr.min = expr_first;
|
|
row->cfa_cell.expr.max = expr_first + expr_size;
|
|
} break;
|
|
|
|
|
|
//// change register rules ////
|
|
|
|
case DW_CFA_Undefined: {
|
|
row->cells[operand1].rule = DW_CFIRegisterRule_Undefined;
|
|
} break;
|
|
|
|
case DW_CFA_SameValue: {
|
|
row->cells[operand1].rule = DW_CFIRegisterRule_SameValue;
|
|
} break;
|
|
|
|
case DW_CFA_Offset: {
|
|
DW_CFICell *cell = &row->cells[operand0];
|
|
cell->rule = DW_CFIRegisterRule_Offset;
|
|
cell->n = operand1*cie->data_align_factor;
|
|
} break;
|
|
|
|
case DW_CFA_OffsetExt: {
|
|
DW_CFICell *cell = &row->cells[operand1];
|
|
cell->rule = DW_CFIRegisterRule_Offset;
|
|
cell->n = operand2*cie->data_align_factor;
|
|
} break;
|
|
|
|
case DW_CFA_OffsetExtSf: {
|
|
DW_CFICell *cell = &row->cells[operand1];
|
|
cell->rule = DW_CFIRegisterRule_Offset;
|
|
cell->n = ((S64)operand2)*cie->data_align_factor;
|
|
} break;
|
|
|
|
case DW_CFA_ValOffset: {
|
|
DW_CFICell *cell = &row->cells[operand1];
|
|
cell->rule = DW_CFIRegisterRule_ValOffset;
|
|
cell->n = operand2*cie->data_align_factor;
|
|
} break;
|
|
|
|
case DW_CFA_ValOffsetSf: {
|
|
DW_CFICell *cell = &row->cells[operand1];
|
|
cell->rule = DW_CFIRegisterRule_ValOffset;
|
|
cell->n = ((S64)operand2)*cie->data_align_factor;
|
|
} break;
|
|
|
|
case DW_CFA_Register: {
|
|
DW_CFICell *cell = &row->cells[operand1];
|
|
cell->rule = DW_CFIRegisterRule_Register;
|
|
cell->n = operand2;
|
|
} break;
|
|
|
|
case DW_CFA_Expr: {
|
|
// setup expr range
|
|
U64 expr_first = range.min + after_decode_off;
|
|
U64 expr_size = operand2;
|
|
step_cursor += expr_size;
|
|
|
|
// commit new rule
|
|
DW_CFICell *cell = &row->cells[operand1];
|
|
cell->rule = DW_CFIRegisterRule_Expression;
|
|
cell->expr.min = expr_first;
|
|
cell->expr.max = expr_first + expr_size;
|
|
} break;
|
|
|
|
case DW_CFA_ValExpr: {
|
|
// setup expr range
|
|
U64 expr_first = range.min + after_decode_off;
|
|
U64 expr_size = operand2;
|
|
step_cursor += expr_size;
|
|
|
|
// commit new rule
|
|
DW_CFICell *cell = &row->cells[operand1];
|
|
cell->rule = DW_CFIRegisterRule_ValExpression;
|
|
cell->expr.min = expr_first;
|
|
cell->expr.max = expr_first + expr_size;
|
|
} break;
|
|
|
|
case DW_CFA_Restore: {
|
|
// check initial row
|
|
if (initial_row == 0) {
|
|
goto done;
|
|
}
|
|
// commit new rule
|
|
row->cells[operand0] = initial_row->cells[operand0];
|
|
} break;
|
|
|
|
case DW_CFA_RestoreExt: {
|
|
// check initial row
|
|
if (initial_row == 0) {
|
|
goto done;
|
|
}
|
|
// commit new rule
|
|
row->cells[operand1] = initial_row->cells[operand1];
|
|
} break;
|
|
|
|
|
|
//// row stack ////
|
|
|
|
case DW_CFA_RememberState: {
|
|
DW_CFIRow *stack_row = free_rows;
|
|
if (stack_row != 0) {
|
|
SLLStackPop(free_rows);
|
|
} else {
|
|
stack_row = dw_unwind_row_alloc_x64(scratch.arena, cells_per_row);
|
|
}
|
|
dw_unwind_row_copy_x64(stack_row, row, cells_per_row);
|
|
SLLStackPush(stack, stack_row);
|
|
} break;
|
|
|
|
case DW_CFA_RestoreState: {
|
|
if (stack != 0) {
|
|
DW_CFIRow *stack_row = stack;
|
|
SLLStackPop(stack);
|
|
dw_unwind_row_copy_x64(row, stack_row, cells_per_row);
|
|
SLLStackPush(free_rows, stack_row);
|
|
} else {
|
|
dw_unwind_row_zero_x64(row, cells_per_row);
|
|
}
|
|
} break;
|
|
}
|
|
|
|
// apply location change
|
|
if (control_bits & DW_CFAControlBits_NewRow) {
|
|
// new ip should always grow the ip
|
|
if (new_table_ip <= table_ip) {
|
|
goto done;
|
|
}
|
|
// stop if this encloses the target ip
|
|
if (table_ip <= target_ip && target_ip < new_table_ip) {
|
|
result = 1;
|
|
goto done;
|
|
}
|
|
// commit new ip
|
|
table_ip = new_table_ip;
|
|
}
|
|
|
|
// advance
|
|
cfi_off = step_cursor;
|
|
}
|
|
done:;
|
|
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|