unwinder.cc 4.83 KB
Newer Older
1 2 3 4 5
// Copyright 2018 the V8 project 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 "include/v8.h"
6
#include "src/common/globals.h"
7
#include "src/execution/frame-constants.h"
8 9 10 11 12 13 14 15 16 17 18 19 20 21

namespace v8 {

namespace {

bool PCIsInCodeRange(const v8::MemoryRange& code_range, void* pc) {
  // Given that the length of the memory range is in bytes and it is not
  // necessarily aligned, we need to do the pointer arithmetic in byte* here.
  const i::byte* pc_as_byte = reinterpret_cast<i::byte*>(pc);
  const i::byte* start = reinterpret_cast<const i::byte*>(code_range.start);
  const i::byte* end = start + code_range.length_in_bytes;
  return pc_as_byte >= start && pc_as_byte < end;
}

22 23 24 25 26 27 28 29
bool IsInJSEntryRange(const UnwindState& unwind_state, void* pc) {
  return PCIsInCodeRange(unwind_state.js_entry_stub.code, pc) ||
         PCIsInCodeRange(unwind_state.js_construct_entry_stub.code, pc) ||
         PCIsInCodeRange(unwind_state.js_run_microtasks_entry_stub.code, pc);
}

bool IsInUnsafeJSEntryRange(const UnwindState& unwind_state, void* pc) {
  return IsInJSEntryRange(unwind_state, pc);
30 31

  // TODO(petermarshall): We can be more precise by checking whether we are
32 33 34
  // in JSEntry but after frame setup and before frame teardown, in which case
  // we are safe to unwind the stack. For now, we bail out if the PC is anywhere
  // within JSEntry.
35 36 37 38 39 40
}

i::Address Load(i::Address address) {
  return *reinterpret_cast<i::Address*>(address);
}

41 42 43 44 45 46 47 48
void* GetReturnAddressFromFP(void* fp, void* pc,
                             const v8::UnwindState& unwind_state) {
  int caller_pc_offset = i::CommonFrameConstants::kCallerPCOffset;
#ifdef V8_TARGET_ARCH_ARM64
  if (IsInJSEntryRange(unwind_state, pc)) {
    caller_pc_offset = i::EntryFrameConstants::kDirectCallerPCOffset;
  }
#endif
49
  return reinterpret_cast<void*>(
50
      Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset));
51 52
}

53 54 55 56 57 58 59 60
void* GetCallerFPFromFP(void* fp, void* pc,
                        const v8::UnwindState& unwind_state) {
  int caller_fp_offset = i::CommonFrameConstants::kCallerFPOffset;
#ifdef V8_TARGET_ARCH_ARM64
  if (IsInJSEntryRange(unwind_state, pc)) {
    caller_fp_offset = i::EntryFrameConstants::kDirectCallerFPOffset;
  }
#endif
61
  return reinterpret_cast<void*>(
62
      Load(reinterpret_cast<i::Address>(fp) + caller_fp_offset));
63 64
}

65 66 67 68 69 70 71 72
void* GetCallerSPFromFP(void* fp, void* pc,
                        const v8::UnwindState& unwind_state) {
  int caller_sp_offset = i::CommonFrameConstants::kCallerSPOffset;
#ifdef V8_TARGET_ARCH_ARM64
  if (IsInJSEntryRange(unwind_state, pc)) {
    caller_sp_offset = i::EntryFrameConstants::kDirectCallerSPOffset;
  }
#endif
73
  return reinterpret_cast<void*>(reinterpret_cast<i::Address>(fp) +
74
                                 caller_sp_offset);
75 76
}

77 78 79 80 81
bool AddressIsInStack(const void* address, const void* stack_base,
                      const void* stack_top) {
  return address <= stack_base && address >= stack_top;
}

82 83 84 85 86
}  // namespace

bool Unwinder::TryUnwindV8Frames(const UnwindState& unwind_state,
                                 RegisterState* register_state,
                                 const void* stack_base) {
87 88
  const void* stack_top = register_state->sp;

89
  void* pc = register_state->pc;
90
  if (PCIsInV8(unwind_state, pc) && !IsInUnsafeJSEntryRange(unwind_state, pc)) {
91
    void* current_fp = register_state->fp;
92
    if (!AddressIsInStack(current_fp, stack_base, stack_top)) return false;
93 94 95

    // Peek at the return address that the caller pushed. If it's in V8, then we
    // assume the caller frame is a JS frame and continue to unwind.
96
    void* next_pc = GetReturnAddressFromFP(current_fp, pc, unwind_state);
97
    while (PCIsInV8(unwind_state, next_pc)) {
98
      current_fp = GetCallerFPFromFP(current_fp, pc, unwind_state);
99
      if (!AddressIsInStack(current_fp, stack_base, stack_top)) return false;
100 101
      pc = next_pc;
      next_pc = GetReturnAddressFromFP(current_fp, pc, unwind_state);
102 103
    }

104
    void* final_sp = GetCallerSPFromFP(current_fp, pc, unwind_state);
105 106 107
    if (!AddressIsInStack(final_sp, stack_base, stack_top)) return false;
    register_state->sp = final_sp;

108 109 110 111
    // We don't check that the final FP value is within the stack bounds because
    // this is just the rbp value that JSEntryStub pushed. On platforms like
    // Win64 this is not used as a dedicated FP register, and could contain
    // anything.
112
    void* final_fp = GetCallerFPFromFP(current_fp, pc, unwind_state);
113 114
    register_state->fp = final_fp;

115
    register_state->pc = next_pc;
116 117 118

    // Link register no longer valid after unwinding.
    register_state->lr = nullptr;
119 120 121 122 123 124 125 126 127 128 129
    return true;
  }
  return false;
}

bool Unwinder::PCIsInV8(const UnwindState& unwind_state, void* pc) {
  return pc && (PCIsInCodeRange(unwind_state.code_range, pc) ||
                PCIsInCodeRange(unwind_state.embedded_code_range, pc));
}

}  // namespace v8