/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
* Copyright 2012 Milan Jurik. All rights reserved.
*/
/*
* interface used by unwind support to query frame descriptor info
*/
#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,
/* skip 9 values */
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}, /* DW_CFA_nop */
{ADDR, NO_OPR}, /* DW_CFA_set_loc - address */
{UNUM8, NO_OPR}, /* DW_CFA_advance_loc1 - delta */
{UNUM16, NO_OPR}, /* DW_CFA_advance_loc2 - delta */
{UNUM32, NO_OPR}, /* DW_CFA_advance_loc4 - delta */
{ULEB128, ULEB128_FAC}, /* DW_CFA_offset_extended - reg, */
/* data factored offset */
{ULEB128, NO_OPR}, /* DW_CFA_restore_extended - register */
{ULEB128, NO_OPR}, /* DW_CFA_undefined - register */
{ULEB128, NO_OPR}, /* DW_CFA_same_value - register */
{ULEB128, ULEB128_SREG}, /* DW_CFA_register - register, register */
{NO_OPR, NO_OPR}, /* DW_CFA_remember_state */
{NO_OPR, NO_OPR}, /* DW_CFA_restore_state */
{ULEB128_SREG, ULEB128}, /* DW_CFA_def_cfa - register, offset */
{ULEB128_SREG, NO_OPR}, /* DW_CFA_def_cfa_register - register */
{ULEB128, NO_OPR}, /* DW_CFA_def_cfa_offset - offset */
{BLOCK, NO_OPR}, /* DW_CFA_def_cfa_expression - expression */
{ULEB128, BLOCK}, /* DW_CFA_expression - reg, expression */
{ULEB128, SLEB128_FAC}, /* DW_CFA_offset_extended_sf - reg, */
/* data factored offset */
{ULEB128_SREG, SLEB128_FAC}, /* DW_CFA_def_cfa_sf - reg, */
/* data factored offset */
{SLEB128_FAC, NO_OPR}, /* DW_CFA_def_cfa_offset_sf - */
/* data fctored offset */
{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}, /* DW_CFA_SUNW_advance_loc - */
/* code factored delta */
{UNUM6, ULEB128_FAC}, /* DW_CFA_SUNW_offset - reg */
/* data factored offset */
{UNUM6, NO_OPR} /* DW_CFA_SUNW_restore */
};
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);
/*
* The entry-point state of old_ctx defines the current
* suspended state of the caller (in new_ctx). If the old info
* will not be refered to again, old_ctx == new_ctx is OK
*/
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: /* CFA = offset + source_reg */
ctx->cfa = (ctx->current_regs)[rs[CF_ADDR].source_reg] +
rs[CF_ADDR].offset;
break;
case constant_rule: /* CFA = offset */
ctx->cfa = rs[CF_ADDR].offset;
break;
case indirect_rule: /* CFA = *(offset + source_reg) */
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: /* RA = *(offset + CFA) */
ctx->ra = *(uint64_t *)(ctx->cfa + rs[RET_ADD].offset);
break;
case register_rule: /* RA = offset + source_reg */
ctx->ra = ctx->current_regs[rs[RET_ADD].source_reg] +
rs[RET_ADD].offset;
break;
case indirect_rule: /* RA = *(offset + source_reg) */
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: /* target_reg = *(offset + CFA) */
ctx->entry_regs[index] = *(uint64_t *)
(ctx->cfa + rs[index].offset);
break;
case is_offset_rule: /* target_reg = offset + CFA */
ctx->entry_regs[index] = ctx->cfa + rs[index].offset;
break;
case register_rule: /* target_reg = offset + source_reg */
ctx->entry_regs[index] =
ctx->current_regs[rs[index].source_reg] +
rs[index].offset;
break;
case constant_rule: /* target_reg = offset */
ctx->entry_regs[index] = rs[index].offset;
break;
case indirect_rule: /* target_reg = *(offset + source_reg) */
ctx->entry_regs[index] = *(uint64_t *)
(ctx->current_regs[rs[index].source_reg] +
rs[index].offset);
break;
}
}
/*
* Input: f->{cie_ops, cie_ops_end, fde_ops, fde_ops_end}
* + location of DWARF opcodes
* ctx->{current_regs, pc}
* + register values and pc at point of suspension
* Output: ctx->{entry_regs, cfa, ra}
* + register values when function was entered
* + Cannonical Frame Address
* + return address
*/
uint64_t
_Unw_Rollback_Registers(struct eh_frame_fields *f,
struct _Unwind_Context *ctx)
{
/* GPRs, RET_ADD, and CF_ADDR */
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) {
/*
* When no FDE we assume all routines have a frame pointer
* and pass back existing callee saves registers
*/
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);
}
/*
* remap two-bit opcodes into a separate range or grab eight-bit opcode
* and advance pointer past it.
*/
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);
/*
* do all field extractions needed for CFA operands and encoded FDE
* fields
*/
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) {
/* verify that register is one which is being tracked */
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:
/* max length of augmentation string is 4 */
rp = (char *)&res;
dp = (char *)data;
while (*rp++ = *dp++)
;
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; /* not implemented */
break;
}
*datap = (void*)data;
return (res);
}
static uint64_t
get_encoded_val(void **datap, ptrdiff_t reloc, int enc)
{
int val = enc & 0xf;
int rel = (enc >> 4) & 0xf;
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 0x2:
res = _Unw_get_val(datap, reloc, UNUM16, 1, 1, 0);
break;
case 0x3:
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 0:
break;
case 1:
if (res != 0)
res += loc;
break;
default:
/* remainder not implemented */
break;
}
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);
}