diff --git a/lldb/include/lldb/Target/TraceCursor.h b/lldb/include/lldb/Target/TraceCursor.h --- a/lldb/include/lldb/Target/TraceCursor.h +++ b/lldb/include/lldb/Target/TraceCursor.h @@ -173,6 +173,11 @@ /// its position. virtual bool GoToId(lldb::user_id_t id) = 0; + /// \return + /// \b true if and only if there's an instruction item with the given \p + /// id. + virtual bool HasId(lldb::user_id_t id) const = 0; + /// \return /// A unique identifier for the instruction or error this cursor is /// pointing to. diff --git a/lldb/include/lldb/Target/TraceInstructionDumper.h b/lldb/include/lldb/Target/TraceInstructionDumper.h --- a/lldb/include/lldb/Target/TraceInstructionDumper.h +++ b/lldb/include/lldb/Target/TraceInstructionDumper.h @@ -15,17 +15,6 @@ namespace lldb_private { -/// Helper struct that holds symbol, disassembly and address information of an -/// instruction. -struct InstructionSymbolInfo { - SymbolContext sc; - Address address; - lldb::addr_t load_address; - lldb::DisassemblerSP disassembler; - lldb::InstructionSP instruction; - lldb_private::ExecutionContext exe_ctx; -}; - /// Class that holds the configuration used by \a TraceInstructionDumper for /// traversing and dumping instructions. struct TraceInstructionDumperOptions { @@ -36,6 +25,10 @@ /// Dump only instruction addresses without disassembly nor symbol /// information. bool raw = false; + /// Dump in json format. + bool json = false; + /// When dumping in JSON format, pretty print the output. + bool pretty_print_json = false; /// For each instruction, print the corresponding timestamp counter if /// available. bool show_tsc = false; @@ -52,6 +45,42 @@ /// state and granularity. class TraceInstructionDumper { public: + /// Helper struct that holds symbol, disassembly and address information of an + /// instruction. + struct SymbolInfo { + SymbolContext sc; + Address address; + lldb::DisassemblerSP disassembler; + lldb::InstructionSP instruction; + lldb_private::ExecutionContext exe_ctx; + }; + + /// Helper struct that holds all the information we know about an instruction + struct InstructionEntry { + lldb::user_id_t id; + lldb::addr_t load_address; + llvm::Optional tsc; + llvm::Optional error; + llvm::Optional symbol_info; + llvm::Optional prev_symbol_info; + }; + + /// Interface used to abstract away the format in which the instruction + /// information will be dumped. + class OutputWriter { + public: + virtual ~OutputWriter() = default; + + /// Indicate a user-level info message. It's not part of the actual trace. + virtual void InfoMessage(llvm::StringRef text) {} + + /// Dump a trace event. + virtual void Event(llvm::StringRef text) = 0; + + /// Dump an instruction or a trace error. + virtual void Instruction(const InstructionEntry &insn) = 0; + }; + /// Create a instruction dumper for the cursor. /// /// \param[in] cursor @@ -83,46 +112,22 @@ /// \b true if there's still more data to traverse in the trace. bool HasMoreData(); -private: /// Indicate to the dumper that no more data is available in the trace. /// This will prevent further iterations. void SetNoMoreData(); - /// Move the cursor one step. - /// - /// \return - /// \b true if the cursor moved. - bool TryMoveOneStep(); +private: + /// Create an instruction entry for the current position without symbol + /// information. + InstructionEntry CreatRawInstructionEntry(); void PrintEvents(); - void PrintMissingInstructionsMessage(); - - void PrintInstructionHeader(); - - void DumpInstructionDisassembly(const InstructionSymbolInfo &insn); - - /// Dump the symbol context of the given instruction address if it's different - /// from the symbol context of the previous instruction in the trace. - /// - /// \param[in] prev_sc - /// The symbol context of the previous instruction in the trace. - /// - /// \param[in] address - /// The address whose symbol information will be dumped. - /// - /// \return - /// The symbol context of the current address, which might differ from the - /// previous one. - void DumpInstructionSymbolContext( - const llvm::Optional &prev_insn, - const InstructionSymbolInfo &insn); - lldb::TraceCursorUP m_cursor_up; TraceInstructionDumperOptions m_options; - Stream &m_s; /// If \b true, all the instructions have been traversed. bool m_no_more_data = false; + std::unique_ptr m_writer_up; }; } // namespace lldb_private diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp --- a/lldb/source/Commands/CommandObjectThread.cpp +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -2128,6 +2128,10 @@ m_count = count; break; } + case 'a': { + m_count = std::numeric_limits::max(); + break; + } case 's': { int32_t skip; if (option_arg.empty() || option_arg.getAsInteger(0, skip) || skip < 0) @@ -2148,6 +2152,10 @@ m_dumper_options.id = id; break; } + case 'F': { + m_output_file.emplace(option_arg); + break; + } case 'r': { m_dumper_options.raw = true; break; @@ -2164,6 +2172,15 @@ m_dumper_options.show_events = true; break; } + case 'j': { + m_dumper_options.json = true; + break; + } + case 'J': { + m_dumper_options.pretty_print_json = true; + m_dumper_options.json = true; + break; + } case 'C': { m_continue = true; break; @@ -2177,6 +2194,7 @@ void OptionParsingStarting(ExecutionContext *execution_context) override { m_count = kDefaultCount; m_continue = false; + m_output_file = llvm::None; m_dumper_options = {}; } @@ -2189,6 +2207,7 @@ // Instance variables to hold the values for command options. size_t m_count; size_t m_continue; + llvm::Optional m_output_file; TraceInstructionDumperOptions m_dumper_options; }; @@ -2238,27 +2257,44 @@ bool DoExecute(Args &args, CommandReturnObject &result) override { ThreadSP thread_sp = GetThread(args, result); - if (!thread_sp) + if (!thread_sp) { + result.AppendError("invalid thread\n"); return false; + } - Stream &s = result.GetOutputStream(); - s.Printf("thread #%u: tid = %" PRIu64 "\n", thread_sp->GetIndexID(), - thread_sp->GetID()); - - if (m_options.m_continue) { - if (!m_last_id) { - result.AppendMessage(" no more data\n"); - return true; - } + if (m_options.m_continue && m_last_id) { // We set up the options to continue one instruction past where // the previous iteration stopped. m_options.m_dumper_options.skip = 1; m_options.m_dumper_options.id = m_last_id; } - const TraceSP &trace_sp = m_exe_ctx.GetTargetSP()->GetTrace(); - TraceInstructionDumper dumper(trace_sp->GetCursor(*thread_sp), s, - m_options.m_dumper_options); + TraceCursorUP cursor_up = + m_exe_ctx.GetTargetSP()->GetTrace()->GetCursor(*thread_sp); + + if (m_options.m_dumper_options.id && + !cursor_up->HasId(*m_options.m_dumper_options.id)) { + result.AppendError("invalid instruction id\n"); + return false; + } + + llvm::Optional out_file; + if (m_options.m_output_file) { + out_file.emplace(m_options.m_output_file->GetPath().c_str(), + File::eOpenOptionWriteOnly | File::eOpenOptionCanCreate, + lldb::eFilePermissionsFileDefault); + } + + TraceInstructionDumper dumper( + std::move(cursor_up), out_file ? *out_file : result.GetOutputStream(), + m_options.m_dumper_options); + + if (m_options.m_continue && !m_last_id) { + // We need to tell the dumper to stop processing data when + // we already ran out of instructions in a previous command + dumper.SetNoMoreData(); + } + m_last_id = dumper.DumpInstructions(m_options.m_count); return true; } diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1120,6 +1120,9 @@ Arg<"Count">, Desc<"The number of instructions to display starting at the most recent " "instruction, or the oldest if --forwards is provided.">; + def thread_trace_dump_instructions_all : Option<"all", "a">, Group<1>, + Desc<"From the starting point of the trace, dump all instructions " + "available.">; def thread_trace_dump_instructions_id: Option<"id", "i">, Group<1>, Arg<"Index">, Desc<"Custom starting instruction id from where to start traversing. This " @@ -1128,14 +1131,22 @@ Arg<"Index">, Desc<"How many instruction to skip from the starting position of the trace " "before starting the traversal.">; - def thread_trace_dump_instructions_raw : Option<"raw", "r">, - Group<1>, + def thread_trace_dump_instructions_raw : Option<"raw", "r">, Group<1>, Desc<"Dump only instruction address without disassembly nor symbol " "information.">; + def thread_trace_dump_instructions_file : Option<"file", "F">, Group<1>, + Arg<"Filename">, + Desc<"Dump the instruction to a file instead of the standard output.">; + def thread_trace_dump_instructions_json: Option<"json", "j">, + Group<1>, + Desc<"Dump in simple JSON format.">; + def thread_trace_dump_instructions_pretty_print: Option<"pretty-json", "J">, + Group<1>, + Desc<"Dump in JSON format but pretty printing the output for easier readability.">; def thread_trace_dump_instructions_show_tsc : Option<"tsc", "t">, Group<1>, Desc<"For each instruction, print the corresponding timestamp counter if " "available.">; - def thread_trace_dump_instructions_hide_events : Option<"events", "e">, + def thread_trace_dump_instructions_show_events : Option<"events", "e">, Group<1>, Desc<"Dump the events that happened during the execution of the target.">; def thread_trace_dump_instructions_continue: Option<"continue", "C">, diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h @@ -41,6 +41,8 @@ lldb::user_id_t GetId() const override; + bool HasId(lldb::user_id_t id) const override; + private: size_t GetInternalInstructionSize(); diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp @@ -118,7 +118,7 @@ } bool TraceCursorIntelPT::GoToId(user_id_t id) { - if (m_decoded_thread_sp->GetInstructionsCount() <= id) + if (!HasId(id)) return false; m_pos = id; m_tsc_range = m_decoded_thread_sp->CalculateTscRange(m_pos, m_tsc_range); @@ -126,4 +126,8 @@ return true; } +bool TraceCursorIntelPT::HasId(lldb::user_id_t id) const { + return id < m_decoded_thread_sp->GetInstructionsCount(); +} + user_id_t TraceCursorIntelPT::GetId() const { return m_pos; } diff --git a/lldb/source/Target/TraceInstructionDumper.cpp b/lldb/source/Target/TraceInstructionDumper.cpp --- a/lldb/source/Target/TraceInstructionDumper.cpp +++ b/lldb/source/Target/TraceInstructionDumper.cpp @@ -9,6 +9,7 @@ #include "lldb/Target/TraceInstructionDumper.h" #include "lldb/Core/Module.h" +#include "lldb/Symbol/CompileUnit.h" #include "lldb/Symbol/Function.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/Process.h" @@ -18,41 +19,23 @@ using namespace lldb_private; using namespace llvm; -TraceInstructionDumper::TraceInstructionDumper( - lldb::TraceCursorUP &&cursor_up, Stream &s, - const TraceInstructionDumperOptions &options) - : m_cursor_up(std::move(cursor_up)), m_options(options), m_s(s) { - // We first set the cursor in its initial position - if (m_options.id) { - if (!m_cursor_up->GoToId(*m_options.id)) { - s.PutCString(" invalid instruction id\n"); - SetNoMoreData(); - return; - } - } else if (m_options.forwards) { - m_cursor_up->Seek(0, TraceCursor::SeekType::Beginning); - } else { - m_cursor_up->Seek(0, TraceCursor::SeekType::End); - } - - m_cursor_up->SetForwards(m_options.forwards); - if (m_options.skip) { - uint64_t to_skip = *m_options.skip; - if (m_cursor_up->Seek((m_options.forwards ? 1 : -1) * to_skip, - TraceCursor::SeekType::Current) < to_skip) { - // This happens when the skip value was more than the number of - // available instructions. - SetNoMoreData(); - } - } +/// \return +/// The given string or \b None if it's empty. +static Optional ToOptionalString(const char *s) { + if (!s) + return None; + return s; } - -bool TraceInstructionDumper::TryMoveOneStep() { - if (!m_cursor_up->Next()) { - SetNoMoreData(); - return false; - } - return true; +/// \return +/// The module name (basename if the module is a file, or the actual name if +/// it's a virtual module), or \b nullptr if no name nor module was found. +static const char * +GetModuleName(const TraceInstructionDumper::InstructionEntry &insn) { + if (!insn.symbol_info || !insn.symbol_info->sc.module_sp) + return nullptr; + return insn.symbol_info->sc.module_sp->GetFileSpec() + .GetFilename() + .AsCString(); } // This custom LineEntry validator is neded because some line_entries have @@ -73,7 +56,7 @@ return a.file == b.file; } -/// Compare the symbol contexts of the provided \a InstructionSymbolInfo +/// Compare the symbol contexts of the provided \a SymbolInfo /// objects. /// /// \return @@ -83,9 +66,9 @@ /// - symbol /// - function /// - line -static bool -IsSameInstructionSymbolContext(const InstructionSymbolInfo &prev_insn, - const InstructionSymbolInfo &insn) { +static bool IsSameInstructionSymbolContext( + const TraceInstructionDumper::SymbolInfo &prev_insn, + const TraceInstructionDumper::SymbolInfo &insn) { // module checks if (insn.sc.module_sp != prev_insn.sc.module_sp) return false; @@ -109,63 +92,210 @@ return curr_line_valid == prev_line_valid; } -void TraceInstructionDumper::DumpInstructionSymbolContext( - const Optional &prev_insn, - const InstructionSymbolInfo &insn) { - if (prev_insn && IsSameInstructionSymbolContext(*prev_insn, insn)) - return; +class OutputWriterCLI : public TraceInstructionDumper::OutputWriter { +public: + OutputWriterCLI(Stream &s, const TraceInstructionDumperOptions &options) + : m_s(s), m_options(options){}; + + void InfoMessage(StringRef text) override { m_s << " " << text << "\n"; } + + void Event(StringRef text) override { m_s.Format(" [{0}]\n", text); } + + void + Instruction(const TraceInstructionDumper::InstructionEntry &insn) override { + if (insn.symbol_info) { + if (!insn.prev_symbol_info || + !IsSameInstructionSymbolContext(*insn.prev_symbol_info, + *insn.symbol_info)) { + m_s << " "; + const char *module_name = GetModuleName(insn); + if (!module_name) + m_s << "(none)"; + else if (!insn.symbol_info->sc.function && !insn.symbol_info->sc.symbol) + m_s.Format("{0}`(none)", module_name); + else + insn.symbol_info->sc.DumpStopContext( + &m_s, insn.symbol_info->exe_ctx.GetTargetPtr(), + insn.symbol_info->address, + /*show_fullpaths=*/false, + /*show_module=*/true, /*show_inlined_frames=*/false, + /*show_function_arguments=*/true, + /*show_function_name=*/true); + m_s << "\n"; + } + } - m_s << " "; + if (insn.error && !m_was_prev_instruction_an_error) + InfoMessage("...missing instructions"); - if (!insn.sc.module_sp) - m_s << "(none)"; - else if (!insn.sc.function && !insn.sc.symbol) - m_s.Format("{0}`(none)", - insn.sc.module_sp->GetFileSpec().GetFilename().AsCString()); - else - insn.sc.DumpStopContext(&m_s, insn.exe_ctx.GetTargetPtr(), insn.address, - /*show_fullpaths=*/false, - /*show_module=*/true, /*show_inlined_frames=*/false, - /*show_function_arguments=*/true, - /*show_function_name=*/true); - m_s << "\n"; -} + m_s.Format(" {0}: ", insn.id); -void TraceInstructionDumper::DumpInstructionDisassembly( - const InstructionSymbolInfo &insn) { - if (!insn.instruction) - return; - m_s << " "; - insn.instruction->Dump(&m_s, /*max_opcode_byte_size=*/0, - /*show_address=*/false, - /*show_bytes=*/false, &insn.exe_ctx, &insn.sc, - /*prev_sym_ctx=*/nullptr, - /*disassembly_addr_format=*/nullptr, - /*max_address_text_size=*/0); + if (m_options.show_tsc) { + m_s << "[tsc="; + + if (insn.tsc) + m_s.Format("{0}", *insn.tsc); + else + m_s << "unavailable"; + + m_s << "] "; + } + + if (insn.error) { + m_s << *insn.error; + m_was_prev_instruction_an_error = true; + } else { + m_s.Format("{0:x+16}", insn.load_address); + if (insn.symbol_info) { + m_s << " "; + insn.symbol_info->instruction->Dump(&m_s, /*max_opcode_byte_size=*/0, + /*show_address=*/false, + /*show_bytes=*/false, + &insn.symbol_info->exe_ctx, + &insn.symbol_info->sc, + /*prev_sym_ctx=*/nullptr, + /*disassembly_addr_format=*/nullptr, + /*max_address_text_size=*/0); + } + m_was_prev_instruction_an_error = false; + } + m_s << "\n"; + } + +private: + Stream &m_s; + TraceInstructionDumperOptions m_options; + bool m_was_prev_instruction_an_error = false; +}; + +class OutputWriterJSON : public TraceInstructionDumper::OutputWriter { + /* schema: + error_message: string + | { + "event": string + } | { + "id": decimal, + "tsc"?: string decimal, + "error": string, + | { + "id": decimal, + "tsc"?: string decimal, + "module"?: string, + "symbol"?: string, + "line"?: decimal, + "column"?: decimal, + "source"?: string, + "mnemonic"?: string, + } + */ +public: + OutputWriterJSON(Stream &s, const TraceInstructionDumperOptions &options) + : m_s(s), m_options(options), + m_j(m_s.AsRawOstream(), + /*IndentSize=*/options.pretty_print_json ? 2 : 0) { + m_j.arrayBegin(); + }; + + ~OutputWriterJSON() { m_j.arrayEnd(); } + + void Event(StringRef text) override { + m_j.object([&] { m_j.attribute("event", text); }); + } + + void + Instruction(const TraceInstructionDumper::InstructionEntry &insn) override { + m_j.object([&] { + m_j.attribute("id", insn.id); + if (m_options.show_tsc) + m_j.attribute( + "tsc", + insn.tsc ? Optional(std::to_string(*insn.tsc)) : None); + + if (insn.error) { + m_j.attribute("error", *insn.error); + return; + } + + m_j.attribute("loadAddress", formatv("{0:x}", insn.load_address)); + if (insn.symbol_info) { + m_j.attribute("module", ToOptionalString(GetModuleName(insn))); + m_j.attribute("symbol", + ToOptionalString( + insn.symbol_info->sc.GetFunctionName().AsCString())); + m_j.attribute( + "mnemonic", + ToOptionalString(insn.symbol_info->instruction->GetMnemonic( + &insn.symbol_info->exe_ctx))); + + if (IsLineEntryValid(insn.symbol_info->sc.line_entry)) { + m_j.attribute( + "source", + ToOptionalString( + insn.symbol_info->sc.line_entry.file.GetPath().c_str())); + m_j.attribute("line", insn.symbol_info->sc.line_entry.line); + m_j.attribute("column", insn.symbol_info->sc.line_entry.column); + } + } + }); + } + +private: + Stream &m_s; + TraceInstructionDumperOptions m_options; + json::OStream m_j; +}; + +static std::unique_ptr +CreateWriter(Stream &s, const TraceInstructionDumperOptions &options) { + if (options.json) + return std::unique_ptr( + new OutputWriterJSON(s, options)); + else + return std::unique_ptr( + new OutputWriterCLI(s, options)); } -void TraceInstructionDumper::SetNoMoreData() { m_no_more_data = true; } +TraceInstructionDumper::TraceInstructionDumper( + lldb::TraceCursorUP &&cursor_up, Stream &s, + const TraceInstructionDumperOptions &options) + : m_cursor_up(std::move(cursor_up)), m_options(options), + m_writer_up(CreateWriter(s, m_options)) { -bool TraceInstructionDumper::HasMoreData() { return !m_no_more_data; } + if (m_options.id) { + if (!m_cursor_up->GoToId(*m_options.id)) { + m_writer_up->InfoMessage("invalid instruction id"); + SetNoMoreData(); + } + } else if (m_options.forwards) { + m_cursor_up->Seek(0, TraceCursor::SeekType::Beginning); + } else { + m_cursor_up->Seek(0, TraceCursor::SeekType::End); + } -void TraceInstructionDumper::PrintMissingInstructionsMessage() { - m_s << " ...missing instructions\n"; + m_cursor_up->SetForwards(m_options.forwards); + if (m_options.skip) { + uint64_t to_skip = *m_options.skip; + if (m_cursor_up->Seek((m_options.forwards ? 1 : -1) * to_skip, + TraceCursor::SeekType::Current) < to_skip) { + // This happens when the skip value was more than the number of + // available instructions. + SetNoMoreData(); + } + } } -void TraceInstructionDumper::PrintInstructionHeader() { - m_s.Format(" {0}: ", m_cursor_up->GetId()); +void TraceInstructionDumper::SetNoMoreData() { m_no_more_data = true; } - if (m_options.show_tsc) { - m_s << "[tsc="; +bool TraceInstructionDumper::HasMoreData() { return !m_no_more_data; } - if (Optional timestamp = - m_cursor_up->GetCounter(lldb::eTraceCounterTSC)) - m_s.Format("{0:x+16}", *timestamp); - else - m_s << "unavailable"; +TraceInstructionDumper::InstructionEntry +TraceInstructionDumper::CreatRawInstructionEntry() { + InstructionEntry insn; + insn.id = m_cursor_up->GetId(); - m_s << "] "; - } + if (m_options.show_tsc) + insn.tsc = m_cursor_up->GetCounter(lldb::eTraceCounterTSC); + return insn; } void TraceInstructionDumper::PrintEvents() { @@ -174,20 +304,21 @@ trace_event_utils::ForEachEvent( m_cursor_up->GetEvents(), [&](TraceEvents event) { - m_s.Format(" [{0}]\n", trace_event_utils::EventToDisplayString(event)); + m_writer_up->Event(trace_event_utils::EventToDisplayString(event)); }); } /// Find the symbol context for the given address reusing the previous /// instruction's symbol context when possible. -static SymbolContext -CalculateSymbolContext(const Address &address, - const InstructionSymbolInfo &prev_insn_info) { +static SymbolContext CalculateSymbolContext( + const Address &address, + const TraceInstructionDumper::SymbolInfo &prev_symbol_info) { AddressRange range; - if (prev_insn_info.sc.GetAddressRange(eSymbolContextEverything, 0, - /*inline_block_range*/ false, range) && + if (prev_symbol_info.sc.GetAddressRange(eSymbolContextEverything, 0, + /*inline_block_range*/ false, + range) && range.Contains(address)) - return prev_insn_info.sc; + return prev_symbol_info.sc; SymbolContext sc; address.CalculateSymbolContext(&sc, eSymbolContextEverything); @@ -197,49 +328,48 @@ /// Find the disassembler for the given address reusing the previous /// instruction's disassembler when possible. static std::tuple -CalculateDisass(const InstructionSymbolInfo &insn_info, - const InstructionSymbolInfo &prev_insn_info, +CalculateDisass(const TraceInstructionDumper::SymbolInfo &symbol_info, + const TraceInstructionDumper::SymbolInfo &prev_symbol_info, const ExecutionContext &exe_ctx) { - if (prev_insn_info.disassembler) { + if (prev_symbol_info.disassembler) { if (InstructionSP instruction = - prev_insn_info.disassembler->GetInstructionList() - .GetInstructionAtAddress(insn_info.address)) - return std::make_tuple(prev_insn_info.disassembler, instruction); + prev_symbol_info.disassembler->GetInstructionList() + .GetInstructionAtAddress(symbol_info.address)) + return std::make_tuple(prev_symbol_info.disassembler, instruction); } - if (insn_info.sc.function) { + if (symbol_info.sc.function) { if (DisassemblerSP disassembler = - insn_info.sc.function->GetInstructions(exe_ctx, nullptr)) { + symbol_info.sc.function->GetInstructions(exe_ctx, nullptr)) { if (InstructionSP instruction = disassembler->GetInstructionList().GetInstructionAtAddress( - insn_info.address)) + symbol_info.address)) return std::make_tuple(disassembler, instruction); } } // We fallback to a single instruction disassembler Target &target = exe_ctx.GetTargetRef(); const ArchSpec arch = target.GetArchitecture(); - AddressRange range(insn_info.address, arch.GetMaximumOpcodeByteSize()); + AddressRange range(symbol_info.address, arch.GetMaximumOpcodeByteSize()); DisassemblerSP disassembler = Disassembler::DisassembleRange(arch, /*plugin_name*/ nullptr, /*flavor*/ nullptr, target, range); return std::make_tuple( disassembler, disassembler ? disassembler->GetInstructionList().GetInstructionAtAddress( - insn_info.address) + symbol_info.address) : InstructionSP()); } Optional TraceInstructionDumper::DumpInstructions(size_t count) { ThreadSP thread_sp = m_cursor_up->GetExecutionContextRef().GetThreadSP(); - if (!thread_sp) { - m_s << "invalid thread"; - return None; - } - bool was_prev_instruction_an_error = false; - InstructionSymbolInfo prev_insn_info; + m_writer_up->InfoMessage(formatv("thread #{0}: tid = {1}", + thread_sp->GetIndexID(), thread_sp->GetID()) + .str()); + + SymbolInfo prev_symbol_info; Optional last_id; ExecutionContext exe_ctx; @@ -247,63 +377,50 @@ for (size_t i = 0; i < count; i++) { if (!HasMoreData()) { - m_s << " no more data\n"; + m_writer_up->InfoMessage("no more data"); break; } last_id = m_cursor_up->GetId(); + if (m_options.forwards) { // When moving forwards, we first print the event before printing // the actual instruction. PrintEvents(); } - if (const char *err = m_cursor_up->GetError()) { - if (!m_cursor_up->IsForwards() && !was_prev_instruction_an_error) - PrintMissingInstructionsMessage(); - - was_prev_instruction_an_error = true; + InstructionEntry insn = CreatRawInstructionEntry(); - PrintInstructionHeader(); - m_s << err; + if (const char *err = m_cursor_up->GetError()) { + insn.error = err; + m_writer_up->Instruction(insn); } else { - if (m_cursor_up->IsForwards() && was_prev_instruction_an_error) - PrintMissingInstructionsMessage(); - - was_prev_instruction_an_error = false; - - InstructionSymbolInfo insn_info; + insn.load_address = m_cursor_up->GetLoadAddress(); if (!m_options.raw) { - insn_info.load_address = m_cursor_up->GetLoadAddress(); - insn_info.exe_ctx = exe_ctx; - insn_info.address.SetLoadAddress(insn_info.load_address, - exe_ctx.GetTargetPtr()); - insn_info.sc = - CalculateSymbolContext(insn_info.address, prev_insn_info); - std::tie(insn_info.disassembler, insn_info.instruction) = - CalculateDisass(insn_info, prev_insn_info, exe_ctx); - - DumpInstructionSymbolContext(prev_insn_info, insn_info); + SymbolInfo symbol_info; + symbol_info.exe_ctx = exe_ctx; + symbol_info.address.SetLoadAddress(insn.load_address, + exe_ctx.GetTargetPtr()); + symbol_info.sc = + CalculateSymbolContext(symbol_info.address, prev_symbol_info); + std::tie(symbol_info.disassembler, symbol_info.instruction) = + CalculateDisass(symbol_info, prev_symbol_info, exe_ctx); + insn.prev_symbol_info = prev_symbol_info; + insn.symbol_info = symbol_info; + prev_symbol_info = symbol_info; } - - PrintInstructionHeader(); - m_s.Format("{0:x+16}", m_cursor_up->GetLoadAddress()); - - if (!m_options.raw) - DumpInstructionDisassembly(insn_info); - - prev_insn_info = insn_info; + m_writer_up->Instruction(insn); } - m_s << "\n"; - if (!m_options.forwards) { // If we move backwards, we print the events after printing // the actual instruction so that reading chronologically // makes sense. PrintEvents(); } - TryMoveOneStep(); + + if (!m_cursor_up->Next()) + SetNoMoreData(); } return last_id; } diff --git a/lldb/test/API/commands/trace/TestTraceDumpInstructions.py b/lldb/test/API/commands/trace/TestTraceDumpInstructions.py --- a/lldb/test/API/commands/trace/TestTraceDumpInstructions.py +++ b/lldb/test/API/commands/trace/TestTraceDumpInstructions.py @@ -28,6 +28,71 @@ substrs=["error: Process is not being traced"], error=True) + def testRawDumpInstructionsInJSON(self): + self.expect("trace load -v " + + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"), + substrs=["intel-pt"]) + + self.expect("thread trace dump instructions --raw --count 5 --forwards --json", + substrs=['''[{"id":0,"loadAddress":"0x400511"},{"id":1,"loadAddress":"0x400518"},{"id":2,"loadAddress":"0x40051f"},{"id":3,"loadAddress":"0x400529"},{"id":4,"loadAddress":"0x40052d"}]''']) + + self.expect("thread trace dump instructions --raw --count 5 --forwards --pretty-json", + substrs=['''[ + { + "id": 0, + "loadAddress": "0x400511" + }, + { + "id": 1, + "loadAddress": "0x400518" + }, + { + "id": 2, + "loadAddress": "0x40051f" + }, + { + "id": 3, + "loadAddress": "0x400529" + }, + { + "id": 4, + "loadAddress": "0x40052d" + } +]''']) + + def testRawDumpInstructionsInJSONToFile(self): + self.expect("trace load -v " + + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"), + substrs=["intel-pt"]) + + outfile = os.path.join(self.getBuildDir(), "output.json") + + self.expect("thread trace dump instructions --raw --count 5 --forwards --pretty-json --file " + outfile) + + with open(outfile, "r") as out: + self.assertEqual(out.read(), '''[ + { + "id": 0, + "loadAddress": "0x400511" + }, + { + "id": 1, + "loadAddress": "0x400518" + }, + { + "id": 2, + "loadAddress": "0x40051f" + }, + { + "id": 3, + "loadAddress": "0x400529" + }, + { + "id": 4, + "loadAddress": "0x40052d" + } +]''') + def testRawDumpInstructions(self): self.expect("trace load -v " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"), @@ -162,6 +227,7 @@ os.path.join(self.getSourceDir(), "intelpt-trace", "trace_bad_image.json")) self.expect("thread trace dump instructions --forwards", substrs=['''thread #1: tid = 3842849 + ...missing instructions 0: 0x0000000000400511 error: no memory mapped at this address 1: 0x0000000000400518 error: no memory mapped at this address''']) @@ -170,8 +236,76 @@ os.path.join(self.getSourceDir(), "intelpt-trace", "trace_wrong_cpu.json")) self.expect("thread trace dump instructions --forwards", substrs=['''thread #1: tid = 3842849 + ...missing instructions 0: error: unknown cpu''']) + def testMultiFileTraceWithMissingModuleInJSON(self): + self.expect("trace load " + + os.path.join(self.getSourceDir(), "intelpt-trace-multi-file", "multi-file-no-ld.json")) + + self.expect("thread trace dump instructions --count 3 --id 4 --forwards --pretty-json", + substrs=['''[ + { + "id": 4, + "loadAddress": "0x400510", + "module": "a.out", + "symbol": null, + "mnemonic": "pushq" + }, + { + "id": 5, + "loadAddress": "0x400516", + "module": "a.out", + "symbol": null, + "mnemonic": "jmpq" + }, + { + "id": 6, + "error": "0x00007ffff7df1950 error: no memory mapped at this address" + } +]''']) + + self.expect("thread trace dump instructions --count 4 --id 20 --forwards --pretty-json", + substrs=['''[ + { + "id": 20, + "loadAddress": "0x400540", + "module": "a.out", + "symbol": "foo()", + "mnemonic": "jmpq" + }, + { + "id": 21, + "loadAddress": "0x7ffff7bd96e0", + "module": "libfoo.so", + "symbol": "foo()", + "mnemonic": "pushq", + "source": "/home/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.cpp", + "line": 3, + "column": 0 + }, + { + "id": 22, + "loadAddress": "0x7ffff7bd96e1", + "module": "libfoo.so", + "symbol": "foo()", + "mnemonic": "movq", + "source": "/home/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.cpp", + "line": 3, + "column": 0 + }, + { + "id": 23, + "loadAddress": "0x7ffff7bd96e4", + "module": "libfoo.so", + "symbol": "foo()", + "mnemonic": "subq", + "source": "/home/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.cpp", + "line": 4, + "column": 0 + } +]''']) + def testMultiFileTraceWithMissingModule(self): self.expect("trace load " + os.path.join(self.getSourceDir(), "intelpt-trace-multi-file", "multi-file-no-ld.json")) @@ -201,8 +335,8 @@ a.out`(none) 4: 0x0000000000400510 pushq 0x200af2(%rip) ; _GLOBAL_OFFSET_TABLE_ + 8 5: 0x0000000000400516 jmpq *0x200af4(%rip) ; _GLOBAL_OFFSET_TABLE_ + 16 - 6: 0x00007ffff7df1950 error: no memory mapped at this address ...missing instructions + 6: 0x00007ffff7df1950 error: no memory mapped at this address a.out`main + 20 at main.cpp:10 7: 0x0000000000400674 movl %eax, -0xc(%rbp) a.out`main + 23 at main.cpp:12 @@ -341,3 +475,15 @@ self.expect("", substrs=['''thread #1: tid = 815455 no more data''']) + + + self.expect("thread trace dump instructions --raw --all --forwards", + substrs=['''thread #1: tid = 815455 + 0: 0x000000000040066f + 1: 0x0000000000400540''', '''5: 0x0000000000400516 + ...missing instructions + 6: 0x00007ffff7df1950 error: no memory mapped at this address + 7: 0x0000000000400674''', '''43: 0x00000000004006a4 + 44: 0x00000000004006a7 + 45: 0x00000000004006a9 + no more data''']) diff --git a/lldb/test/API/commands/trace/TestTraceLoad.py b/lldb/test/API/commands/trace/TestTraceLoad.py --- a/lldb/test/API/commands/trace/TestTraceLoad.py +++ b/lldb/test/API/commands/trace/TestTraceLoad.py @@ -13,11 +13,11 @@ trace_description_file_path = os.path.join(src_dir, "intelpt-multi-core-trace", "trace.json") self.traceLoad(traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"]) self.expect("thread trace dump instructions 2 -t", - substrs=["19521: [tsc=0x008fb5211c143fd8] error: expected tracing enabled event", + substrs=["19521: [tsc=40450075479261144] error: expected tracing enabled event", "m.out`foo() + 65 at multi_thread.cpp:12:21", - "19520: [tsc=0x008fb5211bfbc69e] 0x0000000000400ba7 jg 0x400bb3"]) + "19520: [tsc=40450075477657246] 0x0000000000400ba7 jg 0x400bb3"]) self.expect("thread trace dump instructions 3 -t", - substrs=["67910: [tsc=0x008fb5211bfdf270] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)", + substrs=["67910: [tsc=40450075477799536] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)", "m.out`bar() + 26 at multi_thread.cpp:20:6"]) @testSBAPIAndCommands @@ -26,11 +26,11 @@ trace_description_file_path = os.path.join(src_dir, "intelpt-multi-core-trace", "trace_with_string_numbers.json") self.traceLoad(traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"]) self.expect("thread trace dump instructions 2 -t", - substrs=["19521: [tsc=0x008fb5211c143fd8] error: expected tracing enabled event", + substrs=["19521: [tsc=40450075479261144] error: expected tracing enabled event", "m.out`foo() + 65 at multi_thread.cpp:12:21", - "19520: [tsc=0x008fb5211bfbc69e] 0x0000000000400ba7 jg 0x400bb3"]) + "19520: [tsc=40450075477657246] 0x0000000000400ba7 jg 0x400bb3"]) self.expect("thread trace dump instructions 3 -t", - substrs=["67910: [tsc=0x008fb5211bfdf270] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)", + substrs=["67910: [tsc=40450075477799536] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)", "m.out`bar() + 26 at multi_thread.cpp:20:6"]) @testSBAPIAndCommands @@ -39,11 +39,11 @@ trace_description_file_path = os.path.join(src_dir, "intelpt-multi-core-trace", "trace_missing_threads.json") self.traceLoad(traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"]) self.expect("thread trace dump instructions 3 -t", - substrs=["19521: [tsc=0x008fb5211c143fd8] error: expected tracing enabled event", + substrs=["19521: [tsc=40450075479261144] error: expected tracing enabled event", "m.out`foo() + 65 at multi_thread.cpp:12:21", - "19520: [tsc=0x008fb5211bfbc69e] 0x0000000000400ba7 jg 0x400bb3"]) + "19520: [tsc=40450075477657246] 0x0000000000400ba7 jg 0x400bb3"]) self.expect("thread trace dump instructions 2 -t", - substrs=["67910: [tsc=0x008fb5211bfdf270] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)", + substrs=["67910: [tsc=40450075477799536] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)", "m.out`bar() + 26 at multi_thread.cpp:20:6"]) @testSBAPIAndCommands diff --git a/lldb/test/API/commands/trace/TestTraceTSC.py b/lldb/test/API/commands/trace/TestTraceTSC.py --- a/lldb/test/API/commands/trace/TestTraceTSC.py +++ b/lldb/test/API/commands/trace/TestTraceTSC.py @@ -17,7 +17,7 @@ self.expect("n") self.expect("thread trace dump instructions --tsc -c 1", - patterns=["0: \[tsc=0x[0-9a-fA-F]+\] 0x0000000000400511 movl"]) + patterns=["0: \[tsc=\d+\] 0x0000000000400511 movl"]) @testSBAPIAndCommands @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) @@ -58,7 +58,10 @@ self.expect("n") self.expect("thread trace dump instructions --tsc -c 1", - patterns=["0: \[tsc=0x[0-9a-fA-F]+\] 0x0000000000400511 movl"]) + patterns=["0: \[tsc=\d+\] 0x0000000000400511 movl"]) + + self.expect("thread trace dump instructions --tsc -c 1 --pretty-json", + patterns=['''"tsc": "\d+"''']) @testSBAPIAndCommands @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) @@ -73,6 +76,9 @@ self.expect("thread trace dump instructions --tsc -c 1", patterns=["0: \[tsc=unavailable\] 0x0000000000400511 movl"]) + self.expect("thread trace dump instructions --tsc -c 1 --json", + patterns=['''"tsc":null''']) + @testSBAPIAndCommands @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) def testPSBPeriod(self):