346 lines
9.3 KiB
C
346 lines
9.3 KiB
C
#include "elf-image-cache.h"
|
|
#include "libunwind-l4.h"
|
|
#include "libunwind_i.h"
|
|
#include "os-l4.h"
|
|
|
|
// libunwind uses pipes to validate addresses before local memory accesses,
|
|
// however it should not be relevant for remote unwinding.
|
|
int pipe (int __pipedes[2])
|
|
{
|
|
(void) __pipedes;
|
|
Debug(1, "WARN: Pipe is unimplemented!\n");
|
|
return -1;
|
|
}
|
|
|
|
// Linear search is necessary because l4re binaries do not emit a .eh_frame_hdr section or program header.
|
|
static int linear_eh_frame_search(struct elf_image *ei, unsigned long segbase,
|
|
unsigned long mapoff, unw_addr_space_t as,
|
|
unw_word_t ip, unw_proc_info_t *pi, void *arg)
|
|
{
|
|
Elf_W(Shdr) *shdr = elf_w(find_section) (ei, ".eh_frame");
|
|
if (!shdr)
|
|
return -UNW_ENOINFO;
|
|
|
|
// eh_frame address in remote process
|
|
Elf_W(Addr) eh_frame_start = shdr->sh_offset + segbase - mapoff;
|
|
Elf_W(Addr) eh_frame_end = eh_frame_start + shdr->sh_size;
|
|
|
|
unw_accessors_t *a = unw_get_accessors_int (as);
|
|
int ret = -UNW_ENOINFO;
|
|
unw_word_t addr = eh_frame_start;
|
|
while (addr < eh_frame_end)
|
|
{
|
|
unw_word_t fde_addr = addr;
|
|
if ((ret = dwarf_extract_proc_info_from_fde (as, a, &addr, pi,
|
|
segbase, 0, 0, arg)) < 0)
|
|
return ret;
|
|
|
|
if (ip >= pi->start_ip && ip < pi->end_ip)
|
|
{
|
|
addr = fde_addr;
|
|
if ((ret = dwarf_extract_proc_info_from_fde (as, a, &addr, pi,
|
|
segbase, 1, 0, arg)) < 0)
|
|
return ret;
|
|
|
|
// Have to manually clear the flags here, as dwarf_extract_proc_info_from_fde does not do it,
|
|
// and thus from a failed dwarf_search_unwind_table run for .debug_frame the UNW_PI_FLAG_DEBUG_FRAME flag might be set,
|
|
// which later breaks CFI program execution.
|
|
pi->flags = 0;
|
|
|
|
return UNW_ESUCCESS;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int l4_find_proc_info(unw_addr_space_t as, unw_word_t ip,
|
|
unw_proc_info_t *pi, int need_unwind_info,
|
|
void *arg)
|
|
{
|
|
l4_unw_data_t *data = arg;
|
|
|
|
struct elf_dyn_info edi;
|
|
memset(&edi, 0, sizeof(edi));
|
|
invalidate_edi(&edi);
|
|
|
|
/* where it is mapped in virtual memory */
|
|
unsigned long segbase;
|
|
/* offset in the file */
|
|
unsigned long mapoff;
|
|
char path[256];
|
|
|
|
if (l4_find_elf(data->rm, ip, &segbase, &mapoff, path, sizeof(path)))
|
|
return -UNW_ENOINFO;
|
|
|
|
elf_image_handle_t *elf = get_elf_image(path);
|
|
if (!elf)
|
|
return -UNW_ENOINFO;
|
|
edi.ei = elf->ei;
|
|
|
|
int ret = -UNW_ENOINFO;
|
|
if (tdep_find_unwind_table(&edi, as, path, segbase, mapoff, ip) >= 0)
|
|
{
|
|
if (edi.di_cache.format != -1)
|
|
ret = tdep_search_unwind_table (as, ip, &edi.di_cache,
|
|
pi, need_unwind_info, arg);
|
|
|
|
if (ret == -UNW_ENOINFO && edi.di_debug.format != -1)
|
|
ret = tdep_search_unwind_table (as, ip, &edi.di_debug, pi,
|
|
need_unwind_info, arg);
|
|
|
|
#if UNW_TARGET_ARM
|
|
if (ret == -UNW_ENOINFO && edi.di_arm.format != -1)
|
|
ret = tdep_search_unwind_table (as, ip, &edi.di_arm, pi,
|
|
need_unwind_info, arg);
|
|
#endif
|
|
}
|
|
|
|
// Fall back to lineary searching eh_frame if no binary lookup table (.eh_frame_hdr) or .debug_frame is present,
|
|
// which is what is supported by tdep_find_unwind_table and tdep_search_unwind_table.
|
|
if (ret == -UNW_ENOINFO && edi.di_cache.format == -1)
|
|
ret = linear_eh_frame_search(&edi.ei, segbase, mapoff, as, ip, pi, arg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
l4_get_dyn_info_list_addr (unw_addr_space_t as, unw_word_t *dyn_info_list_addr,
|
|
void *arg)
|
|
{
|
|
#ifndef UNW_LOCAL_ONLY
|
|
# pragma weak _U_dyn_info_list_addr
|
|
if (!_U_dyn_info_list_addr)
|
|
return -UNW_ENOINFO;
|
|
#endif
|
|
// Access the `_U_dyn_info_list` from `LOCAL_ONLY` library, i.e. libunwind.so.
|
|
*dyn_info_list_addr = _U_dyn_info_list_addr ();
|
|
return 0;
|
|
}
|
|
|
|
static int l4_access_mem_remote(unw_addr_space_t as, unw_word_t addr,
|
|
unw_word_t *valp, int write, void *arg)
|
|
{
|
|
(void)as;
|
|
|
|
l4_unw_data_t *data = arg;
|
|
l4_umword_t val;
|
|
int res = data->remote_mem(addr, &val, write, data->remote_mem_arg);
|
|
if (res >= 0)
|
|
*valp = val;
|
|
return res;
|
|
}
|
|
|
|
static int l4_access_reg(unw_addr_space_t as, unw_regnum_t regnum,
|
|
unw_word_t *valp, int write, void *arg)
|
|
{
|
|
(void)as;
|
|
|
|
l4_unw_data_t *data = arg;
|
|
l4_exc_regs_t *regs = data->regs;
|
|
l4_umword_t *reg;
|
|
|
|
Debug(16, "reg=%d write=%d\n", regnum, write);
|
|
|
|
#ifdef ARCH_arm
|
|
if (regnum >= UNW_ARM_R0 && regnum <= UNW_ARM_R12)
|
|
reg = ®s->r[regnum];
|
|
else if (regnum == UNW_TDEP_SP)
|
|
reg = ®s->sp;
|
|
else if (regnum == UNW_ARM_R14)
|
|
reg = ®s->ulr;
|
|
else if (regnum == UNW_TDEP_IP)
|
|
reg = ®s->pc;
|
|
else
|
|
{
|
|
printf("Unknown/unhandled regnum %d\n", regnum);
|
|
return -UNW_EBADREG;
|
|
}
|
|
#elif defined(ARCH_arm64)
|
|
if (regnum >= UNW_AARCH64_X0 && regnum <= UNW_AARCH64_X30)
|
|
reg = ®s->r[regnum - UNW_AARCH64_X0];
|
|
else if(regnum == UNW_AARCH64_SP)
|
|
reg = ®s->sp;
|
|
else if(regnum == UNW_AARCH64_PC)
|
|
reg = ®s->pc;
|
|
else
|
|
{
|
|
printf("Unknown/unhandled regnum %d\n", regnum);
|
|
return -UNW_EBADREG;
|
|
}
|
|
#elif defined(ARCH_x86)
|
|
switch (regnum)
|
|
{
|
|
case UNW_X86_EAX:
|
|
reg = ®s->eax;
|
|
break;
|
|
case UNW_X86_EDX:
|
|
reg = ®s->edx;
|
|
break;
|
|
case UNW_X86_ECX:
|
|
reg = ®s->ecx;
|
|
break;
|
|
case UNW_X86_EBX:
|
|
reg = ®s->ebx;
|
|
break;
|
|
case UNW_X86_ESI:
|
|
reg = ®s->esi;
|
|
break;
|
|
case UNW_X86_EDI:
|
|
reg = ®s->edi;
|
|
break;
|
|
case UNW_X86_EBP:
|
|
reg = ®s->ebp;
|
|
break;
|
|
case UNW_X86_ESP:
|
|
reg = ®s->sp;
|
|
break;
|
|
case UNW_X86_EIP:
|
|
reg = ®s->ip;
|
|
break;
|
|
case UNW_X86_EFLAGS:
|
|
reg = ®s->flags;
|
|
break;
|
|
case UNW_X86_TRAPNO:
|
|
reg = ®s->trapno;
|
|
break;
|
|
default:
|
|
printf("Unknown/unhandled regnum %d\n", regnum);
|
|
return -UNW_EBADREG;
|
|
};
|
|
#elif defined(ARCH_amd64)
|
|
switch (regnum)
|
|
{
|
|
case UNW_X86_64_RAX:
|
|
reg = ®s->rax;
|
|
break;
|
|
case UNW_X86_64_RDX:
|
|
reg = ®s->rdx;
|
|
break;
|
|
case UNW_X86_64_RCX:
|
|
reg = ®s->rcx;
|
|
break;
|
|
case UNW_X86_64_RBX:
|
|
reg = ®s->rbx;
|
|
break;
|
|
case UNW_X86_64_RSI:
|
|
reg = ®s->rsi;
|
|
break;
|
|
case UNW_X86_64_RDI:
|
|
reg = ®s->rdi;
|
|
break;
|
|
case UNW_X86_64_RBP:
|
|
reg = ®s->rbp;
|
|
break;
|
|
case UNW_X86_64_RSP:
|
|
reg = ®s->sp;
|
|
break;
|
|
case UNW_X86_64_RIP:
|
|
reg = ®s->ip;
|
|
break;
|
|
case UNW_X86_64_R8:
|
|
reg = ®s->r8;
|
|
break;
|
|
case UNW_X86_64_R9:
|
|
reg = ®s->r9;
|
|
break;
|
|
case UNW_X86_64_R10:
|
|
reg = ®s->r10;
|
|
break;
|
|
case UNW_X86_64_R11:
|
|
reg = ®s->r11;
|
|
break;
|
|
case UNW_X86_64_R12:
|
|
reg = ®s->r12;
|
|
break;
|
|
case UNW_X86_64_R13:
|
|
reg = ®s->r13;
|
|
break;
|
|
case UNW_X86_64_R14:
|
|
reg = ®s->r14;
|
|
break;
|
|
case UNW_X86_64_R15:
|
|
reg = ®s->r15;
|
|
break;
|
|
default:
|
|
printf("Unknown/unhandled regnum %d\n", regnum);
|
|
return -UNW_EBADREG;
|
|
};
|
|
#elif defined(ARCH_riscv)
|
|
if (regnum == UNW_RISCV_X0)
|
|
{
|
|
if (!write)
|
|
*valp = 0;
|
|
return 0;
|
|
}
|
|
else if (regnum >= UNW_RISCV_X1 && regnum <= UNW_RISCV_X31)
|
|
reg = ®s->r[regnum - UNW_RISCV_X1];
|
|
else if(regnum == UNW_RISCV_PC)
|
|
reg = ®s->pc;
|
|
else
|
|
{
|
|
printf("Unknown/unhandled regnum %d\n", regnum);
|
|
return -UNW_EBADREG;
|
|
}
|
|
#elif defined(ARCH_ppc32)
|
|
if (regnum >= UNW_PPC32_R0 && regnum <= UNW_PPC32_R31)
|
|
reg = ®s->r[regnum - UNW_PPC32_R0];
|
|
else
|
|
{
|
|
printf("Unknown/unhandled regnum %d\n", regnum);
|
|
return -UNW_EBADREG;
|
|
}
|
|
#elif defined(ARCH_sparc)
|
|
printf("Unknown/unhandled regnum %d\n", regnum);
|
|
return -UNW_EBADREG;
|
|
#elif defined(ARCH_mips)
|
|
printf("Unknown/unhandled regnum %d\n", regnum);
|
|
return -UNW_EBADREG;
|
|
#else
|
|
#error libunwind: Add missing architecture support
|
|
#endif
|
|
|
|
if (write)
|
|
*reg = *valp;
|
|
else
|
|
*valp = *reg;
|
|
|
|
Debug(16, "reg %d = 0x%lx\n", regnum, *valp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int l4_access_fpreg(unw_addr_space_t as, unw_regnum_t regnum,
|
|
unw_fpreg_t *fwalp, int write, void *arg)
|
|
{
|
|
(void)as; (void)regnum; (void)fwalp; (void)write; (void)arg;
|
|
printf("l4_access_fpreg(regnum=%d, write=%d): Not implemented\n",
|
|
regnum, write);
|
|
return -UNW_EINVAL;
|
|
}
|
|
|
|
static int l4_resume(unw_addr_space_t as, unw_cursor_t *cp, void *arg)
|
|
{
|
|
(void)as; (void)cp; (void)arg;
|
|
printf("l4_resume(): Not implemented\n");
|
|
return -UNW_EINVAL;
|
|
}
|
|
|
|
static int l4_get_proc_name(unw_addr_space_t as, unw_word_t addr, char *bufp,
|
|
size_t buf_len, unw_word_t *offp, void *arg)
|
|
{
|
|
l4_unw_data_t *data = arg;
|
|
return elf_w(get_proc_name) (as, l4_to_pid(data->rm), addr, bufp, buf_len, offp);
|
|
}
|
|
|
|
unw_accessors_t l4_unw_accessors =
|
|
{
|
|
.find_proc_info = l4_find_proc_info,
|
|
.put_unwind_info = dwarf_put_unwind_info,
|
|
.get_dyn_info_list_addr = l4_get_dyn_info_list_addr,
|
|
.access_mem = l4_access_mem_remote,
|
|
.access_reg = l4_access_reg,
|
|
.access_fpreg = l4_access_fpreg,
|
|
.resume = l4_resume,
|
|
.get_proc_name = l4_get_proc_name,
|
|
};
|