| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "garnet/bin/debug_agent/unwind.h" |
| |
| #include <inttypes.h> |
| #include <ngunwind/fuchsia.h> |
| #include <ngunwind/libunwind.h> |
| #include <algorithm> |
| |
| #include "garnet/bin/debug_agent/process_info.h" |
| |
| namespace debug_agent { |
| |
| namespace { |
| |
| // Libunwind doesn't have a cross-platform typedef for the frame pointer |
| // register so define one. |
| #if defined(__x86_64__) |
| #define LIBUNWIND_FRAME_POINTER_REGISTER UNW_X86_64_RBP |
| #elif defined(__aarch64__) |
| #define LIBUNWIND_FRAME_POINTER_REGISTER UNW_AARCH64_X29 |
| #else |
| #error Need frame pointer. |
| #endif |
| |
| using ModuleVector = std::vector<debug_ipc::Module>; |
| |
| // Callback for libunwind. |
| int LookupDso(void* context, unw_word_t pc, unw_word_t* base, |
| const char** name) { |
| // Context is a ModuleVector sorted by load address, need to find the |
| // largest one smaller than or equal to the pc. |
| // |
| // We could use lower_bound for better perf with lots of modules but we |
| // expect O(10) modules. |
| const ModuleVector* modules = static_cast<const ModuleVector*>(context); |
| for (int i = static_cast<int>(modules->size()) - 1; i >= 0; i--) { |
| const debug_ipc::Module& module = (*modules)[i]; |
| if (pc >= module.base) { |
| *base = module.base; |
| *name = module.name.c_str(); |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| } // namespace |
| |
| zx_status_t UnwindStack(const zx::process& process, uint64_t dl_debug_addr, |
| const zx::thread& thread, uint64_t ip, uint64_t sp, |
| uint64_t bp, size_t max_depth, |
| std::vector<debug_ipc::StackFrame>* stack) { |
| // Get the modules sorted by load address. |
| ModuleVector modules; |
| zx_status_t status = GetModulesForProcess(process, dl_debug_addr, &modules); |
| if (status != ZX_OK) |
| return status; |
| std::sort(modules.begin(), modules.end(), |
| [](const debug_ipc::Module& a, const debug_ipc::Module& b) { |
| return a.base < b.base; |
| }); |
| |
| unw_fuchsia_info_t* fuchsia = |
| unw_create_fuchsia(process.get(), thread.get(), &modules, &LookupDso); |
| if (!fuchsia) |
| return ZX_ERR_INTERNAL; |
| |
| unw_addr_space_t remote_aspace = unw_create_addr_space( |
| const_cast<unw_accessors_t*>(&_UFuchsia_accessors), 0); |
| if (!remote_aspace) |
| return ZX_ERR_INTERNAL; |
| |
| unw_cursor_t cursor; |
| if (unw_init_remote(&cursor, remote_aspace, fuchsia) < 0) |
| return ZX_ERR_INTERNAL; |
| |
| debug_ipc::StackFrame frame; |
| frame.ip = ip; |
| frame.sp = sp; |
| frame.bp = bp; |
| stack->push_back(frame); |
| while (frame.sp >= 0x1000000 && stack->size() < max_depth) { |
| int ret = unw_step(&cursor); |
| if (ret <= 0) |
| break; |
| |
| unw_word_t val; |
| unw_get_reg(&cursor, UNW_REG_IP, &val); |
| frame.ip = val; |
| |
| unw_get_reg(&cursor, UNW_REG_SP, &val); |
| frame.sp = val; |
| |
| unw_get_reg(&cursor, LIBUNWIND_FRAME_POINTER_REGISTER, &val); |
| frame.bp = val; |
| |
| // Note that libunwind may theoretically be able to give us all |
| // callee-saved register values for a given frame. Currently asking for any |
| // register always returns success, making it impossible to tell what is |
| // valid and what is not. |
| // |
| // If we switch unwinders (maybe to LLVM's or a custom one), this should be |
| // re-evaluated. We may be able to attach a vector of Register structs on |
| // each frame for the values we know about. |
| |
| stack->push_back(frame); |
| } |
| |
| // The last stack entry will typically have a 0 IP address. We want to send |
| // this anyway because it will hold the initial stack pointer for the thread, |
| // which in turn allows computation of the first real frame's fingerprint. |
| |
| return ZX_OK; |
| } |
| |
| } // namespace debug_agent |