// Copyright 2013 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 "src/profiler/tick-sample.h"

#include "src/frames-inl.h"
#include "src/vm-state-inl.h"


namespace v8 {
namespace internal {

namespace {

bool IsSamePage(byte* ptr1, byte* ptr2) {
  const uint32_t kPageSize = 4096;
  uintptr_t mask = ~static_cast<uintptr_t>(kPageSize - 1);
  return (reinterpret_cast<uintptr_t>(ptr1) & mask) ==
         (reinterpret_cast<uintptr_t>(ptr2) & mask);
}

// Check if the code at specified address could potentially be a
// frame setup code.
bool IsNoFrameRegion(Address address) {
  struct Pattern {
    int bytes_count;
    byte bytes[8];
    int offsets[4];
  };
  byte* pc = reinterpret_cast<byte*>(address);
  static Pattern patterns[] = {
#if V8_HOST_ARCH_IA32
    // push %ebp
    // mov %esp,%ebp
    {3, {0x55, 0x89, 0xe5}, {0, 1, -1}},
    // pop %ebp
    // ret N
    {2, {0x5d, 0xc2}, {0, 1, -1}},
    // pop %ebp
    // ret
    {2, {0x5d, 0xc3}, {0, 1, -1}},
#elif V8_HOST_ARCH_X64
    // pushq %rbp
    // movq %rsp,%rbp
    {4, {0x55, 0x48, 0x89, 0xe5}, {0, 1, -1}},
    // popq %rbp
    // ret N
    {2, {0x5d, 0xc2}, {0, 1, -1}},
    // popq %rbp
    // ret
    {2, {0x5d, 0xc3}, {0, 1, -1}},
#endif
    {0, {}, {}}
  };
  for (Pattern* pattern = patterns; pattern->bytes_count; ++pattern) {
    for (int* offset_ptr = pattern->offsets; *offset_ptr != -1; ++offset_ptr) {
      int offset = *offset_ptr;
      if (!offset || IsSamePage(pc, pc - offset)) {
        MSAN_MEMORY_IS_INITIALIZED(pc - offset, pattern->bytes_count);
        if (!memcmp(pc - offset, pattern->bytes, pattern->bytes_count))
          return true;
      } else {
        // It is not safe to examine bytes on another page as it might not be
        // allocated thus causing a SEGFAULT.
        // Check the pattern part that's on the same page and
        // pessimistically assume it could be the entire pattern match.
        MSAN_MEMORY_IS_INITIALIZED(pc, pattern->bytes_count - offset);
        if (!memcmp(pc, pattern->bytes + offset, pattern->bytes_count - offset))
          return true;
      }
    }
  }
  return false;
}

}  // namespace

//
// StackTracer implementation
//
DISABLE_ASAN void TickSample::Init(Isolate* isolate,
                                   const v8::RegisterState& regs,
                                   RecordCEntryFrame record_c_entry_frame,
                                   bool update_stats) {
  timestamp = base::TimeTicks::HighResolutionNow();
  this->update_stats = update_stats;

  SampleInfo info;
  if (GetStackSample(isolate, regs, record_c_entry_frame,
                     reinterpret_cast<void**>(&stack[0]), kMaxFramesCount,
                     &info)) {
    state = info.vm_state;
    pc = static_cast<Address>(regs.pc);
    frames_count = static_cast<unsigned>(info.frames_count);
    has_external_callback = info.external_callback_entry != nullptr;
    if (has_external_callback) {
      external_callback_entry =
          static_cast<Address>(info.external_callback_entry);
    } else if (frames_count) {
      // sp register may point at an arbitrary place in memory, make
      // sure MSAN doesn't complain about it.
      MSAN_MEMORY_IS_INITIALIZED(regs.sp, sizeof(Address));
      // Sample potential return address value for frameless invocation of
      // stubs (we'll figure out later, if this value makes sense).
      tos = Memory::Address_at(reinterpret_cast<Address>(regs.sp));
    } else {
      tos = nullptr;
    }
  } else {
    // It is executing JS but failed to collect a stack trace.
    // Mark the sample as spoiled.
    timestamp = base::TimeTicks();
    pc = nullptr;
  }
}

bool TickSample::GetStackSample(Isolate* isolate, const v8::RegisterState& regs,
                                RecordCEntryFrame record_c_entry_frame,
                                void** frames, size_t frames_limit,
                                v8::SampleInfo* sample_info) {
  sample_info->frames_count = 0;
  sample_info->vm_state = isolate->current_vm_state();
  sample_info->external_callback_entry = nullptr;
  if (sample_info->vm_state == GC) return true;

  Address js_entry_sp = isolate->js_entry_sp();
  if (js_entry_sp == 0) return true;  // Not executing JS now.

  if (regs.pc && IsNoFrameRegion(static_cast<Address>(regs.pc))) {
    // Can't collect stack.
    return false;
  }

  ExternalCallbackScope* scope = isolate->external_callback_scope();
  Address handler = Isolate::handler(isolate->thread_local_top());
  // If there is a handler on top of the external callback scope then
  // we have already entrered JavaScript again and the external callback
  // is not the top function.
  if (scope && scope->scope_address() < handler) {
    sample_info->external_callback_entry =
        *scope->callback_entrypoint_address();
  }

  SafeStackFrameIterator it(isolate, reinterpret_cast<Address>(regs.fp),
                            reinterpret_cast<Address>(regs.sp), js_entry_sp);
  size_t i = 0;
  if (record_c_entry_frame == kIncludeCEntryFrame && !it.done() &&
      it.top_frame_type() == StackFrame::EXIT) {
    frames[i++] = isolate->c_function();
  }
  while (!it.done() && i < frames_limit) {
    if (it.frame()->is_interpreted()) {
      // For interpreted frames use the bytecode array pointer as the pc.
      InterpretedFrame* frame = static_cast<InterpretedFrame*>(it.frame());
      // Since the sampler can interrupt execution at any point the
      // bytecode_array might be garbage, so don't dereference it.
      Address bytecode_array =
          reinterpret_cast<Address>(frame->GetBytecodeArray()) - kHeapObjectTag;
      frames[i++] = bytecode_array + BytecodeArray::kHeaderSize +
                    frame->GetBytecodeOffset();
    } else {
      frames[i++] = it.frame()->pc();
    }
    it.Advance();
  }
  sample_info->frames_count = i;
  return true;
}

#if defined(USE_SIMULATOR)
bool SimulatorHelper::FillRegisters(Isolate* isolate,
                                    v8::RegisterState* state) {
  Simulator *simulator = isolate->thread_local_top()->simulator_;
  // Check if there is active simulator.
  if (simulator == NULL) return false;
#if V8_TARGET_ARCH_ARM
  if (!simulator->has_bad_pc()) {
    state->pc = reinterpret_cast<Address>(simulator->get_pc());
  }
  state->sp = reinterpret_cast<Address>(simulator->get_register(Simulator::sp));
  state->fp = reinterpret_cast<Address>(simulator->get_register(
      Simulator::r11));
#elif V8_TARGET_ARCH_ARM64
  state->pc = reinterpret_cast<Address>(simulator->pc());
  state->sp = reinterpret_cast<Address>(simulator->sp());
  state->fp = reinterpret_cast<Address>(simulator->fp());
#elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
  if (!simulator->has_bad_pc()) {
    state->pc = reinterpret_cast<Address>(simulator->get_pc());
  }
  state->sp = reinterpret_cast<Address>(simulator->get_register(Simulator::sp));
  state->fp = reinterpret_cast<Address>(simulator->get_register(Simulator::fp));
#elif V8_TARGET_ARCH_PPC
  if (!simulator->has_bad_pc()) {
    state->pc = reinterpret_cast<Address>(simulator->get_pc());
  }
  state->sp = reinterpret_cast<Address>(simulator->get_register(Simulator::sp));
  state->fp = reinterpret_cast<Address>(simulator->get_register(Simulator::fp));
#elif V8_TARGET_ARCH_S390
  if (!simulator->has_bad_pc()) {
    state->pc = reinterpret_cast<Address>(simulator->get_pc());
  }
  state->sp = reinterpret_cast<Address>(simulator->get_register(Simulator::sp));
  state->fp = reinterpret_cast<Address>(simulator->get_register(Simulator::fp));
#endif
  if (state->sp == 0 || state->fp == 0) {
    // It possible that the simulator is interrupted while it is updating
    // the sp or fp register. ARM64 simulator does this in two steps:
    // first setting it to zero and then setting it to the new value.
    // Bailout if sp/fp doesn't contain the new value.
    //
    // FIXME: The above doesn't really solve the issue.
    // If a 64-bit target is executed on a 32-bit host even the final
    // write is non-atomic, so it might obtain a half of the result.
    // Moreover as long as the register set code uses memcpy (as of now),
    // it is not guaranteed to be atomic even when both host and target
    // are of same bitness.
    return false;
  }
  return true;
}
#endif  // USE_SIMULATOR

}  // namespace internal
}  // namespace v8