test-unwinder-code-pages.cc 28.4 KB
Newer Older
1 2 3 4
// 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.

5 6 7
#include "include/v8-function.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
8
#include "include/v8-unwinder-state.h"
9 10 11 12 13 14 15 16 17 18 19
#include "src/api/api-inl.h"
#include "src/builtins/builtins.h"
#include "src/execution/isolate.h"
#include "src/heap/spaces.h"
#include "src/objects/code-inl.h"
#include "test/cctest/cctest.h"

namespace v8 {
namespace internal {
namespace test_unwinder_code_pages {

20 21
namespace {

22 23 24
#define CHECK_EQ_VALUE_REGISTER(uiuntptr_value, register_value) \
  CHECK_EQ(reinterpret_cast<void*>(uiuntptr_value), register_value)

25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
#ifdef V8_TARGET_ARCH_X64
// How much the JSEntry frame occupies in the stack.
constexpr int kJSEntryFrameSpace = 3;

// Offset where the FP, PC and SP live from the beginning of the JSEntryFrame.
constexpr int kFPOffset = 0;
constexpr int kPCOffset = 1;
constexpr int kSPOffset = 2;

// Builds the stack from {stack} as x64 expects it.
// TODO(solanes): Build the JSEntry stack in the way the builtin builds it.
void BuildJSEntryStack(uintptr_t* stack) {
  stack[0] = reinterpret_cast<uintptr_t>(stack + 0);  // saved FP.
  stack[1] = 100;  // Return address into C++ code.
  stack[2] = reinterpret_cast<uintptr_t>(stack + 2);  // saved SP.
}
41 42 43 44

// Dummy method since we don't save callee saved registers in x64.
void CheckCalleeSavedRegisters(const RegisterState& register_state) {}

45 46
#elif V8_TARGET_ARCH_ARM
// How much the JSEntry frame occupies in the stack.
47
constexpr int kJSEntryFrameSpace = 26;
48 49

// Offset where the FP, PC and SP live from the beginning of the JSEntryFrame.
50 51 52
constexpr int kFPOffset = 0;
constexpr int kPCOffset = 1;
constexpr int kSPOffset = 25;
53 54 55

// Builds the stack from {stack} as it is explained in frame-constants-arm.h.
void BuildJSEntryStack(uintptr_t* stack) {
56 57
  stack[0] = reinterpret_cast<uintptr_t>(stack);  // saved FP.
  stack[1] = 100;  // Return address into C++ code (i.e lr/pc)
58 59 60
  // Set d8 = 150, d9 = 151, ..., d15 = 157.
  for (int i = 0; i < 8; ++i) {
    // Double registers occupy two slots. Therefore, upper bits are zeroed.
61 62
    stack[2 + i * 2] = 0;
    stack[2 + i * 2 + 1] = 150 + i;
63 64 65
  }
  // Set r4 = 160, ..., r10 = 166.
  for (int i = 0; i < 7; ++i) {
66
    stack[18 + i] = 160 + i;
67
  }
68
  stack[25] = reinterpret_cast<uintptr_t>(stack + 25);  // saved SP.
69
}
70 71 72 73 74 75 76 77 78 79 80 81 82

// Checks that the values in the calee saved registers are the same as the ones
// we saved in BuildJSEntryStack.
void CheckCalleeSavedRegisters(const RegisterState& register_state) {
  CHECK_EQ_VALUE_REGISTER(160, register_state.callee_saved->arm_r4);
  CHECK_EQ_VALUE_REGISTER(161, register_state.callee_saved->arm_r5);
  CHECK_EQ_VALUE_REGISTER(162, register_state.callee_saved->arm_r6);
  CHECK_EQ_VALUE_REGISTER(163, register_state.callee_saved->arm_r7);
  CHECK_EQ_VALUE_REGISTER(164, register_state.callee_saved->arm_r8);
  CHECK_EQ_VALUE_REGISTER(165, register_state.callee_saved->arm_r9);
  CHECK_EQ_VALUE_REGISTER(166, register_state.callee_saved->arm_r10);
}

83 84
#elif V8_TARGET_ARCH_ARM64
// How much the JSEntry frame occupies in the stack.
85
constexpr int kJSEntryFrameSpace = 21;
86 87

// Offset where the FP, PC and SP live from the beginning of the JSEntryFrame.
88 89 90
constexpr int kFPOffset = 0;
constexpr int kPCOffset = 1;
constexpr int kSPOffset = 20;
91 92 93

// Builds the stack from {stack} as it is explained in frame-constants-arm64.h.
void BuildJSEntryStack(uintptr_t* stack) {
94 95
  stack[0] = reinterpret_cast<uintptr_t>(stack);  // saved FP.
  stack[1] = 100;  // Return address into C++ code (i.e lr/pc)
96 97
  // Set x19 = 150, ..., x28 = 159.
  for (int i = 0; i < 10; ++i) {
98
    stack[2 + i] = 150 + i;
99 100 101
  }
  // Set d8 = 160, ..., d15 = 167.
  for (int i = 0; i < 8; ++i) {
102
    stack[12 + i] = 160 + i;
103
  }
104
  stack[20] = reinterpret_cast<uintptr_t>(stack + 20);  // saved SP.
105
}
106 107 108 109

// Dummy method since we don't save callee saved registers in arm64.
void CheckCalleeSavedRegisters(const RegisterState& register_state) {}

110 111 112 113 114 115 116
#else
// Dummy constants for the rest of the archs which are not supported.
constexpr int kJSEntryFrameSpace = 1;
constexpr int kFPOffset = 0;
constexpr int kPCOffset = 0;
constexpr int kSPOffset = 0;

117
// Dummy methods to be able to compile.
118
void BuildJSEntryStack(uintptr_t* stack) { UNREACHABLE(); }
119 120 121
void CheckCalleeSavedRegisters(const RegisterState& register_state) {
  UNREACHABLE();
}
122 123 124 125
#endif  // V8_TARGET_ARCH_X64

}  // namespace

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
static const void* fake_stack_base = nullptr;

TEST(Unwind_BadState_Fail_CodePagesAPI) {
  JSEntryStubs entry_stubs;  // Fields are intialized to nullptr.
  RegisterState register_state;
  size_t pages_length = 0;
  MemoryRange* code_pages = nullptr;

  bool unwound = v8::Unwinder::TryUnwindV8Frames(
      entry_stubs, pages_length, code_pages, &register_state, fake_stack_base);
  CHECK(!unwound);
  // The register state should not change when unwinding fails.
  CHECK_NULL(register_state.fp);
  CHECK_NULL(register_state.sp);
  CHECK_NULL(register_state.pc);
}

143
// Unwind a middle JS frame (i.e not the JSEntry one).
144 145 146 147 148 149 150 151 152 153 154 155
TEST(Unwind_BuiltinPCInMiddle_Success_CodePagesAPI) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);

