LLDB mainline
TraceDumper.h
Go to the documentation of this file.
1//===-- TraceDumper.h -------------------------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
11#include <optional>
12
13#ifndef LLDB_TARGET_TRACE_INSTRUCTION_DUMPER_H
14#define LLDB_TARGET_TRACE_INSTRUCTION_DUMPER_H
15
16namespace lldb_private {
17
18/// Class that holds the configuration used by \a TraceDumper for
19/// traversing and dumping instructions.
21 /// If \b true, the cursor will be iterated forwards starting from the
22 /// oldest instruction. Otherwise, the iteration starts from the most
23 /// recent instruction.
24 bool forwards = false;
25 /// Dump only instruction addresses without disassembly nor symbol
26 /// information.
27 bool raw = false;
28 /// Dump in json format.
29 bool json = false;
30 /// When dumping in JSON format, pretty print the output.
31 bool pretty_print_json = false;
32 /// For each trace item, print the corresponding timestamp in nanoseconds if
33 /// available.
34 bool show_timestamps = false;
35 /// Dump the events that happened between instructions.
36 bool show_events = false;
37 /// Dump events and none of the instructions.
38 bool only_events = false;
39 /// For each instruction, print the instruction kind.
41 /// Optional custom id to start traversing from.
42 std::optional<uint64_t> id;
43 /// Optional number of instructions to skip from the starting position
44 /// of the cursor.
45 std::optional<size_t> skip;
46};
47
48/// Class used to dump the instructions of a \a TraceCursor using its current
49/// state and granularity.
51public:
52 /// Helper struct that holds symbol, disassembly and address information of an
53 /// instruction.
54 struct SymbolInfo {
60 };
61
62 /// Helper struct that holds all the information we know about a trace item
63 struct TraceItem {
66 std::optional<double> timestamp;
67 std::optional<uint64_t> hw_clock;
68 std::optional<std::string> sync_point_metadata;
69 std::optional<llvm::StringRef> error;
70 std::optional<lldb::TraceEvent> event;
71 std::optional<SymbolInfo> symbol_info;
72 std::optional<SymbolInfo> prev_symbol_info;
73 std::optional<lldb::cpu_id_t> cpu_id;
74 };
75
76 /// An object representing a traced function call.
77 ///
78 /// A function call is represented using segments and subcalls.
79 ///
80 /// TracedSegment:
81 /// A traced segment is a maximal list of consecutive traced instructions
82 /// that belong to the same function call. A traced segment will end in
83 /// three possible ways:
84 /// - With a call to a function deeper in the callstack. In this case,
85 /// most of the times this nested call will return
86 /// and resume with the next segment of this segment's owning function
87 /// call. More on this later.
88 /// - Abruptly due to end of trace. In this case, we weren't able to trace
89 /// the end of this function call.
90 /// - Simply a return higher in the callstack.
91 ///
92 /// In terms of implementation details, as segment can be represented with
93 /// the beginning and ending instruction IDs from the instruction trace.
94 ///
95 /// UntracedPrefixSegment:
96 /// It might happen that we didn't trace the beginning of a function and we
97 /// saw it for the first time as part of a return. As a way to signal these
98 /// cases, we have a placeholder UntracedPrefixSegment class that completes the
99 /// callgraph.
100 ///
101 /// Example:
102 /// We might have this piece of execution:
103 ///
104 /// main() [offset 0x00 to 0x20] [traced instruction ids 1 to 4]
105 /// foo() [offset 0x00 to 0x80] [traced instruction ids 5 to 20] # main
106 /// invoked foo
107 /// main() [offset 0x24 to 0x40] [traced instruction ids 21 to 30]
108 ///
109 /// In this case, our function main invokes foo. We have 3 segments: main
110 /// [offset 0x00 to 0x20], foo() [offset 0x00 to 0x80], and main() [offset
111 /// 0x24 to 0x40]. We also have the instruction ids from the corresponding
112 /// linear instruction trace for each segment.
113 ///
114 /// But what if we started tracing since the middle of foo? Then we'd have
115 /// an incomplete trace
116 ///
117 /// foo() [offset 0x30 to 0x80] [traced instruction ids 1 to 10]
118 /// main() [offset 0x24 to 0x40] [traced instruction ids 11 to 20]
119 ///
120 /// Notice that we changed the instruction ids because this is a new trace.
121 /// Here, in order to have a somewhat complete tree with good traversal
122 /// capabilities, we can create an UntracedPrefixSegment to signal the portion of
123 /// main() that we didn't trace. We don't know if this segment was in fact
124 /// multiple segments with many function calls. We'll never know. The
125 /// resulting tree looks like the following:
126 ///
127 /// main() [untraced]
128 /// foo() [offset 0x30 to 0x80] [traced instruction ids 1 to 10]
129 /// main() [offset 0x24 to 0x40] [traced instruction ids 11 to 20]
130 ///
131 /// And in pseudo-code:
132 ///
133 /// FunctionCall [
134 /// UntracedPrefixSegment {
135 /// symbol: main()
136 /// nestedCall: FunctionCall [ # this untraced segment has a nested
137 /// call
138 /// TracedSegment {
139 /// symbol: foo()
140 /// fromInstructionId: 1
141 /// toInstructionId: 10
142 /// nestedCall: none # this doesn't have a nested call
143 /// }
144 /// }
145 /// ],
146 /// TracedSegment {
147 /// symbol: main()
148 /// fromInstructionId: 11
149 /// toInstructionId: 20
150 /// nestedCall: none # this also doesn't have a nested call
151 /// }
152 /// ]
153 ///
154 /// We can see the nested structure and how instructions are represented as
155 /// segments.
156 ///
157 ///
158 /// Returns:
159 /// Code doesn't always behave intuitively. Some interesting functions
160 /// might modify the stack and thus change the behavior of common
161 /// instructions like CALL and RET. We try to identify these cases, and
162 /// the result is that the return edge from a segment might connect with a
163 /// function call very high the stack. For example, you might have
164 ///
165 /// main()
166 /// foo()
167 /// bar()
168 /// # here bar modifies the stack and pops foo() from it. Then it
169 /// finished the a RET (return)
170 /// main() # we came back directly to main()
171 ///
172 /// I have observed some trampolines doing this, as well as some std
173 /// functions (like ostream functions). So consumers should be aware of
174 /// this.
175 ///
176 /// There are all sorts of "abnormal" behaviors you can see in code, and
177 /// whenever we fail at identifying what's going on, we prefer to create a
178 /// new tree.
179 ///
180 /// Function call forest:
181 /// A single tree would suffice if a trace didn't contain errors nor
182 /// abnormal behaviors that made our algorithms fail. Sadly these
183 /// anomalies exist and we prefer not to use too many heuristics and
184 /// probably end up lying to the user. So we create a new tree from the
185 /// point we can't continue using the previous tree. This results in
186 /// having a forest instead of a single tree. This is probably the best we
187 /// can do if we consumers want to use this data to perform performance
188 /// analysis or reverse debugging.
189 ///
190 /// Non-functions:
191 /// Not everything in a program is a function. There are blocks of
192 /// instructions that are simply labeled or even regions without symbol
193 /// information that we don't what they are. We treat all of them as
194 /// functions for simplicity.
195 ///
196 /// Errors:
197 /// Whenever an error is found, a new tree with a single segment is
198 /// created. All consecutive errors after the original one are then
199 /// appended to this segment. As a note, something that GDB does is to use
200 /// some heuristics to merge trees that were interrupted by errors. We are
201 /// leaving that out of scope until a feature like that one is really
202 /// needed.
203
204 /// Forward declaration
205 class FunctionCall;
206 using FunctionCallUP = std::unique_ptr<FunctionCall>;
207
209 public:
211 public:
212 /// \param[in] cursor_sp
213 /// A cursor pointing to the beginning of the segment.
214 ///
215 /// \param[in] symbol_info
216 /// The symbol information of the first instruction of the segment.
217 ///
218 /// \param[in] call
219 /// The FunctionCall object that owns this segment.
221 const SymbolInfo &symbol_info, FunctionCall &owning_call)
222 : m_first_insn_id(cursor_sp->GetId()),
223 m_last_insn_id(cursor_sp->GetId()),
224 m_first_symbol_info(symbol_info), m_last_symbol_info(symbol_info),
225 m_owning_call(owning_call) {}
226
227 /// \return
228 /// The chronologically first instruction ID in this segment.
230 /// \return
231 /// The chronologically last instruction ID in this segment.
233
234 /// \return
235 /// The symbol information of the chronologically first instruction ID
236 /// in this segment.
238
239 /// \return
240 /// The symbol information of the chronologically last instruction ID in
241 /// this segment.
243
244 /// \return
245 /// Get the call that owns this segment.
246 const FunctionCall &GetOwningCall() const;
247
248 /// Append a new instruction to this segment.
249 ///
250 /// \param[in] cursor_sp
251 /// A cursor pointing to the new instruction.
252 ///
253 /// \param[in] symbol_info
254 /// The symbol information of the new instruction.
255 void AppendInsn(const lldb::TraceCursorSP &cursor_sp,
256 const SymbolInfo &symbol_info);
257
258 /// Create a nested call at the end of this segment.
259 ///
260 /// \param[in] cursor_sp
261 /// A cursor pointing to the first instruction of the nested call.
262 ///
263 /// \param[in] symbol_info
264 /// The symbol information of the first instruction of the nested call.
266 const SymbolInfo &symbol_info);
267
268 /// Executed the given callback if there's a nested call at the end of
269 /// this segment.
270 void IfNestedCall(std::function<void(const FunctionCall &function_call)>
271 callback) const;
272
273 private:
274 TracedSegment(const TracedSegment &) = delete;
276
277 /// Delimiting instruction IDs taken chronologically.
278 /// \{
281 /// \}
282 /// An optional nested call starting at the end of this segment.
284 /// The symbol information of the delimiting instructions
285 /// \{
288 /// \}
290 };
291
293 public:
294 /// Note: Untraced segments can only exist if have also seen a traced
295 /// segment of the same function call. Thus, we can use those traced
296 /// segments if we want symbol information and such.
297
299 : m_nested_call(std::move(nested_call)) {}
300
301 const FunctionCall &GetNestedCall() const;
302
303 private:
307 };
308
309 /// Create a new function call given an instruction. This will also create a
310 /// segment for that instruction.
311 ///
312 /// \param[in] cursor_sp
313 /// A cursor pointing to the first instruction of that function call.
314 ///
315 /// \param[in] symbol_info
316 /// The symbol information of that first instruction.
317 FunctionCall(const lldb::TraceCursorSP &cursor_sp,
318 const SymbolInfo &symbol_info);
319
320 /// Append a new traced segment to this function call.
321 ///
322 /// \param[in] cursor_sp
323 /// A cursor pointing to the first instruction of the new segment.
324 ///
325 /// \param[in] symbol_info
326 /// The symbol information of that first instruction.
327 void AppendSegment(const lldb::TraceCursorSP &cursor_sp,
328 const SymbolInfo &symbol_info);
329
330 /// \return
331 /// The symbol info of some traced instruction of this call.
332 const SymbolInfo &GetSymbolInfo() const;
333
334 /// \return
335 /// \b true if and only if the instructions in this function call are
336 /// trace errors, in which case this function call is a fake one.
337 bool IsError() const;
338
339 /// \return
340 /// The list of traced segments of this call.
341 const std::deque<TracedSegment> &GetTracedSegments() const;
342
343 /// \return
344 /// A non-const reference to the most-recent traced segment.
346
347 /// Create an untraced segment for this call that jumps to the provided
348 /// nested call.
349 void SetUntracedPrefixSegment(FunctionCallUP &&nested_call);
350
351 /// \return
352 /// A optional to the untraced prefix segment of this call.
353 const std::optional<UntracedPrefixSegment> &
355
356 /// \return
357 /// A pointer to the parent call. It may be \b nullptr.
359
360 void SetParentCall(FunctionCall &parent_call);
361
362 private:
363 /// An optional untraced segment that precedes all the traced segments.
364 std::optional<UntracedPrefixSegment> m_untraced_prefix_segment;
365 /// The traced segments in order. We used a deque to prevent moving these
366 /// objects when appending to the list, which would happen with vector.
367 std::deque<TracedSegment> m_traced_segments;
368 /// The parent call, which might be null. Useful for reconstructing
369 /// callstacks.
371 /// Whether this call represents a list of consecutive errors.
373 };
374
375 /// Interface used to abstract away the format in which the instruction
376 /// information will be dumped.
378 public:
379 virtual ~OutputWriter() = default;
380
381 /// Notify this writer that the cursor ran out of data.
382 virtual void NoMoreData() {}
383
384 /// Dump a trace item (instruction, error or event).
385 virtual void TraceItem(const TraceItem &item) = 0;
386
387 /// Dump a function call forest.
388 virtual void
389 FunctionCallForest(const std::vector<FunctionCallUP> &forest) = 0;
390 };
391
392 /// Create a instruction dumper for the cursor.
393 ///
394 /// \param[in] cursor
395 /// The cursor whose instructions will be dumped.
396 ///
397 /// \param[in] s
398 /// The stream where to dump the instructions to.
399 ///
400 /// \param[in] options
401 /// Additional options for configuring the dumping.
403 const TraceDumperOptions &options);
404
405 /// Dump \a count instructions of the thread trace starting at the current
406 /// cursor position.
407 ///
408 /// This effectively moves the cursor to the next unvisited position, so that
409 /// a subsequent call to this method continues where it left off.
410 ///
411 /// \param[in] count
412 /// The number of instructions to print.
413 ///
414 /// \return
415 /// The instruction id of the last traversed instruction, or \b
416 /// std::nullopt if no instructions were visited.
417 std::optional<lldb::user_id_t> DumpInstructions(size_t count);
418
419 /// Dump all function calls forwards chronologically and hierarchically
420 void DumpFunctionCalls();
421
422private:
423 /// Create a trace item for the current position without symbol information.
425
428 std::unique_ptr<OutputWriter> m_writer_up;
429};
430
431} // namespace lldb_private
432
433#endif // LLDB_TARGET_TRACE_INSTRUCTION_DUMPER_H
A section + offset based address class.
Definition: Address.h:62
"lldb/Target/ExecutionContext.h" A class that contains an execution context.
A stream class that can stream formatted output to a file.
Definition: Stream.h:28
Defines a symbol context baton that can be handed other debug core functions.
Definition: SymbolContext.h:34
FunctionCall & CreateNestedCall(const lldb::TraceCursorSP &cursor_sp, const SymbolInfo &symbol_info)
Create a nested call at the end of this segment.
TracedSegment(const lldb::TraceCursorSP &cursor_sp, const SymbolInfo &symbol_info, FunctionCall &owning_call)
Definition: TraceDumper.h:220
SymbolInfo m_first_symbol_info
The symbol information of the delimiting instructions.
Definition: TraceDumper.h:286
FunctionCallUP m_nested_call
An optional nested call starting at the end of this segment.
Definition: TraceDumper.h:283
void AppendInsn(const lldb::TraceCursorSP &cursor_sp, const SymbolInfo &symbol_info)
Append a new instruction to this segment.
void IfNestedCall(std::function< void(const FunctionCall &function_call)> callback) const
Executed the given callback if there's a nested call at the end of this segment.
TracedSegment & operator=(TracedSegment const &)
lldb::user_id_t m_first_insn_id
Delimiting instruction IDs taken chronologically.
Definition: TraceDumper.h:279
UntracedPrefixSegment(const UntracedPrefixSegment &)=delete
UntracedPrefixSegment(FunctionCallUP &&nested_call)
Note: Untraced segments can only exist if have also seen a traced segment of the same function call.
Definition: TraceDumper.h:298
UntracedPrefixSegment & operator=(UntracedPrefixSegment const &)
const std::deque< TracedSegment > & GetTracedSegments() const
void SetParentCall(FunctionCall &parent_call)
FunctionCall * m_parent_call
The parent call, which might be null.
Definition: TraceDumper.h:370
void SetUntracedPrefixSegment(FunctionCallUP &&nested_call)
Create an untraced segment for this call that jumps to the provided nested call.
std::optional< UntracedPrefixSegment > m_untraced_prefix_segment
An optional untraced segment that precedes all the traced segments.
Definition: TraceDumper.h:364
const SymbolInfo & GetSymbolInfo() const
std::deque< TracedSegment > m_traced_segments
The traced segments in order.
Definition: TraceDumper.h:367
bool m_is_error
Whether this call represents a list of consecutive errors.
Definition: TraceDumper.h:372
const std::optional< UntracedPrefixSegment > & GetUntracedPrefixSegment() const
void AppendSegment(const lldb::TraceCursorSP &cursor_sp, const SymbolInfo &symbol_info)
Append a new traced segment to this function call.
Interface used to abstract away the format in which the instruction information will be dumped.
Definition: TraceDumper.h:377
virtual void TraceItem(const TraceItem &item)=0
Dump a trace item (instruction, error or event).
virtual void NoMoreData()
Notify this writer that the cursor ran out of data.
Definition: TraceDumper.h:382
virtual void FunctionCallForest(const std::vector< FunctionCallUP > &forest)=0
Dump a function call forest.
Class used to dump the instructions of a TraceCursor using its current state and granularity.
Definition: TraceDumper.h:50
std::optional< lldb::user_id_t > DumpInstructions(size_t count)
Dump count instructions of the thread trace starting at the current cursor position.
std::unique_ptr< FunctionCall > FunctionCallUP
Definition: TraceDumper.h:206
lldb::TraceCursorSP m_cursor_sp
Definition: TraceDumper.h:426
std::unique_ptr< OutputWriter > m_writer_up
Definition: TraceDumper.h:428
TraceDumperOptions m_options
Definition: TraceDumper.h:427
TraceItem CreatRawTraceItem()
Create a trace item for the current position without symbol information.
void DumpFunctionCalls()
Dump all function calls forwards chronologically and hierarchically.
A class that represents a running process on the host machine.
std::shared_ptr< lldb_private::Instruction > InstructionSP
Definition: lldb-forward.h:353
std::shared_ptr< lldb_private::Disassembler > DisassemblerSP
Definition: lldb-forward.h:336
std::shared_ptr< lldb_private::TraceCursor > TraceCursorSP
Definition: lldb-forward.h:451
uint64_t user_id_t
Definition: lldb-types.h:82
uint64_t addr_t
Definition: lldb-types.h:80
Class that holds the configuration used by TraceDumper for traversing and dumping instructions.
Definition: TraceDumper.h:20
bool show_control_flow_kind
For each instruction, print the instruction kind.
Definition: TraceDumper.h:40
bool only_events
Dump events and none of the instructions.
Definition: TraceDumper.h:38
bool show_timestamps
For each trace item, print the corresponding timestamp in nanoseconds if available.
Definition: TraceDumper.h:34
std::optional< uint64_t > id
Optional custom id to start traversing from.
Definition: TraceDumper.h:42
bool pretty_print_json
When dumping in JSON format, pretty print the output.
Definition: TraceDumper.h:31
std::optional< size_t > skip
Optional number of instructions to skip from the starting position of the cursor.
Definition: TraceDumper.h:45
bool json
Dump in json format.
Definition: TraceDumper.h:29
bool show_events
Dump the events that happened between instructions.
Definition: TraceDumper.h:36
bool raw
Dump only instruction addresses without disassembly nor symbol information.
Definition: TraceDumper.h:27
bool forwards
If true, the cursor will be iterated forwards starting from the oldest instruction.
Definition: TraceDumper.h:24
Helper struct that holds symbol, disassembly and address information of an instruction.
Definition: TraceDumper.h:54
lldb_private::ExecutionContext exe_ctx
Definition: TraceDumper.h:59
lldb::DisassemblerSP disassembler
Definition: TraceDumper.h:57
Helper struct that holds all the information we know about a trace item.
Definition: TraceDumper.h:63
std::optional< uint64_t > hw_clock
Definition: TraceDumper.h:67
std::optional< SymbolInfo > symbol_info
Definition: TraceDumper.h:71
std::optional< std::string > sync_point_metadata
Definition: TraceDumper.h:68
std::optional< lldb::cpu_id_t > cpu_id
Definition: TraceDumper.h:73
std::optional< llvm::StringRef > error
Definition: TraceDumper.h:69
std::optional< SymbolInfo > prev_symbol_info
Definition: TraceDumper.h:72
std::optional< lldb::TraceEvent > event
Definition: TraceDumper.h:70
std::optional< double > timestamp
Definition: TraceDumper.h:66