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 
IsSamePage(i::byte * ptr1,i::byte * ptr2)17 bool IsSamePage(i::byte* ptr1, i::byte* ptr2) {
18   const uint32_t kPageSize = 4096;
19   uintptr_t mask = ~static_cast<uintptr_t>(kPageSize - 1);
20   return (reinterpret_cast<uintptr_t>(ptr1) & mask) ==
21          (reinterpret_cast<uintptr_t>(ptr2) & mask);
22 }
23 
24 // Check if the code at specified address could potentially be a
25 // frame setup code.
IsNoFrameRegion(i::Address address)26 bool IsNoFrameRegion(i::Address address) {
27   struct Pattern {
28     int bytes_count;
29     i::byte bytes[8];
30     int offsets[4];
31   };
32   i::byte* pc = reinterpret_cast<i::byte*>(address);
33   static Pattern patterns[] = {
34 #if V8_HOST_ARCH_IA32
35     // push %ebp
36     // mov %esp,%ebp
37     {3, {0x55, 0x89, 0xe5}, {0, 1, -1}},
38     // pop %ebp
39     // ret N
40     {2, {0x5d, 0xc2}, {0, 1, -1}},
41     // pop %ebp
42     // ret
43     {2, {0x5d, 0xc3}, {0, 1, -1}},
44 #elif V8_HOST_ARCH_X64
45     // pushq %rbp
46     // movq %rsp,%rbp
47     {4, {0x55, 0x48, 0x89, 0xe5}, {0, 1, -1}},
48     // popq %rbp
49     // ret N
50     {2, {0x5d, 0xc2}, {0, 1, -1}},
51     // popq %rbp
52     // ret
53     {2, {0x5d, 0xc3}, {0, 1, -1}},
54 #endif
55     {0, {}, {}}
56   };
57   for (Pattern* pattern = patterns; pattern->bytes_count; ++pattern) {
58     for (int* offset_ptr = pattern->offsets; *offset_ptr != -1; ++offset_ptr) {
59       int offset = *offset_ptr;
60       if (!offset || IsSamePage(pc, pc - offset)) {
61         MSAN_MEMORY_IS_INITIALIZED(pc - offset, pattern->bytes_count);
62         if (!memcmp(pc - offset, pattern->bytes, pattern->bytes_count))
63           return true;
64       } else {
65         // It is not safe to examine bytes on another page as it might not be
66         // allocated thus causing a SEGFAULT.
67         // Check the pattern part that's on the same page and
68         // pessimistically assume it could be the entire pattern match.
69         MSAN_MEMORY_IS_INITIALIZED(pc, pattern->bytes_count - offset);
70         if (!memcmp(pc, pattern->bytes + offset, pattern->bytes_count - offset))
71           return true;
72       }
73     }
74   }
75   return false;
76 }
77 
78 }  // namespace
79 
80 namespace internal {
81 namespace {
82 
83 #if defined(USE_SIMULATOR)
84 class SimulatorHelper {
85  public:
86   // Returns true if register values were successfully retrieved
87   // from the simulator, otherwise returns false.
88   static bool FillRegisters(Isolate* isolate, v8::RegisterState* state);
89 };
90 
FillRegisters(Isolate * isolate,v8::RegisterState * state)91 bool SimulatorHelper::FillRegisters(Isolate* isolate,
92                                     v8::RegisterState* state) {
93   Simulator* simulator = isolate->thread_local_top()->simulator_;
94   // Check if there is active simulator.
95   if (simulator == NULL) return false;
96 #if V8_TARGET_ARCH_ARM
97   if (!simulator->has_bad_pc()) {
98     state->pc = reinterpret_cast<Address>(simulator->get_pc());
99   }
100   state->sp = reinterpret_cast<Address>(simulator->get_register(Simulator::sp));
101   state->fp =
102       reinterpret_cast<Address>(simulator->get_register(Simulator::r11));
103 #elif V8_TARGET_ARCH_ARM64
104   state->pc = reinterpret_cast<Address>(simulator->pc());
105   state->sp = reinterpret_cast<Address>(simulator->sp());
106   state->fp = reinterpret_cast<Address>(simulator->fp());
107 #elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
108   if (!simulator->has_bad_pc()) {
109     state->pc = reinterpret_cast<Address>(simulator->get_pc());
110   }
111   state->sp = reinterpret_cast<Address>(simulator->get_register(Simulator::sp));
112   state->fp = reinterpret_cast<Address>(simulator->get_register(Simulator::fp));
113 #elif V8_TARGET_ARCH_PPC
114   if (!simulator->has_bad_pc()) {
115     state->pc = reinterpret_cast<Address>(simulator->get_pc());
116   }
117   state->sp = reinterpret_cast<Address>(simulator->get_register(Simulator::sp));
118   state->fp = reinterpret_cast<Address>(simulator->get_register(Simulator::fp));
119 #elif V8_TARGET_ARCH_S390
120   if (!simulator->has_bad_pc()) {
121     state->pc = reinterpret_cast<Address>(simulator->get_pc());
122   }
123   state->sp = reinterpret_cast<Address>(simulator->get_register(Simulator::sp));
124   state->fp = reinterpret_cast<Address>(simulator->get_register(Simulator::fp));
125 #endif
126   if (state->sp == 0 || state->fp == 0) {
127     // It possible that the simulator is interrupted while it is updating
128     // the sp or fp register. ARM64 simulator does this in two steps:
129     // first setting it to zero and then setting it to the new value.
130     // Bailout if sp/fp doesn't contain the new value.
131     //
132     // FIXME: The above doesn't really solve the issue.
133     // If a 64-bit target is executed on a 32-bit host even the final
134     // write is non-atomic, so it might obtain a half of the result.
135     // Moreover as long as the register set code uses memcpy (as of now),
136     // it is not guaranteed to be atomic even when both host and target
137     // are of same bitness.
138     return false;
139   }
140   return true;
141 }
142 #endif  // USE_SIMULATOR
143 
144 }  // namespace
145 }  // namespace internal
146 
147 //
148 // StackTracer implementation
149 //
Init(Isolate * v8_isolate,const RegisterState & reg_state,RecordCEntryFrame record_c_entry_frame,bool update_stats,bool use_simulator_reg_state)150 DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate,
151                                    const RegisterState& reg_state,
152                                    RecordCEntryFrame record_c_entry_frame,
153                                    bool update_stats,
154                                    bool use_simulator_reg_state) {
155   this->update_stats = update_stats;
156   SampleInfo info;
157   RegisterState regs = reg_state;
158   if (!GetStackSample(v8_isolate, &regs, record_c_entry_frame, stack,
159                       kMaxFramesCount, &info, use_simulator_reg_state)) {
160     // It is executing JS but failed to collect a stack trace.
161     // Mark the sample as spoiled.
162     pc = nullptr;
163     return;
164   }
165 
166   state = info.vm_state;
167   pc = regs.pc;
168   frames_count = static_cast<unsigned>(info.frames_count);
169   has_external_callback = info.external_callback_entry != nullptr;
170   if (has_external_callback) {
171     external_callback_entry = info.external_callback_entry;
172   } else if (frames_count) {
173     // sp register may point at an arbitrary place in memory, make
174     // sure MSAN doesn't complain about it.
175     MSAN_MEMORY_IS_INITIALIZED(regs.sp, sizeof(void*));
176     // Sample potential return address value for frameless invocation of
177     // stubs (we'll figure out later, if this value makes sense).
178     tos = i::Memory::Address_at(reinterpret_cast<i::Address>(regs.sp));
179   } else {
180     tos = nullptr;
181   }
182 }
183 
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)184 bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs,
185                                 RecordCEntryFrame record_c_entry_frame,
186                                 void** frames, size_t frames_limit,
187                                 v8::SampleInfo* sample_info,
188                                 bool use_simulator_reg_state) {
189   i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
190   sample_info->frames_count = 0;
191   sample_info->vm_state = isolate->current_vm_state();
192   sample_info->external_callback_entry = nullptr;
193   if (sample_info->vm_state == GC) return true;
194 
195   i::Address js_entry_sp = isolate->js_entry_sp();
196   if (js_entry_sp == nullptr) return true;  // Not executing JS now.
197 
198 #if defined(USE_SIMULATOR)
199   if (use_simulator_reg_state) {
200     if (!i::SimulatorHelper::FillRegisters(isolate, regs)) return false;
201   }
202 #else
203   USE(use_simulator_reg_state);
204 #endif
205   DCHECK(regs->sp);
206 
207   if (regs->pc && IsNoFrameRegion(static_cast<i::Address>(regs->pc))) {
208     // The frame is not setup, so it'd be hard to iterate the stack. Bailout.
209     return false;
210   }
211 
212   i::ExternalCallbackScope* scope = isolate->external_callback_scope();
213   i::Address handler = i::Isolate::handler(isolate->thread_local_top());
214   // If there is a handler on top of the external callback scope then
215   // we have already entrered JavaScript again and the external callback
216   // is not the top function.
217   if (scope && scope->scope_address() < handler) {
218     i::Address* external_callback_entry_ptr =
219         scope->callback_entrypoint_address();
220     sample_info->external_callback_entry =
221         external_callback_entry_ptr == nullptr ? nullptr
222                                                : *external_callback_entry_ptr;
223   }
224 
225   i::SafeStackFrameIterator it(isolate, reinterpret_cast<i::Address>(regs->fp),
226                                reinterpret_cast<i::Address>(regs->sp),
227                                js_entry_sp);
228 
229   // If at this point iterator does not see any frames,
230   // is usually means something is wrong with the FP,
231   // e.g. it is used as a general purpose register in the function.
232   // Bailout.
233   if (it.done()) return false;
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++] = 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<i::Address>(timer->counter());
247       timer = timer->parent();
248     }
249     if (i == frames_limit) break;
250     if (!it.frame()->is_interpreted()) {
251       frames[i++] = it.frame()->pc();
252       continue;
253     }
254     // For interpreted frames use the bytecode array pointer as the pc.
255     i::InterpretedFrame* frame = static_cast<i::InterpretedFrame*>(it.frame());
256     // Since the sampler can interrupt execution at any point the
257     // bytecode_array might be garbage, so don't dereference it.
258     i::Address bytecode_array =
259         reinterpret_cast<i::Address>(frame->GetBytecodeArray()) -
260         i::kHeapObjectTag;
261     frames[i++] = bytecode_array + i::BytecodeArray::kHeaderSize +
262                   frame->GetBytecodeOffset();
263   }
264   sample_info->frames_count = i;
265   return true;
266 }
267 
268 namespace internal {
269 
Init(Isolate * isolate,const v8::RegisterState & state,RecordCEntryFrame record_c_entry_frame,bool update_stats,bool use_simulator_reg_state)270 void TickSample::Init(Isolate* isolate, const v8::RegisterState& state,
271                       RecordCEntryFrame record_c_entry_frame, bool update_stats,
272                       bool use_simulator_reg_state) {
273   v8::TickSample::Init(reinterpret_cast<v8::Isolate*>(isolate), state,
274                        record_c_entry_frame, update_stats,
275                        use_simulator_reg_state);
276   if (pc == nullptr) return;
277   timestamp = base::TimeTicks::HighResolutionNow();
278 }
279 
280 }  // namespace internal
281 }  // namespace v8
282