chromium/base/debug/stack_trace_fuchsia.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif

#include "base/debug/stack_trace.h"

#include <elf.h>
#include <link.h>
#include <stddef.h>
#include <threads.h>
#include <unwind.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/port.h>
#include <zircon/types.h>

#include <algorithm>
#include <array>
#include <iomanip>
#include <iostream>
#include <string_view>
#include <type_traits>

#include "base/atomic_sequence_num.h"
#include "base/debug/elf_reader.h"
#include "base/logging.h"

namespace base {
namespace debug {
namespace {

struct BacktraceData {
  const 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;
}

// Build a "rwx" C string-based representation of the permission bits.
// The output buffer is reused across calls, and should not be retained across
// consecutive invocations of this function.
const char* PermissionFlagsToString(int flags, char permission_buf[4]) {
  char* permission = permission_buf;

  if (flags & PF_R)
    (*permission++) = 'r';

  if (flags & PF_W)
    (*permission++) = 'w';

  if (flags & PF_X)
    (*permission++) = 'x';

  *permission = '\0';

  return permission_buf;
}

// Stores and queries debugging symbol map info for the current process.
class SymbolMap {
 public:
  struct Segment {
    const void* addr = nullptr;
    size_t relative_addr = 0;
    int permission_flags = 0;
    size_t size = 0;
  };

  struct Module {
    // Maximum number of PT_LOAD segments to process per ELF binary. Most
    // binaries have only 2-3 such segments.
    static constexpr size_t kMaxSegmentCount = 8;

    const void* addr = nullptr;
    std::array<Segment, kMaxSegmentCount> segments;
    size_t segment_count = 0;
    char name[ZX_MAX_NAME_LEN + 1] = {0};
    char build_id[kMaxBuildIdStringLength + 1] = {0};
  };

  SymbolMap();

  SymbolMap(const SymbolMap&) = delete;
  SymbolMap& operator=(const SymbolMap&) = delete;

  ~SymbolMap() = default;

  // Gets all entries for the symbol map.
  span<Module> GetModules() { return {modules_.data(), count_}; }

 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.
  std::array<Module, kMaxMapEntries> modules_;

  size_t count_ = 0;
  bool valid_ = false;
};

SymbolMap::SymbolMap() {
  Populate();
}

void SymbolMap::Populate() {
  zx_handle_t process = zx_process_self();

  // Retrieve the debug info struct.
  uintptr_t debug_addr;
  zx_status_t 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;
  }

  // Populate ELF binary metadata into |modules_|.
  while (lmap != nullptr) {
    if (count_ >= kMaxMapEntries)
      break;

    SymbolMap::Module& next_entry = modules_[count_];
    ++count_;

    next_entry.addr = reinterpret_cast<void*>(lmap->l_addr);

    // Create Segment sub-entries for all PT_LOAD headers.
    // Each Segment corresponds to a "mmap" line in the output.
    next_entry.segment_count = 0;
    for (const Elf64_Phdr& phdr : GetElfProgramHeaders(next_entry.addr)) {
      if (phdr.p_type != PT_LOAD)
        continue;

      if (next_entry.segment_count > Module::kMaxSegmentCount) {
        LOG(WARNING) << "Exceeded the maximum number of segments.";
        break;
      }

      Segment segment;
      segment.addr =
          reinterpret_cast<const char*>(next_entry.addr) + phdr.p_vaddr;
      segment.relative_addr = phdr.p_vaddr;
      segment.size = phdr.p_memsz;
      segment.permission_flags = static_cast<int>(phdr.p_flags);

      next_entry.segments[next_entry.segment_count] = std::move(segment);
      ++next_entry.segment_count;
    }

    // Get the human-readable library name from the ELF header, falling back on
    // using names from the link map for binaries that aren't shared libraries.
    std::optional<std::string_view> elf_library_name =
        ReadElfLibraryName(next_entry.addr);
    if (elf_library_name) {
      strlcpy(next_entry.name, elf_library_name->data(),
              elf_library_name->size() + 1);
    } else {
      std::string_view link_map_name(lmap->l_name[0] ? lmap->l_name
                                                     : "<executable>");

      // The "module" stack trace annotation doesn't allow for strings which
      // resemble paths, so extract the filename portion from |link_map_name|.
      size_t directory_prefix_idx = link_map_name.find_last_of("/");
      if (directory_prefix_idx != std::string_view::npos) {
        link_map_name = link_map_name.substr(
            directory_prefix_idx + 1,
            link_map_name.size() - directory_prefix_idx - 1);
      }
      strlcpy(next_entry.name, link_map_name.data(), link_map_name.size() + 1);
    }

    if (!ReadElfBuildId(next_entry.addr, false, next_entry.build_id)) {
      LOG(WARNING) << "Couldn't read build ID.";
      continue;
    }

    lmap = lmap->l_next;
  }

