|
|
|||
File indexing completed on 2026-05-10 08:42:56
0001 //===-- TraceDumper.h -------------------------------------------*- C++ -*-===// 0002 // 0003 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 0004 // See https://llvm.org/LICENSE.txt for license information. 0005 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 0006 // 0007 //===----------------------------------------------------------------------===// 0008 0009 #include "lldb/Symbol/SymbolContext.h" 0010 #include "lldb/Target/TraceCursor.h" 0011 #include <optional> 0012 #include <stack> 0013 0014 #ifndef LLDB_TARGET_TRACE_INSTRUCTION_DUMPER_H 0015 #define LLDB_TARGET_TRACE_INSTRUCTION_DUMPER_H 0016 0017 namespace lldb_private { 0018 0019 /// Class that holds the configuration used by \a TraceDumper for 0020 /// traversing and dumping instructions. 0021 struct TraceDumperOptions { 0022 /// If \b true, the cursor will be iterated forwards starting from the 0023 /// oldest instruction. Otherwise, the iteration starts from the most 0024 /// recent instruction. 0025 bool forwards = false; 0026 /// Dump only instruction addresses without disassembly nor symbol 0027 /// information. 0028 bool raw = false; 0029 /// Dump in json format. 0030 bool json = false; 0031 /// When dumping in JSON format, pretty print the output. 0032 bool pretty_print_json = false; 0033 /// For each trace item, print the corresponding timestamp in nanoseconds if 0034 /// available. 0035 bool show_timestamps = false; 0036 /// Dump the events that happened between instructions. 0037 bool show_events = false; 0038 /// Dump events and none of the instructions. 0039 bool only_events = false; 0040 /// For each instruction, print the instruction kind. 0041 bool show_control_flow_kind = false; 0042 /// Optional custom id to start traversing from. 0043 std::optional<uint64_t> id; 0044 /// Optional number of instructions to skip from the starting position 0045 /// of the cursor. 0046 std::optional<size_t> skip; 0047 }; 0048 0049 /// Class used to dump the instructions of a \a TraceCursor using its current 0050 /// state and granularity. 0051 class TraceDumper { 0052 public: 0053 /// Helper struct that holds symbol, disassembly and address information of an 0054 /// instruction. 0055 struct SymbolInfo { 0056 SymbolContext sc; 0057 Address address; 0058 lldb::DisassemblerSP disassembler; 0059 lldb::InstructionSP instruction; 0060 lldb_private::ExecutionContext exe_ctx; 0061 }; 0062 0063 /// Helper struct that holds all the information we know about a trace item 0064 struct TraceItem { 0065 lldb::user_id_t id; 0066 lldb::addr_t load_address; 0067 std::optional<double> timestamp; 0068 std::optional<uint64_t> hw_clock; 0069 std::optional<std::string> sync_point_metadata; 0070 std::optional<llvm::StringRef> error; 0071 std::optional<lldb::TraceEvent> event; 0072 std::optional<SymbolInfo> symbol_info; 0073 std::optional<SymbolInfo> prev_symbol_info; 0074 std::optional<lldb::cpu_id_t> cpu_id; 0075 }; 0076 0077 /// An object representing a traced function call. 0078 /// 0079 /// A function call is represented using segments and subcalls. 0080 /// 0081 /// TracedSegment: 0082 /// A traced segment is a maximal list of consecutive traced instructions 0083 /// that belong to the same function call. A traced segment will end in 0084 /// three possible ways: 0085 /// - With a call to a function deeper in the callstack. In this case, 0086 /// most of the times this nested call will return 0087 /// and resume with the next segment of this segment's owning function 0088 /// call. More on this later. 0089 /// - Abruptly due to end of trace. In this case, we weren't able to trace 0090 /// the end of this function call. 0091 /// - Simply a return higher in the callstack. 0092 /// 0093 /// In terms of implementation details, as segment can be represented with 0094 /// the beginning and ending instruction IDs from the instruction trace. 0095 /// 0096 /// UntracedPrefixSegment: 0097 /// It might happen that we didn't trace the beginning of a function and we 0098 /// saw it for the first time as part of a return. As a way to signal these 0099 /// cases, we have a placeholder UntracedPrefixSegment class that completes the 0100 /// callgraph. 0101 /// 0102 /// Example: 0103 /// We might have this piece of execution: 0104 /// 0105 /// main() [offset 0x00 to 0x20] [traced instruction ids 1 to 4] 0106 /// foo() [offset 0x00 to 0x80] [traced instruction ids 5 to 20] # main 0107 /// invoked foo 0108 /// main() [offset 0x24 to 0x40] [traced instruction ids 21 to 30] 0109 /// 0110 /// In this case, our function main invokes foo. We have 3 segments: main 0111 /// [offset 0x00 to 0x20], foo() [offset 0x00 to 0x80], and main() [offset 0112 /// 0x24 to 0x40]. We also have the instruction ids from the corresponding 0113 /// linear instruction trace for each segment. 0114 /// 0115 /// But what if we started tracing since the middle of foo? Then we'd have 0116 /// an incomplete trace 0117 /// 0118 /// foo() [offset 0x30 to 0x80] [traced instruction ids 1 to 10] 0119 /// main() [offset 0x24 to 0x40] [traced instruction ids 11 to 20] 0120 /// 0121 /// Notice that we changed the instruction ids because this is a new trace. 0122 /// Here, in order to have a somewhat complete tree with good traversal 0123 /// capabilities, we can create an UntracedPrefixSegment to signal the portion of 0124 /// main() that we didn't trace. We don't know if this segment was in fact 0125 /// multiple segments with many function calls. We'll never know. The 0126 /// resulting tree looks like the following: 0127 /// 0128 /// main() [untraced] 0129 /// foo() [offset 0x30 to 0x80] [traced instruction ids 1 to 10] 0130 /// main() [offset 0x24 to 0x40] [traced instruction ids 11 to 20] 0131 /// 0132 /// And in pseudo-code: 0133 /// 0134 /// FunctionCall [ 0135 /// UntracedPrefixSegment { 0136 /// symbol: main() 0137 /// nestedCall: FunctionCall [ # this untraced segment has a nested 0138 /// call 0139 /// TracedSegment { 0140 /// symbol: foo() 0141 /// fromInstructionId: 1 0142 /// toInstructionId: 10 0143 /// nestedCall: none # this doesn't have a nested call 0144 /// } 0145 /// } 0146 /// ], 0147 /// TracedSegment { 0148 /// symbol: main() 0149 /// fromInstructionId: 11 0150 /// toInstructionId: 20 0151 /// nestedCall: none # this also doesn't have a nested call 0152 /// } 0153 /// ] 0154 /// 0155 /// We can see the nested structure and how instructions are represented as 0156 /// segments. 0157 /// 0158 /// 0159 /// Returns: 0160 /// Code doesn't always behave intuitively. Some interesting functions 0161 /// might modify the stack and thus change the behavior of common 0162 /// instructions like CALL and RET. We try to identify these cases, and 0163 /// the result is that the return edge from a segment might connect with a 0164 /// function call very high the stack. For example, you might have 0165 /// 0166 /// main() 0167 /// foo() 0168 /// bar() 0169 /// # here bar modifies the stack and pops foo() from it. Then it 0170 /// finished the a RET (return) 0171 /// main() # we came back directly to main() 0172 /// 0173 /// I have observed some trampolines doing this, as well as some std 0174 /// functions (like ostream functions). So consumers should be aware of 0175 /// this. 0176 /// 0177 /// There are all sorts of "abnormal" behaviors you can see in code, and 0178 /// whenever we fail at identifying what's going on, we prefer to create a 0179 /// new tree. 0180 /// 0181 /// Function call forest: 0182 /// A single tree would suffice if a trace didn't contain errors nor 0183 /// abnormal behaviors that made our algorithms fail. Sadly these 0184 /// anomalies exist and we prefer not to use too many heuristics and 0185 /// probably end up lying to the user. So we create a new tree from the 0186 /// point we can't continue using the previous tree. This results in 0187 /// having a forest instead of a single tree. This is probably the best we 0188 /// can do if we consumers want to use this data to perform performance 0189 /// analysis or reverse debugging. 0190 /// 0191 /// Non-functions: 0192 /// Not everything in a program is a function. There are blocks of 0193 /// instructions that are simply labeled or even regions without symbol 0194 /// information that we don't what they are. We treat all of them as 0195 /// functions for simplicity. 0196 /// 0197 /// Errors: 0198 /// Whenever an error is found, a new tree with a single segment is 0199 /// created. All consecutive errors after the original one are then 0200 /// appended to this segment. As a note, something that GDB does is to use 0201 /// some heuristics to merge trees that were interrupted by errors. We are 0202 /// leaving that out of scope until a feature like that one is really 0203 /// needed. 0204 0205 /// Forward declaration 0206 class FunctionCall; 0207 using FunctionCallUP = std::unique_ptr<FunctionCall>; 0208 0209 class FunctionCall { 0210 public: 0211 class TracedSegment { 0212 public: 0213 /// \param[in] cursor_sp 0214 /// A cursor pointing to the beginning of the segment. 0215 /// 0216 /// \param[in] symbol_info 0217 /// The symbol information of the first instruction of the segment. 0218 /// 0219 /// \param[in] call 0220 /// The FunctionCall object that owns this segment. 0221 TracedSegment(const lldb::TraceCursorSP &cursor_sp, 0222 const SymbolInfo &symbol_info, FunctionCall &owning_call) 0223 : m_first_insn_id(cursor_sp->GetId()), 0224 m_last_insn_id(cursor_sp->GetId()), 0225 m_first_symbol_info(symbol_info), m_last_symbol_info(symbol_info), 0226 m_owning_call(owning_call) {} 0227 0228 /// \return 0229 /// The chronologically first instruction ID in this segment. 0230 lldb::user_id_t GetFirstInstructionID() const; 0231 /// \return 0232 /// The chronologically last instruction ID in this segment. 0233 lldb::user_id_t GetLastInstructionID() const; 0234 0235 /// \return 0236 /// The symbol information of the chronologically first instruction ID 0237 /// in this segment. 0238 const SymbolInfo &GetFirstInstructionSymbolInfo() const; 0239 0240 /// \return 0241 /// The symbol information of the chronologically last instruction ID in 0242 /// this segment. 0243 const SymbolInfo &GetLastInstructionSymbolInfo() const; 0244 0245 /// \return 0246 /// Get the call that owns this segment. 0247 const FunctionCall &GetOwningCall() const; 0248 0249 /// Append a new instruction to this segment. 0250 /// 0251 /// \param[in] cursor_sp 0252 /// A cursor pointing to the new instruction. 0253 /// 0254 /// \param[in] symbol_info 0255 /// The symbol information of the new instruction. 0256 void AppendInsn(const lldb::TraceCursorSP &cursor_sp, 0257 const SymbolInfo &symbol_info); 0258 0259 /// Create a nested call at the end of this segment. 0260 /// 0261 /// \param[in] cursor_sp 0262 /// A cursor pointing to the first instruction of the nested call. 0263 /// 0264 /// \param[in] symbol_info 0265 /// The symbol information of the first instruction of the nested call. 0266 FunctionCall &CreateNestedCall(const lldb::TraceCursorSP &cursor_sp, 0267 const SymbolInfo &symbol_info); 0268 0269 /// Executed the given callback if there's a nested call at the end of 0270 /// this segment. 0271 void IfNestedCall(std::function<void(const FunctionCall &function_call)> 0272 callback) const; 0273 0274 private: 0275 TracedSegment(const TracedSegment &) = delete; 0276 TracedSegment &operator=(TracedSegment const &); 0277 0278 /// Delimiting instruction IDs taken chronologically. 0279 /// \{ 0280 lldb::user_id_t m_first_insn_id; 0281 lldb::user_id_t m_last_insn_id; 0282 /// \} 0283 /// An optional nested call starting at the end of this segment. 0284 FunctionCallUP m_nested_call; 0285 /// The symbol information of the delimiting instructions 0286 /// \{ 0287 SymbolInfo m_first_symbol_info; 0288 SymbolInfo m_last_symbol_info; 0289 /// \} 0290 FunctionCall &m_owning_call; 0291 }; 0292 0293 class UntracedPrefixSegment { 0294 public: 0295 /// Note: Untraced segments can only exist if have also seen a traced 0296 /// segment of the same function call. Thus, we can use those traced 0297 /// segments if we want symbol information and such. 0298 0299 UntracedPrefixSegment(FunctionCallUP &&nested_call) 0300 : m_nested_call(std::move(nested_call)) {} 0301 0302 const FunctionCall &GetNestedCall() const; 0303 0304 private: 0305 UntracedPrefixSegment(const UntracedPrefixSegment &) = delete; 0306 UntracedPrefixSegment &operator=(UntracedPrefixSegment const &); 0307 FunctionCallUP m_nested_call; 0308 }; 0309 0310 /// Create a new function call given an instruction. This will also create a 0311 /// segment for that instruction. 0312 /// 0313 /// \param[in] cursor_sp 0314 /// A cursor pointing to the first instruction of that function call. 0315 /// 0316 /// \param[in] symbol_info 0317 /// The symbol information of that first instruction. 0318 FunctionCall(const lldb::TraceCursorSP &cursor_sp, 0319 const SymbolInfo &symbol_info); 0320 0321 /// Append a new traced segment to this function call. 0322 /// 0323 /// \param[in] cursor_sp 0324 /// A cursor pointing to the first instruction of the new segment. 0325 /// 0326 /// \param[in] symbol_info 0327 /// The symbol information of that first instruction. 0328 void AppendSegment(const lldb::TraceCursorSP &cursor_sp, 0329 const SymbolInfo &symbol_info); 0330 0331 /// \return 0332 /// The symbol info of some traced instruction of this call. 0333 const SymbolInfo &GetSymbolInfo() const; 0334 0335 /// \return 0336 /// \b true if and only if the instructions in this function call are 0337 /// trace errors, in which case this function call is a fake one. 0338 bool IsError() const; 0339 0340 /// \return 0341 /// The list of traced segments of this call. 0342 const std::deque<TracedSegment> &GetTracedSegments() const; 0343 0344 /// \return 0345 /// A non-const reference to the most-recent traced segment. 0346 TracedSegment &GetLastTracedSegment(); 0347 0348 /// Create an untraced segment for this call that jumps to the provided 0349 /// nested call. 0350 void SetUntracedPrefixSegment(FunctionCallUP &&nested_call); 0351 0352 /// \return 0353 /// A optional to the untraced prefix segment of this call. 0354 const std::optional<UntracedPrefixSegment> & 0355 GetUntracedPrefixSegment() const; 0356 0357 /// \return 0358 /// A pointer to the parent call. It may be \b nullptr. 0359 FunctionCall *GetParentCall() const; 0360 0361 void SetParentCall(FunctionCall &parent_call); 0362 0363 private: 0364 /// An optional untraced segment that precedes all the traced segments. 0365 std::optional<UntracedPrefixSegment> m_untraced_prefix_segment; 0366 /// The traced segments in order. We used a deque to prevent moving these 0367 /// objects when appending to the list, which would happen with vector. 0368 std::deque<TracedSegment> m_traced_segments; 0369 /// The parent call, which might be null. Useful for reconstructing 0370 /// callstacks. 0371 FunctionCall *m_parent_call = nullptr; 0372 /// Whether this call represents a list of consecutive errors. 0373 bool m_is_error; 0374 }; 0375 0376 /// Interface used to abstract away the format in which the instruction 0377 /// information will be dumped. 0378 class OutputWriter { 0379 public: 0380 virtual ~OutputWriter() = default; 0381 0382 /// Notify this writer that the cursor ran out of data. 0383 virtual void NoMoreData() {} 0384 0385 /// Dump a trace item (instruction, error or event). 0386 virtual void TraceItem(const TraceItem &item) = 0; 0387 0388 /// Dump a function call forest. 0389 virtual void 0390 FunctionCallForest(const std::vector<FunctionCallUP> &forest) = 0; 0391 }; 0392 0393 /// Create a instruction dumper for the cursor. 0394 /// 0395 /// \param[in] cursor 0396 /// The cursor whose instructions will be dumped. 0397 /// 0398 /// \param[in] s 0399 /// The stream where to dump the instructions to. 0400 /// 0401 /// \param[in] options 0402 /// Additional options for configuring the dumping. 0403 TraceDumper(lldb::TraceCursorSP cursor_sp, Stream &s, 0404 const TraceDumperOptions &options); 0405 0406 /// Dump \a count instructions of the thread trace starting at the current 0407 /// cursor position. 0408 /// 0409 /// This effectively moves the cursor to the next unvisited position, so that 0410 /// a subsequent call to this method continues where it left off. 0411 /// 0412 /// \param[in] count 0413 /// The number of instructions to print. 0414 /// 0415 /// \return 0416 /// The instruction id of the last traversed instruction, or \b 0417 /// std::nullopt if no instructions were visited. 0418 std::optional<lldb::user_id_t> DumpInstructions(size_t count); 0419 0420 /// Dump all function calls forwards chronologically and hierarchically 0421 void DumpFunctionCalls(); 0422 0423 private: 0424 /// Create a trace item for the current position without symbol information. 0425 TraceItem CreatRawTraceItem(); 0426 0427 lldb::TraceCursorSP m_cursor_sp; 0428 TraceDumperOptions m_options; 0429 std::unique_ptr<OutputWriter> m_writer_up; 0430 }; 0431 0432 } // namespace lldb_private 0433 0434 #endif // LLDB_TARGET_TRACE_INSTRUCTION_DUMPER_H
| [ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
|
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |
|