[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