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