blob: ca6eb269faa2405509987a8bcaaf53de0588e3c4 [file] [log] [blame]
// Copyright 2020 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/symbolizer.h"
#include "src/execution/vm-state.h"
#include "src/profiler/profile-generator.h"
#include "src/profiler/profiler-stats.h"
#include "src/profiler/tick-sample.h"
namespace v8 {
namespace internal {
Symbolizer::Symbolizer(CodeMap* code_map) : code_map_(code_map) {}
CodeEntry* Symbolizer::FindEntry(Address address,
Address* out_instruction_start) {
CodeEntry* entry = code_map_->FindEntry(address, out_instruction_start);
if (entry) entry->mark_used();
return entry;
}
namespace {
CodeEntry* EntryForVMState(StateTag tag) {
switch (tag) {
case GC:
return CodeEntry::gc_entry();
case JS:
case PARSER:
case COMPILER:
case BYTECODE_COMPILER:
case ATOMICS_WAIT:
// DOM events handlers are reported as OTHER / EXTERNAL entries.
// To avoid confusing people, let's put all these entries into
// one bucket.
case OTHER:
case EXTERNAL:
return CodeEntry::program_entry();
case IDLE:
return CodeEntry::idle_entry();
}
}
} // namespace
Symbolizer::SymbolizedSample Symbolizer::SymbolizeTickSample(
const TickSample& sample) {
ProfileStackTrace stack_trace;
// Conservatively reserve space for stack frames + pc + function + vm-state.
// There could in fact be more of them because of inlined entries.
stack_trace.reserve(sample.frames_count + 3);
// The ProfileNode knows nothing about all versions of generated code for
// the same JS function. The line number information associated with
// the latest version of generated code is used to find a source line number
// for a JS function. Then, the detected source line is passed to
// ProfileNode to increase the tick count for this source line.
const int no_line_info = v8::CpuProfileNode::kNoLineNumberInfo;
int src_line = no_line_info;
bool src_line_not_found = true;
if (sample.pc != nullptr) {
if (sample.has_external_callback && sample.state == EXTERNAL) {
// Don't use PC when in external callback code, as it can point
// inside a callback's code, and we will erroneously report
// that a callback calls itself.
stack_trace.push_back(
{FindEntry(reinterpret_cast<Address>(sample.external_callback_entry)),
no_line_info});
} else {
Address attributed_pc = reinterpret_cast<Address>(sample.pc);
Address pc_entry_instruction_start = kNullAddress;
CodeEntry* pc_entry =
FindEntry(attributed_pc, &pc_entry_instruction_start);
// If there is no pc_entry, we're likely in native code. Find out if the
// top of the stack (the return address) was pointing inside a JS
// function, meaning that we have encountered a frameless invocation.
if (!pc_entry && !sample.has_external_callback) {
attributed_pc = reinterpret_cast<Address>(sample.tos);
pc_entry = FindEntry(attributed_pc, &pc_entry_instruction_start);
}
// If pc is in the function code before it set up stack frame or after the
// frame was destroyed, SafeStackFrameIterator incorrectly thinks that
// ebp contains the return address of the current function and skips the
// caller's frame. Check for this case and just skip such samples.
if (pc_entry) {
int pc_offset =
static_cast<int>(attributed_pc - pc_entry_instruction_start);
// TODO(petermarshall): pc_offset can still be negative in some cases.
src_line = pc_entry->GetSourceLine(pc_offset);
if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) {
src_line = pc_entry->line_number();
}
src_line_not_found = false;
stack_trace.push_back({pc_entry, src_line});
if (pc_entry->builtin_id() == Builtins::kFunctionPrototypeApply ||
pc_entry->builtin_id() == Builtins::kFunctionPrototypeCall) {
// When current function is either the Function.prototype.apply or the
// Function.prototype.call builtin the top frame is either frame of
// the calling JS function or internal frame.
// In the latter case we know the caller for sure but in the
// former case we don't so we simply replace the frame with
// 'unresolved' entry.
if (!sample.has_external_callback) {
ProfilerStats::Instance()->AddReason(
ProfilerStats::Reason::kInCallOrApply);
stack_trace.push_back(
{CodeEntry::unresolved_entry(), no_line_info});
}
}
}
}
for (unsigned i = 0; i < sample.frames_count; ++i) {
Address stack_pos = reinterpret_cast<Address>(sample.stack[i]);
Address instruction_start = kNullAddress;
CodeEntry* entry = FindEntry(stack_pos, &instruction_start);
int line_number = no_line_info;
if (entry) {
// Find out if the entry has an inlining stack associated.
int pc_offset = static_cast<int>(stack_pos - instruction_start);
// TODO(petermarshall): pc_offset can still be negative in some cases.
const std::vector<CodeEntryAndLineNumber>* inline_stack =
entry->GetInlineStack(pc_offset);
if (inline_stack) {
int most_inlined_frame_line_number = entry->GetSourceLine(pc_offset);
for (auto entry : *inline_stack) {
stack_trace.push_back(entry);
}
// This is a bit of a messy hack. The line number for the most-inlined
// frame (the function at the end of the chain of function calls) has
// the wrong line number in inline_stack. The actual line number in
// this function is stored in the SourcePositionTable in entry. We fix
// up the line number for the most-inlined frame here.
// TODO(petermarshall): Remove this and use a tree with a node per
// inlining_id.
DCHECK(!inline_stack->empty());
size_t index = stack_trace.size() - inline_stack->size();
stack_trace[index].line_number = most_inlined_frame_line_number;
}
// Skip unresolved frames (e.g. internal frame) and get source line of
// the first JS caller.
if (src_line_not_found) {
src_line = entry->GetSourceLine(pc_offset);
if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) {
src_line = entry->line_number();
}
src_line_not_found = false;
}
line_number = entry->GetSourceLine(pc_offset);
// The inline stack contains the top-level function i.e. the same
// function as entry. We don't want to add it twice. The one from the
// inline stack has the correct line number for this particular inlining
// so we use it instead of pushing entry to stack_trace.
if (inline_stack) continue;
}
stack_trace.push_back({entry, line_number});
}
}
if (FLAG_prof_browser_mode) {
bool no_symbolized_entries = true;
for (auto e : stack_trace) {
if (e.code_entry != nullptr) {
no_symbolized_entries = false;
break;
}
}
// If no frames were symbolized, put the VM state entry in.
if (no_symbolized_entries) {
if (sample.pc == nullptr) {
ProfilerStats::Instance()->AddReason(ProfilerStats::Reason::kNullPC);
} else {
ProfilerStats::Instance()->AddReason(
ProfilerStats::Reason::kNoSymbolizedFrames);
}
stack_trace.push_back({EntryForVMState(sample.state), no_line_info});
}
}
return SymbolizedSample{stack_trace, src_line};
}
} // namespace internal
} // namespace v8