blob: 8af598a79df02e48c5372f4fb30bd5df65f47a0c [file] [log] [blame]
/* libunwind - a platform-independent unwind library
Copyright 2016 The Fuchsia Authors. All rights reserved.
This file is part of libunwind.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
// This merges everything into one file to keep it all together.
// There's no need for one file per function here.
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/debug.h>
#include "fuchsia.h"
#include "fuchsia_i.h"
#include "libunwind_i.h"
HIDDEN int
tdep_get_elf_image (struct elf_image *ei, pid_t pid, unw_word_t ip,
unsigned long *segbase, unsigned long *mapoff,
char *path, size_t pathlen)
{
// This maps in the whole image, which we don't need.
int rc = -1;
return rc;
}
static zx_status_t
read_mem (zx_handle_t h, zx_vaddr_t vaddr, void* ptr, size_t len)
{
size_t actual;
zx_status_t status = zx_process_read_memory (h, vaddr, ptr, len, &actual);
if (status < 0)
{
Debug (3, "read_mem @0x%" PRIxPTR " FAILED %d\n", vaddr, status);
return status;
}
if (len != actual)
{
// TODO: Use %zd when MG-164 is fixed.
Debug (3, "read_mem @0x%" PRIxPTR " FAILED, short read %zd\n", vaddr, actual);
return ZX_ERR_IO;
}
return ZX_OK;
}
static zx_status_t
get_inferior_greg_buf_size (zx_handle_t thread, uint32_t* regset_size)
{
// The general regs are defined to be in regset zero.
zx_status_t status = zx_thread_read_state (thread, ZX_THREAD_STATE_REGSET0,
NULL, 0, regset_size);
assert (status != ZX_OK);
if (status == ZX_ERR_BUFFER_TOO_SMALL)
status = ZX_OK;
return status;
}
static zx_status_t
read_inferior_gregs (zx_handle_t thread, void* buf, size_t regset_size)
{
uint32_t buf_size = (uint32_t) regset_size;
// By convention the general regs are in regset 0.
zx_status_t status = zx_thread_read_state (thread, ZX_THREAD_STATE_REGSET0, buf, buf_size, &buf_size);
return status;
}
static uint32_t
get_uint32 (void* buf)
{
uint32_t value = 0;
memcpy (&value, buf, sizeof (value));
return value;
}
static uint64_t
get_uint64 (void* buf)
{
uint64_t value = 0;
memcpy (&value, buf, sizeof (value));
return value;
}
// Subroutine of remote_find_proc_info to simplify it.
static int
get_unwind_info (unw_fuchsia_info_t* cxt, unw_addr_space_t as, unw_word_t ip)
{
struct as_elf_dyn_info* edi = &cxt->edi;
unsigned long segbase, mapoff;
// Can we use the previously found info?
if ((edi->di_cache.format != -1
&& ip >= edi->di_cache.start_ip && ip < edi->di_cache.end_ip)
|| (edi->di_debug.format != -1
&& ip >= edi->di_debug.start_ip && ip < edi->di_debug.end_ip))
return 0;
unwi_invalidate_as_edi(edi);
edi->arg = cxt;
unw_word_t base;
const char* name;
if (!cxt->lookup_dso (cxt->dsos, ip, &base, &name))
{
Debug (3, "pc 0x%lx not in any dso\n", (long) ip);
return -UNW_ENOINFO;
}
Debug (3, "pc 0x%lx in dso %s, base 0x%lx\n", (long) ip, name, (long) base);
segbase = base;
mapoff = 0;
int ret = dwarf_as_find_unwind_table (edi, as, name, segbase, mapoff, ip);
Debug (3, "dwarf_as_find_unwind_table returned %d\n", ret);
if (ret < 0)
return -UNW_ENOINFO;
/* This can happen in corner cases where dynamically generated
code falls into the same page that contains the data-segment
and the page-offset of the code is within the first page of
the executable. */
if (edi->di_cache.format != -1
&& (ip < edi->di_cache.start_ip || ip >= edi->di_cache.end_ip))
edi->di_cache.format = -1;
if (edi->di_debug.format != -1
&& (ip < edi->di_debug.start_ip || ip >= edi->di_debug.end_ip))
edi->di_debug.format = -1;
if (edi->di_cache.format == -1
&& edi->di_debug.format == -1)
return -UNW_ENOINFO;
return 0;
}
static int
remote_find_proc_info (unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi,
int need_unwind_info, void *arg)
{
Debug (3, "called, as %p, ip 0x%llx, need_unwind_info %d\n", as, (long long) ip, need_unwind_info);
unw_fuchsia_info_t* cxt = arg;
int ret;
ret = get_unwind_info (cxt, as, ip);
if (ret < 0)
{
Debug (3, "get_unwind_info failed: %d\n", ret);
return -UNW_ENOINFO;
}
ret = -UNW_ENOINFO;
if (ret == -UNW_ENOINFO && cxt->edi.di_cache.format != -1)
ret = dwarf_search_unwind_table (as, ip, &cxt->edi.di_cache, pi,
need_unwind_info, arg);
if (ret == -UNW_ENOINFO && cxt->edi.di_debug.format != -1)
ret = dwarf_search_unwind_table (as, ip, &cxt->edi.di_debug, pi,
need_unwind_info, arg);
Debug (3, "returning %d\n", ret);
return ret;
}
static void
remote_put_unwind_info (unw_addr_space_t as, unw_proc_info_t *pi, void *arg)
{
Debug (3, "called\n");
// FIXME: This is what the ptrace code does, but I think this should do what
// the dwarf put_unwind_info does. See dwarf/Gparser.c. In particular,
// dwarf_extract_proc_info_from_fde calls mempool_alloc.
free (pi->unwind_info);
pi->unwind_info = NULL;
}
static int
remote_get_dyn_info_list_addr (unw_addr_space_t as, unw_word_t *dil_addr,
void *arg)
{
Debug (3, "called\n");
return -UNW_ENOINFO;
}
static void
fill_hex (char *buf, size_t buf_size, void *src, size_t size)
{
if (buf_size < size * 3 + 1)
{
*buf = 0;
return;
}
char *p = buf;
unsigned char *s = (unsigned char*) src;
while (size > 0)
{
sprintf (p, " %02x", *s);
p += 3;
++s;
--size;
}
*p = 0;
}
static int
remote_access_mem (unw_addr_space_t as, unw_word_t addr, unw_word_t *val,
int write, void *arg)
{
Debug (3, "called, addr 0x%lx\n", (long) addr);
unw_fuchsia_info_t* cxt = arg;
zx_handle_t process = cxt->process;
if (write)
{
Debug (3, "writing to mem\n");
return -UNW_EINVAL;
}
zx_status_t status = read_mem (process, addr, val, sizeof(*val));
if (status < 0)
return -UNW_EINVAL;
char dump[3 * 8 + 1];
fill_hex (dump, sizeof(dump), val, sizeof(*val));
Debug (3, "returning 0, val %s\n", dump);
return 0;
}
static int
remote_access_raw_mem (unw_addr_space_t as, unw_word_t addr,
void* buf, size_t size, int write, void *arg)
{
Debug (3, "called, addr 0x%lx, size %lu\n", (long) addr, (long) size);
unw_fuchsia_info_t* cxt = arg;
zx_handle_t process = cxt->process;
if (write)
{
Debug (3, "writing to mem\n");
return -UNW_EINVAL;
}
zx_status_t status = read_mem (process, addr, buf, size);
if (status < 0)
{
Debug (3, "read failed: %d\n", status);
return -UNW_EINVAL;
}
char dump[3 * 8 + 1];
size_t to_dump = 8;
if (size < 8)
to_dump = size;
fill_hex (dump, sizeof(dump), buf, to_dump);
Debug (3, "returning 0, val %s%s\n", dump, to_dump < size ? " ..." : "");
return 0;
}
static int
remote_access_reg (unw_addr_space_t as, unw_regnum_t reg, unw_word_t *val,
int write, void *arg)
{
Debug (3, "called, regno %d\n", (int) reg);
unw_fuchsia_info_t* cxt = arg;
zx_handle_t thread = cxt->thread;
if (write)
{
Debug (3, "writing to reg\n");
return -UNW_EINVAL;
}
if (!unw_is_greg (reg))
{
Debug (3, "bad regnum: %d\n", (int) reg);
return -UNW_EBADREG;
}
uint32_t regset_size;
zx_status_t status = get_inferior_greg_buf_size (thread, &regset_size);
if (status != ZX_OK)
{
Debug (3, "unable to get greg buf size: %d\n", status);
return -UNW_EUNSPEC;
}
char* buf = malloc (regset_size);
if (buf == NULL)
{
Debug (3, "malloc failed\n");
return -UNW_ENOMEM;
}
zx_status_t r = read_inferior_gregs (thread, buf, regset_size);
if (r < 0)
{
Debug (3, "error reading gregs: %d\n", r);
free (buf);
return -UNW_EUNSPEC;
}
if (sizeof(*val) == sizeof(uint32_t))
*val = get_uint32 (buf + fuchsia_greg_offset[reg]);
else
*val = get_uint64 (buf + fuchsia_greg_offset[reg]);
Debug (3, "reg val: 0x%llx\n", (long long) *val);
free (buf);
return 0;
}
static int
remote_access_fpreg (unw_addr_space_t as, unw_regnum_t reg, unw_fpreg_t *val,
int write, void *arg)
{
Debug (3, "called\n");
return -UNW_EBADREG;
}
static int
remote_resume (unw_addr_space_t as, unw_cursor_t *c, void *arg)
{
Debug (3, "called\n");
// TODO(dje): It's not clear what a good return value is here, but OTOH
// we don't need this.
return -UNW_EUNSPEC;
}
static int
remote_get_proc_name (unw_addr_space_t as, unw_word_t ip,
char *buf, size_t buf_len, unw_word_t *offp, void *arg)
{
Debug (3, "called\n");
return -UNW_ENOINFO;
}
const unw_accessors_t _UFuchsia_accessors =
{
.find_proc_info = remote_find_proc_info,
.put_unwind_info = remote_put_unwind_info,
.get_dyn_info_list_addr = remote_get_dyn_info_list_addr,
.access_mem = remote_access_mem,
.access_raw_mem = remote_access_raw_mem,
.access_reg = remote_access_reg,
.access_fpreg = remote_access_fpreg,
.resume = remote_resume,
.get_proc_name = remote_get_proc_name
};
unw_fuchsia_info_t*
unw_create_fuchsia(zx_handle_t process, zx_handle_t thread,
struct dsoinfo* dsos, unw_dso_lookup_func_t* lookup_dso)
{
unw_fuchsia_info_t* result = malloc (sizeof (*result));
if (result == NULL)
return NULL;
memset (result, 0, sizeof (*result));
result->process = process;
result->thread = thread;
result->dsos = dsos;
result->lookup_dso = lookup_dso;
return result;
}
void
unw_destroy_fuchsia(unw_fuchsia_info_t* info)
{
free (info);
}