// 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