| // Copyright 2017 The Chromium 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 "base/debug/stack_trace.h" |
| |
| #include <link.h> |
| #include <string.h> |
| #include <threads.h> |
| #include <unwind.h> |
| #include <zircon/process.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/definitions.h> |
| #include <zircon/syscalls/port.h> |
| #include <zircon/types.h> |
| |
| #include <algorithm> |
| #include <iomanip> |
| #include <iostream> |
| |
| #include "base/logging.h" |
| #include "starboard/common/string.h" |
| #include "starboard/types.h" |
| |
| namespace base { |
| namespace debug { |
| |
| namespace { |
| |
| const char kProcessNamePrefix[] = "app:"; |
| const size_t kProcessNamePrefixLen = arraysize(kProcessNamePrefix) - 1; |
| |
| struct BacktraceData { |
| void** trace_array; |
| size_t* count; |
| size_t max; |
| }; |
| |
| _Unwind_Reason_Code UnwindStore(struct _Unwind_Context* context, |
| void* user_data) { |
| BacktraceData* data = reinterpret_cast<BacktraceData*>(user_data); |
| uintptr_t pc = _Unwind_GetIP(context); |
| data->trace_array[*data->count] = reinterpret_cast<void*>(pc); |
| *data->count += 1; |
| if (*data->count == data->max) |
| return _URC_END_OF_STACK; |
| return _URC_NO_REASON; |
| } |
| |
| // Stores and queries debugging symbol map info for the current process. |
| class SymbolMap { |
| public: |
| struct Entry { |
| void* addr; |
| char name[ZX_MAX_NAME_LEN + kProcessNamePrefixLen]; |
| }; |
| |
| SymbolMap(); |
| ~SymbolMap() = default; |
| |
| // Gets the symbol map entry for |address|. Returns null if no entry could be |
| // found for the address, or if the symbol map could not be queried. |
| Entry* GetForAddress(void* address); |
| |
| private: |
| // Component builds of Chrome pull about 250 shared libraries (on Linux), so |
| // 512 entries should be enough in most cases. |
| static const size_t kMaxMapEntries = 512; |
| |
| void Populate(); |
| |
| // Sorted in descending order by address, for lookup purposes. |
| Entry entries_[kMaxMapEntries]; |
| |
| size_t count_ = 0; |
| bool valid_ = false; |
| |
| DISALLOW_COPY_AND_ASSIGN(SymbolMap); |
| }; |
| |
| SymbolMap::SymbolMap() { |
| Populate(); |
| } |
| |
| SymbolMap::Entry* SymbolMap::GetForAddress(void* address) { |
| if (!valid_) { |
| return nullptr; |
| } |
| |
| // Working backwards in the address space, return the first map entry whose |
| // address comes before |address| (thereby enclosing it.) |
| for (size_t i = 0; i < count_; ++i) { |
| if (address >= entries_[i].addr) { |
| return &entries_[i]; |
| } |
| } |
| return nullptr; |
| } |
| |
| void SymbolMap::Populate() { |
| zx_handle_t process = zx_process_self(); |
| |
| // Try to fetch the name of the process' main executable, which was set as the |
| // name of the |process| kernel object. |
| // TODO(wez): Object names can only have up to ZX_MAX_NAME_LEN characters, so |
| // if we keep hitting problems with truncation, find a way to plumb argv[0] |
| // through to here instead, e.g. using CommandLine::GetProgramName(). |
| char app_name[arraysize(SymbolMap::Entry::name)]; |
| strcpy(app_name, kProcessNamePrefix); |
| zx_status_t status = zx_object_get_property( |
| process, ZX_PROP_NAME, app_name + kProcessNamePrefixLen, |
| sizeof(app_name) - kProcessNamePrefixLen); |
| if (status != ZX_OK) { |
| DPLOG(WARNING) |
| << "Couldn't get name, falling back to 'app' for program name: " |
| << status; |
| strlcat(app_name, "app", sizeof(app_name)); |
| } |
| |
| // Retrieve the debug info struct. |
| uintptr_t debug_addr; |
| status = zx_object_get_property(process, ZX_PROP_PROCESS_DEBUG_ADDR, |
| &debug_addr, sizeof(debug_addr)); |
| if (status != ZX_OK) { |
| DPLOG(ERROR) << "Couldn't get symbol map for process: " << status; |
| return; |
| } |
| r_debug* debug_info = reinterpret_cast<r_debug*>(debug_addr); |
| |
| // Get the link map from the debug info struct. |
| link_map* lmap = reinterpret_cast<link_map*>(debug_info->r_map); |
| if (!lmap) { |
| DPLOG(ERROR) << "Null link_map for process."; |
| return; |
| } |
| |
| // Copy the contents of the link map linked list to |entries_|. |
| while (lmap != nullptr) { |
| if (count_ >= arraysize(entries_)) { |
| break; |
| } |
| SymbolMap::Entry* next_entry = &entries_[count_]; |
| count_++; |
| |
| next_entry->addr = reinterpret_cast<void*>(lmap->l_addr); |
| char* name_to_use = lmap->l_name[0] ? lmap->l_name : app_name; |
| strlcpy(next_entry->name, name_to_use, sizeof(next_entry->name)); |
| lmap = lmap->l_next; |
| } |
| |
| std::sort( |
| &entries_[0], &entries_[count_ - 1], |
| [](const Entry& a, const Entry& b) -> bool { return a.addr >= b.addr; }); |
| |
| valid_ = true; |
| } |
| |
| } // namespace |
| |
| // static |
| bool EnableInProcessStackDumping() { |
| // StackTrace works to capture the current stack (e.g. for diagnostics added |
| // to code), but for local capture and print of backtraces, we just let the |
| // system crashlogger take over. It handles printing out a nicely formatted |
| // backtrace with dso information, relative offsets, etc. that we can then |
| // filter with addr2line in the run script to get file/line info. |
| return true; |
| } |
| |
| StackTrace::StackTrace(size_t count) : count_(0) { |
| BacktraceData data = {&trace_[0], &count_, |
| std::min(count, static_cast<size_t>(kMaxTraces))}; |
| _Unwind_Backtrace(&UnwindStore, &data); |
| } |
| |
| void StackTrace::PrintWithPrefix(const char* prefix_string) const { |
| OutputToStreamWithPrefix(&std::cerr, prefix_string); |
| } |
| |
| // Sample stack trace output is designed to be similar to Fuchsia's crashlogger: |
| // bt#00: pc 0x1527a058aa00 (app:/system/base_unittests,0x18bda00) |
| // bt#01: pc 0x1527a0254b5c (app:/system/base_unittests,0x1587b5c) |
| // bt#02: pc 0x15279f446ece (app:/system/base_unittests,0x779ece) |
| // ... |
| // bt#21: pc 0x1527a05b51b4 (app:/system/base_unittests,0x18e81b4) |
| // bt#22: pc 0x54fdbf3593de (libc.so,0x1c3de) |
| // bt#23: end |
| void StackTrace::OutputToStreamWithPrefix(std::ostream* os, |
| const char* prefix_string) const { |
| SymbolMap map; |
| |
| size_t i = 0; |
| for (; (i < count_) && os->good(); ++i) { |
| SymbolMap::Entry* entry = map.GetForAddress(trace_[i]); |
| if (prefix_string) |
| *os << prefix_string; |
| if (entry) { |
| size_t offset = reinterpret_cast<uintptr_t>(trace_[i]) - |
| reinterpret_cast<uintptr_t>(entry->addr); |
| *os << "bt#" << std::setw(2) << std::setfill('0') << i << std::setw(0) |
| << ": pc " << trace_[i] << " (" << entry->name << ",0x" << std::hex |
| << offset << std::dec << std::setw(0) << ")\n"; |
| } else { |
| // Fallback if the DSO map isn't available. |
| // Logged PC values are absolute memory addresses, and the shared object |
| // name is not emitted. |
| *os << "bt#" << std::setw(2) << std::setfill('0') << i << std::setw(0) |
| << ": pc " << trace_[i] << "\n"; |
| } |
| } |
| |
| (*os) << "bt#" << std::setw(2) << i << ": end\n"; |
| } |
| |
| } // namespace debug |
| } // namespace base |