• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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