#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "cfi.h"
#include <stdlib.h>
#include "libdwflP.h"
#include "dwarf.h"
#include <system.h>
#define DWARF_EXPR_STACK_MAX 0x100
#define DWARF_EXPR_STEPS_MAX 0x1000
int
internal_function
__libdwfl_frame_reg_get (Dwfl_Frame *state, unsigned regno, Dwarf_Addr *val)
{
Ebl *ebl = state->thread->process->ebl;
if (! ebl_dwarf_to_regno (ebl, ®no))
return -1;
if (regno >= ebl_frame_nregs (ebl))
return -1;
if ((state->regs_set[regno / sizeof (*state->regs_set) / 8]
& ((uint64_t) 1U << (regno % (sizeof (*state->regs_set) * 8)))) == 0)
return 1;
if (val)
*val = state->regs[regno];
return 0;
}
bool
internal_function
__libdwfl_frame_reg_set (Dwfl_Frame *state, unsigned regno, Dwarf_Addr val)
{
Ebl *ebl = state->thread->process->ebl;
if (! ebl_dwarf_to_regno (ebl, ®no))
return false;
if (regno >= ebl_frame_nregs (ebl))
return false;
if (ebl_get_elfclass (ebl) == ELFCLASS32)
val &= 0xffffffff;
state->regs_set[regno / sizeof (*state->regs_set) / 8] |=
((uint64_t) 1U << (regno % (sizeof (*state->regs_set) * 8)));
state->regs[regno] = val;
return true;
}
static int
bra_compar (const void *key_voidp, const void *elem_voidp)
{
Dwarf_Word offset = (uintptr_t) key_voidp;
const Dwarf_Op *op = elem_voidp;
return (offset > op->offset) - (offset < op->offset);
}
struct eval_stack {
Dwarf_Addr *addrs;
size_t used;
size_t allocated;
};
static bool
do_push (struct eval_stack *stack, Dwarf_Addr val)
{
if (stack->used >= DWARF_EXPR_STACK_MAX)
{
__libdwfl_seterrno (DWFL_E_INVALID_DWARF);
return false;
}
if (stack->used == stack->allocated)
{
stack->allocated = MAX (stack->allocated * 2, 32);
Dwarf_Addr *new_addrs;
new_addrs = realloc (stack->addrs,
stack->allocated * sizeof (*stack->addrs));
if (new_addrs == NULL)
{
__libdwfl_seterrno (DWFL_E_NOMEM);
return false;
}
stack->addrs = new_addrs;
}
stack->addrs[stack->used++] = val;
return true;
}
static bool
do_pop (struct eval_stack *stack, Dwarf_Addr *val)
{
if (stack->used == 0)
{
__libdwfl_seterrno (DWFL_E_INVALID_DWARF);
return false;
}
*val = stack->addrs[--stack->used];
return true;
}
static bool
expr_eval (Dwfl_Frame *state, Dwarf_Frame *frame, const Dwarf_Op *ops,
size_t nops, Dwarf_Addr *result, Dwarf_Addr bias)
{
Dwfl_Process *process = state->thread->process;
if (nops == 0)
{
__libdwfl_seterrno (DWFL_E_INVALID_DWARF);
return false;
}
struct eval_stack stack =
{
.addrs = NULL,
.used = 0,
.allocated = 0
};
#define pop(x) do_pop(&stack, x)
#define push(x) do_push(&stack, x)
Dwarf_Addr val1, val2;
bool is_location = false;
size_t steps_count = 0;
for (const Dwarf_Op *op = ops; op < ops + nops; op++)
{
if (++steps_count > DWARF_EXPR_STEPS_MAX)
{
__libdwfl_seterrno (DWFL_E_INVALID_DWARF);
return false;
}
switch (op->atom)
{
case DW_OP_lit0 ... DW_OP_lit31:
if (! push (op->atom - DW_OP_lit0))
{
free (stack.addrs);
return false;
}
break;
case DW_OP_addr:
if (! push (op->number + bias))
{
free (stack.addrs);
return false;
}
break;
case DW_OP_GNU_encoded_addr:
__libdwfl_seterrno (DWFL_E_UNSUPPORTED_DWARF);
return false;
case DW_OP_const1u:
case DW_OP_const1s:
case DW_OP_const2u:
case DW_OP_const2s:
case DW_OP_const4u:
case DW_OP_const4s:
case DW_OP_const8u:
case DW_OP_const8s:
case DW_OP_constu:
case DW_OP_consts:
if (! push (op->number))
{
free (stack.addrs);
return false;
}
break;
case DW_OP_reg0 ... DW_OP_reg31:
if (INTUSE (dwfl_frame_reg) (state, op->atom - DW_OP_reg0, &val1) != 0
|| ! push (val1))
{
free (stack.addrs);
return false;
}
break;
case DW_OP_regx:
if (INTUSE (dwfl_frame_reg) (state, op->number, &val1) != 0 || ! push (val1))
{
free (stack.addrs);
return false;
}
break;
case DW_OP_breg0 ... DW_OP_breg31:
if (INTUSE (dwfl_frame_reg) (state, op->atom - DW_OP_breg0, &val1) != 0)
{
free (stack.addrs);
return false;
}
val1 += op->number;
if (! push (val1))
{
free (stack.addrs);
return false;
}
break;
case DW_OP_bregx:
if (INTUSE (dwfl_frame_reg) (state, op->number, &val1) != 0)
{
free (stack.addrs);
return false;
}
val1 += op->number2;
if (! push (val1))
{
free (stack.addrs);
return false;
}
break;
case DW_OP_dup:
if (! pop (&val1) || ! push (val1) || ! push (val1))
{
free (stack.addrs);
return false;
}
break;
case DW_OP_drop:
if (! pop (&val1))
{
free (stack.addrs);
return false;
}
break;
case DW_OP_pick:
if (stack.used <= op->number)
{
free (stack.addrs);
__libdwfl_seterrno (DWFL_E_INVALID_DWARF);
return false;
}
if (! push (stack.addrs[stack.used - 1 - op->number]))
{
free (stack.addrs);
return false;
}
break;
case DW_OP_over:
if (! pop (&val1) || ! pop (&val2)
|| ! push (val2) || ! push (val1) || ! push (val2))
{
free (stack.addrs);
return false;
}
break;
case DW_OP_swap:
if (! pop (&val1) || ! pop (&val2) || ! push (val1) || ! push (val2))
{
free (stack.addrs);
return false;
}
break;
case DW_OP_rot:
{
Dwarf_Addr val3;
if (! pop (&val1) || ! pop (&val2) || ! pop (&val3)
|| ! push (val1) || ! push (val3) || ! push (val2))
{
free (stack.addrs);
return false;
}
}
break;
case DW_OP_deref:
case DW_OP_deref_size:
if (process->callbacks->memory_read == NULL)
{
free (stack.addrs);
__libdwfl_seterrno (DWFL_E_INVALID_ARGUMENT);
return false;
}
if (! pop (&val1)
|| ! process->callbacks->memory_read (process->dwfl, val1, &val1,
process->callbacks_arg))
{
free (stack.addrs);
return false;
}
if (op->atom == DW_OP_deref_size)
{
const int elfclass = frame->cache->e_ident[EI_CLASS];
const unsigned addr_bytes = elfclass == ELFCLASS32 ? 4 : 8;
if (op->number > addr_bytes)
{
free (stack.addrs);
__libdwfl_seterrno (DWFL_E_INVALID_DWARF);
return false;
}
#if BYTE_ORDER == BIG_ENDIAN
if (op->number == 0)
val1 = 0;
else
val1 >>= (addr_bytes - op->number) * 8;
#else
if (op->number < 8)
val1 &= (1ULL << (op->number * 8)) - 1;
#endif
}
if (! push (val1))
{
free (stack.addrs);
return false;
}
break;
#define UNOP(atom, expr) \
case atom: \
if (! pop (&val1) || ! push (expr)) \
{ \
free (stack.addrs); \
return false; \
} \
break;
UNOP (DW_OP_abs, llabs ((int64_t) val1))
UNOP (DW_OP_neg, -(int64_t) val1)
UNOP (DW_OP_not, ~val1)
#undef UNOP
case DW_OP_plus_uconst:
if (! pop (&val1) || ! push (val1 + op->number))
{
free (stack.addrs);
return false;
}
break;
#define BINOP(atom, op) \
case atom: \
if (! pop (&val2) || ! pop (&val1) || ! push (val1 op val2)) \
{ \
free (stack.addrs); \
return false; \
} \
break;
#define BINOP_SIGNED(atom, op) \
case atom: \
if (! pop (&val2) || ! pop (&val1) \
|| ! push ((int64_t) val1 op (int64_t) val2)) \
{ \
free (stack.addrs); \
return false; \
} \
break;
BINOP (DW_OP_and, &)
case DW_OP_div:
if (! pop (&val2) || ! pop (&val1))
{
free (stack.addrs);
return false;
}
if (val2 == 0)
{
free (stack.addrs);
__libdwfl_seterrno (DWFL_E_INVALID_DWARF);
return false;
}
if (! push ((int64_t) val1 / (int64_t) val2))
{
free (stack.addrs);
return false;
}
break;
BINOP (DW_OP_minus, -)
case DW_OP_mod:
if (! pop (&val2) || ! pop (&val1))
{
free (stack.addrs);
return false;
}
if (val2 == 0)
{
free (stack.addrs);
__libdwfl_seterrno (DWFL_E_INVALID_DWARF);
return false;
}
if (! push (val1 % val2))
{
free (stack.addrs);
return false;
}
break;
BINOP (DW_OP_mul, *)
BINOP (DW_OP_or, |)
BINOP (DW_OP_plus, +)
BINOP (DW_OP_shl, <<)
BINOP (DW_OP_shr, >>)
BINOP_SIGNED (DW_OP_shra, >>)
BINOP (DW_OP_xor, ^)
BINOP_SIGNED (DW_OP_le, <=)
BINOP_SIGNED (DW_OP_ge, >=)
BINOP_SIGNED (DW_OP_eq, ==)
BINOP_SIGNED (DW_OP_lt, <)
BINOP_SIGNED (DW_OP_gt, >)
BINOP_SIGNED (DW_OP_ne, !=)
#undef BINOP
#undef BINOP_SIGNED
case DW_OP_bra:
if (! pop (&val1))
{
free (stack.addrs);
return false;
}
if (val1 == 0)
break;
FALLTHROUGH;
case DW_OP_skip:;
Dwarf_Word offset = op->offset + 1 + 2 + (int16_t) op->number;
const Dwarf_Op *found = bsearch ((void *) (uintptr_t) offset, ops, nops,
sizeof (*ops), bra_compar);
if (found == NULL)
{
free (stack.addrs);
__libdwfl_seterrno (DWFL_E_INVALID_DWARF);
return false;
}
op = found - 1;
break;
case DW_OP_nop:
break;
case DW_OP_call_frame_cfa:;
Dwarf_Op *cfa_ops;
size_t cfa_nops;
Dwarf_Addr cfa;
if (frame == NULL
|| dwarf_frame_cfa (frame, &cfa_ops, &cfa_nops) != 0
|| ! expr_eval (state, NULL, cfa_ops, cfa_nops, &cfa, bias)
|| ! push (cfa))
{
__libdwfl_seterrno (DWFL_E_LIBDW);
free (stack.addrs);
return false;
}
is_location = true;
break;
case DW_OP_stack_value:
is_location = false;
break;
default:
__libdwfl_seterrno (DWFL_E_INVALID_DWARF);
return false;
}
}
if (! pop (result))
{
free (stack.addrs);
return false;
}
free (stack.addrs);
if (is_location)
{
if (process->callbacks->memory_read == NULL)
{
__libdwfl_seterrno (DWFL_E_INVALID_ARGUMENT);
return false;
}
if (! process->callbacks->memory_read (process->dwfl, *result, result,
process->callbacks_arg))
return false;
}
return true;
#undef push
#undef pop
}
static Dwfl_Frame *
new_unwound (Dwfl_Frame *state)
{
assert (state->unwound == NULL);
Dwfl_Thread *thread = state->thread;
Dwfl_Process *process = thread->process;
Ebl *ebl = process->ebl;
size_t nregs = ebl_frame_nregs (ebl);
assert (nregs > 0);
Dwfl_Frame *unwound;
unwound = malloc (sizeof (*unwound) + sizeof (*unwound->regs) * nregs);
if (unlikely (unwound == NULL))
return NULL;
state->unwound = unwound;
unwound->thread = thread;
unwound->unwound = NULL;
unwound->signal_frame = false;
unwound->initial_frame = false;
unwound->pc_state = DWFL_FRAME_STATE_ERROR;
memset (unwound->regs_set, 0, sizeof (unwound->regs_set));
return unwound;
}
static void
handle_cfi (Dwfl_Frame *state, Dwarf_Addr pc, Dwarf_CFI *cfi, Dwarf_Addr bias)
{
Dwarf_Frame *frame;
if (INTUSE(dwarf_cfi_addrframe) (cfi, pc, &frame) != 0)
{
__libdwfl_seterrno (DWFL_E_LIBDW);
return;
}
Dwfl_Frame *unwound = new_unwound (state);
if (unwound == NULL)
{
__libdwfl_seterrno (DWFL_E_NOMEM);
return;
}
unwound->signal_frame = frame->fde->cie->signal_frame;
Dwfl_Thread *thread = state->thread;
Dwfl_Process *process = thread->process;
Ebl *ebl = process->ebl;
size_t nregs = ebl_frame_nregs (ebl);
assert (nregs > 0);
unsigned ra = frame->fde->cie->return_address_register;
bool ra_set = false;
if (! ebl_dwarf_to_regno (ebl, &ra))
{
__libdwfl_seterrno (DWFL_E_INVALID_REGISTER);
return;
}
for (unsigned regno = 0; regno < nregs; regno++)
{
Dwarf_Op reg_ops_mem[3], *reg_ops;
size_t reg_nops;
if (dwarf_frame_register (frame, regno, reg_ops_mem, ®_ops,
®_nops) != 0)
{
__libdwfl_seterrno (DWFL_E_LIBDW);
continue;
}
Dwarf_Addr regval;
if (reg_nops == 0)
{
if (reg_ops == reg_ops_mem)
{
if (regno == ra)
unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED;
continue;
}
else if (reg_ops == NULL)
{
if (INTUSE (dwfl_frame_reg) (state, regno, ®val) != 0)
continue;
}
else
{
__libdwfl_seterrno (DWFL_E_INVALID_DWARF);
continue;
}
}
else if (! expr_eval (state, frame, reg_ops, reg_nops, ®val, bias))
{
continue;
}
if (regno == frame->fde->cie->return_address_register)
regval &= ebl_func_addr_mask (ebl);
if (ra_set && regno != frame->fde->cie->return_address_register)
{
unsigned r = regno;
if (ebl_dwarf_to_regno (ebl, &r) && r == ra)
continue;
}
if (! __libdwfl_frame_reg_set (unwound, regno, regval))
{
__libdwfl_seterrno (DWFL_E_INVALID_REGISTER);
continue;
}
else if (! ra_set)
{
unsigned r = regno;
if (ebl_dwarf_to_regno (ebl, &r) && r == ra)
ra_set = true;
}
}
if (unwound->pc_state == DWFL_FRAME_STATE_ERROR)
{
int res = INTUSE (dwfl_frame_reg) (unwound,
frame->fde->cie->return_address_register,
&unwound->pc);
if (res == 0)
{
if (unwound->pc == 0)
unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED;
else
{
unwound->pc_state = DWFL_FRAME_STATE_PC_SET;
unwound->pc += ebl_ra_offset (ebl);
}
}
else
{
unsigned pcreg = frame->fde->cie->return_address_register;
if (! ebl_dwarf_to_regno (ebl, &pcreg)
|| pcreg >= ebl_frame_nregs (ebl))
__libdwfl_seterrno (DWFL_E_INVALID_REGISTER);
else
unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED;
}
}
free (frame);
}
static bool
setfunc (int firstreg, unsigned nregs, const Dwarf_Word *regs, void *arg)
{
Dwfl_Frame *state = arg;
Dwfl_Frame *unwound = state->unwound;
if (firstreg < 0)
{
assert (firstreg == -1);
assert (nregs == 1);
assert (unwound->pc_state == DWFL_FRAME_STATE_PC_UNDEFINED);
unwound->pc = *regs;
unwound->pc_state = DWFL_FRAME_STATE_PC_SET;
return true;
}
while (nregs--)
if (! __libdwfl_frame_reg_set (unwound, firstreg++, *regs++))
return false;
return true;
}
static bool
getfunc (int firstreg, unsigned nregs, Dwarf_Word *regs, void *arg)
{
Dwfl_Frame *state = arg;
assert (firstreg >= 0);
while (nregs--)
if (INTUSE (dwfl_frame_reg) (state, firstreg++, regs++) != 0)
return false;
return true;
}
static bool
readfunc (Dwarf_Addr addr, Dwarf_Word *datap, void *arg)
{
Dwfl_Frame *state = arg;
Dwfl_Thread *thread = state->thread;
Dwfl_Process *process = thread->process;
return process->callbacks->memory_read (process->dwfl, addr, datap,
process->callbacks_arg);
}
void
internal_function
__libdwfl_frame_unwind (Dwfl_Frame *state)
{
if (state->unwound)
return;
Dwarf_Addr pc;
bool ok = INTUSE(dwfl_frame_pc) (state, &pc, NULL);
if (!ok)
return;
if (! state->initial_frame && ! state->signal_frame)
pc--;
Dwfl_Module *mod = INTUSE(dwfl_addrmodule) (state->thread->process->dwfl, pc);
if (mod == NULL)
__libdwfl_seterrno (DWFL_E_NO_DWARF);
else
{
Dwarf_Addr bias;
Dwarf_CFI *cfi_eh = INTUSE(dwfl_module_eh_cfi) (mod, &bias);
if (cfi_eh)
{
handle_cfi (state, pc - bias, cfi_eh, bias);
if (state->unwound)
return;
}
Dwarf_CFI *cfi_dwarf = INTUSE(dwfl_module_dwarf_cfi) (mod, &bias);
if (cfi_dwarf)
{
handle_cfi (state, pc - bias, cfi_dwarf, bias);
if (state->unwound)
return;
}
}
assert (state->unwound == NULL);
Dwfl_Thread *thread = state->thread;
Dwfl_Process *process = thread->process;
Ebl *ebl = process->ebl;
if (new_unwound (state) == NULL)
{
__libdwfl_seterrno (DWFL_E_NOMEM);
return;
}
state->unwound->pc_state = DWFL_FRAME_STATE_PC_UNDEFINED;
bool signal_frame = false;
if (! ebl_unwind (ebl, pc, setfunc, getfunc, readfunc, state, &signal_frame))
{
assert (state->unwound->unwound == NULL);
free (state->unwound);
state->unwound = NULL;
return;
}
assert (state->unwound->pc_state == DWFL_FRAME_STATE_PC_SET);
state->unwound->signal_frame = signal_frame;
}