  JSEntryStubs entry_stubs = isolate->GetJSEntryStubs();
  MemoryRange code_pages[v8::Isolate::kMinCodePagesBufferSize];
  size_t pages_length =
      isolate->CopyCodePages(arraysize(code_pages), code_pages);
  CHECK_LE(pages_length, arraysize(code_pages));
  RegisterState register_state;

156 157
  // {stack} here mocks the stack, where the top of the stack (i.e the lowest
  // addresses) are represented by lower indices.
158 159
  uintptr_t stack[3];
  void* stack_base = stack + arraysize(stack);
160 161 162 163
  // Index on the stack for the topmost fp (i.e the one right before the C++
  // frame).
  const int topmost_fp_index = 0;
  stack[0] = reinterpret_cast<uintptr_t>(stack + 2);  // saved FP.
164
  stack[1] = 202;  // Return address into C++ code.
165
  stack[2] = reinterpret_cast<uintptr_t>(stack + 2);  // saved SP.
166 167 168 169 170

  register_state.sp = stack;
  register_state.fp = stack;

  // Put the current PC inside of a valid builtin.
171
  Code builtin = i_isolate->builtins()->code(Builtin::kStringEqual);
172 173 174 175 176 177 178 179
  const uintptr_t offset = 40;
  CHECK_LT(offset, builtin.InstructionSize());
  register_state.pc =
      reinterpret_cast<void*>(builtin.InstructionStart() + offset);

