LLDB mainline
TraceDumper.cpp
Go to the documentation of this file.
1//===-- TraceDumper.cpp ---------------------------------------------------===//
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
10#include "lldb/Core/Module.h"
14#include "lldb/Target/Process.h"
16#include <optional>
17
18using namespace lldb;
19using namespace lldb_private;
20using namespace llvm;
21
22/// \return
23/// The given string or \b std::nullopt if it's empty.
24static std::optional<const char *> ToOptionalString(const char *s) {
25 if (!s)
26 return std::nullopt;
27 return s;
28}
29
30static const char *GetModuleName(const SymbolContext &sc) {
31 if (!sc.module_sp)
32 return nullptr;
33 return sc.module_sp->GetFileSpec().GetFilename().AsCString();
34}
35
36/// \return
37/// The module name (basename if the module is a file, or the actual name if
38/// it's a virtual module), or \b nullptr if no name nor module was found.
39static const char *GetModuleName(const TraceDumper::TraceItem &item) {
40 if (!item.symbol_info)
41 return nullptr;
42 return GetModuleName(item.symbol_info->sc);
43}
44
45// This custom LineEntry validator is neded because some line_entries have
46// 0 as line, which is meaningless. Notice that LineEntry::IsValid only
47// checks that line is not LLDB_INVALID_LINE_NUMBER, i.e. UINT32_MAX.
48static bool IsLineEntryValid(const LineEntry &line_entry) {
49 return line_entry.IsValid() && line_entry.line > 0;
50}
51
52/// \return
53/// \b true if the provided line entries match line, column and source file.
54/// This function assumes that the line entries are valid.
55static bool FileLineAndColumnMatches(const LineEntry &a, const LineEntry &b) {
56 if (a.line != b.line)
57 return false;
58 if (a.column != b.column)
59 return false;
60 return a.GetFile() == b.GetFile();
61}
62
63/// Compare the symbol contexts of the provided \a SymbolInfo
64/// objects.
65///
66/// \return
67/// \a true if both instructions belong to the same scope level analized
68/// in the following order:
69/// - module
70/// - symbol
71/// - function
72/// - inlined function
73/// - source line info
74static bool
76 const TraceDumper::SymbolInfo &insn,
77 bool check_source_line_info = true) {
78 // module checks
79 if (insn.sc.module_sp != prev_insn.sc.module_sp)
80 return false;
81
82 // symbol checks
83 if (insn.sc.symbol != prev_insn.sc.symbol)
84 return false;
85
86 // function checks
87 if (!insn.sc.function && !prev_insn.sc.function)
88 return true; // This means two dangling instruction in the same module. We
89 // can assume they are part of the same unnamed symbol
90 else if (insn.sc.function != prev_insn.sc.function)
91 return false;
92
93 Block *inline_block_a =
94 insn.sc.block ? insn.sc.block->GetContainingInlinedBlock() : nullptr;
95 Block *inline_block_b = prev_insn.sc.block
97 : nullptr;
98 if (inline_block_a != inline_block_b)
99 return false;
100
101 // line entry checks
102 if (!check_source_line_info)
103 return true;
104
105 const bool curr_line_valid = IsLineEntryValid(insn.sc.line_entry);
106 const bool prev_line_valid = IsLineEntryValid(prev_insn.sc.line_entry);
107 if (curr_line_valid && prev_line_valid)
109 prev_insn.sc.line_entry);
110 return curr_line_valid == prev_line_valid;
111}
112
114public:
115 OutputWriterCLI(Stream &s, const TraceDumperOptions &options, Thread &thread)
116 : m_s(s), m_options(options) {
117 m_s.Format("thread #{0}: tid = {1}\n", thread.GetIndexID(), thread.GetID());
118 };
119
120 void NoMoreData() override { m_s << " no more data\n"; }
121
123 const std::vector<TraceDumper::FunctionCallUP> &forest) override {
124 for (size_t i = 0; i < forest.size(); i++) {
125 m_s.Format("\n[call tree #{0}]\n", i);
126 DumpFunctionCallTree(*forest[i]);
127 }
128 }
129
130 void TraceItem(const TraceDumper::TraceItem &item) override {
131 if (item.symbol_info) {
132 if (!item.prev_symbol_info ||
134 *item.symbol_info)) {
135 m_s << " ";
136 const char *module_name = GetModuleName(item);
137 if (!module_name)
138 m_s << "(none)";
139 else if (!item.symbol_info->sc.function && !item.symbol_info->sc.symbol)
140 m_s.Format("{0}`(none)", module_name);
141 else
142 item.symbol_info->sc.DumpStopContext(
143 &m_s, item.symbol_info->exe_ctx.GetTargetPtr(),
144 item.symbol_info->address,
145 /*show_fullpaths=*/false,
146 /*show_module=*/true, /*show_inlined_frames=*/false,
147 /*show_function_arguments=*/true,
148 /*show_function_name=*/true);
149 m_s << "\n";
150 }
151 }
152
154 m_s << " ...missing instructions\n";
155
156 m_s.Format(" {0}: ", item.id);
157
158 if (m_options.show_timestamps) {
159 if (item.timestamp)
160 m_s << formatv("[{0:3} ns]", *item.timestamp);
161 else
162 m_s << "[unavailable]";
163 }
164
165 if (item.event) {
166 m_s << "(event) " << TraceCursor::EventKindToString(*item.event);
167 switch (*item.event) {
169 m_s.Format(" [new CPU={0}]",
170 item.cpu_id ? std::to_string(*item.cpu_id) : "unavailable");
171 break;
173 m_s.Format(" [{0}]", item.hw_clock ? std::to_string(*item.hw_clock)
174 : "unavailable");
175 break;
178 break;
180 m_s.Format(" [{0}]", item.sync_point_metadata);
181 break;
182 }
183 } else if (item.error) {
184 m_s << "(error) " << *item.error;
185 } else {
186 m_s.Format("{0:x+16}", item.load_address);
187 if (item.symbol_info && item.symbol_info->instruction) {
188 m_s << " ";
189 item.symbol_info->instruction->Dump(
190 &m_s, /*max_opcode_byte_size=*/0,
191 /*show_address=*/false,
192 /*show_bytes=*/false, m_options.show_control_flow_kind,
193 &item.symbol_info->exe_ctx, &item.symbol_info->sc,
194 /*prev_sym_ctx=*/nullptr,
195 /*disassembly_addr_format=*/nullptr,
196 /*max_address_text_size=*/0);
197 }
198 }
199
201 m_s << "\n";
202 }
203
204private:
205 void
207 if (segment.GetOwningCall().IsError()) {
208 m_s << "<tracing errors>";
209 return;
210 }
211
212 const SymbolContext &first_sc = segment.GetFirstInstructionSymbolInfo().sc;
213 first_sc.DumpStopContext(
214 &m_s, segment.GetFirstInstructionSymbolInfo().exe_ctx.GetTargetPtr(),
215 segment.GetFirstInstructionSymbolInfo().address,
216 /*show_fullpaths=*/false,
217 /*show_module=*/true, /*show_inlined_frames=*/false,
218 /*show_function_arguments=*/true,
219 /*show_function_name=*/true);
220 m_s << " to ";
221 const SymbolContext &last_sc = segment.GetLastInstructionSymbolInfo().sc;
222 if (IsLineEntryValid(first_sc.line_entry) &&
223 IsLineEntryValid(last_sc.line_entry)) {
224 m_s.Format("{0}:{1}", last_sc.line_entry.line, last_sc.line_entry.column);
225 } else {
226 last_sc.DumpStopContext(
227 &m_s, segment.GetFirstInstructionSymbolInfo().exe_ctx.GetTargetPtr(),
228 segment.GetLastInstructionSymbolInfo().address,
229 /*show_fullpaths=*/false,
230 /*show_module=*/false, /*show_inlined_frames=*/false,
231 /*show_function_arguments=*/false,
232 /*show_function_name=*/false);
233 }
234 }
235
237 if (function_call.IsError()) {
238 m_s << "tracing error";
239 }
240 const SymbolContext &sc = function_call.GetSymbolInfo().sc;
241
242 const char *module_name = GetModuleName(sc);
243 if (!module_name)
244 m_s << "(none)";
245 else if (!sc.function && !sc.symbol)
246 m_s << module_name << "`(none)";
247 else
248 m_s << module_name << "`" << sc.GetFunctionName().AsCString();
249 }
250
252 if (function_call.GetUntracedPrefixSegment()) {
253 m_s.Indent();
254 DumpUntracedContext(function_call);
255 m_s << "\n";
256
257 m_s.IndentMore();
258 DumpFunctionCallTree(function_call.GetUntracedPrefixSegment()->GetNestedCall());
259 m_s.IndentLess();
260 }
261
263 function_call.GetTracedSegments()) {
264 m_s.Indent();
266 m_s.Format(" [{0}, {1}]\n", segment.GetFirstInstructionID(),
267 segment.GetLastInstructionID());
268
269 segment.IfNestedCall([&](const TraceDumper::FunctionCall &nested_call) {
270 m_s.IndentMore();
271 DumpFunctionCallTree(nested_call);
272 m_s.IndentLess();
273 });
274 }
275 }
276
280};
281
283 /* schema:
284 error_message: string
285 | {
286 "event": string,
287 "id": decimal,
288 "tsc"?: string decimal,
289 "cpuId"? decimal,
290 } | {
291 "error": string,
292 "id": decimal,
293 "tsc"?: string decimal,
294 | {
295 "loadAddress": string decimal,
296 "id": decimal,
297 "hwClock"?: string decimal,
298 "syncPointMetadata"?: string,
299 "timestamp_ns"?: string decimal,
300 "module"?: string,
301 "symbol"?: string,
302 "line"?: decimal,
303 "column"?: decimal,
304 "source"?: string,
305 "mnemonic"?: string,
306 "controlFlowKind"?: string,
307 }
308 */
309public:
311 : m_s(s), m_options(options),
312 m_j(m_s.AsRawOstream(),
313 /*IndentSize=*/options.pretty_print_json ? 2 : 0) {
314 m_j.arrayBegin();
315 };
316
317 ~OutputWriterJSON() { m_j.arrayEnd(); }
318
320 const std::vector<TraceDumper::FunctionCallUP> &forest) override {
321 for (size_t i = 0; i < forest.size(); i++) {
322 m_j.object([&] { DumpFunctionCallTree(*forest[i]); });
323 }
324 }
325
327 if (function_call.GetUntracedPrefixSegment()) {
328 m_j.attributeObject("untracedPrefixSegment", [&] {
329 m_j.attributeObject("nestedCall", [&] {
331 function_call.GetUntracedPrefixSegment()->GetNestedCall());
332 });
333 });
334 }
335
336 if (!function_call.GetTracedSegments().empty()) {
337 m_j.attributeArray("tracedSegments", [&] {
339 function_call.GetTracedSegments()) {
340 m_j.object([&] {
341 m_j.attribute("firstInstructionId",
342 std::to_string(segment.GetFirstInstructionID()));
343 m_j.attribute("lastInstructionId",
344 std::to_string(segment.GetLastInstructionID()));
345 segment.IfNestedCall(
346 [&](const TraceDumper::FunctionCall &nested_call) {
347 m_j.attributeObject(
348 "nestedCall", [&] { DumpFunctionCallTree(nested_call); });
349 });
350 });
351 }
352 });
353 }
354 }
355
357 m_j.attribute("event", TraceCursor::EventKindToString(*item.event));
358 switch (*item.event) {
360 m_j.attribute("cpuId", item.cpu_id);
361 break;
363 m_j.attribute("hwClock", item.hw_clock);
364 break;
367 break;
369 m_j.attribute("syncPointMetadata", item.sync_point_metadata);
370 break;
371 }
372 }
373
375 m_j.attribute("loadAddress", formatv("{0:x}", item.load_address));
376 if (item.symbol_info) {
377 m_j.attribute("module", ToOptionalString(GetModuleName(item)));
378 m_j.attribute(
379 "symbol",
380 ToOptionalString(item.symbol_info->sc.GetFunctionName().AsCString()));
381
382 if (lldb::InstructionSP instruction = item.symbol_info->instruction) {
383 ExecutionContext exe_ctx = item.symbol_info->exe_ctx;
384 m_j.attribute("mnemonic",
385 ToOptionalString(instruction->GetMnemonic(&exe_ctx)));
386 if (m_options.show_control_flow_kind) {
387 lldb::InstructionControlFlowKind instruction_control_flow_kind =
388 instruction->GetControlFlowKind(&exe_ctx);
389 m_j.attribute("controlFlowKind",
392 instruction_control_flow_kind)));
393 }
394 }
395
396 if (IsLineEntryValid(item.symbol_info->sc.line_entry)) {
397 m_j.attribute(
398 "source",
400 item.symbol_info->sc.line_entry.GetFile().GetPath().c_str()));
401 m_j.attribute("line", item.symbol_info->sc.line_entry.line);
402 m_j.attribute("column", item.symbol_info->sc.line_entry.column);
403 }
404 }
405 }
406
407 void TraceItem(const TraceDumper::TraceItem &item) override {
408 m_j.object([&] {
409 m_j.attribute("id", item.id);
410 if (m_options.show_timestamps)
411 m_j.attribute("timestamp_ns", item.timestamp
412 ? std::optional<std::string>(
413 std::to_string(*item.timestamp))
414 : std::nullopt);
415
416 if (item.event) {
417 DumpEvent(item);
418 } else if (item.error) {
419 m_j.attribute("error", *item.error);
420 } else {
421 DumpInstruction(item);
422 }
423 });
424 }
425
426private:
429 json::OStream m_j;
430};
431
432static std::unique_ptr<TraceDumper::OutputWriter>
433CreateWriter(Stream &s, const TraceDumperOptions &options, Thread &thread) {
434 if (options.json)
435 return std::unique_ptr<TraceDumper::OutputWriter>(
436 new OutputWriterJSON(s, options));
437 else
438 return std::unique_ptr<TraceDumper::OutputWriter>(
439 new OutputWriterCLI(s, options, thread));
440}
441
443 const TraceDumperOptions &options)
444 : m_cursor_sp(std::move(cursor_sp)), m_options(options),
446 s, m_options, *m_cursor_sp->GetExecutionContextRef().GetThreadSP())) {
447
448 if (m_options.id)
449 m_cursor_sp->GoToId(*m_options.id);
450 else if (m_options.forwards)
452 else
454
455 m_cursor_sp->SetForwards(m_options.forwards);
456 if (m_options.skip) {
457 m_cursor_sp->Seek((m_options.forwards ? 1 : -1) * *m_options.skip,
458 lldb::eTraceCursorSeekTypeCurrent);
459 }
460}
461
463 TraceItem item = {};
464 item.id = m_cursor_sp->GetId();
465
466 if (m_options.show_timestamps)
467 item.timestamp = m_cursor_sp->GetWallClockTime();
468 return item;
469}
470
471/// Find the symbol context for the given address reusing the previous
472/// instruction's symbol context when possible.
473static SymbolContext
475 const SymbolContext &prev_symbol_context) {
477 if (prev_symbol_context.GetAddressRange(eSymbolContextEverything, 0,
478 /*inline_block_range*/ true, range) &&
479 range.Contains(address))
480 return prev_symbol_context;
481
482 SymbolContext sc;
483 address.CalculateSymbolContext(&sc, eSymbolContextEverything);
484 return sc;
485}
486
487/// Find the disassembler for the given address reusing the previous
488/// instruction's disassembler when possible.
489static std::tuple<DisassemblerSP, InstructionSP>
491 const TraceDumper::SymbolInfo &prev_symbol_info,
492 const ExecutionContext &exe_ctx) {
493 if (prev_symbol_info.disassembler) {
494 if (InstructionSP instruction =
495 prev_symbol_info.disassembler->GetInstructionList()
496 .GetInstructionAtAddress(symbol_info.address))
497 return std::make_tuple(prev_symbol_info.disassembler, instruction);
498 }
499
500 if (symbol_info.sc.function) {
501 if (DisassemblerSP disassembler =
502 symbol_info.sc.function->GetInstructions(exe_ctx, nullptr)) {
503 if (InstructionSP instruction =
504 disassembler->GetInstructionList().GetInstructionAtAddress(
505 symbol_info.address))
506 return std::make_tuple(disassembler, instruction);
507 }
508 }
509 // We fallback to a single instruction disassembler
510 Target &target = exe_ctx.GetTargetRef();
511 const ArchSpec arch = target.GetArchitecture();
512 lldb_private::AddressRange range(symbol_info.address,
515 arch, /*plugin_name=*/nullptr,
516 /*flavor=*/nullptr, /*cpu=*/nullptr, /*features=*/nullptr, target, range);
517 return std::make_tuple(
518 disassembler,
519 disassembler ? disassembler->GetInstructionList().GetInstructionAtAddress(
520 symbol_info.address)
521 : InstructionSP());
522}
523
526 const TraceDumper::SymbolInfo &prev_symbol_info) {
527 TraceDumper::SymbolInfo symbol_info;
528 symbol_info.exe_ctx = exe_ctx;
529 symbol_info.address.SetLoadAddress(load_address, exe_ctx.GetTargetPtr());
530 symbol_info.sc =
531 CalculateSymbolContext(symbol_info.address, prev_symbol_info.sc);
532 std::tie(symbol_info.disassembler, symbol_info.instruction) =
533 CalculateDisass(symbol_info, prev_symbol_info, exe_ctx);
534 return symbol_info;
535}
536
537std::optional<lldb::user_id_t> TraceDumper::DumpInstructions(size_t count) {
538 ThreadSP thread_sp = m_cursor_sp->GetExecutionContextRef().GetThreadSP();
539
540 SymbolInfo prev_symbol_info;
541 std::optional<lldb::user_id_t> last_id;
542
543 ExecutionContext exe_ctx;
544 thread_sp->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx);
545
546 for (size_t insn_seen = 0; insn_seen < count && m_cursor_sp->HasValue();
547 m_cursor_sp->Next()) {
548
549 last_id = m_cursor_sp->GetId();
551
552 if (m_cursor_sp->IsEvent() && m_options.show_events) {
553 item.event = m_cursor_sp->GetEventType();
554 switch (*item.event) {
556 item.cpu_id = m_cursor_sp->GetCPU();
557 break;
559 item.hw_clock = m_cursor_sp->GetHWClock();
560 break;
563 break;
565 item.sync_point_metadata = m_cursor_sp->GetSyncPointMetadata();
566 break;
567 }
568 m_writer_up->TraceItem(item);
569 } else if (m_cursor_sp->IsError()) {
570 item.error = m_cursor_sp->GetError();
571 m_writer_up->TraceItem(item);
572 } else if (m_cursor_sp->IsInstruction() && !m_options.only_events) {
573 insn_seen++;
574 item.load_address = m_cursor_sp->GetLoadAddress();
575
576 if (!m_options.raw) {
577 SymbolInfo symbol_info =
578 CalculateSymbolInfo(exe_ctx, item.load_address, prev_symbol_info);
579 item.prev_symbol_info = prev_symbol_info;
580 item.symbol_info = symbol_info;
581 prev_symbol_info = symbol_info;
582 }
583 m_writer_up->TraceItem(item);
584 }
585 }
586 if (!m_cursor_sp->HasValue())
587 m_writer_up->NoMoreData();
588 return last_id;
589}
590
592 const TraceCursorSP &cursor_sp,
593 const TraceDumper::SymbolInfo &symbol_info) {
594 m_last_insn_id = cursor_sp->GetId();
595 m_last_symbol_info = symbol_info;
596}
597
602
607
609 std::function<void(const FunctionCall &function_call)> callback) const {
610 if (m_nested_call)
611 callback(*m_nested_call);
612}
613
618
621 const TraceCursorSP &cursor_sp,
622 const TraceDumper::SymbolInfo &symbol_info) {
623 m_nested_call = std::make_unique<FunctionCall>(cursor_sp, symbol_info);
624 m_nested_call->SetParentCall(m_owning_call);
625 return *m_nested_call;
626}
627
633
638
643
645 const TraceCursorSP &cursor_sp,
646 const TraceDumper::SymbolInfo &symbol_info) {
647 m_is_error = cursor_sp->IsError();
648 AppendSegment(cursor_sp, symbol_info);
649}
650
652 const TraceCursorSP &cursor_sp,
653 const TraceDumper::SymbolInfo &symbol_info) {
654 m_traced_segments.emplace_back(cursor_sp, symbol_info, *this);
655}
656
659 return m_traced_segments.back().GetLastInstructionSymbolInfo();
660}
661
663
664const std::deque<TraceDumper::FunctionCall::TracedSegment> &
668
673
674const std::optional<TraceDumper::FunctionCall::UntracedPrefixSegment> &
678
680 TraceDumper::FunctionCallUP &&nested_call) {
681 m_untraced_prefix_segment.emplace(std::move(nested_call));
682}
683
687
692
693/// Given an instruction that happens after a return, find the ancestor function
694/// call that owns it. If this ancestor doesn't exist, create a new ancestor and
695/// make it the root of the tree.
696///
697/// \param[in] last_function_call
698/// The function call that performs the return.
699///
700/// \param[in] symbol_info
701/// The symbol information of the instruction after the return.
702///
703/// \param[in] cursor_sp
704/// The cursor pointing to the instruction after the return.
705///
706/// \param[in,out] roots
707/// The object owning the roots. It might be modified if a new root needs to
708/// be created.
709///
710/// \return
711/// A reference to the function call that owns the new instruction
713 TraceDumper::FunctionCall &last_function_call,
714 const TraceDumper::SymbolInfo &symbol_info, const TraceCursorSP &cursor_sp,
715 std::vector<TraceDumper::FunctionCallUP> &roots) {
716
717 // We omit the current node because we can't return to itself.
718 TraceDumper::FunctionCall *ancestor = last_function_call.GetParentCall();
719
720 for (; ancestor; ancestor = ancestor->GetParentCall()) {
721 // This loop traverses the tree until it finds a call that we can return to.
722 if (IsSameInstructionSymbolContext(ancestor->GetSymbolInfo(), symbol_info,
723 /*check_source_line_info=*/false)) {
724 // We returned to this symbol, so we are assuming we are returning there
725 // Note: If this is not robust enough, we should actually check if we
726 // returning to the instruction that follows the last instruction from
727 // that call, as that's the behavior of CALL instructions.
728 ancestor->AppendSegment(cursor_sp, symbol_info);
729 return *ancestor;
730 }
731 }
732
733 // We didn't find the call we were looking for, so we now create a synthetic
734 // one that will contain the new instruction in its first traced segment.
736 std::make_unique<TraceDumper::FunctionCall>(cursor_sp, symbol_info);
737 // This new root will own the previous root through an untraced prefix segment.
738 new_root->SetUntracedPrefixSegment(std::move(roots.back()));
739 roots.pop_back();
740 // We update the roots container to point to the new root
741 roots.emplace_back(std::move(new_root));
742 return *roots.back();
743}
744
745/// Append an instruction to a function call forest. The new instruction might
746/// be appended to the current segment, to a new nest call, or return to an
747/// ancestor call.
748///
749/// \param[in] exe_ctx
750/// The exeuction context of the traced thread.
751///
752/// \param[in] last_function_call
753/// The chronologically most recent function call before the new instruction.
754///
755/// \param[in] prev_symbol_info
756/// The symbol information of the previous instruction in the trace.
757///
758/// \param[in] symbol_info
759/// The symbol information of the new instruction.
760///
761/// \param[in] cursor_sp
762/// The cursor pointing to the new instruction.
763///
764/// \param[in,out] roots
765/// The object owning the roots. It might be modified if a new root needs to
766/// be created.
767///
768/// \return
769/// A reference to the function call that owns the new instruction.
771 const ExecutionContext &exe_ctx,
772 TraceDumper::FunctionCall *last_function_call,
773 const TraceDumper::SymbolInfo &prev_symbol_info,
774 const TraceDumper::SymbolInfo &symbol_info, const TraceCursorSP &cursor_sp,
775 std::vector<TraceDumper::FunctionCallUP> &roots) {
776 if (!last_function_call || last_function_call->IsError()) {
777 // We create a brand new root
778 roots.emplace_back(
779 std::make_unique<TraceDumper::FunctionCall>(cursor_sp, symbol_info));
780 return *roots.back();
781 }
782
784 if (symbol_info.sc.GetAddressRange(
785 eSymbolContextBlock | eSymbolContextFunction | eSymbolContextSymbol,
786 0, /*inline_block_range*/ true, range)) {
787 if (range.GetBaseAddress() == symbol_info.address) {
788 // Our instruction is the first instruction of a function. This has
789 // to be a call. This should also identify if a trampoline or the linker
790 // is making a call using a non-CALL instruction.
791 return last_function_call->GetLastTracedSegment().CreateNestedCall(
792 cursor_sp, symbol_info);
793 }
794 }
795 if (IsSameInstructionSymbolContext(prev_symbol_info, symbol_info,
796 /*check_source_line_info=*/false)) {
797 // We are still in the same function. This can't be a call because otherwise
798 // we would be in the first instruction of the symbol.
799 last_function_call->GetLastTracedSegment().AppendInsn(cursor_sp,
800 symbol_info);
801 return *last_function_call;
802 }
803 // Now we are in a different symbol. Let's see if this is a return or a
804 // call
805 const InstructionSP &insn = last_function_call->GetLastTracedSegment()
809 insn ? insn->GetControlFlowKind(&exe_ctx)
811
812 switch (insn_kind) {
815 // This is a regular call
816 return last_function_call->GetLastTracedSegment().CreateNestedCall(
817 cursor_sp, symbol_info);
818 }
821 // We should have caught most trampolines and linker functions earlier, so
822 // let's assume this is a regular return.
824 *last_function_call, symbol_info, cursor_sp, roots);
825 }
826 default:
827 // we changed symbols not using a call or return and we are not in the
828 // beginning of a symbol, so this should be something very artificial
829 // or maybe a jump to some label in the middle of it section.
830
831 // We first check if it's a return from an inline method
832 if (prev_symbol_info.sc.block &&
833 prev_symbol_info.sc.block->GetContainingInlinedBlock()) {
835 *last_function_call, symbol_info, cursor_sp, roots);
836 }
837 // Now We assume it's a call. We should revisit this in the future.
838 // Ideally we should be able to decide whether to create a new tree,
839 // or go deeper or higher in the stack.
840 return last_function_call->GetLastTracedSegment().CreateNestedCall(
841 cursor_sp, symbol_info);
842 }
843}
844
845/// Append an error to a function call forest. The new error might be appended
846/// to the current segment if it contains errors or will create a new root.
847///
848/// \param[in] last_function_call
849/// The chronologically most recent function call before the new error.
850///
851/// \param[in] cursor_sp
852/// The cursor pointing to the new error.
853///
854/// \param[in,out] roots
855/// The object owning the roots. It might be modified if a new root needs to
856/// be created.
857///
858/// \return
859/// A reference to the function call that owns the new error.
861 TraceDumper::FunctionCall *last_function_call, TraceCursorSP &cursor_sp,
862 std::vector<TraceDumper::FunctionCallUP> &roots) {
863 if (last_function_call && last_function_call->IsError()) {
864 last_function_call->GetLastTracedSegment().AppendInsn(
865 cursor_sp, TraceDumper::SymbolInfo{});
866 return *last_function_call;
867 } else {
868 roots.emplace_back(std::make_unique<TraceDumper::FunctionCall>(
869 cursor_sp, TraceDumper::SymbolInfo{}));
870 return *roots.back();
871 }
872}
873
874static std::vector<TraceDumper::FunctionCallUP>
876 const ExecutionContext &exe_ctx) {
877
878 std::vector<TraceDumper::FunctionCallUP> roots;
879 TraceDumper::SymbolInfo prev_symbol_info;
880
881 TraceDumper::FunctionCall *last_function_call = nullptr;
882
883 for (; cursor_sp->HasValue(); cursor_sp->Next()) {
884 if (cursor_sp->IsError()) {
885 last_function_call = &AppendErrorToFunctionCallForest(last_function_call,
886 cursor_sp, roots);
887 prev_symbol_info = {};
888 } else if (cursor_sp->IsInstruction()) {
890 exe_ctx, cursor_sp->GetLoadAddress(), prev_symbol_info);
891
892 last_function_call = &AppendInstructionToFunctionCallForest(
893 exe_ctx, last_function_call, prev_symbol_info, symbol_info, cursor_sp,
894 roots);
895 prev_symbol_info = symbol_info;
896 } else if (cursor_sp->GetEventType() == eTraceEventCPUChanged) {
897 // TODO: In case of a CPU change, we create a new root because we haven't
898 // investigated yet if a call tree can safely continue or if interrupts
899 // could have polluted the original call tree.
900 last_function_call = nullptr;
901 prev_symbol_info = {};
902 }
903 }
904
905 return roots;
906}
907
909 ThreadSP thread_sp = m_cursor_sp->GetExecutionContextRef().GetThreadSP();
910 ExecutionContext exe_ctx;
911 thread_sp->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx);
912
913 m_writer_up->FunctionCallForest(
915}
static std::vector< TraceDumper::FunctionCallUP > CreateFunctionCallForest(TraceCursorSP &cursor_sp, const ExecutionContext &exe_ctx)
static bool IsLineEntryValid(const LineEntry &line_entry)
static bool IsSameInstructionSymbolContext(const TraceDumper::SymbolInfo &prev_insn, const TraceDumper::SymbolInfo &insn, bool check_source_line_info=true)
Compare the symbol contexts of the provided SymbolInfo objects.
static std::unique_ptr< TraceDumper::OutputWriter > CreateWriter(Stream &s, const TraceDumperOptions &options, Thread &thread)
static const char * GetModuleName(const SymbolContext &sc)
static TraceDumper::FunctionCall & AppendInstructionToFunctionCallForest(const ExecutionContext &exe_ctx, TraceDumper::FunctionCall *last_function_call, const TraceDumper::SymbolInfo &prev_symbol_info, const TraceDumper::SymbolInfo &symbol_info, const TraceCursorSP &cursor_sp, std::vector< TraceDumper::FunctionCallUP > &roots)
Append an instruction to a function call forest.
static TraceDumper::SymbolInfo CalculateSymbolInfo(const ExecutionContext &exe_ctx, lldb::addr_t load_address, const TraceDumper::SymbolInfo &prev_symbol_info)
static std::tuple< DisassemblerSP, InstructionSP > CalculateDisass(const TraceDumper::SymbolInfo &symbol_info, const TraceDumper::SymbolInfo &prev_symbol_info, const ExecutionContext &exe_ctx)
Find the disassembler for the given address reusing the previous instruction's disassembler when poss...
static TraceDumper::FunctionCall & AppendReturnedInstructionToFunctionCallForest(TraceDumper::FunctionCall &last_function_call, const TraceDumper::SymbolInfo &symbol_info, const TraceCursorSP &cursor_sp, std::vector< TraceDumper::FunctionCallUP > &roots)
Given an instruction that happens after a return, find the ancestor function call that owns it.
static bool FileLineAndColumnMatches(const LineEntry &a, const LineEntry &b)
static SymbolContext CalculateSymbolContext(const Address &address, const SymbolContext &prev_symbol_context)
Find the symbol context for the given address reusing the previous instruction's symbol context when ...
static std::optional< const char * > ToOptionalString(const char *s)
TraceDumper::FunctionCall & AppendErrorToFunctionCallForest(TraceDumper::FunctionCall *last_function_call, TraceCursorSP &cursor_sp, std::vector< TraceDumper::FunctionCallUP > &roots)
Append an error to a function call forest.
void FunctionCallForest(const std::vector< TraceDumper::FunctionCallUP > &forest) override
Dump a function call forest.
void TraceItem(const TraceDumper::TraceItem &item) override
Dump a trace item (instruction, error or event).
bool m_was_prev_instruction_an_error
TraceDumperOptions m_options
OutputWriterCLI(Stream &s, const TraceDumperOptions &options, Thread &thread)
void DumpSegmentContext(const TraceDumper::FunctionCall::TracedSegment &segment)
void DumpFunctionCallTree(const TraceDumper::FunctionCall &function_call)
void NoMoreData() override
Notify this writer that the cursor ran out of data.
void DumpUntracedContext(const TraceDumper::FunctionCall &function_call)
void DumpInstruction(const TraceDumper::TraceItem &item)
void DumpEvent(const TraceDumper::TraceItem &item)
void TraceItem(const TraceDumper::TraceItem &item) override
Dump a trace item (instruction, error or event).
OutputWriterJSON(Stream &s, const TraceDumperOptions &options)
void DumpFunctionCallTree(const TraceDumper::FunctionCall &function_call)
json::OStream m_j
void FunctionCallForest(const std::vector< TraceDumper::FunctionCallUP > &forest) override
Dump a function call forest.
TraceDumperOptions m_options
A section + offset based address range class.
Address & GetBaseAddress()
Get accessor for the base address of the range.
bool Contains(const Address &so_addr) const
Check if a section offset address is contained in this range.
A section + offset based address class.
Definition Address.h:62
bool SetLoadAddress(lldb::addr_t load_addr, Target *target, bool allow_section_end=false)
Set the address to represent load_addr.
Definition Address.cpp:1034
uint32_t CalculateSymbolContext(SymbolContext *sc, lldb::SymbolContextItem resolve_scope=lldb::eSymbolContextEverything) const
Reconstruct a symbol context from an address.
Definition Address.cpp:819
An architecture specification class.
Definition ArchSpec.h:32
uint32_t GetMaximumOpcodeByteSize() const
Definition ArchSpec.cpp:929
A class that describes a single lexical block.
Definition Block.h:41
Block * GetContainingInlinedBlock()
Get the inlined block that contains this block.
Definition Block.cpp:206
const char * AsCString(const char *value_if_empty=nullptr) const
Get the string value as a C string.
static lldb::DisassemblerSP DisassembleRange(const ArchSpec &arch, const char *plugin_name, const char *flavor, const char *cpu, const char *features, Target &target, llvm::ArrayRef< AddressRange > disasm_ranges, bool force_live_memory=false)
"lldb/Target/ExecutionContext.h" A class that contains an execution context.
Target * GetTargetPtr() const
Returns a pointer to the target object.
Target & GetTargetRef() const
Returns a reference to the target object.
lldb::DisassemblerSP GetInstructions(const ExecutionContext &exe_ctx, const char *flavor, bool force_live_memory=false)
Definition Function.cpp:469
static const char * GetNameForInstructionControlFlowKind(lldb::InstructionControlFlowKind instruction_control_flow_kind)
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.
Function * function
The Function for a given query.
ConstString GetFunctionName(Mangled::NamePreference preference=Mangled::ePreferDemangled) const
Find a name of the innermost function for the symbol context.
Block * block
The Block for a given query.
lldb::ModuleSP module_sp
The Module for a given query.
bool DumpStopContext(Stream *s, ExecutionContextScope *exe_scope, const Address &so_addr, bool show_fullpaths, bool show_module, bool show_inlined_frames, bool show_function_arguments, bool show_function_name, bool show_function_display_name=false, std::optional< Stream::HighlightSettings > settings=std::nullopt) const
Dump the stop context in this object to a Stream.
bool GetAddressRange(uint32_t scope, uint32_t range_idx, bool use_inline_block_range, AddressRange &range) const
Get the address range contained within a symbol context.
Symbol * symbol
The Symbol for a given query.
LineEntry line_entry
The LineEntry for a given query.
const ArchSpec & GetArchitecture() const
Definition Target.h:1182
static const char * EventKindToString(lldb::TraceEvent event_kind)
FunctionCall & CreateNestedCall(const lldb::TraceCursorSP &cursor_sp, const SymbolInfo &symbol_info)
Create a nested call at the end of this segment.
SymbolInfo m_first_symbol_info
The symbol information of the delimiting instructions.
FunctionCallUP m_nested_call
An optional nested call starting at the end of this segment.
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.
lldb::user_id_t m_first_insn_id
Delimiting instruction IDs taken chronologically.
const std::deque< TracedSegment > & GetTracedSegments() const
void SetParentCall(FunctionCall &parent_call)
FunctionCall * m_parent_call
The parent call, which might be null.
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.
const SymbolInfo & GetSymbolInfo() const
std::deque< TracedSegment > m_traced_segments
The traced segments in order.
FunctionCall(const lldb::TraceCursorSP &cursor_sp, const SymbolInfo &symbol_info)
Create a new function call given an instruction.
bool m_is_error
Whether this call represents a list of consecutive errors.
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.
std::optional< lldb::user_id_t > DumpInstructions(size_t count)
Dump count instructions of the thread trace starting at the current cursor position.
TraceDumper(lldb::TraceCursorSP cursor_sp, Stream &s, const TraceDumperOptions &options)
Create a instruction dumper for the cursor.
std::unique_ptr< FunctionCall > FunctionCallUP
lldb::TraceCursorSP m_cursor_sp
std::unique_ptr< OutputWriter > m_writer_up
TraceDumperOptions m_options
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::Thread > ThreadSP
@ eTraceEventSyncPoint
The underlying tracing technology emitted a synchronization event used by trace processors.
@ eTraceEventCPUChanged
Event due to CPU change for a thread.
@ eTraceEventHWClockTick
Event due to a CPU HW clock tick.
@ eTraceEventDisabledHW
Tracing was disable for some time due to a hardware trigger.
@ eTraceEventDisabledSW
Tracing was disabled for some time due to a software trigger.
std::shared_ptr< lldb_private::Instruction > InstructionSP
std::shared_ptr< lldb_private::Disassembler > DisassemblerSP
InstructionControlFlowKind
Architecture-agnostic categorization of instructions for traversing the control flow of a trace.
@ eInstructionControlFlowKindReturn
The instruction is a near (function) return.
@ eInstructionControlFlowKindOther
The instruction is something not listed below, i.e.
@ eInstructionControlFlowKindFarCall
The instruction is a call-like far transfer.
@ eInstructionControlFlowKindFarReturn
The instruction is a return-like far transfer.
@ eInstructionControlFlowKindCall
The instruction is a near (function) call.
@ eTraceCursorSeekTypeEnd
The end of the trace, i.e the most recent item.
@ eTraceCursorSeekTypeBeginning
The beginning of the trace, i.e the oldest item.
std::shared_ptr< lldb_private::TraceCursor > TraceCursorSP
uint64_t user_id_t
Definition lldb-types.h:82
uint64_t addr_t
Definition lldb-types.h:80
A line table entry class.
Definition LineEntry.h:21
uint16_t column
The column number of the source line, or zero if there is no column information.
Definition LineEntry.h:155
bool IsValid() const
Check if a line entry object is valid.
Definition LineEntry.cpp:35
uint32_t line
The source line number, or LLDB_INVALID_LINE_NUMBER if there is no line number information.
Definition LineEntry.h:151
const FileSpec & GetFile() const
Helper to access the file.
Definition LineEntry.h:134
Class that holds the configuration used by TraceDumper for traversing and dumping instructions.
Definition TraceDumper.h:21
bool json
Dump in json format.
Definition TraceDumper.h:30
Helper struct that holds symbol, disassembly and address information of an instruction.
Definition TraceDumper.h:55
lldb_private::ExecutionContext exe_ctx
Definition TraceDumper.h:60
Helper struct that holds all the information we know about a trace item.
Definition TraceDumper.h:64
std::optional< uint64_t > hw_clock
Definition TraceDumper.h:68
std::optional< SymbolInfo > symbol_info
Definition TraceDumper.h:72
std::optional< std::string > sync_point_metadata
Definition TraceDumper.h:69
std::optional< lldb::cpu_id_t > cpu_id
Definition TraceDumper.h:74
std::optional< llvm::StringRef > error
Definition TraceDumper.h:70
std::optional< SymbolInfo > prev_symbol_info
Definition TraceDumper.h:73
std::optional< lldb::TraceEvent > event
Definition TraceDumper.h:71
std::optional< double > timestamp
Definition TraceDumper.h:67