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