  bool unwound = v8::Unwinder::TryUnwindV8Frames(
      entry_stubs, pages_length, code_pages, &register_state, stack_base);
  CHECK(unwound);
180 181 182
  CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index], register_state.fp);
  CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index + 1], register_state.pc);
  CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index + 2], register_state.sp);
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
}

// The unwinder should be able to unwind even if we haven't properly set up the
// current frame, as long as there is another JS frame underneath us (i.e. as
// long as the PC isn't in JSEntry). This test puts the PC at the start
// of a JS builtin and creates a fake JSEntry frame before it on the stack. The
// unwinder should be able to unwind to the C++ frame before the JSEntry frame.
TEST(Unwind_BuiltinPCAtStart_Success_CodePagesAPI) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);

  JSEntryStubs entry_stubs = isolate->GetJSEntryStubs();
  MemoryRange code_pages[v8::Isolate::kMinCodePagesBufferSize];
  RegisterState register_state;

  const size_t code_length = 40;
  uintptr_t code[code_length] = {0};

  // We use AddCodeRange so that |code| is inserted in order.
  i_isolate->AddCodeRange(reinterpret_cast<Address>(code),
                          code_length * sizeof(uintptr_t));
  size_t pages_length =
      isolate->CopyCodePages(arraysize(code_pages), code_pages);
  CHECK_LE(pages_length, arraysize(code_pages));

  uintptr_t stack[6];
  void* stack_base = stack + arraysize(stack);
  stack[0] = 101;
  // Return address into JS code. It doesn't matter that this is not actually in
  // JSEntry, because we only check that for the top frame.
  stack[1] = reinterpret_cast<uintptr_t>(code + 10);
215 216 217 218
  // Index on the stack for the topmost fp (i.e the one right before the C++
  // frame).
  const int topmost_fp_index = 2;
  stack[2] = reinterpret_cast<uintptr_t>(stack + 5);  // saved FP.
219
  stack[3] = 303;  // Return address into C++ code.
220
  stack[4] = reinterpret_cast<uintptr_t>(stack + 4);
221 222 223 224 225 226 227
  stack[5] = 505;

  register_state.sp = stack;
  register_state.fp = stack + 2;  // FP to the JSEntry frame.

  // Put the current PC at the start of a valid builtin, so that we are setting
  // up the frame.
228
  Code builtin = i_isolate->builtins()->code(Builtin::kStringEqual);
229 230 231 232 233 234
  register_state.pc = reinterpret_cast<void*>(builtin.InstructionStart());

  bool unwound = v8::Unwinder::TryUnwindV8Frames(
      entry_stubs, pages_length, code_pages, &register_state, stack_base);

  CHECK(unwound);
235 236 237
  CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index], register_state.fp);
  CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index + 1], register_state.pc);
  CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index + 2], register_state.sp);
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
}

const char* foo_source = R"(
  function foo(a, b) {
    let x = a * b;
    let y = x ^ b;
    let z = y / a;
    return x + y - z;
  };
  %PrepareFunctionForOptimization(foo);
  foo(1, 2);
  foo(1, 2);
  %OptimizeFunctionOnNextCall(foo);
  foo(1, 2);
)";

bool PagesContainsAddress(size_t length, MemoryRange* pages,
                          Address search_address) {
  byte* addr = reinterpret_cast<byte*>(search_address);
  auto it = std::find_if(pages, pages + length, [addr](const MemoryRange& r) {
    const byte* page_start = reinterpret_cast<const byte*>(r.start);
    const byte* page_end = page_start + r.length_in_bytes;
    return addr >= page_start && addr < page_end;
  });
  return it != pages + length;
}

