#ifndef _LIBCRUN_
#include "lint.h"
#endif
#include <sys/types.h>
#include "stack_unwind.h"
#include "unwind_context.h"
#include "reg_num.h"
enum CFA_ops {
DW_CFA_nop = 0x00,
DW_CFA_set_loc = 0x01,
DW_CFA_advance_loc1 = 0x02,
DW_CFA_advance_loc2 = 0x03,
DW_CFA_advance_loc4 = 0x04,
DW_CFA_offset_extended = 0x05,
DW_CFA_restore_extended = 0x06,
DW_CFA_undefined = 0x07,
DW_CFA_same_value = 0x08,
DW_CFA_register = 0x09,
DW_CFA_remember_state = 0x0a,
DW_CFA_restore_state = 0x0b,
DW_CFA_def_cfa = 0x0c,
DW_CFA_def_cfa_register = 0x0d,
DW_CFA_def_cfa_offset = 0x0e,
DW_CFA_def_cfa_expression = 0x0f,
DW_CFA_expression = 0x10,
DW_CFA_offset_extended_sf = 0x11,
DW_CFA_def_cfa_sf = 0x12,
DW_CFA_def_cfa_offset_sf = 0x13,
DW_CFA_SUNW_advance_loc = 0x1d,
DW_CFA_SUNW_offset = 0x1e,
DW_CFA_SUNW_restore = 0x1f,
DW_CFA_advance_loc = 0x40,
DW_CFA_offset = 0x80,
DW_CFA_restore = 0xc0
};
struct operation_desc {
enum operand_desc op1;
enum operand_desc op2;
};
struct operation_desc cfa_operations[] = {
{NO_OPR, NO_OPR},
{ADDR, NO_OPR},
{UNUM8, NO_OPR},
{UNUM16, NO_OPR},
{UNUM32, NO_OPR},
{ULEB128, ULEB128_FAC},
{ULEB128, NO_OPR},
{ULEB128, NO_OPR},
{ULEB128, NO_OPR},
{ULEB128, ULEB128_SREG},
{NO_OPR, NO_OPR},
{NO_OPR, NO_OPR},
{ULEB128_SREG, ULEB128},
{ULEB128_SREG, NO_OPR},
{ULEB128, NO_OPR},
{BLOCK, NO_OPR},
{ULEB128, BLOCK},
{ULEB128, SLEB128_FAC},
{ULEB128_SREG, SLEB128_FAC},
{SLEB128_FAC, NO_OPR},
{NO_OPR, NO_OPR},
{NO_OPR, NO_OPR},
{NO_OPR, NO_OPR},
{NO_OPR, NO_OPR},
{NO_OPR, NO_OPR},
{NO_OPR, NO_OPR},
{NO_OPR, NO_OPR},
{NO_OPR, NO_OPR},
{NO_OPR, NO_OPR},
{UNUM6_CFAC, NO_OPR},
{UNUM6, ULEB128_FAC},
{UNUM6, NO_OPR}
};
uint64_t interpret_ops(void *data, void *data_end,
ptrdiff_t reloc, uint64_t current_loc, uint64_t pc,
struct register_state f_state[],
struct register_state f_start_state[],
int daf, int caf, int enc);
void
_Unw_Propagate_Registers(struct _Unwind_Context *old_ctx,
struct _Unwind_Context *new_ctx)
{
new_ctx->current_regs[SP_RSP] = old_ctx->cfa;
new_ctx->pc = old_ctx->ra;
new_ctx->current_regs[FP_RBP] = old_ctx->entry_regs[FP_RBP];
new_ctx->current_regs[GPR_RBX] = old_ctx->entry_regs[GPR_RBX];
new_ctx->current_regs[EIR_R12] = old_ctx->entry_regs[EIR_R12];
new_ctx->current_regs[EIR_R13] = old_ctx->entry_regs[EIR_R13];
new_ctx->current_regs[EIR_R14] = old_ctx->entry_regs[EIR_R14];
new_ctx->current_regs[EIR_R15] = old_ctx->entry_regs[EIR_R15];
}
void
fix_cfa(struct _Unwind_Context *ctx, struct register_state *rs)
{
switch (rs[CF_ADDR].rule) {
default:
ctx->cfa = 0;
break;
case register_rule:
ctx->cfa = (ctx->current_regs)[rs[CF_ADDR].source_reg] +
rs[CF_ADDR].offset;
break;
case constant_rule:
ctx->cfa = rs[CF_ADDR].offset;
break;
case indirect_rule:
ctx->cfa = *(uint64_t *)
(ctx->current_regs[rs[CF_ADDR].source_reg] +
rs[CF_ADDR].offset);
break;
}
ctx->entry_regs[SP_RSP] = ctx->cfa;
}
void
fix_ra(struct _Unwind_Context *ctx, struct register_state *rs)
{
switch (rs[RET_ADD].rule) {
case undefined_rule:
default:
ctx->ra = 0;
break;
case offset_rule:
ctx->ra = *(uint64_t *)(ctx->cfa + rs[RET_ADD].offset);
break;
case register_rule:
ctx->ra = ctx->current_regs[rs[RET_ADD].source_reg] +
rs[RET_ADD].offset;
break;
case indirect_rule:
ctx->ra = *(uint64_t *)
(ctx->current_regs[rs[RET_ADD].source_reg] +
rs[RET_ADD].offset);
break;
}
}
void
fix_reg(struct _Unwind_Context *ctx, struct register_state *rs, int index)
{
switch (rs[index].rule) {
default:
ctx->entry_regs[index] = ctx->current_regs[index];
break;
case offset_rule:
ctx->entry_regs[index] = *(uint64_t *)
(ctx->cfa + rs[index].offset);
break;
case is_offset_rule:
ctx->entry_regs[index] = ctx->cfa + rs[index].offset;
break;
case register_rule:
ctx->entry_regs[index] =
ctx->current_regs[rs[index].source_reg] +
rs[index].offset;
break;
case constant_rule:
ctx->entry_regs[index] = rs[index].offset;
break;
case indirect_rule:
ctx->entry_regs[index] = *(uint64_t *)
(ctx->current_regs[rs[index].source_reg] +
rs[index].offset);
break;
}
}
uint64_t
_Unw_Rollback_Registers(struct eh_frame_fields *f,
struct _Unwind_Context *ctx)
{
struct register_state func_state[18];
struct register_state func_start_state[18];
struct register_state nop = { 0, undefined_rule, 0 };
int i;
uint64_t first_pc;
if (f == 0) {
if (ctx->current_regs[FP_RBP] < ctx->current_regs[SP_RSP]) {
ctx->cfa = 0;
ctx->ra = 0;
ctx->pc = 0;
return (0);
}
ctx->entry_regs[FP_RBP] = ((uint64_t *)
(ctx->current_regs[FP_RBP]))[0];
ctx->cfa = ctx->current_regs[FP_RBP] + 16;
ctx->entry_regs[SP_RSP] = ctx->cfa;
ctx->entry_regs[GPR_RBX] = ctx->current_regs[GPR_RBX];
ctx->entry_regs[EIR_R12] = ctx->current_regs[EIR_R12];
ctx->entry_regs[EIR_R13] = ctx->current_regs[EIR_R13];
ctx->entry_regs[EIR_R14] = ctx->current_regs[EIR_R14];
ctx->entry_regs[EIR_R15] = ctx->current_regs[EIR_R15];
ctx->ra = ((uint64_t *)ctx->cfa)[-1];
return (ctx->cfa);
}
for (i = 0; i < 18; i++)
func_start_state[i] = nop;
first_pc = interpret_ops(f->cie_ops, f->cie_ops_end,
f->cie_reloc, ctx->func, ctx->pc, func_start_state, 0,
f->data_align, f->code_align, f->code_enc);
for (i = 0; i < 18; i++)
func_state[i] = func_start_state[i];
(void) interpret_ops(f->fde_ops, f->fde_ops_end,
f->fde_reloc, first_pc, ctx->pc, func_state, func_start_state,
f->data_align, f->code_align, f->code_enc);
fix_cfa(ctx, func_state);
if (ctx->cfa < ctx->current_regs[SP_RSP]) {
ctx->cfa = 0;
ctx->ra = 0;
ctx->pc = 0;
return (0);
}
fix_ra(ctx, func_state);
fix_reg(ctx, func_state, GPR_RBX);
fix_reg(ctx, func_state, FP_RBP);
fix_reg(ctx, func_state, EIR_R12);
fix_reg(ctx, func_state, EIR_R13);
fix_reg(ctx, func_state, EIR_R14);
fix_reg(ctx, func_state, EIR_R15);
return (ctx->cfa);
}
static enum CFA_ops
separate_op(void **pp)
{
uint8_t c = **((uint8_t **)pp);
if (c & 0xc0) {
switch (c & 0xc0) {
case DW_CFA_advance_loc:
return (DW_CFA_SUNW_advance_loc);
case DW_CFA_offset:
return (DW_CFA_SUNW_offset);
case DW_CFA_restore:
return (DW_CFA_SUNW_restore);
}
} else {
*pp = (void *)((*(intptr_t *)pp) + 1);
}
return (c);
}
static uint64_t
extractuleb(void **datap)
{
uint8_t *data = *(uint8_t **)datap;
uint64_t res = 0;
int more = 1;
int shift = 0;
int val;
while (more) {
val = (*data) & 0x7f;
more = ((*data++) & 0x80) >> 7;
res = res | val << shift;
shift += 7;
}
*datap = (void *)data;
return (res);
}
static uint64_t
extractsleb(void** datap)
{
uint8_t *data = *datap;
int64_t res = 0;
int more = 1;
int shift = 0;
unsigned int val;
while (more) {
val = (*data) & 0x7f;
more = ((*data++) & 0x80) >> 7;
res = res | val<< shift;
shift += 7;
}
*datap = (void*) data;
res = (res << (64 - shift)) >> (64 - shift);
return (res);
}
static uint64_t get_encoded_val(void **datap, ptrdiff_t reloc, int enc);
uint64_t
_Unw_get_val(void **datap, ptrdiff_t reloc,
enum operand_desc opr, int daf, int caf, int enc)
{
intptr_t data = (intptr_t)*datap;
uint64_t res;
char *dp, *rp;
switch (opr) {
case NO_OPR:
res = 0;
break;
case ULEB128_FAC:
return (daf * extractuleb(datap));
case ULEB128:
return (extractuleb(datap));
case ULEB128_SREG:
res = (uint64_t)(*((uint8_t *)data));
data += 1;
switch (res) {
case GPR_RBX:
case FP_RBP:
case SP_RSP:
case EIR_R12:
case EIR_R13:
case EIR_R14:
case EIR_R15:
break;
default:
res = BAD_REG;
break;
}
break;
case UNUM6:
res = (uint64_t)(0x3f & *((uint8_t *)data));
data += 1;
break;
case UNUM8:
res = (uint64_t)(*((uint8_t *)data));
data += 1;
break;
case UNUM16:
res = (uint64_t)(*((uint16_t *)data));
data += 2;
break;
case UNUM32:
res = (uint64_t)(*((uint32_t *)data));
data += 4;
break;
case UNUM6_CFAC:
res = caf * (uint64_t)(0x3f & *((uint8_t *)data));
data += 1;
break;
case UNUM8_CFAC:
res = caf * (uint64_t)(*((uint8_t *)data));
data += 1;
break;
case UNUM16_CFAC:
res = caf * (uint64_t)(*((uint16_t *)data));
data += 2;
break;
case UNUM32_CFAC:
res = caf * (uint64_t)(*((uint32_t *)data));
data += 4;
break;
case UNUM64:
res = (uint64_t)(*((uint64_t *)data));
data += 8;
break;
case SNUM8:
res = (uint64_t)(int64_t)(*((int8_t *)data));
data += 1;
break;
case SNUM16:
res = (uint64_t)(int64_t)(*((int16_t *)data));
data += 2;
break;
case SNUM32:
res = (uint64_t)(int64_t)(*((int32_t *)data));
data += 4;
break;
case SNUM64:
res = (uint64_t)(*((int64_t *)data));
data += 8;
break;
case SLEB128_FAC:
return (daf * extractsleb(datap));
case SLEB128:
return (extractsleb(datap));
case ZTSTRING:
rp = (char *)&res;
dp = (char *)data;
while ((*rp++ = *dp++) != '\0')
;
data = (intptr_t)dp;
break;
case ADDR:
return (get_encoded_val(datap, reloc, enc));
case SIZE:
return (get_encoded_val(datap, reloc, enc & 0x7));
case BLOCK:
res = 0;
break;
}
*datap = (void*)data;
return (res);
}
static uint64_t
get_encoded_val(void **datap, ptrdiff_t reloc, int enc)
{
const uint8_t val = enc & 0xf;
const uint8_t rel = enc & 0x70;
const boolean_t indirect = (enc & 0x80) != 0;
intptr_t loc = ((intptr_t)*datap) + reloc;
uint64_t res = 0;
switch (val) {
case 0x01:
res = _Unw_get_val(datap, reloc, ULEB128, 1, 1, 0);
break;
case 0x02:
res = _Unw_get_val(datap, reloc, UNUM16, 1, 1, 0);
break;
case 0x03:
res = _Unw_get_val(datap, reloc, UNUM32, 1, 1, 0);
break;
case 0x04:
res = _Unw_get_val(datap, reloc, UNUM64, 1, 1, 0);
break;
case 0x09:
res = _Unw_get_val(datap, reloc, SLEB128, 1, 1, 0);
break;
case 0x0a:
res = _Unw_get_val(datap, reloc, SNUM16, 1, 1, 0);
break;
case 0x0b:
res = _Unw_get_val(datap, reloc, SNUM32, 1, 1, 0);
break;
case 0x0c:
res = _Unw_get_val(datap, reloc, SNUM64, 1, 1, 0);
break;
}
switch (rel) {
case 0x00:
break;
case 0x10:
if (res != 0)
res += loc;
break;
default:
break;
}
if (indirect) {
void *addr = (void *)(uintptr_t)res;
res = _Unw_get_val(&addr, reloc, UNUM64, 1, 1, 0);
}
return (res);
}
int interpret_op(void **datap, ptrdiff_t reloc,
uint64_t *reached_pc_p, uint64_t pc,
struct register_state f_state[],
struct register_state f_start_state[],
int daf, int caf, int enc);
uint64_t
interpret_ops(void *data, void *data_end,
ptrdiff_t reloc,
uint64_t start_pc, uint64_t pc,
struct register_state f_state[],
struct register_state f_start_state[],
int daf, int caf, int enc)
{
void *d = data;
uint64_t reached_pc = start_pc;
while (d < data_end) {
if (interpret_op(&d, reloc, &reached_pc, pc,
f_state, f_start_state, daf, caf, enc))
break;
}
return (reached_pc);
}
int
interpret_op(void **datap, ptrdiff_t reloc,
uint64_t *reached_pc_p, uint64_t pc,
struct register_state f_state[],
struct register_state f_start_state[],
int daf, int caf, int enc)
{
enum CFA_ops op = separate_op(datap);
enum operand_desc opr1 = (cfa_operations[op]).op1;
enum operand_desc opr2 = (cfa_operations[op]).op2;
uint64_t val1 = _Unw_get_val(datap, reloc, opr1, daf, caf, enc);
uint64_t val2 = _Unw_get_val(datap, reloc, opr2, daf, caf, enc);
if ((opr1 == ULEB128_SREG && val1 == BAD_REG) ||
(opr2 == ULEB128_SREG && val2 == BAD_REG))
return (0);
switch (op) {
case DW_CFA_nop:
break;
case DW_CFA_set_loc:
if (val1 > pc)
return (1);
*reached_pc_p = val1;
break;
case DW_CFA_advance_loc1:
case DW_CFA_advance_loc2:
case DW_CFA_advance_loc4:
if (*reached_pc_p + val1 > pc)
return (1);
*reached_pc_p += val1;
break;
case DW_CFA_offset_extended:
f_state[val1].rule = offset_rule;
f_state[val1].source_reg = CF_ADDR;
f_state[val1].offset = val2;
break;
case DW_CFA_restore_extended:
if (f_start_state != 0)
f_state[val1] = f_start_state[val1];
break;
case DW_CFA_undefined:
f_state[val1].rule = undefined_rule;
break;
case DW_CFA_same_value:
f_state[val1].rule = same_value_rule;
break;
case DW_CFA_register:
f_state[val1].rule = register_rule;
f_state[val1].source_reg = val2;
f_state[val1].offset = 0;
break;
case DW_CFA_remember_state:
break;
case DW_CFA_restore_state:
break;
case DW_CFA_def_cfa:
f_state[CF_ADDR].rule = register_rule;
f_state[CF_ADDR].source_reg = val1;
f_state[CF_ADDR].offset = val2;
break;
case DW_CFA_def_cfa_register:
f_state[CF_ADDR].source_reg = val1;
break;
case DW_CFA_def_cfa_offset:
f_state[CF_ADDR].offset = val1;
break;
case DW_CFA_def_cfa_expression:
break;
case DW_CFA_expression:
break;
case DW_CFA_offset_extended_sf:
f_state[val1].rule = offset_rule;
f_state[val1].source_reg = CF_ADDR;
f_state[val1].offset = val2;
break;
case DW_CFA_def_cfa_sf:
f_state[CF_ADDR].rule = register_rule;
f_state[CF_ADDR].source_reg = val1;
f_state[CF_ADDR].offset = val2;
break;
case DW_CFA_def_cfa_offset_sf:
f_state[CF_ADDR].offset = val1;
break;
case DW_CFA_SUNW_advance_loc:
if (*reached_pc_p + val1 > pc)
return (1);
*reached_pc_p += val1;
break;
case DW_CFA_SUNW_offset:
f_state[val1].rule = offset_rule;
f_state[val1].source_reg = CF_ADDR;
f_state[val1].offset = val2;
break;
case DW_CFA_SUNW_restore:
if (f_start_state != 0)
f_state[val1] = f_start_state[val1];
break;
}
return (0);
}