v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
tick-sample.cc
Go to the documentation of this file.
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
6
7#include <cinttypes>
8
16#include "src/heap/heap-inl.h" // For Heap::code_range.
19
20namespace v8 {
21namespace internal {
22namespace {
23
24bool IsSamePage(i::Address ptr1, i::Address ptr2) {
25 const uint32_t kPageSize = 4096;
26 i::Address mask = ~static_cast<i::Address>(kPageSize - 1);
27 return (ptr1 & mask) == (ptr2 & mask);
28}
29
30// Check if the code at specified address could potentially be a
31// frame setup code.
32bool IsNoFrameRegion(i::Address address) {
33 struct Pattern {
34 int bytes_count;
35 uint8_t bytes[8];
36 int offsets[4];
37 };
38 static Pattern patterns[] = {
39#if V8_HOST_ARCH_IA32
40 // push %ebp
41 // mov %esp,%ebp
42 {3, {0x55, 0x89, 0xE5}, {0, 1, -1}},
43 // pop %ebp
44 // ret N
45 {2, {0x5D, 0xC2}, {0, 1, -1}},
46 // pop %ebp
47 // ret
48 {2, {0x5D, 0xC3}, {0, 1, -1}},
49#elif V8_HOST_ARCH_X64
50 // pushq %rbp
51 // movq %rsp,%rbp
52 {4, {0x55, 0x48, 0x89, 0xE5}, {0, 1, -1}},
53 // popq %rbp
54 // ret N
55 {2, {0x5D, 0xC2}, {0, 1, -1}},
56 // popq %rbp
57 // ret
58 {2, {0x5D, 0xC3}, {0, 1, -1}},
59#endif
60 {0, {}, {}}
61 };
62 uint8_t* pc = reinterpret_cast<uint8_t*>(address);
63 for (Pattern* pattern = patterns; pattern->bytes_count; ++pattern) {
64 for (int* offset_ptr = pattern->offsets; *offset_ptr != -1; ++offset_ptr) {
65 int offset = *offset_ptr;
66 if (!offset || IsSamePage(address, address - offset)) {
68 if (!memcmp(pc - offset, pattern->bytes, pattern->bytes_count))
69 return true;
70 } else {
71 // It is not safe to examine bytes on another page as it might not be
72 // allocated thus causing a SEGFAULT.
73 // Check the pattern part that's on the same page and
74 // pessimistically assume it could be the entire pattern match.
76 if (!memcmp(pc, pattern->bytes + offset, pattern->bytes_count - offset))
77 return true;
78 }
79 }
80 }
81 return false;
82}
83
84#if defined(USE_SIMULATOR)
85class SimulatorHelper {
86 public:
87 // Returns true if register values were successfully retrieved
88 // from the simulator, otherwise returns false.
89 static bool FillRegisters(Isolate* isolate, v8::RegisterState* state);
90};
91
92bool SimulatorHelper::FillRegisters(Isolate* isolate,
93 v8::RegisterState* state) {
94 Simulator* simulator = isolate->thread_local_top()->simulator_;
95 // Check if there is active simulator.
96 if (simulator == nullptr) return false;
97#if V8_TARGET_ARCH_ARM
98 if (!simulator->has_bad_pc()) {
99 state->pc = reinterpret_cast<void*>(simulator->get_pc());
100 }
101 state->sp = reinterpret_cast<void*>(simulator->get_register(Simulator::sp));
102 state->fp = reinterpret_cast<void*>(simulator->get_register(Simulator::r11));
103 state->lr = reinterpret_cast<void*>(simulator->get_register(Simulator::lr));
104#elif V8_TARGET_ARCH_ARM64
105 state->pc = reinterpret_cast<void*>(simulator->pc());
106 state->sp = reinterpret_cast<void*>(simulator->sp());
107 state->fp = reinterpret_cast<void*>(simulator->fp());
108 state->lr = reinterpret_cast<void*>(simulator->lr());
109#elif V8_TARGET_ARCH_MIPS64 || V8_TARGET_ARCH_LOONG64
110 if (!simulator->has_bad_pc()) {
111 state->pc = reinterpret_cast<void*>(simulator->get_pc());
112 }
113 state->sp = reinterpret_cast<void*>(simulator->get_register(Simulator::sp));
114 state->fp = reinterpret_cast<void*>(simulator->get_register(Simulator::fp));
115#elif V8_TARGET_ARCH_PPC64
116 if (!simulator->has_bad_pc()) {
117 state->pc = reinterpret_cast<void*>(simulator->get_pc());
118 }
119 state->sp = reinterpret_cast<void*>(simulator->get_register(Simulator::sp));
120 state->fp = reinterpret_cast<void*>(simulator->get_register(Simulator::fp));
121 state->lr = reinterpret_cast<void*>(simulator->get_lr());
122#elif V8_TARGET_ARCH_S390X
123 if (!simulator->has_bad_pc()) {
124 state->pc = reinterpret_cast<void*>(simulator->get_pc());
125 }
126 state->sp = reinterpret_cast<void*>(simulator->get_register(Simulator::sp));
127 state->fp = reinterpret_cast<void*>(simulator->get_register(Simulator::fp));
128 state->lr = reinterpret_cast<void*>(simulator->get_register(Simulator::ra));
129#elif V8_TARGET_ARCH_RISCV64
130 if (!simulator->has_bad_pc()) {
131 state->pc = reinterpret_cast<void*>(simulator->get_pc());
132 }
133 state->sp = reinterpret_cast<void*>(simulator->get_register(Simulator::sp));
134 state->fp = reinterpret_cast<void*>(simulator->get_register(Simulator::fp));
135 state->lr = reinterpret_cast<void*>(simulator->get_register(Simulator::ra));
136#elif V8_TARGET_ARCH_RISCV32
137 if (!simulator->has_bad_pc()) {
138 state->pc = reinterpret_cast<void*>(simulator->get_pc());
139 }
140 state->sp = reinterpret_cast<void*>(simulator->get_register(Simulator::sp));
141 state->fp = reinterpret_cast<void*>(simulator->get_register(Simulator::fp));
142 state->lr = reinterpret_cast<void*>(simulator->get_register(Simulator::ra));
143#endif
144 if (state->sp == 0 || state->fp == 0) {
145 // It possible that the simulator is interrupted while it is updating
146 // the sp or fp register. ARM64 simulator does this in two steps:
147 // first setting it to zero and then setting it to the new value.
148 // Bailout if sp/fp doesn't contain the new value.
149 //
150 // FIXME: The above doesn't really solve the issue.
151 // If a 64-bit target is executed on a 32-bit host even the final
152 // write is non-atomic, so it might obtain a half of the result.
153 // Moreover as long as the register set code uses memcpy (as of now),
154 // it is not guaranteed to be atomic even when both host and target
155 // are of same bitness.
156 return false;
157 }
158 return true;
159}
160#endif // USE_SIMULATOR
161
162} // namespace
163
165 const RegisterState& reg_state,
166 RecordCEntryFrame record_c_entry_frame,
167 bool update_stats,
168 bool use_simulator_reg_state,
169 base::TimeDelta sampling_interval,
170 const std::optional<uint64_t> trace_id) {
171 update_stats_ = update_stats;
173 RegisterState regs = reg_state;
174 if (!GetStackSample(v8_isolate, &regs, record_c_entry_frame, stack,
175 kMaxFramesCount, &info, &state,
176 use_simulator_reg_state)) {
177 // It is executing JS but failed to collect a stack trace.
178 // Mark the sample as spoiled.
179 pc = nullptr;
180 return;
181 }
182
183 if (state != StateTag::EXTERNAL) {
184 state = info.vm_state;
185 }
186 pc = regs.pc;
187 frames_count = static_cast<unsigned>(info.frames_count);
188 has_external_callback = info.external_callback_entry != nullptr;
189 context = info.context;
190 embedder_context = info.embedder_context;
191 embedder_state = info.embedder_state;
193 external_callback_entry = info.external_callback_entry;
194 } else if (frames_count) {
195 // sp register may point at an arbitrary place in memory, make
196 // sure sanitizers don't complain about it.
197 ASAN_UNPOISON_MEMORY_REGION(regs.sp, sizeof(void*));
198 MSAN_MEMORY_IS_INITIALIZED(regs.sp, sizeof(void*));
199 // Sample potential return address value for frameless invocation of
200 // stubs (we'll figure out later, if this value makes sense).
201
202 // TODO(petermarshall): This read causes guard page violations on Windows.
203 // Either fix this mechanism for frameless stubs or remove it.
204 // tos =
205 // i::ReadUnalignedValue<void*>(reinterpret_cast<i::Address>(regs.sp));
206 tos = nullptr;
207 } else {
208 tos = nullptr;
209 }
210 sampling_interval_ = sampling_interval;
211 trace_id_ = trace_id;
213}
214
215// IMPORTANT: 'GetStackSample' is sensitive to stack overflows. For this reason
216// we try not to use any function/method marked as V8_EXPORT_PRIVATE with their
217// only use-site in 'GetStackSample': The resulting linker stub needs quite
218// a bit of stack space and has caused stack overflow crashes in the past.
220 RecordCEntryFrame record_c_entry_frame,
221 void** frames, size_t frames_limit,
222 v8::SampleInfo* sample_info,
223 StateTag* out_state,
224 bool use_simulator_reg_state) {
225 i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
226 sample_info->frames_count = 0;
227 sample_info->vm_state = isolate->current_vm_state();
228 sample_info->external_callback_entry = nullptr;
230 sample_info->embedder_context = nullptr;
231 sample_info->context = nullptr;
232
233 if (sample_info->vm_state == GC || v8_isolate->heap()->IsInGC()) {
234 // GC can happen any time, not directly caused by its caller. Don't collect
235 // stacks for it. We check for both GC VMState and IsInGC, since we can
236 // observe LOGGING VM states during GC.
237 // TODO(leszeks): We could still consider GC stacks (as long as this isn't a
238 // moving GC), e.g. to surface if one particular function is triggering all
239 // the GCs. However, this is a user-visible change, and we would need to
240 // adjust the symbolizer and devtools to expose this information.
241 return true;
242 }
243
244 EmbedderState* embedder_state = isolate->current_embedder_state();
245 if (embedder_state != nullptr) {
246 sample_info->embedder_context =
247 reinterpret_cast<void*>(embedder_state->native_context_address());
248 sample_info->embedder_state = embedder_state->GetState();
249 }
250
251 Tagged<Context> top_context = isolate->context();
252 if (top_context.ptr() != i::Context::kNoContext &&
253 top_context.ptr() != i::Context::kInvalidContext) {
254 Tagged<NativeContext> top_native_context = top_context->native_context();
255 sample_info->context = reinterpret_cast<void*>(top_native_context.ptr());
256 }
257
258 i::Address js_entry_sp = isolate->js_entry_sp();
259 if (js_entry_sp == 0) return true; // Not executing JS now.
260#if V8_ENABLE_WEBASSEMBLY
261 // With stack-switching, the js_entry_sp and current sp may be in different
262 // stacks. Use the active stack base instead as the upper bound to correctly
263 // validate addresses in the stack frame iterator.
264 wasm::StackMemory* stack = isolate->isolate_data()->active_stack();
265 if (stack != nullptr && stack->jmpbuf()->parent != nullptr) {
266 js_entry_sp = stack->base();
267 }
268#endif
269
270#if defined(USE_SIMULATOR)
271 if (use_simulator_reg_state) {
272 if (!i::SimulatorHelper::FillRegisters(isolate, regs)) {
273 i::ProfilerStats::Instance()->AddReason(
274 i::ProfilerStats::Reason::kSimulatorFillRegistersFailed);
275 return false;
276 }
277 }
278#else
279 USE(use_simulator_reg_state);
280#endif
281 DCHECK(regs->sp);
282
283 // Check whether we interrupted setup/teardown of a stack frame in JS code.
284 // Avoid this check for C++ code, as that would trigger false positives.
285 // TODO(petermarshall): Code range is always null on ia32 so this check for
286 // IsNoFrameRegion will never actually run there.
287 if (regs->pc &&
288 isolate->heap()->code_region().contains(
289 reinterpret_cast<i::Address>(regs->pc)) &&
290 IsNoFrameRegion(reinterpret_cast<i::Address>(regs->pc))) {
291 // The frame is not setup, so it'd be hard to iterate the stack. Bailout.
292 i::ProfilerStats::Instance()->AddReason(
293 i::ProfilerStats::Reason::kNoFrameRegion);
294 return false;
295 }
296
297 i::ExternalCallbackScope* scope = isolate->external_callback_scope();
298 i::Address handler = i::Isolate::handler(isolate->thread_local_top());
299 // If there is a handler on top of the external callback scope then
300 // we have already entered JavaScript again and the external callback
301 // is not the top function.
302 if (scope && scope->JSStackComparableAddress() < handler) {
303 i::Address* external_callback_entry_ptr =
305 sample_info->external_callback_entry =
306 external_callback_entry_ptr == nullptr
307 ? nullptr
308 : reinterpret_cast<void*>(*external_callback_entry_ptr);
309 }
310 // 'Fast API calls' are similar to fast C calls (see frames.cc) in that
311 // they don't build an exit frame when entering C from JS. They have the
312 // added speciality of having separate "fast" and "default" callbacks, the
313 // latter being the regular API callback called before the JS function is
314 // optimized. When TurboFan optimizes the JS caller, the fast callback
315 // gets executed instead of the default one, therefore we need to store
316 // its address in the sample.
317 IsolateData* isolate_data = isolate->isolate_data();
318 Address fast_c_fp = isolate_data->fast_c_call_caller_fp();
319 if (fast_c_fp != kNullAddress &&
320 isolate_data->fast_api_call_target() != kNullAddress) {
321 sample_info->external_callback_entry =
322 reinterpret_cast<void*>(isolate_data->fast_api_call_target());
323 if (out_state) {
324 *out_state = StateTag::EXTERNAL;
325 }
326 }
327
329 isolate, reinterpret_cast<i::Address>(regs->pc),
330 reinterpret_cast<i::Address>(regs->fp),
331 reinterpret_cast<i::Address>(regs->sp),
332 reinterpret_cast<i::Address>(regs->lr), js_entry_sp);
333
334 if (it.done()) return true;
335
336 size_t i = 0;
337 if (record_c_entry_frame == kIncludeCEntryFrame &&
338 (it.top_frame_type() == internal::StackFrame::EXIT ||
339 it.top_frame_type() == internal::StackFrame::BUILTIN_EXIT)) {
340 // While BUILTIN_EXIT definitely represents a call to CEntry the EXIT frame
341 // might represent either a call to CEntry or an optimized call to
342 // Api callback. In the latter case the ExternalCallbackScope points to
343 // the same function, so skip adding a frame in that case in order to avoid
344 // double-reporting.
345 void* c_function = reinterpret_cast<void*>(isolate->c_function());
346 if (sample_info->external_callback_entry != c_function) {
347 frames[i] = c_function;
348 i++;
349 }
350 }
351#ifdef V8_RUNTIME_CALL_STATS
352 i::RuntimeCallTimer* timer =
353 isolate->counters()->runtime_call_stats()->current_timer();
354#endif // V8_RUNTIME_CALL_STATS
355 for (; !it.done() && i < frames_limit; it.Advance()) {
356#ifdef V8_RUNTIME_CALL_STATS
357 while (timer && reinterpret_cast<i::Address>(timer) < it.frame()->fp() &&
358 i < frames_limit) {
359 frames[i++] = reinterpret_cast<void*>(timer->counter());
360 timer = timer->parent();
361 }
362 if (i == frames_limit) break;
363#endif // V8_RUNTIME_CALL_STATS
364
365 if (it.frame()->is_interpreted()) {
366 // For interpreted frames use the bytecode array pointer as the pc.
367 i::InterpretedFrame* frame =
368 static_cast<i::InterpretedFrame*>(it.frame());
369 // Since the sampler can interrupt execution at any point the
370 // bytecode_array might be garbage, so don't actually dereference it. We
371 // avoid the frame->GetXXX functions since they call Cast<BytecodeArray>,
372 // which has a heap access in its DCHECK.
373 i::Address bytecode_array = base::Memory<i::Address>(
374 frame->fp() + i::InterpreterFrameConstants::kBytecodeArrayFromFp);
375 i::Address bytecode_offset = base::Memory<i::Address>(
376 frame->fp() + i::InterpreterFrameConstants::kBytecodeOffsetFromFp);
377
378 // If the bytecode array is a heap object and the bytecode offset is a
379 // Smi, use those, otherwise fall back to using the frame's pc.
380 if (HAS_STRONG_HEAP_OBJECT_TAG(bytecode_array) &&
381 HAS_SMI_TAG(bytecode_offset)) {
382 frames[i++] = reinterpret_cast<void*>(
383 bytecode_array + i::Internals::SmiValue(bytecode_offset));
384 continue;
385 }
386 }
387 // For arm64, the PC for the frame sometimes doesn't come from the stack,
388 // but from the link register instead. For this reason, we skip
389 // authenticating it.
390 frames[i++] = reinterpret_cast<void*>(it.frame()->unauthenticated_pc());
391 }
392 sample_info->frames_count = i;
393 return true;
394}
395
396void TickSample::print() const {
397 PrintF("TickSample: at %p\n", this);
398 PrintF(" - state: %s\n", StateToString(state));
399 PrintF(" - pc: %p\n", pc);
400 PrintF(" - stack: (%u frames)\n", frames_count);
401 for (unsigned i = 0; i < frames_count; i++) {
402 PrintF(" %p\n", stack[i]);
403 }
404 PrintF(" - has_external_callback: %d\n", has_external_callback);
405 PrintF(" - %s: %p\n",
406 has_external_callback ? "external_callback_entry" : "tos", tos);
407 PrintF(" - update_stats: %d\n", update_stats_);
408 PrintF(" - sampling_interval: %" PRId64 "\n",
410 PrintF("\n");
411}
412
413} // namespace internal
414} // namespace v8
#define ASAN_UNPOISON_MEMORY_REGION(start, size)
Definition asan.h:71
#define DISABLE_ASAN
Definition asan.h:62
int64_t InMicroseconds() const
Definition time.cc:251
static TimeTicks Now()
Definition time.cc:736
bool IsInGC() const
Definition heap.h:526
Address fast_api_call_target() const
Address fast_c_call_caller_fp() const
Address fp() const
Definition frames.h:297
V8_INLINE constexpr StorageType ptr() const
#define HAS_STRONG_HEAP_OBJECT_TAG(value)
Definition globals.h:1774
#define HAS_SMI_TAG(value)
Definition globals.h:1771
Handle< SharedFunctionInfo > info
int32_t offset
std::string pattern
uint32_t const mask
#define MSAN_MEMORY_IS_INITIALIZED(start, size)
Definition msan.h:37
constexpr size_t kPageSize
Definition globals.h:42
T & Memory(Address addr)
Definition memory.h:18
void PrintF(const char *format,...)
Definition utils.cc:39
refactor address components for immediate indexing make OptimizeMaglevOnNextCall optimize to turbofan instead of maglev filter for tracing turbofan compilation nullptr
Definition flags.cc:1263
static constexpr Address kNullAddress
Definition v8-internal.h:53
const char * StateToString(StateTag state)
StateTag
Definition v8-unwinder.h:36
@ EXTERNAL
Definition v8-unwinder.h:43
#define DCHECK(condition)
Definition logging.h:482
#define USE(...)
Definition macros.h:293
EmbedderStateTag embedder_state
Definition v8-unwinder.h:57
void * embedder_context
Definition v8-unwinder.h:55
size_t frames_count
Definition v8-unwinder.h:51
void * external_callback_entry
Definition v8-unwinder.h:52
StateTag vm_state
Definition v8-unwinder.h:56
std::optional< uint64_t > trace_id_
base::TimeDelta sampling_interval_
Definition tick-sample.h:91
static constexpr unsigned kMaxFramesCount
Definition tick-sample.h:80
base::TimeTicks timestamp
Definition tick-sample.h:90
EmbedderStateTag embedder_state
Definition tick-sample.h:94
static bool GetStackSample(Isolate *isolate, v8::RegisterState *state, RecordCEntryFrame record_c_entry_frame, void **frames, size_t frames_limit, v8::SampleInfo *sample_info, StateTag *out_state=nullptr, bool use_simulator_reg_state=true)
void Init(Isolate *isolate, const v8::RegisterState &state, RecordCEntryFrame record_c_entry_frame, bool update_stats, bool use_simulator_reg_state=true, base::TimeDelta sampling_interval=base::TimeDelta(), const std::optional< uint64_t > trace_id=std::nullopt)