// 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 "include/v8-profiler.h"
|
#include "src/counters.h"
|
#include "src/frames-inl.h"
|
#include "src/msan.h"
|
#include "src/simulator.h"
|
#include "src/vm-state-inl.h"
|
|
namespace v8 {
|
namespace {
|
|
bool IsSamePage(i::Address ptr1, i::Address ptr2) {
|
const uint32_t kPageSize = 4096;
|
i::Address mask = ~static_cast<i::Address>(kPageSize - 1);
|
return (ptr1 & mask) == (ptr2 & mask);
|
}
|
|
// Check if the code at specified address could potentially be a
|
// frame setup code.
|
bool IsNoFrameRegion(i::Address address) {
|
struct Pattern {
|
int bytes_count;
|
i::byte bytes[8];
|
int offsets[4];
|
};
|
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, {}, {}}
|
};
|
i::byte* pc = reinterpret_cast<i::byte*>(address);
|
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(address, address - 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
|
|
namespace internal {
|
namespace {
|
|
#if defined(USE_SIMULATOR)
|
class SimulatorHelper {
|
public:
|
// Returns true if register values were successfully retrieved
|
// from the simulator, otherwise returns false.
|
static bool FillRegisters(Isolate* isolate, v8::RegisterState* state);
|
};
|
|
bool SimulatorHelper::FillRegisters(Isolate* isolate,
|
v8::RegisterState* state) {
|
Simulator* simulator = isolate->thread_local_top()->simulator_;
|
// Check if there is active simulator.
|
if (simulator == nullptr) return false;
|
#if V8_TARGET_ARCH_ARM
|
if (!simulator->has_bad_pc()) {
|
state->pc = reinterpret_cast<void*>(simulator->get_pc());
|
}
|
state->sp = reinterpret_cast<void*>(simulator->get_register(Simulator::sp));
|
state->fp = reinterpret_cast<void*>(simulator->get_register(Simulator::r11));
|
#elif V8_TARGET_ARCH_ARM64
|
state->pc = reinterpret_cast<void*>(simulator->pc());
|
state->sp = reinterpret_cast<void*>(simulator->sp());
|
state->fp = reinterpret_cast<void*>(simulator->fp());
|
#elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
|
if (!simulator->has_bad_pc()) {
|
state->pc = reinterpret_cast<void*>(simulator->get_pc());
|
}
|
state->sp = reinterpret_cast<void*>(simulator->get_register(Simulator::sp));
|
state->fp = reinterpret_cast<void*>(simulator->get_register(Simulator::fp));
|
#elif V8_TARGET_ARCH_PPC
|
if (!simulator->has_bad_pc()) {
|
state->pc = reinterpret_cast<void*>(simulator->get_pc());
|
}
|
state->sp = reinterpret_cast<void*>(simulator->get_register(Simulator::sp));
|
state->fp = reinterpret_cast<void*>(simulator->get_register(Simulator::fp));
|
#elif V8_TARGET_ARCH_S390
|
if (!simulator->has_bad_pc()) {
|
state->pc = reinterpret_cast<void*>(simulator->get_pc());
|
}
|
state->sp = reinterpret_cast<void*>(simulator->get_register(Simulator::sp));
|
state->fp = reinterpret_cast<void*>(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
|
} // namespace internal
|
|
//
|
// StackTracer implementation
|
//
|
DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate,
|
const RegisterState& reg_state,
|
RecordCEntryFrame record_c_entry_frame,
|
bool update_stats,
|
bool use_simulator_reg_state) {
|
this->update_stats = update_stats;
|
SampleInfo info;
|
RegisterState regs = reg_state;
|
if (!GetStackSample(v8_isolate, ®s, record_c_entry_frame, stack,
|
kMaxFramesCount, &info, use_simulator_reg_state)) {
|
// It is executing JS but failed to collect a stack trace.
|
// Mark the sample as spoiled.
|
pc = nullptr;
|
return;
|
}
|
|
state = info.vm_state;
|
pc = 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 = 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(void*));
|
// Sample potential return address value for frameless invocation of
|
// stubs (we'll figure out later, if this value makes sense).
|
tos = reinterpret_cast<void*>(
|
i::Memory<i::Address>(reinterpret_cast<i::Address>(regs.sp)));
|
} else {
|
tos = nullptr;
|
}
|
}
|
|
bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs,
|
RecordCEntryFrame record_c_entry_frame,
|
void** frames, size_t frames_limit,
|
v8::SampleInfo* sample_info,
|
bool use_simulator_reg_state) {
|
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
|
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;
|
|
i::Address js_entry_sp = isolate->js_entry_sp();
|
if (js_entry_sp == 0) return true; // Not executing JS now.
|
|
#if defined(USE_SIMULATOR)
|
if (use_simulator_reg_state) {
|
if (!i::SimulatorHelper::FillRegisters(isolate, regs)) return false;
|
}
|
#else
|
USE(use_simulator_reg_state);
|
#endif
|
DCHECK(regs->sp);
|
|
// Check whether we interrupted setup/teardown of a stack frame in JS code.
|
// Avoid this check for C++ code, as that would trigger false positives.
|
if (regs->pc &&
|
isolate->heap()->memory_allocator()->code_range()->contains(
|
reinterpret_cast<i::Address>(regs->pc)) &&
|
IsNoFrameRegion(reinterpret_cast<i::Address>(regs->pc))) {
|
// The frame is not setup, so it'd be hard to iterate the stack. Bailout.
|
return false;
|
}
|
|
i::ExternalCallbackScope* scope = isolate->external_callback_scope();
|
i::Address handler = i::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) {
|
i::Address* external_callback_entry_ptr =
|
scope->callback_entrypoint_address();
|
sample_info->external_callback_entry =
|
external_callback_entry_ptr == nullptr
|
? nullptr
|
: reinterpret_cast<void*>(*external_callback_entry_ptr);
|
}
|
|
i::SafeStackFrameIterator it(isolate, reinterpret_cast<i::Address>(regs->fp),
|
reinterpret_cast<i::Address>(regs->sp),
|
js_entry_sp);
|
if (it.done()) return true;
|
|
size_t i = 0;
|
if (record_c_entry_frame == kIncludeCEntryFrame &&
|
(it.top_frame_type() == internal::StackFrame::EXIT ||
|
it.top_frame_type() == internal::StackFrame::BUILTIN_EXIT)) {
|
frames[i++] = reinterpret_cast<void*>(isolate->c_function());
|
}
|
i::RuntimeCallTimer* timer =
|
isolate->counters()->runtime_call_stats()->current_timer();
|
for (; !it.done() && i < frames_limit; it.Advance()) {
|
while (timer && reinterpret_cast<i::Address>(timer) < it.frame()->fp() &&
|
i < frames_limit) {
|
frames[i++] = reinterpret_cast<void*>(timer->counter());
|
timer = timer->parent();
|
}
|
if (i == frames_limit) break;
|
if (it.frame()->is_interpreted()) {
|
// For interpreted frames use the bytecode array pointer as the pc.
|
i::InterpretedFrame* frame =
|
static_cast<i::InterpretedFrame*>(it.frame());
|
// Since the sampler can interrupt execution at any point the
|
// bytecode_array might be garbage, so don't actually dereference it. We
|
// avoid the frame->GetXXX functions since they call BytecodeArray::cast,
|
// which has a heap access in its DCHECK.
|
i::Object* bytecode_array = i::Memory<i::Object*>(
|
frame->fp() + i::InterpreterFrameConstants::kBytecodeArrayFromFp);
|
i::Object* bytecode_offset = i::Memory<i::Object*>(
|
frame->fp() + i::InterpreterFrameConstants::kBytecodeOffsetFromFp);
|
|
// If the bytecode array is a heap object and the bytecode offset is a
|
// Smi, use those, otherwise fall back to using the frame's pc.
|
if (HAS_HEAP_OBJECT_TAG(bytecode_array) && HAS_SMI_TAG(bytecode_offset)) {
|
frames[i++] = reinterpret_cast<void*>(
|
reinterpret_cast<i::Address>(bytecode_array) +
|
i::Internals::SmiValue(bytecode_offset));
|
continue;
|
}
|
}
|
frames[i++] = reinterpret_cast<void*>(it.frame()->pc());
|
}
|
sample_info->frames_count = i;
|
return true;
|
}
|
|
namespace internal {
|
|
void TickSample::Init(Isolate* isolate, const v8::RegisterState& state,
|
RecordCEntryFrame record_c_entry_frame, bool update_stats,
|
bool use_simulator_reg_state) {
|
v8::TickSample::Init(reinterpret_cast<v8::Isolate*>(isolate), state,
|
record_c_entry_frame, update_stats,
|
use_simulator_reg_state);
|
if (pc == nullptr) return;
|
timestamp = base::TimeTicks::HighResolutionNow();
|
}
|
|
} // namespace internal
|
} // namespace v8
|