#include "src/profiler/tick-sample.h"
#include <cinttypes>
#include "include/v8-profiler.h"
#include "src/base/sanitizer/asan.h"
#include "src/base/sanitizer/msan.h"
#include "src/execution/embedder-state.h"
#include "src/execution/frames-inl.h"
#include "src/execution/simulator.h"
#include "src/execution/vm-state-inl.h"
#include "src/heap/heap-inl.h"
#include "src/logging/counters.h"
#include "src/profiler/profiler-stats.h"
namespace v8 {
namespace internal {
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);
}
bool IsNoFrameRegion(i::Address address) {
struct Pattern {
int bytes_count;
uint8_t bytes[8];
int offsets[4];
};
static Pattern patterns[] = {
#if V8_HOST_ARCH_IA32
{3, {0x55, 0x89, 0xE5}, {0, 1, -1}},
{2, {0x5D, 0xC2}, {0, 1, -1}},
{2, {0x5D, 0xC3}, {0, 1, -1}},
#elif V8_HOST_ARCH_X64
{4, {0x55, 0x48, 0x89, 0xE5}, {0, 1, -1}},
{2, {0x5D, 0xC2}, {0, 1, -1}},
{2, {0x5D, 0xC3}, {0, 1, -1}},
#endif
{0, {}, {}}
};
uint8_t* pc = reinterpret_cast<uint8_t*>(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 {
MSAN_MEMORY_IS_INITIALIZED(pc, pattern->bytes_count - offset);
if (!memcmp(pc, pattern->bytes + offset, pattern->bytes_count - offset))
return true;
}
}
}
return false;
}
#if defined(USE_SIMULATOR)
class SimulatorHelper {
public:
static bool FillRegisters(Isolate* isolate, v8::RegisterState* state);
};
bool SimulatorHelper::FillRegisters(Isolate* isolate,
v8::RegisterState* state) {
Simulator* simulator = isolate->thread_local_top()->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));
state->lr = reinterpret_cast<void*>(simulator->get_register(Simulator::lr));
#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());
state->lr = reinterpret_cast<void*>(simulator->lr());
#elif V8_TARGET_ARCH_MIPS64 || V8_TARGET_ARCH_LOONG64
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_PPC64
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));
state->lr = reinterpret_cast<void*>(simulator->get_lr());
#elif V8_TARGET_ARCH_S390X
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));
state->lr = reinterpret_cast<void*>(simulator->get_register(Simulator::ra));
#elif V8_TARGET_ARCH_RISCV64
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));
state->lr = reinterpret_cast<void*>(simulator->get_register(Simulator::ra));
#elif V8_TARGET_ARCH_RISCV32
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));
state->lr = reinterpret_cast<void*>(simulator->get_register(Simulator::ra));
#endif
if (state->sp == 0 || state->fp == 0) {
return false;
}
return true;
}
#endif
}
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,
base::TimeDelta sampling_interval,
const std::optional<uint64_t> trace_id) {
update_stats_ = update_stats;
SampleInfo info;
RegisterState regs = reg_state;
if (!GetStackSample(v8_isolate, ®s, record_c_entry_frame, stack,
kMaxFramesCount, &info, &state,
use_simulator_reg_state)) {
pc = nullptr;
return;
}
if (!IsExternal(state)) {
state = info.vm_state;
}
pc = regs.pc;
frames_count = static_cast<unsigned>(info.frames_count);
has_external_callback = info.external_callback_entry != nullptr;
context = info.context;
embedder_context = info.embedder_context;
embedder_state = info.embedder_state;
if (has_external_callback) {
external_callback_entry = info.external_callback_entry;
} else if (frames_count) {
ASAN_UNPOISON_MEMORY_REGION(regs.sp, sizeof(void*));
MSAN_MEMORY_IS_INITIALIZED(regs.sp, sizeof(void*));
tos = nullptr;
} else {
tos = nullptr;
}
sampling_interval_ = sampling_interval;
trace_id_ = trace_id;
timestamp = base::TimeTicks::Now();
}
bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs,
RecordCEntryFrame record_c_entry_frame,
void** frames, size_t frames_limit,
v8::SampleInfo* sample_info,
StateTag* out_state,
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;
sample_info->embedder_state = EmbedderStateTag::EMPTY;
sample_info->embedder_context = nullptr;
sample_info->context = nullptr;
if (sample_info->vm_state == GC || v8_isolate->heap()->IsInGC()) {
return true;
}
EmbedderState* embedder_state = isolate->current_embedder_state();
if (embedder_state != nullptr) {
sample_info->embedder_context =
reinterpret_cast<void*>(embedder_state->native_context_address());
sample_info->embedder_state = embedder_state->GetState();
}
Tagged<Context> top_context = isolate->context();
if (top_context.ptr() != i::Context::kNoContext) {
Tagged<NativeContext> top_native_context = top_context->native_context();
sample_info->context = reinterpret_cast<void*>(top_native_context.ptr());
}
i::Address js_entry_sp = isolate->js_entry_sp();
if (js_entry_sp == 0) return true;
#if V8_ENABLE_WEBASSEMBLY
wasm::StackMemory* stack = isolate->isolate_data()->active_stack();
if (stack != nullptr && stack->jmpbuf()->parent != nullptr) {
js_entry_sp = stack->base();
}
#endif
#if defined(USE_SIMULATOR)
if (use_simulator_reg_state) {
if (!i::SimulatorHelper::FillRegisters(isolate, regs)) {
i::ProfilerStats::Instance()->AddReason(
i::ProfilerStats::Reason::kSimulatorFillRegistersFailed);
return false;
}
}
#else
USE(use_simulator_reg_state);
#endif
DCHECK(regs->sp);
if (regs->pc &&
isolate->heap()->code_region().contains(
reinterpret_cast<i::Address>(regs->pc)) &&
IsNoFrameRegion(reinterpret_cast<i::Address>(regs->pc))) {
i::ProfilerStats::Instance()->AddReason(
i::ProfilerStats::Reason::kNoFrameRegion);
return false;
}
i::ExternalCallbackScope* scope = isolate->external_callback_scope();
i::Address handler = i::Isolate::handler(isolate->thread_local_top());
if (scope && scope->JSStackComparableAddress() < 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);
}
IsolateData* isolate_data = isolate->isolate_data();
Address fast_c_fp = isolate_data->fast_c_call_caller_fp();
if (fast_c_fp != kNullAddress &&
isolate_data->fast_api_call_target() != kNullAddress) {
sample_info->external_callback_entry =
reinterpret_cast<void*>(isolate_data->fast_api_call_target());
if (out_state) {
*out_state = StateTag::EXTERNAL;
}
}
i::StackFrameIteratorForProfiler it(
isolate, reinterpret_cast<i::Address>(regs->pc),
reinterpret_cast<i::Address>(regs->fp),
reinterpret_cast<i::Address>(regs->sp),
reinterpret_cast<i::Address>(regs->lr), 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)) {
void* c_function = reinterpret_cast<void*>(isolate->c_function());
if (sample_info->external_callback_entry != c_function) {
frames[i] = c_function;
i++;
}
}
#ifdef V8_RUNTIME_CALL_STATS
i::RuntimeCallTimer* timer =
isolate->counters()->runtime_call_stats()->current_timer();
#endif
for (; !it.done() && i < frames_limit; it.Advance()) {
#ifdef V8_RUNTIME_CALL_STATS
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;
#endif
if (it.frame()->is_interpreted()) {
i::InterpretedFrame* frame =
static_cast<i::InterpretedFrame*>(it.frame());
i::Address bytecode_array = base::Memory<i::Address>(
frame->fp() + i::InterpreterFrameConstants::kBytecodeArrayFromFp);
i::Address bytecode_offset = base::Memory<i::Address>(
frame->fp() + i::InterpreterFrameConstants::kBytecodeOffsetFromFp);
if (HAS_STRONG_HEAP_OBJECT_TAG(bytecode_array) &&
HAS_SMI_TAG(bytecode_offset)) {
frames[i++] = reinterpret_cast<void*>(
bytecode_array + i::Internals::SmiValue(bytecode_offset));
continue;
}
}
frames[i++] = reinterpret_cast<void*>(it.frame()->unauthenticated_pc());
}
sample_info->frames_count = i;
return true;
}
void TickSample::print() const {
PrintF("TickSample: at %p\n", this);
PrintF(" - state: %s\n", ToString(state));
PrintF(" - pc: %p\n", pc);
PrintF(" - stack: (%u frames)\n", frames_count);
for (unsigned i = 0; i < frames_count; i++) {
PrintF(" %p\n", stack[i]);
}
PrintF(" - has_external_callback: %d\n", has_external_callback);
PrintF(" - %s: %p\n",
has_external_callback ? "external_callback_entry" : "tos", tos);
PrintF(" - update_stats: %d\n", update_stats_);
PrintF(" - sampling_interval: %" PRId64 "\n",
sampling_interval_.InMicroseconds());
PrintF("\n");
}
}
}