1 // Copyright 2020 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/symbolizer.h"
6
7 #include "src/execution/vm-state.h"
8 #include "src/profiler/profile-generator.h"
9 #include "src/profiler/profiler-stats.h"
10 #include "src/profiler/tick-sample.h"
11
12 namespace v8 {
13 namespace internal {
14
Symbolizer(CodeMap * code_map)15 Symbolizer::Symbolizer(CodeMap* code_map) : code_map_(code_map) {}
16
FindEntry(Address address,Address * out_instruction_start)17 CodeEntry* Symbolizer::FindEntry(Address address,
18 Address* out_instruction_start) {
19 return code_map_->FindEntry(address, out_instruction_start);
20 }
21
22 namespace {
23
EntryForVMState(StateTag tag)24 CodeEntry* EntryForVMState(StateTag tag) {
25 switch (tag) {
26 case GC:
27 return CodeEntry::gc_entry();
28 case JS:
29 case PARSER:
30 case COMPILER:
31 case BYTECODE_COMPILER:
32 case ATOMICS_WAIT:
33 // DOM events handlers are reported as OTHER / EXTERNAL entries.
34 // To avoid confusing people, let's put all these entries into
35 // one bucket.
36 case OTHER:
37 case EXTERNAL:
38 return CodeEntry::program_entry();
39 case IDLE:
40 return CodeEntry::idle_entry();
41 }
42 }
43
44 } // namespace
45
SymbolizeTickSample(const TickSample & sample)46 Symbolizer::SymbolizedSample Symbolizer::SymbolizeTickSample(
47 const TickSample& sample) {
48 ProfileStackTrace stack_trace;
49 // Conservatively reserve space for stack frames + pc + function + vm-state.
50 // There could in fact be more of them because of inlined entries.
51 stack_trace.reserve(sample.frames_count + 3);
52
53 // The ProfileNode knows nothing about all versions of generated code for
54 // the same JS function. The line number information associated with
55 // the latest version of generated code is used to find a source line number
56 // for a JS function. Then, the detected source line is passed to
57 // ProfileNode to increase the tick count for this source line.
58 const int no_line_info = v8::CpuProfileNode::kNoLineNumberInfo;
59 int src_line = no_line_info;
60 bool src_line_not_found = true;
61
62 if (sample.pc != nullptr) {
63 if (sample.has_external_callback && sample.state == EXTERNAL) {
64 // Don't use PC when in external callback code, as it can point
65 // inside a callback's code, and we will erroneously report
66 // that a callback calls itself.
67 stack_trace.push_back(
68 {FindEntry(reinterpret_cast<Address>(sample.external_callback_entry)),
69 no_line_info});
70 } else {
71 Address attributed_pc = reinterpret_cast<Address>(sample.pc);
72 Address pc_entry_instruction_start = kNullAddress;
73 CodeEntry* pc_entry =
74 FindEntry(attributed_pc, &pc_entry_instruction_start);
75 // If there is no pc_entry, we're likely in native code. Find out if the
76 // top of the stack (the return address) was pointing inside a JS
77 // function, meaning that we have encountered a frameless invocation.
78 if (!pc_entry && !sample.has_external_callback) {
79 attributed_pc = reinterpret_cast<Address>(sample.tos);
80 pc_entry = FindEntry(attributed_pc, &pc_entry_instruction_start);
81 }
82 // If pc is in the function code before it set up stack frame or after the
83 // frame was destroyed, SafeStackFrameIterator incorrectly thinks that
84 // ebp contains the return address of the current function and skips the
85 // caller's frame. Check for this case and just skip such samples.
86 if (pc_entry) {
87 int pc_offset =
88 static_cast<int>(attributed_pc - pc_entry_instruction_start);
89 // TODO(petermarshall): pc_offset can still be negative in some cases.
90 src_line = pc_entry->GetSourceLine(pc_offset);
91 if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) {
92 src_line = pc_entry->line_number();
93 }
94 src_line_not_found = false;
95 stack_trace.push_back({pc_entry, src_line});
96
97 if (pc_entry->builtin() == Builtin::kFunctionPrototypeApply ||
98 pc_entry->builtin() == Builtin::kFunctionPrototypeCall) {
99 // When current function is either the Function.prototype.apply or the
100 // Function.prototype.call builtin the top frame is either frame of
101 // the calling JS function or internal frame.
102 // In the latter case we know the caller for sure but in the
103 // former case we don't so we simply replace the frame with
104 // 'unresolved' entry.
105 if (!sample.has_external_callback) {
106 ProfilerStats::Instance()->AddReason(
107 ProfilerStats::Reason::kInCallOrApply);
108 stack_trace.push_back(
109 {CodeEntry::unresolved_entry(), no_line_info});
110 }
111 }
112 }
113 }
114
115 for (unsigned i = 0; i < sample.frames_count; ++i) {
116 Address stack_pos = reinterpret_cast<Address>(sample.stack[i]);
117 Address instruction_start = kNullAddress;
118 CodeEntry* entry = FindEntry(stack_pos, &instruction_start);
119 int line_number = no_line_info;
120 if (entry) {
121 // Find out if the entry has an inlining stack associated.
122 int pc_offset = static_cast<int>(stack_pos - instruction_start);
123 // TODO(petermarshall): pc_offset can still be negative in some cases.
124 const std::vector<CodeEntryAndLineNumber>* inline_stack =
125 entry->GetInlineStack(pc_offset);
126 if (inline_stack) {
127 int most_inlined_frame_line_number = entry->GetSourceLine(pc_offset);
128 for (auto inline_stack_entry : *inline_stack) {
129 stack_trace.push_back(inline_stack_entry);
130 }
131
132 // This is a bit of a messy hack. The line number for the most-inlined
133 // frame (the function at the end of the chain of function calls) has
134 // the wrong line number in inline_stack. The actual line number in
135 // this function is stored in the SourcePositionTable in entry. We fix
136 // up the line number for the most-inlined frame here.
137 // TODO(petermarshall): Remove this and use a tree with a node per
138 // inlining_id.
139 DCHECK(!inline_stack->empty());
140 size_t index = stack_trace.size() - inline_stack->size();
141 stack_trace[index].line_number = most_inlined_frame_line_number;
142 }
143 // Skip unresolved frames (e.g. internal frame) and get source line of
144 // the first JS caller.
145 if (src_line_not_found) {
146 src_line = entry->GetSourceLine(pc_offset);
147 if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) {
148 src_line = entry->line_number();
149 }
150 src_line_not_found = false;
151 }
152 line_number = entry->GetSourceLine(pc_offset);
153
154 // The inline stack contains the top-level function i.e. the same
155 // function as entry. We don't want to add it twice. The one from the
156 // inline stack has the correct line number for this particular inlining
157 // so we use it instead of pushing entry to stack_trace.
158 if (inline_stack) continue;
159 }
160 stack_trace.push_back({entry, line_number});
161 }
162 }
163
164 if (FLAG_prof_browser_mode) {
165 bool no_symbolized_entries = true;
166 for (auto e : stack_trace) {
167 if (e.code_entry != nullptr) {
168 no_symbolized_entries = false;
169 break;
170 }
171 }
172 // If no frames were symbolized, put the VM state entry in.
173 if (no_symbolized_entries) {
174 if (sample.pc == nullptr) {
175 ProfilerStats::Instance()->AddReason(ProfilerStats::Reason::kNullPC);
176 } else {
177 ProfilerStats::Instance()->AddReason(
178 ProfilerStats::Reason::kNoSymbolizedFrames);
179 }
180 stack_trace.push_back({EntryForVMState(sample.state), no_line_info});
181 }
182 }
183
184 return SymbolizedSample{stack_trace, src_line};
185 }
186
187 } // namespace internal
188 } // namespace v8
189