Home | History | Annotate | Download | only in profiler
      1 // Copyright 2013 the V8 project authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "src/profiler/tick-sample.h"
      6 
      7 #include "include/v8-profiler.h"
      8 #include "src/counters.h"
      9 #include "src/frames-inl.h"
     10 #include "src/msan.h"
     11 #include "src/simulator.h"
     12 #include "src/vm-state-inl.h"
     13 
     14 namespace v8 {
     15 namespace {
     16 
     17 bool IsSamePage(i::Address ptr1, i::Address ptr2) {
     18   const uint32_t kPageSize = 4096;
     19   i::Address mask = ~static_cast<i::Address>(kPageSize - 1);
     20   return (ptr1 & mask) == (ptr2 & mask);
     21 }
     22 
     23 // Check if the code at specified address could potentially be a
     24 // frame setup code.
     25 bool IsNoFrameRegion(i::Address address) {
     26   struct Pattern {
     27     int bytes_count;
     28     i::byte bytes[8];
     29     int offsets[4];
     30   };
     31   static Pattern patterns[] = {
     32 #if V8_HOST_ARCH_IA32
     33     // push %ebp
     34     // mov %esp,%ebp
     35     {3, {0x55, 0x89, 0xE5}, {0, 1, -1}},
     36     // pop %ebp
     37     // ret N
     38     {2, {0x5D, 0xC2}, {0, 1, -1}},
     39     // pop %ebp
     40     // ret
     41     {2, {0x5D, 0xC3}, {0, 1, -1}},
     42 #elif V8_HOST_ARCH_X64
     43     // pushq %rbp
     44     // movq %rsp,%rbp
     45     {4, {0x55, 0x48, 0x89, 0xE5}, {0, 1, -1}},
     46     // popq %rbp
     47     // ret N
     48     {2, {0x5D, 0xC2}, {0, 1, -1}},
     49     // popq %rbp
     50     // ret
     51     {2, {0x5D, 0xC3}, {0, 1, -1}},
     52 #endif
     53     {0, {}, {}}
     54   };
     55   i::byte* pc = reinterpret_cast<i::byte*>(address);
     56   for (Pattern* pattern = patterns; pattern->bytes_count; ++pattern) {
     57     for (int* offset_ptr = pattern->offsets; *offset_ptr != -1; ++offset_ptr) {
     58       int offset = *offset_ptr;
     59       if (!offset || IsSamePage(address, address - offset)) {
     60         MSAN_MEMORY_IS_INITIALIZED(pc - offset, pattern->bytes_count);
     61         if (!memcmp(pc - offset, pattern->bytes, pattern->bytes_count))
     62           return true;
     63       } else {
     64         // It is not safe to examine bytes on another page as it might not be
     65         // allocated thus causing a SEGFAULT.
     66         // Check the pattern part that's on the same page and
     67         // pessimistically assume it could be the entire pattern match.
     68         MSAN_MEMORY_IS_INITIALIZED(pc, pattern->bytes_count - offset);
     69         if (!memcmp(pc, pattern->bytes + offset, pattern->bytes_count - offset))
     70           return true;
     71       }
     72     }
     73   }
     74   return false;
     75 }
     76 
     77 }  // namespace
     78 
     79 namespace internal {
     80 namespace {
     81 
     82 #if defined(USE_SIMULATOR)
     83 class SimulatorHelper {
     84  public:
     85   // Returns true if register values were successfully retrieved
     86   // from the simulator, otherwise returns false.
     87   static bool FillRegisters(Isolate* isolate, v8::RegisterState* state);
     88 };
     89 
     90 bool SimulatorHelper::FillRegisters(Isolate* isolate,
     91                                     v8::RegisterState* state) {
     92   Simulator* simulator = isolate->thread_local_top()->simulator_;
     93   // Check if there is active simulator.
     94   if (simulator == nullptr) return false;
     95 #if V8_TARGET_ARCH_ARM
     96   if (!simulator->has_bad_pc()) {
     97     state->pc = reinterpret_cast<void*>(simulator->get_pc());
     98   }
     99   state->sp = reinterpret_cast<void*>(simulator->get_register(Simulator::sp));
    100   state->fp = reinterpret_cast<void*>(simulator->get_register(Simulator::r11));
    101 #elif V8_TARGET_ARCH_ARM64
    102   state->pc = reinterpret_cast<void*>(simulator->pc());
    103   state->sp = reinterpret_cast<void*>(simulator->sp());
    104   state->fp = reinterpret_cast<void*>(simulator->fp());
    105 #elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
    106   if (!simulator->has_bad_pc()) {
    107     state->pc = reinterpret_cast<void*>(simulator->get_pc());
    108   }
    109   state->sp = reinterpret_cast<void*>(simulator->get_register(Simulator::sp));
    110   state->fp = reinterpret_cast<void*>(simulator->get_register(Simulator::fp));
    111 #elif V8_TARGET_ARCH_PPC
    112   if (!simulator->has_bad_pc()) {
    113     state->pc = reinterpret_cast<void*>(simulator->get_pc());
    114   }
    115   state->sp = reinterpret_cast<void*>(simulator->get_register(Simulator::sp));
    116   state->fp = reinterpret_cast<void*>(simulator->get_register(Simulator::fp));
    117 #elif V8_TARGET_ARCH_S390
    118   if (!simulator->has_bad_pc()) {
    119     state->pc = reinterpret_cast<void*>(simulator->get_pc());
    120   }
    121   state->sp = reinterpret_cast<void*>(simulator->get_register(Simulator::sp));
    122   state->fp = reinterpret_cast<void*>(simulator->get_register(Simulator::fp));
    123 #endif
    124   if (state->sp == 0 || state->fp == 0) {
    125     // It possible that the simulator is interrupted while it is updating
    126     // the sp or fp register. ARM64 simulator does this in two steps:
    127     // first setting it to zero and then setting it to the new value.
    128     // Bailout if sp/fp doesn't contain the new value.
    129     //
    130     // FIXME: The above doesn't really solve the issue.
    131     // If a 64-bit target is executed on a 32-bit host even the final
    132     // write is non-atomic, so it might obtain a half of the result.
    133     // Moreover as long as the register set code uses memcpy (as of now),
    134     // it is not guaranteed to be atomic even when both host and target
    135     // are of same bitness.
    136     return false;
    137   }
    138   return true;
    139 }
    140 #endif  // USE_SIMULATOR
    141 
    142 }  // namespace
    143 }  // namespace internal
    144 
    145 //
    146 // StackTracer implementation
    147 //
    148 DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate,
    149                                    const RegisterState& reg_state,
    150                                    RecordCEntryFrame record_c_entry_frame,
    151                                    bool update_stats,
    152                                    bool use_simulator_reg_state) {
    153   this->update_stats = update_stats;
    154   SampleInfo info;
    155   RegisterState regs = reg_state;
    156   if (!GetStackSample(v8_isolate, &regs, record_c_entry_frame, stack,
    157                       kMaxFramesCount, &info, use_simulator_reg_state)) {
    158     // It is executing JS but failed to collect a stack trace.
    159     // Mark the sample as spoiled.
    160     pc = nullptr;
    161     return;
    162   }
    163 
    164   state = info.vm_state;
    165   pc = regs.pc;
    166   frames_count = static_cast<unsigned>(info.frames_count);
    167   has_external_callback = info.external_callback_entry != nullptr;
    168   if (has_external_callback) {
    169     external_callback_entry = info.external_callback_entry;
    170   } else if (frames_count) {
    171     // sp register may point at an arbitrary place in memory, make
    172     // sure MSAN doesn't complain about it.
    173     MSAN_MEMORY_IS_INITIALIZED(regs.sp, sizeof(void*));
    174     // Sample potential return address value for frameless invocation of
    175     // stubs (we'll figure out later, if this value makes sense).
    176     tos = reinterpret_cast<void*>(
    177         i::Memory<i::Address>(reinterpret_cast<i::Address>(regs.sp)));
    178   } else {
    179     tos = nullptr;
    180   }
    181 }
    182 
    183 bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs,
    184                                 RecordCEntryFrame record_c_entry_frame,
    185                                 void** frames, size_t frames_limit,
    186                                 v8::SampleInfo* sample_info,
    187                                 bool use_simulator_reg_state) {
    188   i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
    189   sample_info->frames_count = 0;
    190   sample_info->vm_state = isolate->current_vm_state();
    191   sample_info->external_callback_entry = nullptr;
    192   if (sample_info->vm_state == GC) return true;
    193 
    194   i::Address js_entry_sp = isolate->js_entry_sp();
    195   if (js_entry_sp == 0) return true;  // Not executing JS now.
    196 
    197 #if defined(USE_SIMULATOR)
    198   if (use_simulator_reg_state) {
    199     if (!i::SimulatorHelper::FillRegisters(isolate, regs)) return false;
    200   }
    201 #else
    202   USE(use_simulator_reg_state);
    203 #endif
    204   DCHECK(regs->sp);
    205 
    206   // Check whether we interrupted setup/teardown of a stack frame in JS code.
    207   // Avoid this check for C++ code, as that would trigger false positives.
    208   if (regs->pc &&
    209       isolate->heap()->memory_allocator()->code_range()->contains(
    210           reinterpret_cast<i::Address>(regs->pc)) &&
    211       IsNoFrameRegion(reinterpret_cast<i::Address>(regs->pc))) {
    212     // The frame is not setup, so it'd be hard to iterate the stack. Bailout.
    213     return false;
    214   }
    215 
    216   i::ExternalCallbackScope* scope = isolate->external_callback_scope();
    217   i::Address handler = i::Isolate::handler(isolate->thread_local_top());
    218   // If there is a handler on top of the external callback scope then
    219   // we have already entrered JavaScript again and the external callback
    220   // is not the top function.
    221   if (scope && scope->scope_address() < handler) {
    222     i::Address* external_callback_entry_ptr =
    223         scope->callback_entrypoint_address();
    224     sample_info->external_callback_entry =
    225         external_callback_entry_ptr == nullptr
    226             ? nullptr
    227             : reinterpret_cast<void*>(*external_callback_entry_ptr);
    228   }
    229 
    230   i::SafeStackFrameIterator it(isolate, reinterpret_cast<i::Address>(regs->fp),
    231                                reinterpret_cast<i::Address>(regs->sp),
    232                                js_entry_sp);
    233   if (it.done()) return true;
    234 
    235   size_t i = 0;
    236   if (record_c_entry_frame == kIncludeCEntryFrame &&
    237       (it.top_frame_type() == internal::StackFrame::EXIT ||
    238        it.top_frame_type() == internal::StackFrame::BUILTIN_EXIT)) {
    239     frames[i++] = reinterpret_cast<void*>(isolate->c_function());
    240   }
    241   i::RuntimeCallTimer* timer =
    242       isolate->counters()->runtime_call_stats()->current_timer();
    243   for (; !it.done() && i < frames_limit; it.Advance()) {
    244     while (timer && reinterpret_cast<i::Address>(timer) < it.frame()->fp() &&
    245            i < frames_limit) {
    246       frames[i++] = reinterpret_cast<void*>(timer->counter());
    247       timer = timer->parent();
    248     }
    249     if (i == frames_limit) break;
    250     if (it.frame()->is_interpreted()) {
    251       // For interpreted frames use the bytecode array pointer as the pc.
    252       i::InterpretedFrame* frame =
    253           static_cast<i::InterpretedFrame*>(it.frame());
    254       // Since the sampler can interrupt execution at any point the
    255       // bytecode_array might be garbage, so don't actually dereference it. We
    256       // avoid the frame->GetXXX functions since they call BytecodeArray::cast,
    257       // which has a heap access in its DCHECK.
    258       i::Object* bytecode_array = i::Memory<i::Object*>(
    259           frame->fp() + i::InterpreterFrameConstants::kBytecodeArrayFromFp);
    260       i::Object* bytecode_offset = i::Memory<i::Object*>(
    261           frame->fp() + i::InterpreterFrameConstants::kBytecodeOffsetFromFp);
    262 
    263       // If the bytecode array is a heap object and the bytecode offset is a
    264       // Smi, use those, otherwise fall back to using the frame's pc.
    265       if (HAS_HEAP_OBJECT_TAG(bytecode_array) && HAS_SMI_TAG(bytecode_offset)) {
    266         frames[i++] = reinterpret_cast<void*>(
    267             reinterpret_cast<i::Address>(bytecode_array) +
    268             i::Internals::SmiValue(bytecode_offset));
    269         continue;
    270       }
    271     }
    272     frames[i++] = reinterpret_cast<void*>(it.frame()->pc());
    273   }
    274   sample_info->frames_count = i;
    275   return true;
    276 }
    277 
    278 namespace internal {
    279 
    280 void TickSample::Init(Isolate* isolate, const v8::RegisterState& state,
    281                       RecordCEntryFrame record_c_entry_frame, bool update_stats,
    282                       bool use_simulator_reg_state) {
    283   v8::TickSample::Init(reinterpret_cast<v8::Isolate*>(isolate), state,
    284                        record_c_entry_frame, update_stats,
    285                        use_simulator_reg_state);
    286   if (pc == nullptr) return;
    287   timestamp = base::TimeTicks::HighResolutionNow();
    288 }
    289 
    290 }  // namespace internal
    291 }  // namespace v8
    292