initial upload

This commit is contained in:
Ryan Fleury
2024-01-10 19:57:50 -08:00
commit a42ec6aeff
308 changed files with 162362 additions and 0 deletions
+840
View File
@@ -0,0 +1,840 @@
// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
////////////////////////////////
//~ allen: Unwind Functions
//- mem view construction
internal UNW_MemView
unw_memview_from_data(String8 data, U64 base_vaddr){
UNW_MemView result = {0};
result.data = data.str;
result.addr_first = base_vaddr;
result.addr_opl = base_vaddr + data.size;
return(result);
}
//- mem view user face for unwind users
internal B32
unw_memview_read(UNW_MemView *memview, U64 addr, U64 size, void *out){
B32 result = 0;
if (memview->addr_first <= addr && addr + size <= memview->addr_opl){
MemoryCopy(out, (U8*)memview->data + addr - memview->addr_first, size);
result = 1;
}
return(result);
}
////////////////////////////////
//~ allen: PE X64 Unwind Functions
//- main interface
internal UNW_Result
unw_pe_x64(String8 bindata, PE_BinInfo *bin,
U64 base_vaddr, UNW_MemView *memview, UNW_X64_Regs *regs){
UNW_Result result = {0};
U64 missed_read_addr = 0;
//- grab ip_voff (several places can use this)
U64 ip_voff = regs->rip.u64 - base_vaddr;
//- get pdata entry from current ip
PE_IntelPdata *initial_pdata = 0;
{
U64 initial_pdata_off = pe_intel_pdata_off_from_voff__binary_search(bindata, bin, ip_voff);
if (initial_pdata_off != 0){
initial_pdata = (PE_IntelPdata*)(bindata.str + initial_pdata_off);
}
}
//- no pdata; unwind by reading stack pointer
if (initial_pdata == 0){
// read ip from stack pointer
U64 sp = regs->rsp.u64;
U64 new_ip = 0;
if (!unw_memview_read_struct(memview, sp, &new_ip)){
missed_read_addr = sp;
goto error_out;
}
// advance stack pointer
U64 new_sp = sp + 8;
// commit registers
regs->rip.u64 = new_ip;
regs->rsp.u64 = new_sp;
}
//- got pdata; perform unwinding with exception handling
else{
// try epilog unwind
B32 did_epilog_unwind = 0;
if (unw_pe_x64__voff_is_in_epilog(bindata, bin, ip_voff, initial_pdata)){
result = unw_pe_x64__epilog(bindata, bin, base_vaddr, memview, regs);
did_epilog_unwind = 1;
}
// try xdata unwind
if (!did_epilog_unwind){
B32 did_machframe = 0;
// get frame reg
REGS_Reg64 *frame_reg = 0;
U64 frame_off = 0;
{
U64 unwind_info_off = initial_pdata->voff_unwind_info;
UNW_PE_Info *unwind_info = (UNW_PE_Info*)(pe_ptr_from_voff(bindata, bin, unwind_info_off));
U32 frame_reg_id = UNW_PE_INFO_REG_FROM_FRAME(unwind_info->frame);
U64 frame_off_val = UNW_PE_INFO_OFF_FROM_FRAME(unwind_info->frame);
if (frame_reg_id != 0){
frame_reg = unw_pe_x64__gpr_reg(regs, frame_reg_id);
// TODO(allen): at this point if frame_reg is zero, the exe is corrupted.
}
frame_off = frame_off_val;
}
PE_IntelPdata *last_pdata = 0;
PE_IntelPdata *pdata = initial_pdata;
for (;pdata != last_pdata;){
//- get unwind info & codes
U64 unwind_info_off = pdata->voff_unwind_info;
UNW_PE_Info *unwind_info = (UNW_PE_Info*)(pe_ptr_from_voff(bindata, bin, unwind_info_off));
UNW_PE_Code *unwind_codes = (UNW_PE_Code*)(unwind_info + 1);
//- get frame base
U64 frame_base = regs->rsp.u64;
if (frame_reg != 0){
U64 raw_frame_base = frame_reg->u64;
U64 adjusted_frame_base = raw_frame_base - frame_off*16;
if (adjusted_frame_base < raw_frame_base){
frame_base = adjusted_frame_base;
}
else{
frame_base = 0;
}
}
//- rjf: bad unwind info -> abort
if(unwind_info == 0)
{
result.dead = 1;
goto error_out;
}
//- op code interpreter
UNW_PE_Code *code_ptr = unwind_codes;
UNW_PE_Code *code_opl = unwind_codes + unwind_info->codes_num;
for (UNW_PE_Code *next_code_ptr = 0; code_ptr < code_opl; code_ptr = next_code_ptr){
// extract op code parts
U32 op_code = UNW_PE_CODE_FROM_FLAGS(code_ptr->flags);
U32 op_info = UNW_PE_INFO_FROM_FLAGS(code_ptr->flags);
// determine number of op code slots
U32 slot_count = unw_pe_x64__slot_count_from_op_code(op_code);
if (op_code == UNW_PE_OpCode_ALLOC_LARGE && op_info == 1){
slot_count += 1;
}
// check op code slot count
if (slot_count == 0 || code_ptr + slot_count > code_opl){
result.dead = 1;
goto end_xdata_unwind;
}
// set next op code pointer
next_code_ptr = code_ptr + slot_count;
// interpret this op code
U64 code_voff = pdata->voff_first + code_ptr->off_in_prolog;
if (code_voff <= ip_voff){
switch (op_code){
case UNW_PE_OpCode_PUSH_NONVOL:
{
// read value from stack pointer
U64 sp = regs->rsp.u64;
U64 value = 0;
if (!unw_memview_read_struct(memview, sp, &value)){
missed_read_addr = sp;
goto error_out;
}
// advance stack pointer
U64 new_sp = sp + 8;
// commit registers
REGS_Reg64 *reg = unw_pe_x64__gpr_reg(regs, op_info);
reg->u64 = value;
regs->rsp.u64 = new_sp;
}break;
case UNW_PE_OpCode_ALLOC_LARGE:
{
// read alloc size
U64 size = 0;
if (op_info == 0){
size = code_ptr[1].u16*8;
}
else if (op_info == 1){
size = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16);
}
else{
result.dead = 1;
goto end_xdata_unwind;
}
// advance stack pointer
U64 sp = regs->rsp.u64;
U64 new_sp = sp + size;
// advance stack pointer
regs->rsp.u64 = new_sp;
}break;
case UNW_PE_OpCode_ALLOC_SMALL:
{
// advance stack pointer
regs->rsp.u64 += op_info*8 + 8;
}break;
case UNW_PE_OpCode_SET_FPREG:
{
// put stack pointer back to the frame base
regs->rsp.u64 = frame_base;
}break;
case UNW_PE_OpCode_SAVE_NONVOL:
{
// read value from frame base
U64 off = code_ptr[1].u16*8;
U64 addr = frame_base + off;
U64 value = 0;
if (!unw_memview_read_struct(memview, addr, &value)){
missed_read_addr = addr;
goto error_out;
}
// commit to register
REGS_Reg64 *reg = unw_pe_x64__gpr_reg(regs, op_info);
reg->u64 = value;
}break;
case UNW_PE_OpCode_SAVE_NONVOL_FAR:
{
// read value from frame base
U64 off = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16);
U64 addr = frame_base + off;
U64 value = 0;
if (!unw_memview_read_struct(memview, addr, &value)){
missed_read_addr = addr;
goto error_out;
}
// commit to register
REGS_Reg64 *reg = unw_pe_x64__gpr_reg(regs, op_info);
reg->u64 = value;
}break;
case UNW_PE_OpCode_EPILOG:
{
result.dead = 1;
}break;
case UNW_PE_OpCode_SPARE_CODE:
{
Assert(!"Hit me!");
// TODO(allen): ???
}break;
case UNW_PE_OpCode_SAVE_XMM128:
{
// read new register values
U8 buf[16];
U64 off = code_ptr[1].u16*16;
U64 addr = frame_base + off;
if (!unw_memview_read(memview, addr, 16, buf)){
missed_read_addr = addr;
goto error_out;
}
// commit to register
void *xmm_reg = (&regs->ymm0) + op_info;
MemoryCopy(xmm_reg, buf, sizeof(buf));
}break;
case UNW_PE_OpCode_SAVE_XMM128_FAR:
{
// read new register values
U8 buf[16];
U64 off = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16);
U64 addr = frame_base + off;
if (!unw_memview_read(memview, addr, 16, buf)){
missed_read_addr = addr;
goto error_out;
}
// commit to register
void *xmm_reg = (&regs->ymm0) + op_info;
MemoryCopy(xmm_reg, buf, sizeof(buf));
}break;
case UNW_PE_OpCode_PUSH_MACHFRAME:
{
// NOTE(rjf): this was found by stepping through kernel code after an exception was
// thrown, encountered in the exception_stepping_tests (after the throw) in mule_main
if (op_info > 1){
result.dead = 1;
goto end_xdata_unwind;
}
// read values
U64 sp_og = regs->rsp.u64;
U64 sp_adj = sp_og;
if (op_info == 1){
sp_adj += 8;
}
U64 ip_value = 0;
if (!unw_memview_read_struct(memview, sp_adj, &ip_value)){
missed_read_addr = sp_adj;
goto error_out;
}
U64 sp_after_ip = sp_adj + 8;
U16 ss_value = 0;
if (!unw_memview_read_struct(memview, sp_after_ip, &ss_value)){
missed_read_addr = sp_after_ip;
goto error_out;
}
U64 sp_after_ss = sp_after_ip + 8;
U64 rflags_value = 0;
if (!unw_memview_read_struct(memview, sp_after_ss, &rflags_value)){
missed_read_addr = sp_after_ss;
goto error_out;
}
U64 sp_after_rflags = sp_after_ss + 8;
U64 sp_value = 0;
if (!unw_memview_read_struct(memview, sp_after_rflags, &sp_value)){
missed_read_addr = sp_after_rflags;
goto error_out;
}
// commit registers
regs->rip.u64 = ip_value;
regs->ss.u16 = ss_value;
regs->rflags.u64 = rflags_value;
regs->rsp.u64 = sp_value;
// mark machine frame
did_machframe = 1;
}break;
}
}
}
//- iterate pdata chain
U32 flags = UNW_PE_INFO_FLAGS_FROM_HDR(unwind_info->header);
if (!(flags & UNW_PE_InfoFlag_CHAINED)){
break;
}
U64 code_count_rounded = AlignPow2(unwind_info->codes_num, 2);
U64 code_size = code_count_rounded*sizeof(UNW_PE_OpCode);
U64 chained_pdata_off = unwind_info_off + sizeof(UNW_PE_Info) + code_size;
last_pdata = pdata;
pdata = (PE_IntelPdata*)pe_ptr_from_voff(bindata, bin, chained_pdata_off);
}
if (!did_machframe){
U64 sp = regs->rsp.u64;
U64 new_ip = 0;
if (!unw_memview_read_struct(memview, sp, &new_ip)){
missed_read_addr = sp;
goto error_out;
}
// advance stack pointer
U64 new_sp = sp + 8;
// commit registers
regs->rip.u64 = new_ip;
regs->rsp.u64 = new_sp;
}
end_xdata_unwind:;
}
}
error_out:;
if (missed_read_addr != 0){
result.dead = 1;
result.missed_read = 1;
result.missed_read_addr = missed_read_addr;
}
if (!result.dead){
result.stack_pointer = regs->rsp.u64;
}
return(result);
}
//- pe x64 helpers
internal UNW_Result
unw_pe_x64__epilog(String8 bindata, PE_BinInfo *bin,
U64 base_vaddr, UNW_MemView*memview, UNW_X64_Regs *regs){
UNW_Result result = {0};
U64 missed_read_addr = 0;
//- setup parsing context
U64 ip_voff = regs->rip.u64 - base_vaddr;
U64 sec_number = pe_section_num_from_voff(bindata, bin, ip_voff);
COFF_SectionHeader *sec = coff_section_header_from_num(bindata, bin->section_array_off, sec_number);
void* inst_base = pe_ptr_from_section_num(bindata, bin, sec_number);
U64 inst_size = sec->vsize;
//- setup parsing variables
B32 keep_parsing = 1;
U64 off = ip_voff - sec->voff;
//- parsing loop
for (;keep_parsing;){
keep_parsing = 0;
U8 inst_byte = 0;
if (off + sizeof(inst_byte) <= inst_size){
void *ptr = (U8*)inst_base + off;
MemoryCopy(&inst_byte, ptr, sizeof(inst_byte));
}
off += 1;
U8 rex = 0;
if ((inst_byte & 0xF0) == 0x40){
rex = inst_byte & 0xF; // rex prefix
if (off + sizeof(inst_base) <= inst_size){
void *ptr = (U8*)inst_base + off;
MemoryCopy(&inst_byte, ptr, sizeof(inst_byte));
}
off += 1;
}
switch (inst_byte){
// pop
case 0x58:
case 0x59:
case 0x5A:
case 0x5B:
case 0x5C:
case 0x5D:
case 0x5E:
case 0x5F:
{
U64 sp = regs->rsp.u64;
U64 value = 0;
if (!unw_memview_read_struct(memview, sp, &value)){
missed_read_addr = sp;
goto error_out;
}
// modify register
UNW_PE_X64_GprReg gpr_reg = (inst_byte - 0x58) + (rex & 1)*8;
REGS_Reg64 *reg = unw_pe_x64__gpr_reg(regs, gpr_reg);
// not a final instruction
keep_parsing = 1;
// commit registers
reg->u64 = value;
regs->rsp.u64 = sp + 8;
}break;
// add $nnnn,%rsp
case 0x81:
{
// skip one byte (we already know what it is in this scenario)
off += 1;
// read the 4-byte immediate
S32 imm = 0;
if (off + sizeof(imm) < inst_size){
void *ptr = (U8*)inst_base + off;
MemoryCopy(&imm, ptr, sizeof(imm));
}
off += 4;
// not a final instruction
keep_parsing = 1;
// update stack pointer
regs->rsp.u64 = (U64)(regs->rsp.u64 + imm);
}break;
// add $n,%rsp
case 0x83:
{
// skip one byte (we already know what it is in this scenario)
off += 1;
// read the 1-byte immediate
S8 imm = 0;
if (off + sizeof(imm) < inst_size){
void *ptr = (U8*)inst_base + off;
MemoryCopy(&imm, ptr, sizeof(imm));
}
off += 1;
// update stack pointer
regs->rsp.u64 = (U64)(regs->rsp.u64 + imm);
keep_parsing = 1;
}break;
// lea imm8/imm32,$rsp
case 0x8D:
{
// read source register
U8 modrm = 0;
if (off + sizeof(modrm) < inst_size){
void *ptr = (U8*)inst_base + off;
MemoryCopy(&modrm, ptr, sizeof(modrm));
}
UNW_PE_X64_GprReg gpr_reg = (modrm & 7) + (rex & 1)*8;
REGS_Reg64 *reg = unw_pe_x64__gpr_reg(regs, gpr_reg);
U64 reg_value = reg->u64;
// advance to the immediate
off += 1;
S32 imm = 0;
// read 1-byte immediate
if ((modrm >> 6) == 1){
S8 imm8 = 0;
if (off + sizeof(imm8) < inst_size){
void *ptr = (U8*)inst_base + off;
MemoryCopy(&imm8, ptr, sizeof(imm8));
}
imm = imm8;
off += 1;
}
// read 4-byte immediate
else{
if (off + sizeof(imm) < inst_size){
void *ptr = (U8*)inst_base + off;
MemoryCopy(&imm, ptr, sizeof(imm));
}
off += 4;
}
regs->rsp.u64 = (U64)(reg_value + imm);
keep_parsing = 1;
}break;
// ret $nn
case 0xC2:
{
// read new ip
U64 sp = regs->rsp.u64;
U64 new_ip = 0;
if (!unw_memview_read_struct(memview, sp, &new_ip)){
missed_read_addr = sp;
goto error_out;
}
// read 2-byte immediate & advance stack pointer
U16 imm = 0;
if (off + sizeof(imm) < inst_size){
void *ptr = (U8*)inst_base + off;
MemoryCopy(&imm, ptr, sizeof(imm));
}
U64 new_sp = sp + 8 + imm;
// commit registers
regs->rip.u64 = new_ip;
regs->rsp.u64 = new_sp;
}break;
// ret / rep; ret
case 0xF3:
{
Assert(!"Hit me!");
}
case 0xC3:
{
// read new ip
U64 sp = regs->rsp.u64;
U64 new_ip = 0;
if (!unw_memview_read_struct(memview, sp, &new_ip)){
missed_read_addr = sp;
goto error_out;
}
// advance stack pointer
U64 new_sp = sp + 8;
// commit registers
regs->rip.u64 = new_ip;
regs->rsp.u64 = new_sp;
}break;
// jmp nnnn
case 0xE9:
{
Assert(!"Hit Me");
// TODO(allen): general idea: read the immediate, move the ip, leave the sp, done
// we don't have any cases to exercise this right now. no guess implementation!
}break;
// jmp n
case 0xEB:
{
Assert(!"Hit Me");
// TODO(allen): general idea: read the immediate, move the ip, leave the sp, done
// we don't have any cases to exercise this right now. no guess implementation!
}break;
}
}
error_out:;
if (missed_read_addr != 0){
result.dead = 1;
result.missed_read = 1;
result.missed_read_addr = missed_read_addr;
}
return(result);
}
internal U32
unw_pe_x64__slot_count_from_op_code(UNW_PE_OpCode opcode){
U32 result = 0;
switch (opcode){
case UNW_PE_OpCode_PUSH_NONVOL: result = 1; break;
case UNW_PE_OpCode_ALLOC_LARGE: result = 2; break;
case UNW_PE_OpCode_ALLOC_SMALL: result = 1; break;
case UNW_PE_OpCode_SET_FPREG: result = 1; break;
case UNW_PE_OpCode_SAVE_NONVOL: result = 2; break;
case UNW_PE_OpCode_SAVE_NONVOL_FAR: result = 3; break;
case UNW_PE_OpCode_EPILOG: result = 2; break;
case UNW_PE_OpCode_SPARE_CODE: result = 3; break;
case UNW_PE_OpCode_SAVE_XMM128: result = 2; break;
case UNW_PE_OpCode_SAVE_XMM128_FAR: result = 3; break;
case UNW_PE_OpCode_PUSH_MACHFRAME: result = 1; break;
}
return(result);
}
internal B32
unw_pe_x64__voff_is_in_epilog(String8 bindata, PE_BinInfo *bin,
U64 voff, PE_IntelPdata *final_pdata){
// NOTE(allen): There are restrictions placed on how an epilog is allowed
// to be formed (https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog?view=msvc-160)
// Here we interpret machine code directly according to the rules
// given there to determine if the code we're looking at looks like an epilog.
// TODO(allen): Figure out how to verify this.
//- setup parsing context
U64 sec_number = pe_section_num_from_voff(bindata, bin, voff);
COFF_SectionHeader *sec = coff_section_header_from_num(bindata, bin->section_array_off, sec_number);
void* inst_base = pe_ptr_from_section_num(bindata, bin, sec_number);
U64 inst_size = sec->vsize;
//- setup parsing variables
B32 is_epilog = 0;
B32 keep_parsing = 1;
U64 off = voff - sec->voff;
//- check first instruction
{
B32 inst_read_success = 0;
U8 inst[4];
if (off + sizeof(inst) < inst_size){
void *ptr = (U8*)inst_base + off;
MemoryCopy(&inst, ptr, sizeof(inst));
inst_read_success = 1;
}
if (!inst_read_success){
keep_parsing = 0;
}
else{
if ((inst[0] & 0xF8) == 0x48){
switch (inst[1]){
// add $nnnn,%rsp
case 0x81:
{
if (inst[0] == 0x48 && inst[2] == 0xC4){
off += 7;
}
else{
keep_parsing = 0;
}
}break;
// add $n,%rsp
case 0x83:
{
if (inst[0] == 0x48 && inst[2] == 0xC4){
off += 4;
}
else{
keep_parsing = 0;
}
}break;
// lea n(reg),%rsp
case 0x8D:
{
if ((inst[0] & 0x06) == 0 &&
((inst[2] >> 3) & 0x07) == 0x04 &&
(inst[2] & 0x07) != 0x04){
U8 imm_size = (inst[2] >> 6);
// 1-byte immediate
if (imm_size == 1){
off += 4;
}
// 4-byte immediate
else if (imm_size == 2){
off += 7;
}
else{
keep_parsing = 0;
}
}
else{
keep_parsing = 0;
}
}break;
}
}
}
}
//- parsing loop
if (keep_parsing){
for (;;){
// read inst
U8 inst_byte = 0;
if (off + sizeof(inst_byte) < inst_size){
void *ptr = (U8*)inst_base + off;
MemoryCopy(&inst_byte, ptr, sizeof(inst_byte));
}
else{
goto loop_break;
}
// when (... I don't know ...) rely on the next byte
U64 check_off = off;
U8 check_inst_byte = inst_byte;
if ((inst_byte & 0xF0) == 0x40){
check_off = off + 1;
if (off + sizeof(check_inst_byte) < inst_size){
void *ptr = (U8*)inst_base + off;
MemoryCopy(&check_inst_byte, ptr, sizeof(check_inst_byte));
}
else{
goto loop_break;
}
}
switch (check_inst_byte){
// pop
case 0x58:case 0x59:case 0x5A:case 0x5B:
case 0x5C:case 0x5D:case 0x5E:case 0x5F:
{
off = check_off + 1;
}break;
// ret
case 0xC2:case 0xC3:
{
is_epilog = 1;
goto loop_break;
}break;
// jmp nnnn
case 0xE9:
{
U64 imm_off = check_off + 1;
S32 imm = 0;
if (off + sizeof(imm) < inst_size){
void *ptr = (U8*)inst_base + off;
MemoryCopy(&imm, ptr, sizeof(imm));
}
else{
goto loop_break;
}
U64 next_off = (U64)(imm_off + sizeof(imm) + imm);
if (!(final_pdata->voff_first <= next_off && next_off < final_pdata->voff_one_past_last)){
goto loop_break;
}
off = next_off;
// TODO(allen): why isn't this just the end of the epilog?
}break;
// rep; ret (for amd64 prediction bug)
case 0xF3:
{
U8 next_inst_byte = 0;
if (off + sizeof(next_inst_byte) < inst_size){
void *ptr = (U8*)inst_base + off;
MemoryCopy(&next_inst_byte, ptr, sizeof(next_inst_byte));
}
is_epilog = (next_inst_byte == 0xC3);
goto loop_break;
}break;
default: goto loop_break;
}
}
loop_break:;
}
//- fill result
B32 result = is_epilog;
return(result);
}
internal REGS_Reg64*
unw_pe_x64__gpr_reg(UNW_X64_Regs *regs, UNW_PE_X64_GprReg unw_reg){
static REGS_Reg64 dummy = {0};
REGS_Reg64 *result = &dummy;
switch (unw_reg){
case UNW_PE_X64_GprReg_RAX: result = &regs->rax; break;
case UNW_PE_X64_GprReg_RCX: result = &regs->rcx; break;
case UNW_PE_X64_GprReg_RDX: result = &regs->rdx; break;
case UNW_PE_X64_GprReg_RBX: result = &regs->rbx; break;
case UNW_PE_X64_GprReg_RSP: result = &regs->rsp; break;
case UNW_PE_X64_GprReg_RBP: result = &regs->rbp; break;
case UNW_PE_X64_GprReg_RSI: result = &regs->rsi; break;
case UNW_PE_X64_GprReg_RDI: result = &regs->rdi; break;
case UNW_PE_X64_GprReg_R8 : result = &regs->r8 ; break;
case UNW_PE_X64_GprReg_R9 : result = &regs->r9 ; break;
case UNW_PE_X64_GprReg_R10: result = &regs->r10; break;
case UNW_PE_X64_GprReg_R11: result = &regs->r11; break;
case UNW_PE_X64_GprReg_R12: result = &regs->r12; break;
case UNW_PE_X64_GprReg_R13: result = &regs->r13; break;
case UNW_PE_X64_GprReg_R14: result = &regs->r14; break;
case UNW_PE_X64_GprReg_R15: result = &regs->r15; break;
}
return(result);
}
+314
View File
@@ -0,0 +1,314 @@
// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
#ifndef UNWIND_H
#define UNWIND_H
////////////////////////////////
//~ allen: Unwind Types
// * applies to (any X,Y: unwind(X, Y))
typedef struct UNW_MemView{
// Upgrade Path:
// 1. A list of ranges like this one
// 2. Binary-searchable list of ranges
// 3. In-line growth strategy for missing pages (hardwired to source of new data)
// 4. Abstracted source of new data
void *data;
U64 addr_first;
U64 addr_opl;
} UNW_MemView;
typedef struct UNW_Result{
B32 dead;
B32 missed_read;
U64 missed_read_addr;
U64 stack_pointer;
} UNW_Result;
////////////////////////////////
//~ allen: X64 Unwind Types
// * applies to (any X: unwind(X, X64))
typedef REGS_RegBlockX64 UNW_X64_Regs;
////////////////////////////////
//~ allen: PE X64 Unwind Types
//- pe format unwind types
#define UNW_PE_OpCodeXList(X) \
X(PUSH_NONVOL , 0) \
X(ALLOC_LARGE , 1) \
X(ALLOC_SMALL , 2) \
X(SET_FPREG , 3) \
X(SAVE_NONVOL , 4) \
X(SAVE_NONVOL_FAR, 5) \
X(EPILOG , 6) \
X(SPARE_CODE , 7) \
X(SAVE_XMM128 , 8) \
X(SAVE_XMM128_FAR, 9) \
X(PUSH_MACHFRAME , 10)
#define UNW_PE_CODE_FROM_FLAGS(f) ((f)&0xF)
#define UNW_PE_INFO_FROM_FLAGS(f) (((f) >> 4)&0xF)
typedef U32 UNW_PE_OpCode;
enum UNW_PE_OpCodeEnum{
#define X(N,C) UNW_PE_OpCode_##N = C,
UNW_PE_OpCodeXList(X)
#undef X
};
typedef union UNW_PE_Code{
struct{
U8 off_in_prolog;
U8 flags;
};
U16 u16;
} UNW_PE_Code;
typedef U8 UNW_PE_InfoFlags;
enum UNW_PE_InfoFlagsEnum{
UNW_PE_InfoFlag_EHANDLER = (1 << 0),
UNW_PE_InfoFlag_UHANDLER = (1 << 1),
UNW_PE_InfoFlag_FHANDLER = 3,
UNW_PE_InfoFlag_CHAINED = (1 << 2),
} UNW_PE_InfoFlagsEnum;
#define UNW_PE_INFO_VERSION_FROM_HDR(x) ((x)&0x7)
#define UNW_PE_INFO_FLAGS_FROM_HDR(x) (((x) >> 3)&0x1F)
#define UNW_PE_INFO_REG_FROM_FRAME(x) ((x)&0xF)
#define UNW_PE_INFO_OFF_FROM_FRAME(x) (((x) >> 4)&0xF)
typedef struct UNW_PE_Info{
U8 header;
U8 prolog_size;
U8 codes_num;
U8 frame;
} UNW_PE_Info;
////////////////////////////////
//~ allen: PE X64 Unwind Types
// * applies to (unwind(PE, X64))
typedef U8 UNW_PE_X64_GprReg;
enum{
UNW_PE_X64_GprReg_RAX = 0,
UNW_PE_X64_GprReg_RCX = 1,
UNW_PE_X64_GprReg_RDX = 2,
UNW_PE_X64_GprReg_RBX = 3,
UNW_PE_X64_GprReg_RSP = 4,
UNW_PE_X64_GprReg_RBP = 5,
UNW_PE_X64_GprReg_RSI = 6,
UNW_PE_X64_GprReg_RDI = 7,
UNW_PE_X64_GprReg_R8 = 8,
UNW_PE_X64_GprReg_R9 = 9,
UNW_PE_X64_GprReg_R10 = 10,
UNW_PE_X64_GprReg_R11 = 11,
UNW_PE_X64_GprReg_R12 = 12,
UNW_PE_X64_GprReg_R13 = 13,
UNW_PE_X64_GprReg_R14 = 14,
UNW_PE_X64_GprReg_R15 = 15,
};
////////////////////////////////
//~ allen: ELF/DW Unwind Types
// * applies to (any X: unwind(ELF/DW, X))
// EH: Exception Frames
typedef U8 UNW_DW_EhPtrEnc;
enum{
UNW_DW_EhPtrEnc_TYPE_MASK = 0x0F,
UNW_DW_EhPtrEnc_PTR = 0x00, // Pointer sized unsigned value
UNW_DW_EhPtrEnc_ULEB128 = 0x01, // Unsigned LE base-128 value
UNW_DW_EhPtrEnc_UDATA2 = 0x02, // Unsigned 16-bit value
UNW_DW_EhPtrEnc_UDATA4 = 0x03, // Unsigned 32-bit value
UNW_DW_EhPtrEnc_UDATA8 = 0x04, // Unsigned 64-bit value
UNW_DW_EhPtrEnc_SIGNED = 0x08, // Signed pointer
UNW_DW_EhPtrEnc_SLEB128 = 0x09, // Signed LE base-128 value
UNW_DW_EhPtrEnc_SDATA2 = 0x0A, // Signed 16-bit value
UNW_DW_EhPtrEnc_SDATA4 = 0x0B, // Signed 32-bit value
UNW_DW_EhPtrEnc_SDATA8 = 0x0C, // Signed 64-bit value
};
enum{
UNW_DW_EhPtrEnc_MODIF_MASK = 0x70,
UNW_DW_EhPtrEnc_PCREL = 0x10, // Value is relative to the current program counter.
UNW_DW_EhPtrEnc_TEXTREL = 0x20, // Value is relative to the .text section.
UNW_DW_EhPtrEnc_DATAREL = 0x30, // Value is relative to the .got or .eh_frame_hdr section.
UNW_DW_EhPtrEnc_FUNCREL = 0x40, // Value is relative to the function.
UNW_DW_EhPtrEnc_ALIGNED = 0x50, // Value is aligned to an address unit sized boundary.
};
enum{
UNW_DW_EhPtrEnc_INDIRECT = 0x80, // This flag indicates that value is stored in virtual memory.
UNW_DW_EhPtrEnc_OMIT = 0xFF,
};
typedef struct UNW_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
} UNW_DW_EhPtrCtx;
// CIE: Common Information Entry
typedef struct UNW_DW_CIEUnpacked{
U8 version;
UNW_DW_EhPtrEnc lsda_encoding;
UNW_DW_EhPtrEnc addr_encoding;
B8 has_augmentation_size;
U64 augmentation_size;
String8 augmentation;
U64 code_align_factor;
S64 data_align_factor;
U64 ret_addr_reg;
U64 handler_ip;
U64 cfi_range_min;
U64 cfi_range_max;
} UNW_DW_CIEUnpacked;
typedef struct UNW_DW_CIEUnpackedNode{
struct UNW_DW_CIEUnpackedNode *next;
UNW_DW_CIEUnpacked cie;
U64 offset;
} UNW_DW_CIEUnpackedNode;
// FDE: Frame Description Entry
typedef struct UNW_DW_FDEUnpacked{
U64 ip_voff_min;
U64 ip_voff_max;
U64 lsda_ip;
U64 cfi_range_min;
U64 cfi_range_max;
} UNW_DW_FDEUnpacked;
// CFI: Call Frame Information
typedef struct UNW_DW_CFIRecords{
B32 valid;
UNW_DW_CIEUnpacked cie;
UNW_DW_FDEUnpacked fde;
} UNW_DW_CFIRecords;
typedef enum UNW_DW_CFICFARule{
UNW_DW_CFICFARule_REGOFF,
UNW_DW_CFICFARule_EXPR,
} UNW_DW_CFICFARule;
typedef struct UNW_DW_CFICFACell{
UNW_DW_CFICFARule rule;
union{
struct{
U64 reg_idx;
S64 offset;
};
U64 expr_min;
U64 expr_max;
};
} UNW_DW_CFICFACell;
typedef enum UNW_DW_CFIRegisterRule{
UNW_DW_CFIRegisterRule_SAME_VALUE,
UNW_DW_CFIRegisterRule_UNDEFINED,
UNW_DW_CFIRegisterRule_OFFSET,
UNW_DW_CFIRegisterRule_VAL_OFFSET,
UNW_DW_CFIRegisterRule_REGISTER,
UNW_DW_CFIRegisterRule_EXPRESSION,
UNW_DW_CFIRegisterRule_VAL_EXPRESSION,
} UNW_DW_CFIRegisterRule;
typedef struct UNW_DW_CFICell{
UNW_DW_CFIRegisterRule rule;
union{
S64 n;
struct{
U64 expr_min;
U64 expr_max;
};
};
} UNW_DW_CFICell;
typedef struct UNW_DW_CFIRow{
struct UNW_DW_CFIRow *next;
UNW_DW_CFICell *cells;
UNW_DW_CFICFACell cfa_cell;
} UNW_DW_CFIRow;
typedef struct UNW_DW_CFIMachine{
U64 cells_per_row;
UNW_DW_CIEUnpacked *cie;
UNW_DW_EhPtrCtx *ptr_ctx;
UNW_DW_CFIRow *initial_row;
U64 fde_ip;
} UNW_DW_CFIMachine;
typedef U8 UNW_DW_CFADecode;
enum{
UNW_DW_CFADecode_NOP = 0x0,
// 1,2,4,8 reserved for literal byte sizes
UNW_DW_CFADecode_ADDRESS = 0x9,
UNW_DW_CFADecode_ULEB128 = 0xA,
UNW_DW_CFADecode_SLEB128 = 0xB,
};
typedef U16 UNW_DW_CFAControlBits;
enum{
UNW_DW_CFAControlBits_DEC1_MASK = 0x00F,
UNW_DW_CFAControlBits_DEC2_MASK = 0x0F0,
UNW_DW_CFAControlBits_IS_REG_0 = 0x100,
UNW_DW_CFAControlBits_IS_REG_1 = 0x200,
UNW_DW_CFAControlBits_IS_REG_2 = 0x400,
UNW_DW_CFAControlBits_NEW_ROW = 0x800,
};
////////////////////////////////
//~ allen: Unwind Functions
//- mem view construction
internal UNW_MemView unw_memview_from_data(String8 data, U64 base_vaddr);
//- mem view user face for unwind users
internal B32 unw_memview_read(UNW_MemView *memview, U64 addr, U64 size, void *out);
#define unw_memview_read_struct(v,addr,p) unw_memview_read((v), (addr), sizeof(*(p)), (p))
////////////////////////////////
//~ allen: PE X64 Unwind Functions
//- main interface
internal UNW_Result unw_pe_x64(String8 bindata, PE_BinInfo *bin,
U64 base_vaddr, UNW_MemView *memview,
UNW_X64_Regs *regs_inout);
//- pe x64 helpers
internal UNW_Result unw_pe_x64__epilog(String8 bindata, PE_BinInfo *bin,
U64 base_vaddr, UNW_MemView*memview,
UNW_X64_Regs *regs_inout);
internal U32 unw_pe_x64__slot_count_from_op_code(UNW_PE_OpCode opcode);
internal B32 unw_pe_x64__voff_is_in_epilog(String8 bindata, PE_BinInfo *bin,
U64 voff, PE_IntelPdata *final_pdata);
internal REGS_Reg64 *unw_pe_x64__gpr_reg(UNW_X64_Regs *regs, UNW_PE_X64_GprReg unw_reg);
#endif //UNWIND_H