Index: lldb/include/lldb/Target/TraceDumper.h =================================================================== --- lldb/include/lldb/Target/TraceDumper.h +++ lldb/include/lldb/Target/TraceDumper.h @@ -46,6 +46,9 @@ llvm::Optional skip = llvm::None; }; +/// Simpler dumper options for TraceCallGraphDumper. +enum class PrintStyle { Normal, JSON, PrettyJSON }; + /// Class used to dump the instructions of a \a TraceCursor using its current /// state and granularity. class TraceDumper { @@ -119,7 +122,7 @@ private: /// Create a trace item for the current position without symbol information. - TraceItem CreatRawTraceItem(); + TraceItem CreateRawTraceItem(); lldb::TraceCursorSP m_cursor_sp; TraceDumperOptions m_options; @@ -130,34 +133,27 @@ class TraceCallGraphDumper { public: TraceCallGraphDumper(const lldb::TraceCursorSP &cursor_sp, - const lldb::TraceCallGraphSP &call_graph_sp, Stream &s); + const lldb::TraceCallGraphSP &call_graph_sp, Stream &s, + const PrintStyle &print_style); void Dump(); -private: - void DumpErrorSegment(TraceErrorSegment &error_segment); - - void DumpCall(TraceCall &call); - - void DumpFunctionCall(TraceCallFunction &call); - - void DumpSymbolCall(TraceCallSymbol &call); - - void DumpInlinedFunctionCall(TraceCallInlinedFunction &call); + /// Interface used to abstract away the format in which the call graph + /// will be dumped. + class OutputWriter { + public: + virtual ~OutputWriter() = default; - void DumpSectionCall(TraceCallSection &call); + /// Dump a call graph (call tree or error) + virtual void Dump() = 0; - void DumpUnknownRegion(TraceCallUnknownRegion &call); + private: + virtual void DumpErrorSegment(TraceErrorSegment &error_segment) = 0; - void DumpCallWrapper( - std::function call_description_dumper, - std::function insn_description_dumper, - TraceCall &call_wrapper); + virtual void DumpCall(TraceCall &call); + }; - lldb::TraceCursorSP m_cursor_sp; - lldb::TraceCallGraphSP m_graph_sp; - Stream &m_s; - ExecutionContext m_exe_ctx; + std::unique_ptr m_writer_up; }; } // namespace lldb_private Index: lldb/source/Commands/CommandObjectThread.cpp =================================================================== --- lldb/source/Commands/CommandObjectThread.cpp +++ lldb/source/Commands/CommandObjectThread.cpp @@ -2116,8 +2116,6 @@ class CommandObjectTraceDumpFunctionCalls : public CommandObjectParsed { public: - enum class PrintStyle { Normal, JSON, PrettyJSON }; - class CommandOptions : public Options { public: CommandOptions() { OptionParsingStarting(nullptr); } @@ -2234,7 +2232,8 @@ File::eOpenOptionTruncate); } TraceCallGraphDumper(*cursor_sp, *graph_sp, - out_file ? *out_file : result.GetOutputStream()) + out_file ? *out_file : result.GetOutputStream(), + m_options.m_options.print_style) .Dump(); return true; Index: lldb/source/Target/TraceDumper.cpp =================================================================== --- lldb/source/Target/TraceDumper.cpp +++ lldb/source/Target/TraceDumper.cpp @@ -340,7 +340,7 @@ } } -TraceDumper::TraceItem TraceDumper::CreatRawTraceItem() { +TraceDumper::TraceItem TraceDumper::CreateRawTraceItem() { TraceItem item = {}; item.id = m_cursor_sp->GetId(); @@ -428,7 +428,7 @@ m_cursor_sp->Next()) { last_id = m_cursor_sp->GetId(); - TraceItem item = CreatRawTraceItem(); + TraceItem item = CreateRawTraceItem(); if (m_cursor_sp->IsEvent() && m_options.show_events) { item.event = m_cursor_sp->GetEventType(); @@ -469,246 +469,390 @@ return last_id; } -TraceCallGraphDumper::TraceCallGraphDumper( - const TraceCursorSP &cursor_sp, const TraceCallGraphSP &call_graph_sp, - Stream &s) - : m_cursor_sp(cursor_sp), m_graph_sp(call_graph_sp), m_s(s), - m_exe_ctx(cursor_sp->GetExecutionContextRef()) {} - // Overload pattern-based syntax sugar for easier variant matching. template struct match : Ts... { using Ts::operator()...; }; template match(Ts...) -> match; -/// Utility for indenting and writing to a stream in one line. -static Stream &WithIndent(Stream &s) { - s.Indent(); - return s; -} +class GraphOutputWriterCLI : public TraceCallGraphDumper::OutputWriter { +public: + GraphOutputWriterCLI(const TraceCursorSP &cursor_sp, + const TraceCallGraphSP &call_graph_sp, Stream &s) + : m_cursor_sp(cursor_sp), m_graph_sp(call_graph_sp), + m_exe_ctx(cursor_sp->GetExecutionContextRef()), m_s(s) {}; + + /// Utility for indenting and writing to a stream in one line. + Stream &WithIndent() { + m_s.Indent(); + return m_s; + } -/// RAII-based utility for handling indentation and de-indentation of a stream. -struct AddIndent { - AddIndent(Stream &s) : m_s(s) { m_s.IndentMore(); } - ~AddIndent() { m_s.IndentLess(); } + /// RAII-based utility for handling indentation and de-indentation of a stream. + struct AddIndent { + AddIndent(Stream &s) : m_s(s) { m_s.IndentMore(); } + ~AddIndent() { m_s.IndentLess(); } -private: - Stream &m_s; -}; + private: + Stream &m_s; + }; -void TraceCallGraphDumper::Dump() { - Thread &thread = m_exe_ctx.GetThreadRef(); - m_s.Format("thread #{0}: tid = {1}\n", thread.GetIndexID(), thread.GetID()); + void Dump() override { + Thread &thread = m_exe_ctx.GetThreadRef(); + llvm::ArrayRef trees = m_graph_sp->GetTrees(); + m_s.Format("thread #{0}: tid = {1}\n", thread.GetIndexID(), thread.GetID()); - llvm::ArrayRef trees = m_graph_sp->GetTrees(); - AddIndent _(m_s); - for (size_t i = 0; i < trees.size(); i++) { - m_s << "\n"; - std::visit( - match{[&](TraceErrorSegment &errors) { - WithIndent(m_s).Format("\n", i); - DumpErrorSegment(errors); - }, - [&](TraceInstructionCallTree &tree) { - WithIndent(m_s).Format("\n", i); - DumpCall(tree.GetRootCall()); - }}, - *trees[i]); + AddIndent _(m_s); + for (size_t i = 0; i < trees.size(); i++) { + m_s << "\n"; + std::visit( + match{[&](TraceErrorSegment &errors) { + WithIndent().Format("\n", i); + DumpErrorSegment(errors); + }, + [&](TraceInstructionCallTree &tree) { + WithIndent().Format("\n", i); + AddIndent _(m_s); + DumpCall(tree.GetRootCall()); + }}, + *trees[i]); + } } -} -void TraceCallGraphDumper::DumpErrorSegment(TraceErrorSegment &error_segment) { - AddIndent _(m_s); - - auto print_error = [&](lldb::user_id_t error_id) { - m_cursor_sp->GoToId(error_id); - WithIndent(m_s).Format("|{0}: {1}\n", error_id, m_cursor_sp->GetError()); - }; +private: + static const char *ToDisplayString(const ModuleSP &module_sp) { + if (const char *str = module_sp->GetFileSpec().GetFilename().AsCString()) + return str; + return "(none)"; + } - auto [from, to] = error_segment.GetRange(); - print_error(from); - if (from != to) { - uint64_t remaining = error_segment.GetErrorCount() - 2; - if (remaining >= 2) { - WithIndent(m_s).Format("| ... {0} error{1}\n", remaining, - remaining == 1 ? "" : "s"); + void DumpErrorSegment(TraceErrorSegment &error_segment) override { + AddIndent _(m_s); + + auto print_error = [&](lldb::user_id_t error_id) { + m_cursor_sp->GoToId(error_id); + WithIndent().Format("|{0}: {1}\n", error_id, m_cursor_sp->GetError()); + }; + + auto [from, to] = error_segment.GetRange(); + print_error(from); + if (from != to) { + uint64_t remaining = error_segment.GetErrorCount() - 2; + if (remaining >= 2) { + WithIndent().Format("| ... {0} error{1}\n", remaining, + remaining == 1 ? "" : "s"); + } + print_error(to); } - print_error(to); } -} -static const char *ToDisplayString(const ModuleSP &module_sp) { - if (const char *str = module_sp->GetFileSpec().GetFilename().AsCString()) - return str; - return "(none)"; -} - -void TraceCallGraphDumper::DumpCallWrapper( - std::function call_description_dumper, - std::function insn_description_dumper, - TraceCall &call_wrapper) { + void DumpCall(TraceCall &call) override { + std::visit( + match{[&](TraceCallInlinedFunction &call) { + DumpInlinedFunctionCall(call); + }, + [&](TraceCallFunction &call) { DumpFunctionCall(call); }, + [&](TraceCallSymbol &call) { DumpSymbolCall(call); }, + [&](TraceCallSection &call) { DumpSectionCall(call); }, + [&](TraceCallUnknownRegion &call) { DumpUnknownRegion(call); }}, + call); + } - WithIndent(m_s) << "-> "; - call_description_dumper(); - m_s << "\n"; + void DumpCallWrapper( + std::function call_description_dumper, + std::function insn_description_dumper, + TraceCall &call_wrapper) { - auto dump_insn = [&](lldb::user_id_t id) { - m_cursor_sp->GoToId(id); - WithIndent(m_s).Format(" |{0}: {1:x+16}", id, - m_cursor_sp->GetLoadAddress()); - insn_description_dumper(*m_cursor_sp); + WithIndent() << "-> "; + call_description_dumper(); m_s << "\n"; - }; - std::visit( - [&](auto &&call) { - for (const TraceCallSegmentUP &segment_up : call.GetSegments()) { - AddIndent _(m_s); - if (auto range = segment_up->GetRange()) { - auto [from, to] = *range; - dump_insn(from); - if (from != to) { - uint64_t mid_instructions = - segment_up->GetExclusiveInsnCount() - 2; - if (mid_instructions >= 1) { - WithIndent(m_s).Format(" |... {0} instruction{1}\n", - mid_instructions, - mid_instructions == 1 ? "" : "s"); + auto dump_insn = [&](lldb::user_id_t id) { + m_cursor_sp->GoToId(id); + WithIndent().Format(" |{0}: {1:x+16}", id, + m_cursor_sp->GetLoadAddress()); + insn_description_dumper(*m_cursor_sp); + m_s << "\n"; + }; + + std::visit( + [&](auto &&call) { + for (const TraceCallSegmentUP &segment_up : call.GetSegments()) { + AddIndent _(m_s); + if (auto range = segment_up->GetRange()) { + auto [from, to] = *range; + dump_insn(from); + if (from != to) { + uint64_t mid_instructions = + segment_up->GetExclusiveInsnCount() - 2; + if (mid_instructions >= 1) { + WithIndent().Format(" |... {0} instruction{1}\n", + mid_instructions, + mid_instructions == 1 ? "" : "s"); + } + dump_insn(to); } - dump_insn(to); + } + if (TraceCall *child_call = segment_up->GetChildCall()) { + AddIndent _(m_s); + DumpCall(*child_call); } } - if (TraceCall *child_call = segment_up->GetChildCall()) { - DumpCall(*child_call); - } - } - }, - call_wrapper); -} + }, + call_wrapper); + } -void TraceCallGraphDumper::DumpSectionCall(TraceCallSection &call) { - Target &target = m_exe_ctx.GetTargetRef(); - lldb::addr_t inlined_function_base_address = - call.GetSection()->GetLoadBaseAddress(&target); - - DumpCallWrapper([&] { call.GetSection()->DumpName(m_s.AsRawOstream()); }, - [&](const TraceCursor &insn) { - if (inlined_function_base_address != LLDB_INVALID_ADDRESS) - m_s.Format(" <+{0}>", insn.GetLoadAddress() - - inlined_function_base_address); - }, - call.GetWrappingCall()); -} + void DumpSectionCall(TraceCallSection &call) { + Target &target = m_exe_ctx.GetTargetRef(); + lldb::addr_t inlined_function_base_address = + call.GetSection()->GetLoadBaseAddress(&target); + + DumpCallWrapper([&] { call.GetSection()->DumpName(m_s.AsRawOstream()); }, + [&](const TraceCursor &insn) { + if (inlined_function_base_address != LLDB_INVALID_ADDRESS) + m_s.Format(" <+{0}>", insn.GetLoadAddress() - + inlined_function_base_address); + }, + call.GetWrappingCall()); + } -void TraceCallGraphDumper::DumpInlinedFunctionCall( - TraceCallInlinedFunction &call) { - Block &inlined_block = call.GetInlinedBlock(); - Target &target = m_exe_ctx.GetTargetRef(); - - lldb::addr_t inlined_function_base_address = LLDB_INVALID_ADDRESS; - if (AddressRange range; inlined_block.GetRangeAtIndex(0, range)) - inlined_function_base_address = - range.GetBaseAddress().GetLoadAddress(&target); - - DumpCallWrapper( - [&] { - m_s.Format("[inlined] {0}`{1}", - ToDisplayString(call.GetOwningFunctionCall() - .GetFunction() - .CalculateSymbolContextModule()), - inlined_block.GetInlinedFunctionInfo()->GetName()); - }, - [&](const TraceCursor &insn) { - lldb::addr_t load_address = insn.GetLoadAddress(); - AddressRange range; - if (inlined_function_base_address != LLDB_INVALID_ADDRESS) - m_s.Format(" <+{0}>", load_address - inlined_function_base_address); - - Address addr; - if (target.ResolveLoadAddress(load_address, addr)) { - LineEntry line_entry; - addr.CalculateSymbolContextLineEntry(line_entry); - - if (line_entry.IsValid()) { - m_s << " at "; - line_entry.DumpStopContext(&m_s, /*show_fullpaths*/ false); + void DumpInlinedFunctionCall( + TraceCallInlinedFunction &call) { + Block &inlined_block = call.GetInlinedBlock(); + Target &target = m_exe_ctx.GetTargetRef(); + + lldb::addr_t inlined_function_base_address = LLDB_INVALID_ADDRESS; + if (AddressRange range; inlined_block.GetRangeAtIndex(0, range)) + inlined_function_base_address = + range.GetBaseAddress().GetLoadAddress(&target); + + DumpCallWrapper( + [&] { + m_s.Format("[inlined] {0}`{1}", + ToDisplayString(call.GetOwningFunctionCall() + .GetFunction() + .CalculateSymbolContextModule()), + inlined_block.GetInlinedFunctionInfo()->GetName()); + }, + [&](const TraceCursor &insn) { + lldb::addr_t load_address = insn.GetLoadAddress(); + AddressRange range; + if (inlined_function_base_address != LLDB_INVALID_ADDRESS) + m_s.Format(" <+{0}>", load_address - inlined_function_base_address); + + Address addr; + if (target.ResolveLoadAddress(load_address, addr)) { + LineEntry line_entry; + addr.CalculateSymbolContextLineEntry(line_entry); + + if (line_entry.IsValid()) { + m_s << " at "; + line_entry.DumpStopContext(&m_s, false); + } } - } - }, - call.GetWrappingCall()); -} + }, + call.GetWrappingCall()); + } -void TraceCallGraphDumper::DumpFunctionCall(TraceCallFunction &call) { - Function &function = call.GetFunction(); - Target &target = m_exe_ctx.GetTargetRef(); - lldb::addr_t function_base_address = - function.GetAddressRange().GetBaseAddress().GetLoadAddress(&target); - - DumpCallWrapper( - [&] { - m_s.Format("{0}`{1}", - ToDisplayString(function.CalculateSymbolContextModule()), - function.GetName()); - }, - [&](const TraceCursor &insn) { - lldb::addr_t load_address = insn.GetLoadAddress(); - if (function_base_address != LLDB_INVALID_ADDRESS) - m_s.Format(" <+{0}>", load_address - function_base_address); - - Address addr; - if (target.ResolveLoadAddress(load_address, addr)) { - LineEntry line_entry; - addr.CalculateSymbolContextLineEntry(line_entry); - if (line_entry.IsValid()) { - m_s << " at "; - line_entry.DumpStopContext(&m_s, /*show_fullpaths*/ false); + void DumpFunctionCall(TraceCallFunction &call) { + Function &function = call.GetFunction(); + Target &target = m_exe_ctx.GetTargetRef(); + lldb::addr_t function_base_address = + function.GetAddressRange().GetBaseAddress().GetLoadAddress(&target); + + DumpCallWrapper( + [&] { + m_s.Format("{0}`{1}", + ToDisplayString(function.CalculateSymbolContextModule()), + function.GetName()); + }, + [&](const TraceCursor &insn) { + lldb::addr_t load_address = insn.GetLoadAddress(); + if (function_base_address != LLDB_INVALID_ADDRESS) + m_s.Format(" <+{0}>", load_address - function_base_address); + + Address addr; + if (target.ResolveLoadAddress(load_address, addr)) { + LineEntry line_entry; + addr.CalculateSymbolContextLineEntry(line_entry); + if (line_entry.IsValid()) { + m_s << " at "; + line_entry.DumpStopContext(&m_s, false); + } } - } - }, - call.GetWrappingCall()); -} - -void TraceCallGraphDumper::DumpSymbolCall(TraceCallSymbol &call) { - Symbol &symbol = call.GetSymbol(); - Target &target = m_exe_ctx.GetTargetRef(); + }, + call.GetWrappingCall()); + } - DumpCallWrapper( - [&] { - m_s.Format("{0}`{1}", - ToDisplayString(symbol.CalculateSymbolContextModule()), - symbol.GetName()); - }, - [&](const TraceCursor &insn) { - lldb::addr_t load_address = insn.GetLoadAddress(); - lldb::addr_t symbol_offset = - load_address - symbol.GetLoadAddress(&target); - m_s.Format(" <+{0}>", symbol_offset); - - Address addr; - if (target.ResolveLoadAddress(load_address, addr)) { - LineEntry line_entry; - addr.CalculateSymbolContextLineEntry(line_entry); - if (line_entry.IsValid()) { - m_s << " at "; - line_entry.DumpStopContext(&m_s, /*show_fullpaths*/ false); + void DumpSymbolCall(TraceCallSymbol &call) { + Symbol &symbol = call.GetSymbol(); + Target &target = m_exe_ctx.GetTargetRef(); + + DumpCallWrapper( + [&] { + m_s.Format("{0}`{1}", + ToDisplayString(symbol.CalculateSymbolContextModule()), + symbol.GetName()); + }, + [&](const TraceCursor &insn) { + lldb::addr_t load_address = insn.GetLoadAddress(); + lldb::addr_t symbol_offset = + load_address - symbol.GetLoadAddress(&target); + m_s.Format(" <+{0}>", symbol_offset); + + Address addr; + if (target.ResolveLoadAddress(load_address, addr)) { + LineEntry line_entry; + addr.CalculateSymbolContextLineEntry(line_entry); + if (line_entry.IsValid()) { + m_s << " at "; + line_entry.DumpStopContext(&m_s, false); + } } + }, + call.GetWrappingCall()); + } + + void DumpUnknownRegion(TraceCallUnknownRegion &call) { + DumpCallWrapper([&] { m_s << "unknown region"; }, + [&](const TraceCursor &insn) {}, call.GetWrappingCall()); + } + + lldb::TraceCursorSP m_cursor_sp; + lldb::TraceCallGraphSP m_graph_sp; + ExecutionContext m_exe_ctx; + Stream &m_s; +}; + +class GraphOutputWriterJSON : public TraceCallGraphDumper::OutputWriter { + /* schema: + [ // array + { + "error": { + "from": decimal, + "to": decimal, + } || // each element in the array is either error or call + "function | inline | section | symbol | unknown": { // type of call + "segments": [ // child calls + { + "function | inline | section | symbol | unknown": { // nested + ... + } + } || // each element in the segments is either nested call or insns + { + "from": start instruction id of this call + "to": end instruction id of this call + } + ... + ] + } } }, - call.GetWrappingCall()); -} + ... + ] + */ +public: + GraphOutputWriterJSON(const TraceCursorSP &cursor_sp, + const TraceCallGraphSP &call_graph_sp, Stream &s, unsigned indent_size) + : m_graph_sp(call_graph_sp), m_s(s), + m_j(m_s.AsRawOstream(), indent_size) { + m_j.arrayBegin(); + }; + + ~GraphOutputWriterJSON() { m_j.arrayEnd(); } + + void Dump() override { + llvm::ArrayRef trees = m_graph_sp->GetTrees(); + + for (size_t i = 0; i < trees.size(); i++) { + std::visit( + match{[&](TraceErrorSegment &errors) { + m_j.object([&] { + DumpErrorSegment(errors); + }); + }, + [&](TraceInstructionCallTree &tree) { + m_j.object([&] { + DumpCall(tree.GetRootCall()); + }); + }}, + *trees[i]); + } + } + +private: + void DumpErrorSegment(TraceErrorSegment &error_segment) override { + auto range = error_segment.GetRange(); + m_j.attributeObject("error", [&] { + m_j.attribute("from", range.first); + m_j.attribute("to", range.second); + }); + } + + void DumpCall(TraceCall &call) override { + std::visit( + match{[&](TraceCallInlinedFunction &call) { + DumpCallWrapper("inline", call.GetWrappingCall()); + }, + [&](TraceCallFunction &call) { + DumpCallWrapper("function", call.GetWrappingCall()); + }, + [&](TraceCallSymbol &call) { + DumpCallWrapper("symbol", call.GetWrappingCall()); + }, + [&](TraceCallSection &call) { + DumpCallWrapper("section", call.GetWrappingCall()); + }, + [&](TraceCallUnknownRegion &call) { + DumpCallWrapper("unknown", call.GetWrappingCall()); + }}, + call); + } + + void DumpCallWrapper(const char* name, TraceCall &call_wrapper) { + m_j.attributeObject(name, [&] { + std::visit( + [&](auto &&call) { + m_j.attributeArray("segments", [&] { + for (const TraceCallSegmentUP &segment_up : call.GetSegments()) { + // traced segments + if (auto range = segment_up->GetRange()) { + m_j.object([&] { + m_j.attribute("from", range->first); + m_j.attribute("to", range->second); + }); + } -void TraceCallGraphDumper::DumpUnknownRegion(TraceCallUnknownRegion &call) { - DumpCallWrapper([&] { m_s << "unknown region"; }, - [&](const TraceCursor &insn) {}, call.GetWrappingCall()); + // nested + if (TraceCall *child_call = segment_up->GetChildCall()) { + m_j.object([&] { + DumpCall(*child_call); + }); + } + } + }); + }, + call_wrapper); + }); + } + + lldb::TraceCallGraphSP m_graph_sp; + Stream &m_s; + json::OStream m_j; +}; + +TraceCallGraphDumper::TraceCallGraphDumper( + const TraceCursorSP &cursor_sp, const TraceCallGraphSP &call_graph_sp, + Stream &s, const PrintStyle &print_style) { + if (print_style == PrintStyle::JSON) + m_writer_up = std::unique_ptr( + new GraphOutputWriterJSON(cursor_sp, call_graph_sp, s, 0)); + else if (print_style == PrintStyle::PrettyJSON) + m_writer_up = std::unique_ptr( + new GraphOutputWriterJSON(cursor_sp, call_graph_sp, s, 2)); + else + m_writer_up = std::unique_ptr( + new GraphOutputWriterCLI(cursor_sp, call_graph_sp, s)); } -void TraceCallGraphDumper::DumpCall(TraceCall &call) { - AddIndent _(m_s); - std::visit( - match{[&](TraceCallInlinedFunction &call) { - DumpInlinedFunctionCall(call); - }, - [&](TraceCallFunction &call) { DumpFunctionCall(call); }, - [&](TraceCallSymbol &call) { DumpSymbolCall(call); }, - [&](TraceCallSection &call) { DumpSectionCall(call); }, - [&](TraceCallUnknownRegion &call) { DumpUnknownRegion(call); }}, - call); +void TraceCallGraphDumper::Dump() { + m_writer_up->Dump(); }