[Lldb-commits] [lldb] efbfde0 - [trace] Add an option to dump instructions in json and to a file
Walter Erquinigo via lldb-commits
lldb-commits at lists.llvm.org
Wed Jun 22 11:14:31 PDT 2022
Author: Walter Erquinigo
Date: 2022-06-22T11:14:22-07:00
New Revision: efbfde0dd0f92d89767df53cbfb883ecf93ffa83
URL: https://github.com/llvm/llvm-project/commit/efbfde0dd0f92d89767df53cbfb883ecf93ffa83
DIFF: https://github.com/llvm/llvm-project/commit/efbfde0dd0f92d89767df53cbfb883ecf93ffa83.diff
LOG: [trace] Add an option to dump instructions in json and to a file
In order to provide simple scripting support on top of instruction traces, a simple solution is to enhance the `dump instructions` command and allow printing in json and directly to a file. The format is verbose and not space efficient, but it's not supposed to be used for really large traces, in which case the TraceCursor API is the way to go.
- add a -j option for printing the dump in json
- add a -J option for pretty printing the json output
- add a -F option for specifying an output file
- add a -a option for dumping all the instructions available starting at the initial point configured with the other flags
- add tests for all cases
- refactored the instruction dumper and abstracted the actual "printing" logic. There are two writer implementations: CLI and JSON. This made the dumper itself much more readable and maintanable
sample output:
```
(lldb) thread trace dump instructions -t -a --id 100 -J
[
{
"id": 100,
"tsc": "43591204528448966"
"loadAddress": "0x407a91",
"module": "a.out",
"symbol": "void std::deque<Foo, std::allocator<Foo>>::_M_push_back_aux<Foo>(Foo&&)",
"mnemonic": "movq",
"source": "/usr/include/c++/8/bits/deque.tcc",
"line": 492,
"column": 30
},
...
```
Differential Revision: https://reviews.llvm.org/D128316
Added:
Modified:
lldb/include/lldb/Target/TraceCursor.h
lldb/include/lldb/Target/TraceInstructionDumper.h
lldb/source/Commands/CommandObjectThread.cpp
lldb/source/Commands/Options.td
lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp
lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h
lldb/source/Target/TraceInstructionDumper.cpp
lldb/test/API/commands/trace/TestTraceDumpInstructions.py
lldb/test/API/commands/trace/TestTraceLoad.py
lldb/test/API/commands/trace/TestTraceTSC.py
Removed:
################################################################################
diff --git a/lldb/include/lldb/Target/TraceCursor.h b/lldb/include/lldb/Target/TraceCursor.h
index 606f6886964a7..79c4cd371e4f4 100644
--- a/lldb/include/lldb/Target/TraceCursor.h
+++ b/lldb/include/lldb/Target/TraceCursor.h
@@ -173,6 +173,11 @@ class TraceCursor {
/// 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
index 53a1a03ee1ce2..5afff20317198 100644
--- 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 @@ struct TraceInstructionDumperOptions {
/// 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 @@ struct TraceInstructionDumperOptions {
/// 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<uint64_t> tsc;
+ llvm::Optional<llvm::StringRef> error;
+ llvm::Optional<SymbolInfo> symbol_info;
+ llvm::Optional<SymbolInfo> 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 @@ class TraceInstructionDumper {
/// \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
diff erent
- /// 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
diff er from the
- /// previous one.
- void DumpInstructionSymbolContext(
- const llvm::Optional<InstructionSymbolInfo> &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<OutputWriter> m_writer_up;
};
} // namespace lldb_private
diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp
index 11affe8a7c13e..037bbafdf8940 100644
--- a/lldb/source/Commands/CommandObjectThread.cpp
+++ b/lldb/source/Commands/CommandObjectThread.cpp
@@ -2128,6 +2128,10 @@ class CommandObjectTraceDumpInstructions : public CommandObjectParsed {
m_count = count;
break;
}
+ case 'a': {
+ m_count = std::numeric_limits<decltype(m_count)>::max();
+ break;
+ }
case 's': {
int32_t skip;
if (option_arg.empty() || option_arg.getAsInteger(0, skip) || skip < 0)
@@ -2148,6 +2152,10 @@ class CommandObjectTraceDumpInstructions : public CommandObjectParsed {
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 @@ class CommandObjectTraceDumpInstructions : public CommandObjectParsed {
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 @@ class CommandObjectTraceDumpInstructions : public CommandObjectParsed {
void OptionParsingStarting(ExecutionContext *execution_context) override {
m_count = kDefaultCount;
m_continue = false;
+ m_output_file = llvm::None;
m_dumper_options = {};
}
@@ -2189,6 +2207,7 @@ class CommandObjectTraceDumpInstructions : public CommandObjectParsed {
// Instance variables to hold the values for command options.
size_t m_count;
size_t m_continue;
+ llvm::Optional<FileSpec> m_output_file;
TraceInstructionDumperOptions m_dumper_options;
};
@@ -2238,27 +2257,44 @@ class CommandObjectTraceDumpInstructions : public CommandObjectParsed {
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<StreamFile> 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
index 4ab1f375eccdc..9da3ca36b5af6 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1120,6 +1120,9 @@ let Command = "thread trace dump instructions" in {
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 @@ let Command = "thread trace dump instructions" in {
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.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp
index 8e02c9f97aec2..0e06905f027de 100644
--- a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp
+++ b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp
@@ -118,7 +118,7 @@ TraceCursorIntelPT::GetInstructionControlFlowType() {
}
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 @@ bool TraceCursorIntelPT::GoToId(user_id_t id) {
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/Plugins/Trace/intel-pt/TraceCursorIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h
index 7b5431ab1822c..48ab738ba4578 100644
--- a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h
+++ b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h
@@ -41,6 +41,8 @@ class TraceCursorIntelPT : public TraceCursor {
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/Target/TraceInstructionDumper.cpp b/lldb/source/Target/TraceInstructionDumper.cpp
index 1bb99c41ef722..2da11eae7ca01 100644
--- 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;
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<const char *> 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 @@ static bool FileLineAndColumnMatches(const LineEntry &a, const LineEntry &b) {
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 @@ static bool FileLineAndColumnMatches(const LineEntry &a, const LineEntry &b) {
/// - 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 @@ IsSameInstructionSymbolContext(const InstructionSymbolInfo &prev_insn,
return curr_line_valid == prev_line_valid;
}
-void TraceInstructionDumper::DumpInstructionSymbolContext(
- const Optional<InstructionSymbolInfo> &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::string>(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<TraceInstructionDumper::OutputWriter>
+CreateWriter(Stream &s, const TraceInstructionDumperOptions &options) {
+ if (options.json)
+ return std::unique_ptr<TraceInstructionDumper::OutputWriter>(
+ new OutputWriterJSON(s, options));
+ else
+ return std::unique_ptr<TraceInstructionDumper::OutputWriter>(
+ 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<uint64_t> 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 @@ void TraceInstructionDumper::PrintEvents() {
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 @@ CalculateSymbolContext(const Address &address,
/// Find the disassembler for the given address reusing the previous
/// instruction's disassembler when possible.
static std::tuple<DisassemblerSP, InstructionSP>
-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<lldb::user_id_t>
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<lldb::user_id_t> last_id;
ExecutionContext exe_ctx;
@@ -247,63 +377,50 @@ TraceInstructionDumper::DumpInstructions(size_t count) {
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
index 7bf3c1006ddbc..50214d273e184 100644
--- a/lldb/test/API/commands/trace/TestTraceDumpInstructions.py
+++ b/lldb/test/API/commands/trace/TestTraceDumpInstructions.py
@@ -28,6 +28,71 @@ def testErrorMessages(self):
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 @@ def testWrongImage(self):
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 @@ def testWrongCPU(self):
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 @@ def testMultiFileTraceWithMissingModule(self):
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 @@ def testMultiFileTraceWithMissingModule(self):
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
index 80783dbf0ac30..0c0a110289d4d 100644
--- a/lldb/test/API/commands/trace/TestTraceLoad.py
+++ b/lldb/test/API/commands/trace/TestTraceLoad.py
@@ -13,11 +13,11 @@ def testLoadMultiCoreTrace(self):
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 @@ def testLoadMultiCoreTraceWithStringNumbers(self):
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 @@ def testLoadMultiCoreTraceWithMissingThreads(self):
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
index a50363e44f640..d4aca1cf0946c 100644
--- a/lldb/test/API/commands/trace/TestTraceTSC.py
+++ b/lldb/test/API/commands/trace/TestTraceTSC.py
@@ -17,7 +17,7 @@ def testTscPerThread(self):
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 @@ def testTscPerProcess(self):
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 @@ def testDumpingAfterTracingWithoutTsc(self):
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):
More information about the lldb-commits
mailing list