// Check that we can unwind when the pc is within an optimized code object on
// the V8 heap.
TEST(Unwind_CodeObjectPCInMiddle_Success_CodePagesAPI) {
  FLAG_allow_natives_syntax = true;
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
  HandleScope scope(i_isolate);

  JSEntryStubs entry_stubs = isolate->GetJSEntryStubs();
  MemoryRange code_pages[v8::Isolate::kMinCodePagesBufferSize];
  RegisterState register_state;

  uintptr_t stack[3];
  void* stack_base = stack + arraysize(stack);
280 281 282 283
  // Index on the stack for the topmost fp (i.e the one right before the C++
  // frame).
  const int topmost_fp_index = 0;
  stack[0] = reinterpret_cast<uintptr_t>(stack + 2);  // saved FP.
284
  stack[1] = 202;  // Return address into C++ code.
285
  stack[2] = reinterpret_cast<uintptr_t>(stack + 2);  // saved SP.
286 287 288 289 290 291 292 293 294 295 296 297 298

  register_state.sp = stack;
  register_state.fp = stack;

  // Create an on-heap code object. Make sure we run the function so that it is
  // compiled and not just marked for lazy compilation.
  CompileRun(foo_source);
  v8::Local<v8::Function> local_foo = v8::Local<v8::Function>::Cast(
      env.local()->Global()->Get(env.local(), v8_str("foo")).ToLocalChecked());
  Handle<JSFunction> foo =
      Handle<JSFunction>::cast(v8::Utils::OpenHandle(*local_foo));

  // Put the current PC inside of the created code object.
299
  AbstractCode abstract_code = foo->abstract_code(i_isolate);
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
  // We don't produce optimized code when run with --no-opt.
  if (!abstract_code.IsCode() && FLAG_opt == false) return;
  CHECK(abstract_code.IsCode());

  Code code = abstract_code.GetCode();
  // We don't want the offset too early or it could be the `push rbp`
  // instruction (which is not at the start of generated code, because the lazy
  // deopt check happens before frame setup).
  const uintptr_t offset = code.InstructionSize() - 20;
  CHECK_LT(offset, code.InstructionSize());
  Address pc = code.InstructionStart() + offset;
  register_state.pc = reinterpret_cast<void*>(pc);

  // Get code pages from the API now that the code obejct exists and check that
  // our code objects is on one of the pages.
  size_t pages_length =
      isolate->CopyCodePages(arraysize(code_pages), code_pages);
  CHECK_LE(pages_length, arraysize(code_pages));
  CHECK(PagesContainsAddress(pages_length, code_pages, pc));

  bool unwound = v8::Unwinder::TryUnwindV8Frames(
      entry_stubs, pages_length, code_pages, &register_state, stack_base);
  CHECK(unwound);
323 324 325
  CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index], register_state.fp);
  CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index + 1], register_state.pc);
  CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index + 2], register_state.sp);
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
}

// If the PC is within JSEntry but we haven't set up the frame yet, then we
// cannot unwind.
TEST(Unwind_JSEntryBeforeFrame_Fail_CodePagesAPI) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();

  JSEntryStubs entry_stubs = isolate->GetJSEntryStubs();
  MemoryRange code_pages[1];
  size_t pages_length = 1;
  RegisterState register_state;

  const size_t code_length = 40;
  uintptr_t code[code_length] = {0};
  code_pages[0].start = code;
  code_pages[0].length_in_bytes = code_length * sizeof(uintptr_t);

  // Pretend that it takes 5 instructions to set up the frame in JSEntry.
  entry_stubs.js_entry_stub.code.start = code + 10;
  entry_stubs.js_entry_stub.code.length_in_bytes = 10 * sizeof(uintptr_t);

  uintptr_t stack[10];
  void* stack_base = stack + arraysize(stack);
  stack[0] = 101;
  stack[1] = 111;
  stack[2] = 121;
  stack[3] = 131;
  stack[4] = 141;
355 356
  stack[5] = 151;  // Here's where the saved fp would be. We are not going to be
                   // unwinding so we do not need to set it up correctly.
357
  stack[6] = 100;  // Return address into C++ code.
358
  stack[7] = 303;  // Here's where the saved SP would be.
359 360 361
  stack[8] = 404;
  stack[9] = 505;

362 363
  register_state.sp = &stack[5];
  register_state.fp = &stack[9];
364 365

  // Put the current PC inside of JSEntry, before the frame is set up.
366 367
  uintptr_t* jsentry_pc_value = code + 12;
  register_state.pc = jsentry_pc_value;
368 369 370 371
  bool unwound = v8::Unwinder::TryUnwindV8Frames(
      entry_stubs, pages_length, code_pages, &register_state, stack_base);
  CHECK(!unwound);
  // The register state should not change when unwinding fails.
