1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129

base / profiler / frame_pointer_unwinder.cc [blame]

// Copyright 2021 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/profiler/frame_pointer_unwinder.h"

#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/notreached.h"
#include "base/numerics/clamped_math.h"
#include "base/profiler/module_cache.h"
#include "build/build_config.h"

#if BUILDFLAG(IS_APPLE)
#include <pthread/stack_np.h>
#endif

namespace {

// Given a frame pointer, returns the frame pointer of the calling stack
// frame and places the return address of the calling stack frame into
// `return_address`. Shim around `pthread_stack_frame_decode_np` where
// available since it handles pointer authentication on supported platforms.
// NB: The caller *must* ensure that there are 2+ uintptr_t's worth of memory at
// `frame_pointer`.
uintptr_t DecodeFrame(uintptr_t frame_pointer, uintptr_t* return_address) {
#if BUILDFLAG(IS_APPLE)
  if (__builtin_available(iOS 12, *)) {
    return pthread_stack_frame_decode_np(frame_pointer, return_address);
  }
#endif
  const uintptr_t* fp = reinterpret_cast<uintptr_t*>(frame_pointer);

  // MSAN does not consider the frame pointers and return addresses to have
  // have been initialized in the normal sense, but they are actually
  // initialized.
  MSAN_UNPOISON(fp, sizeof(uintptr_t) * 2);

  uintptr_t next_frame = *fp;
  *return_address = *(fp + 1);
  return next_frame;
}

}  // namespace

namespace base {

FramePointerUnwinder::FramePointerUnwinder(
    CanUnwindFromDelegate can_unwind_from_delegate)
    : can_unwind_from_delegate_(can_unwind_from_delegate) {}

FramePointerUnwinder::~FramePointerUnwinder() = default;

bool FramePointerUnwinder::CanUnwindFrom(const Frame& current_frame) const {
  if (can_unwind_from_delegate_) {
    return can_unwind_from_delegate_.Run(current_frame);
  }

  return current_frame.module && current_frame.module->IsNative();
}

UnwindResult FramePointerUnwinder::TryUnwind(
    UnwinderStateCapture* capture_state,
    RegisterContext* thread_context,
    uintptr_t stack_top,
    std::vector<Frame>* stack) {
  // We expect the frame corresponding to the |thread_context| register state to
  // exist within |stack|.
  DCHECK_GT(stack->size(), 0u);
#if defined(ARCH_CPU_ARM64)
  constexpr uintptr_t align_mask = 0x1;
#elif defined(ARCH_CPU_X86_64)
  constexpr uintptr_t align_mask = 0xf;
#endif

  uintptr_t next_frame = RegisterContextFramePointer(thread_context);
  uintptr_t frame_lower_bound = RegisterContextStackPointer(thread_context);
  const auto is_fp_valid = [&](uintptr_t fp) {
    // Ensure there's space on the stack to read two values: the caller's
    // frame pointer and the return address.
    return next_frame >= frame_lower_bound &&
           ClampAdd(next_frame, sizeof(uintptr_t) * 2) <= stack_top &&
           (next_frame & align_mask) == 0;
  };
  if (!is_fp_valid(next_frame))
    return UnwindResult::kAborted;

  for (;;) {
    if (!stack->back().module) {
      return UnwindResult::kAborted;
    }
    if (!stack->back().module->IsNative()) {
      // This is a non-native module associated with the auxiliary unwinder
      // (e.g. corresponding to a frame in V8 generated code). Report as
      // UNRECOGNIZED_FRAME to allow that unwinder to unwind the frame.
      return UnwindResult::kUnrecognizedFrame;
    }
    uintptr_t retaddr;
    uintptr_t frame = next_frame;
    next_frame = DecodeFrame(frame, &retaddr);
    frame_lower_bound = frame + 1;
    // If `next_frame` is 0, we've hit the root and `retaddr` isn't useful.
    // Bail without recording the frame.
    if (next_frame == 0)
      return UnwindResult::kCompleted;
    const ModuleCache::Module* module =
        module_cache()->GetModuleForAddress(retaddr);
    // V8 doesn't conform to the x86_64 ABI re: stack alignment. For V8 frames,
    // let the V8 unwinder determine whether the FP is valid or not.
    bool is_non_native_module = module && !module->IsNative();
    // If the FP doesn't look correct, don't record this frame.
    if (!is_non_native_module && !is_fp_valid(next_frame))
      return UnwindResult::kAborted;

    RegisterContextFramePointer(thread_context) = next_frame;
    RegisterContextInstructionPointer(thread_context) = retaddr;
    RegisterContextStackPointer(thread_context) = frame + sizeof(uintptr_t) * 2;
    stack->emplace_back(retaddr, module);
  }

  NOTREACHED();
}

}  // namespace base