blob: eb491c01e41ad9f9ad00a4e2bdea774b018cce71 [file] [log] [blame]
// 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