372 373
  CHECK_EQ_VALUE_REGISTER(&stack[9], register_state.fp);
  CHECK_EQ_VALUE_REGISTER(&stack[5], register_state.sp);
374
  CHECK_EQ(jsentry_pc_value, register_state.pc);
375 376

  // Change the PC to a few instructions later, after the frame is set up.
377 378
  jsentry_pc_value = code + 16;
  register_state.pc = jsentry_pc_value;
379 380 381 382 383 384
  unwound = v8::Unwinder::TryUnwindV8Frames(
      entry_stubs, pages_length, code_pages, &register_state, stack_base);
  // TODO(petermarshall): More precisely check position within JSEntry rather
  // than just assuming the frame is unreadable.
  CHECK(!unwound);
  // The register state should not change when unwinding fails.
385 386
  CHECK_EQ_VALUE_REGISTER(&stack[9], register_state.fp);
  CHECK_EQ_VALUE_REGISTER(&stack[5], register_state.sp);
387
  CHECK_EQ(jsentry_pc_value, register_state.pc);
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
}

// Creates a fake stack with two JS frames on top of a C++ frame and checks that
// the unwinder correctly unwinds past the JS frames and returns the C++ frame's
// details.
TEST(Unwind_TwoJSFrames_Success_CodePagesAPI) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();

  JSEntryStubs entry_stubs = isolate->GetJSEntryStubs();
  MemoryRange code_pages[1];
  size_t pages_length = 1;
  RegisterState register_state;

  // Use a fake code range so that we can initialize it to 0s.
  const size_t code_length = 40;
  uintptr_t code[code_length] = {0};
  code_pages[0].start = code;
  code_pages[0].length_in_bytes = code_length * sizeof(uintptr_t);

  // Our fake stack has three frames - one C++ frame and two JS frames (on top).
  // The stack grows from high addresses to low addresses.
410
  uintptr_t stack[5 + kJSEntryFrameSpace];
411 412 413
  void* stack_base = stack + arraysize(stack);
  stack[0] = 101;
  stack[1] = 111;
414
  stack[2] = reinterpret_cast<uintptr_t>(stack + 5);  // saved FP.
415
  // The fake return address is in the JS code range.
416 417
  const void* jsentry_pc = code + 10;
  stack[3] = reinterpret_cast<uintptr_t>(jsentry_pc);
418
  stack[4] = 141;
419 420
  const int top_of_js_entry = 5;
  BuildJSEntryStack(&stack[top_of_js_entry]);
421 422 423 424 425 426 427

  register_state.sp = stack;
  register_state.fp = stack + 2;

  // Put the current PC inside of the code range so it looks valid.
  register_state.pc = code + 30;

428 429 430 431
  // Put the PC in the JSEntryRange.
  entry_stubs.js_entry_stub.code.start = jsentry_pc;
  entry_stubs.js_entry_stub.code.length_in_bytes = sizeof(uintptr_t);

432 433 434 435
  bool unwound = v8::Unwinder::TryUnwindV8Frames(
      entry_stubs, pages_length, code_pages, &register_state, stack_base);

  CHECK(unwound);
436
  CHECK_EQ_VALUE_REGISTER(stack[top_of_js_entry + kFPOffset],
437
                          register_state.fp);
438
  CHECK_EQ_VALUE_REGISTER(stack[top_of_js_entry + kPCOffset],
439
                          register_state.pc);
440
  CHECK_EQ_VALUE_REGISTER(stack[top_of_js_entry + kSPOffset],
441
                          register_state.sp);
442
  CheckCalleeSavedRegisters(register_state);
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
}

// If the PC is in JSEntry then the frame might not be set up correctly, meaning
// we can't unwind the stack properly.
TEST(Unwind_JSEntry_Fail_CodePagesAPI) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);

  JSEntryStubs entry_stubs = isolate->GetJSEntryStubs();
  MemoryRange code_pages[v8::Isolate::kMinCodePagesBufferSize];
  size_t pages_length =
      isolate->CopyCodePages(arraysize(code_pages), code_pages);
  CHECK_LE(pages_length, arraysize(code_pages));
  RegisterState register_state;

459
  Code js_entry = i_isolate->heap()->builtin(Builtin::kJSEntry);
