view usr/src/lib/libc/amd64/unwind/call_frame_inst.c @ 13622:e5889df1eaac

2077 lots of unreachable breaks in illumos gate Reviewed by: Dan McDonald <danmcd@nexenta.com> Reviewed by: Garrett D'Amore <garrett@damore.org> Approved by: Richard Lowe <richlowe@richlowe.net>
author Milan Jurik <milan.jurik@xylab.cz>
date Sat, 18 Feb 2012 19:52:02 +0100
parents febeba71273d
children
line wrap: on
line source

/*
 * 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);
}