  valid_ = true;
}

// Returns true if |address| is contained by any of the memory regions
// mapped for |module_entry|.
bool ModuleContainsFrameAddress(const void* address,
                                const SymbolMap::Module& module_entry) {
  for (size_t i = 0; i < module_entry.segment_count; ++i) {
    const SymbolMap::Segment& segment = module_entry.segments[i];
    const void* segment_end = reinterpret_cast<const void*>(
        reinterpret_cast<const char*>(segment.addr) + segment.size - 1);

    if (address >= segment.addr && address <= segment_end) {
      return true;
    }
  }
  return false;
}

}  // 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;
}

size_t CollectStackTrace(span<const void*> trace) {
  size_t frame_count = 0;
  BacktraceData data = {trace.data(), &frame_count, trace.size()};
  _Unwind_Backtrace(&UnwindStore, &data);
  return frame_count;
}

// static
void StackTrace::PrintMessageWithPrefix(cstring_view prefix_string,
                                        cstring_view message) {
  std::cerr << prefix_string << message;
}

void StackTrace::PrintWithPrefixImpl(cstring_view prefix_string) const {
  OutputToStreamWithPrefixImpl(&std::cerr, prefix_string);
}

// Emits stack trace data using the symbolizer markup format specified at:
// https://fuchsia.googlesource.com/zircon/+/master/docs/symbolizer_markup.md
void StackTrace::OutputToStreamWithPrefixImpl(
    std::ostream* os,
    cstring_view prefix_string) const {
  SymbolMap map;

  int module_id = 0;
  for (const SymbolMap::Module& module_entry : map.GetModules()) {
    // Don't emit information on modules that aren't useful for the actual
    // stack trace, so as to reduce the load on the symbolizer and syslog.
    bool should_emit_module = false;
    for (size_t i = 0; i < count_ && !should_emit_module; ++i) {
      should_emit_module = ModuleContainsFrameAddress(trace_[i], module_entry);
    }
    if (!should_emit_module) {
      continue;
    }

    *os << "{{{module:" << module_id << ":" << module_entry.name
        << ":elf:" << module_entry.build_id << "}}}\n";

    for (size_t i = 0; i < module_entry.segment_count; ++i) {
      const SymbolMap::Segment& segment = module_entry.segments[i];

      char permission_string[4] = {};
      *os << "{{{mmap:" << segment.addr << ":0x" << std::hex << segment.size
          << std::dec << ":load:" << module_id << ":"
          << PermissionFlagsToString(segment.permission_flags,
                                     permission_string)
          << ":"
          << "0x" << std::hex << segment.relative_addr << std::dec << "}}}\n";
    }

    ++module_id;
  }

  for (size_t i = 0; i < count_; ++i)
    *os << "{{{bt:" << i << ":" << trace_[i] << "}}}\n";

  *os << "{{{reset}}}\n";
}

}  // namespace debug
}  // namespace base