460 461 462 463 464 465 466 467 468 469 470 471
  byte* start = reinterpret_cast<byte*>(js_entry.InstructionStart());
  register_state.pc = start + 10;

  bool unwound = v8::Unwinder::TryUnwindV8Frames(
      entry_stubs, pages_length, code_pages, &register_state, fake_stack_base);
  CHECK(!unwound);
  // The register state should not change when unwinding fails.
  CHECK_NULL(register_state.fp);
  CHECK_NULL(register_state.sp);
  CHECK_EQ(start + 10, register_state.pc);
}

472 473
// Tries to unwind a middle frame (i.e not a JSEntry frame) first with a wrong
// stack base, and then with the correct one.
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
TEST(Unwind_StackBounds_Basic_CodePagesAPI) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();

  JSEntryStubs entry_stubs = isolate->GetJSEntryStubs();
  MemoryRange code_pages[1];
  size_t pages_length = 1;
  RegisterState register_state;

  const size_t code_length = 10;
  uintptr_t code[code_length] = {0};
  code_pages[0].start = code;
  code_pages[0].length_in_bytes = code_length * sizeof(uintptr_t);

  uintptr_t stack[3];
489
  stack[0] = reinterpret_cast<uintptr_t>(stack + 2);  // saved FP.
490
  stack[1] = 202;                                     // saved PC.
491
  stack[2] = 303;  // saved SP.
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527

  register_state.sp = stack;
  register_state.fp = stack;
  register_state.pc = code;

  void* wrong_stack_base = reinterpret_cast<void*>(
      reinterpret_cast<uintptr_t>(stack) - sizeof(uintptr_t));
  bool unwound = v8::Unwinder::TryUnwindV8Frames(
      entry_stubs, pages_length, code_pages, &register_state, wrong_stack_base);
  CHECK(!unwound);

  // Correct the stack base and unwinding should succeed.
  void* correct_stack_base = stack + arraysize(stack);
  unwound =
      v8::Unwinder::TryUnwindV8Frames(entry_stubs, pages_length, code_pages,
                                      &register_state, correct_stack_base);
  CHECK(unwound);
}

TEST(Unwind_StackBounds_WithUnwinding_CodePagesAPI) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();

  JSEntryStubs entry_stubs = isolate->GetJSEntryStubs();
  MemoryRange code_pages[1];
  size_t pages_length = 1;
  RegisterState register_state;

  // Use a fake code range so that we can initialize it to 0s.
  const size_t code_length = 40;
  uintptr_t code[code_length] = {0};
  code_pages[0].start = code;
  code_pages[0].length_in_bytes = code_length * sizeof(uintptr_t);

  // Our fake stack has two frames - one C++ frame and one JS frame (on top).
  // The stack grows from high addresses to low addresses.
528
  uintptr_t stack[9 + kJSEntryFrameSpace];
529 530 531 532 533 534
  void* stack_base = stack + arraysize(stack);
  stack[0] = 101;
  stack[1] = 111;
  stack[2] = 121;
  stack[3] = 131;
  stack[4] = 141;
535
  stack[5] = reinterpret_cast<uintptr_t>(stack + 9);  // saved FP.
536 537 538
  const void* jsentry_pc = code + 20;
  stack[6] = reinterpret_cast<uintptr_t>(jsentry_pc);  // JS code.
  stack[7] = 303;                                      // saved SP.
539
  stack[8] = 404;
540 541 542 543 544 545 546 547
  const int top_of_js_entry = 9;
  BuildJSEntryStack(&stack[top_of_js_entry]);
  // Override FP and PC
  stack[top_of_js_entry + kFPOffset] =
      reinterpret_cast<uintptr_t>(stack) +
      (9 + kJSEntryFrameSpace + 1) * sizeof(uintptr_t);  // saved FP (OOB).
  stack[top_of_js_entry + kPCOffset] =
      reinterpret_cast<uintptr_t>(code + 20);  // JS code.
548 549 550 551 552 553 554

  register_state.sp = stack;
  register_state.fp = stack + 5;

  // Put the current PC inside of the code range so it looks valid.
  register_state.pc = code + 30;

555 556 557 558
  // Put the PC in the JSEntryRange.
  entry_stubs.js_entry_stub.code.start = jsentry_pc;
  entry_stubs.js_entry_stub.code.length_in_bytes = sizeof(uintptr_t);

559 560 561 562 563 564
  // Unwind will fail because stack[9] FP points outside of the stack.
  bool unwound = v8::Unwinder::TryUnwindV8Frames(
      entry_stubs, pages_length, code_pages, &register_state, stack_base);
  CHECK(!unwound);

  // Change the return address so that it is not in range. We will not range
565
  // check the stack's FP value because we have finished unwinding and the
566
  // contents of rbp does not necessarily have to be the FP in this case.
567
  stack[top_of_js_entry + kPCOffset] = 202;
568 569 570
  unwound = v8::Unwinder::TryUnwindV8Frames(
      entry_stubs, pages_length, code_pages, &register_state, stack_base);
  CHECK(unwound);
571
  CheckCalleeSavedRegisters(register_state);
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
}

TEST(PCIsInV8_BadState_Fail_CodePagesAPI) {
  void* pc = nullptr;
  size_t pages_length = 0;
  MemoryRange* code_pages = nullptr;

  CHECK(!v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
}

TEST(PCIsInV8_ValidStateNullPC_Fail_CodePagesAPI) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();

  void* pc = nullptr;

  MemoryRange code_pages[v8::Isolate::kMinCodePagesBufferSize];
  size_t pages_length =
      isolate->CopyCodePages(arraysize(code_pages), code_pages);
  CHECK_LE(pages_length, arraysize(code_pages));

  CHECK(!v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
}

void TestRangeBoundaries(size_t pages_length, MemoryRange* code_pages,
                         byte* range_start, size_t range_length) {
  void* pc = range_start - 1;
  CHECK(!v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
  pc = range_start;
  CHECK(v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
  pc = range_start + 1;
  CHECK(v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
  pc = range_start + range_length - 1;
  CHECK(v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
  pc = range_start + range_length;
  CHECK(!v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
  pc = range_start + range_length + 1;
  CHECK(!v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
}

TEST(PCIsInV8_InAllCodePages_CodePagesAPI) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();

  MemoryRange code_pages[v8::Isolate::kMinCodePagesBufferSize];
  size_t pages_length =
      isolate->CopyCodePages(arraysize(code_pages), code_pages);
  CHECK_LE(pages_length, arraysize(code_pages));

  for (size_t i = 0; i < pages_length; i++) {
    byte* range_start =
        const_cast<byte*>(reinterpret_cast<const byte*>(code_pages[i].start));
    size_t range_length = code_pages[i].length_in_bytes;
    TestRangeBoundaries(pages_length, code_pages, range_start, range_length);
  }
}

// PCIsInV8 doesn't check if the PC is in JSEntry directly. It's assumed that
// the CodeRange or EmbeddedCodeRange contain JSEntry.
TEST(PCIsInV8_InJSEntryRange_CodePagesAPI) {
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);

  MemoryRange code_pages[v8::Isolate::kMinCodePagesBufferSize];
  size_t pages_length =
      isolate->CopyCodePages(arraysize(code_pages), code_pages);
  CHECK_LE(pages_length, arraysize(code_pages));

641
  Code js_entry = i_isolate->heap()->builtin(Builtin::kJSEntry);
642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663
  byte* start = reinterpret_cast<byte*>(js_entry.InstructionStart());
  size_t length = js_entry.InstructionSize();

  void* pc = start;
  CHECK(v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
  pc = start + 1;
  CHECK(v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
  pc = start + length - 1;
  CHECK(v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
}

// Large code objects can be allocated in large object space. Check that this is
// inside the CodeRange.
TEST(PCIsInV8_LargeCodeObject_CodePagesAPI) {
  FLAG_allow_natives_syntax = true;
  LocalContext env;
  v8::Isolate* isolate = env->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
  HandleScope scope(i_isolate);

  // Create a big function that ends up in CODE_LO_SPACE.
  const int instruction_size = Page::kPageSize + 1;
664
  CHECK_GT(instruction_size, MemoryChunkLayout::MaxRegularCodeObjectSize());
665 666 667 668 669 670 671 672 673 674 675 676
  std::unique_ptr<byte[]> instructions(new byte[instruction_size]);

  CodeDesc desc;
  desc.buffer = instructions.get();
  desc.buffer_size = instruction_size;
  desc.instr_size = instruction_size;
  desc.reloc_size = 0;
  desc.constant_pool_size = 0;
  desc.unwinding_info = nullptr;
  desc.unwinding_info_size = 0;
  desc.origin = nullptr;
  Handle<Code> foo_code =
677
      Factory::CodeBuilder(i_isolate, desc, CodeKind::WASM_FUNCTION).Build();
678 679 680 681 682 683 684 685 686 687 688 689 690

  CHECK(i_isolate->heap()->InSpace(*foo_code, CODE_LO_SPACE));
  byte* start = reinterpret_cast<byte*>(foo_code->InstructionStart());

  MemoryRange code_pages[v8::Isolate::kMinCodePagesBufferSize];
  size_t pages_length =
      isolate->CopyCodePages(arraysize(code_pages), code_pages);
  CHECK_LE(pages_length, arraysize(code_pages));

  void* pc = start;
  CHECK(v8::Unwinder::PCIsInV8(pages_length, code_pages, pc));
}

691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764
#ifdef USE_SIMULATOR
// TODO(v8:10026): Make this also work without the simulator. The part that
// needs modifications is getting the RegisterState.
class UnwinderTestHelper {
 public:
  explicit UnwinderTestHelper(const std::string& test_function)
      : isolate_(CcTest::isolate()) {
    CHECK(!instance_);
    instance_ = this;
    v8::HandleScope scope(isolate_);
    v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
    global->Set(v8_str("TryUnwind"),
                v8::FunctionTemplate::New(isolate_, TryUnwind));
    LocalContext env(isolate_, nullptr, global);
    CompileRun(v8_str(test_function.c_str()));
  }

  ~UnwinderTestHelper() { instance_ = nullptr; }

 private:
  static void TryUnwind(const v8::FunctionCallbackInfo<v8::Value>& args) {
    instance_->DoTryUnwind();
  }

  void DoTryUnwind() {
    // Set up RegisterState.
    v8::RegisterState register_state;
    SimulatorHelper simulator_helper;
    if (!simulator_helper.Init(isolate_)) return;
    simulator_helper.FillRegisters(&register_state);
    // At this point, the PC will point to a Redirection object, which is not
    // in V8 as far as the unwinder is concerned. To make this work, point to
    // the return address, which is in V8, instead.
    register_state.pc = register_state.lr;

    JSEntryStubs entry_stubs = isolate_->GetJSEntryStubs();
    MemoryRange code_pages[v8::Isolate::kMinCodePagesBufferSize];
    size_t pages_length =
        isolate_->CopyCodePages(arraysize(code_pages), code_pages);
    CHECK_LE(pages_length, arraysize(code_pages));

    void* stack_base = reinterpret_cast<void*>(0xffffffffffffffffL);
    bool unwound = v8::Unwinder::TryUnwindV8Frames(
        entry_stubs, pages_length, code_pages, &register_state, stack_base);
    // Check that we have successfully unwound past js_entry_sp.
    CHECK(unwound);
    CHECK_GT(register_state.sp,
             reinterpret_cast<void*>(CcTest::i_isolate()->js_entry_sp()));
  }

  v8::Isolate* isolate_;

  static UnwinderTestHelper* instance_;
};

UnwinderTestHelper* UnwinderTestHelper::instance_;

TEST(Unwind_TwoNestedFunctions_CodePagesAPI) {
  i::FLAG_allow_natives_syntax = true;
  const char* test_script =
      "function test_unwinder_api_inner() {"
      "  TryUnwind();"
      "  return 0;"
      "}"
      "function test_unwinder_api_outer() {"
      "  return test_unwinder_api_inner();"
      "}"
      "%NeverOptimizeFunction(test_unwinder_api_inner);"
      "%NeverOptimizeFunction(test_unwinder_api_outer);"
      "test_unwinder_api_outer();";

  UnwinderTestHelper helper(test_script);
}
#endif
765

766
#undef CHECK_EQ_VALUE_REGISTER
767 768 769
}  // namespace test_unwinder_code_pages
}  // namespace internal
}  // namespace v8