wallace created this revision.
wallace added a reviewer: clayborg.
Herald added subscribers: lldb-commits, dang.
Herald added a reviewer: JDevlieghere.
Herald added a project: LLDB.
wallace requested review of this revision.

Depends on D87589 <https://reviews.llvm.org/D87589>.

In D87589 <https://reviews.llvm.org/D87589> I added the basic instruction 
decoding functionality and the Dump action was printing the raw strings. I'm 
now pretty printing the instructions, with an output like the following:

  pid: '1234', tid: '1981309'
    a.out`main
    [57] 0x400549 <+13>: movl   %eax, -0x4(%rbp)
    a.out`bar()
    [56] 0x40053b <+46>: retq
    [55] 0x40053a <+45>: leave
    [54] 0x400537 <+42>: movl   -0x4(%rbp), %eax
    [53] 0x400535 <+40>: jle    0x400525                  ; <+24> at main.cpp:7
    [52] 0x400531 <+36>: cmpl   $0x3, -0x8(%rbp)
    [51] 0x40052d <+32>: addl   $0x1, -0x8(%rbp)
    [50] 0x40052a <+29>: addl   %eax, -0x4(%rbp)
    a.out`foo()
    [49] 0x400567 <+15>: retq
    [48] 0x400566 <+14>: popq   %rbp
    [47] 0x400563 <+11>: movl   -0x4(%rbp), %eax
    [46] 0x40055c <+4>: movl   $0x2a, -0x4(%rbp)
    [45] 0x400559 <+1>: movq   %rsp, %rbp
    [44] 0x400558 <+0>: pushq  %rbp

There's also a --raw flag that prints simply the instructions.

An important remark is that the decoder can fail to decode some instructions, 
which we are printing, for example:

pid: '1234', tid: '3842849'

  [4] 0x400529 <+28>: cmpl   $0x3, -0x8(%rbp)
  [3] error -13. 'no memory mapped at this address'
  [2] 0x40052d <+32>: jle    0x400521  

As an implementation note, I'm using lldb's Disassembler. I couldn't move the 
entire printing logic to the disassembler because it assumes that all 
instuctions printed are valid, which conflicts with what the intel-pt decoder 
outputs.

Finally, the instruction dumping command is as follows:

  trace dump -i [-rv] [-c <count>] [-o <offset>] [-t <thread-id>]


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D87730

Files:
  lldb/include/lldb/Core/Disassembler.h
  lldb/include/lldb/Target/Trace.h
  lldb/source/Commands/CommandObjectTrace.cpp
  lldb/source/Commands/Options.td
  lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp
  lldb/test/API/commands/trace/TestTraceDump.py
  lldb/test/API/commands/trace/intelpt-trace-multi-function/a.out
  lldb/test/API/commands/trace/intelpt-trace-multi-function/main.cpp
  lldb/test/API/commands/trace/intelpt-trace-multi-function/multi-function.trace
  lldb/test/API/commands/trace/intelpt-trace-multi-function/other.cpp
  lldb/test/API/commands/trace/intelpt-trace-multi-function/other.h
  lldb/test/API/commands/trace/intelpt-trace-multi-function/trace.json

Index: lldb/test/API/commands/trace/intelpt-trace-multi-function/trace.json
===================================================================
--- /dev/null
+++ lldb/test/API/commands/trace/intelpt-trace-multi-function/trace.json
@@ -0,0 +1,31 @@
+{
+  "trace": {
+    "type": "intel-pt",
+    "pt_cpu": {
+      "vendor": "intel",
+      "family": 6,
+      "model": 79,
+      "stepping": 1
+    }
+  },
+  "processes": [
+    {
+      "pid": 1234,
+      "triple": "x86_64-*-linux",
+      "threads": [
+        {
+          "tid": 1981309,
+          "traceFile": "multi-function.trace"
+        }
+      ],
+      "modules": [
+        {
+          "file": "a.out",
+          "systemPath": "a.out",
+          "loadAddress": "0x0000000000400000",
+          "uuid": "13A5952D-66DE-EE86-C44F-CD91816AE259-CD06AB8D"
+        }
+      ]
+    }
+  ]
+}
Index: lldb/test/API/commands/trace/intelpt-trace-multi-function/other.h
===================================================================
--- /dev/null
+++ lldb/test/API/commands/trace/intelpt-trace-multi-function/other.h
@@ -0,0 +1 @@
+int foo();
Index: lldb/test/API/commands/trace/intelpt-trace-multi-function/other.cpp
===================================================================
--- /dev/null
+++ lldb/test/API/commands/trace/intelpt-trace-multi-function/other.cpp
@@ -0,0 +1,4 @@
+int foo() {
+  int x = 42;
+  return x;
+}
Index: lldb/test/API/commands/trace/intelpt-trace-multi-function/main.cpp
===================================================================
--- /dev/null
+++ lldb/test/API/commands/trace/intelpt-trace-multi-function/main.cpp
@@ -0,0 +1,16 @@
+#include "other.h"
+
+int bar() {
+  int ret = 0;
+
+  for (int i = 0; i < 4; i++)
+    ret += foo();
+
+  return ret;
+}
+
+int main() {
+  int ret = bar();
+
+  return ret > 0;
+}
Index: lldb/test/API/commands/trace/TestTraceDump.py
===================================================================
--- lldb/test/API/commands/trace/TestTraceDump.py
+++ lldb/test/API/commands/trace/TestTraceDump.py
@@ -51,51 +51,113 @@
         trace_definition_file = os.path.join(src_dir, "intelpt-trace", "trace2.json")
         self.expect("trace load " + trace_definition_file)
 
+        # Dump the instructions of the currently selected thread in raw mode
+        self.expect("trace dump -i -r", substrs=['''Instructions:
+pid: '2', tid: '21'
+  0x40052d
+  0x400529
+  0x400525
+  0x400521
+  0x40052d
+  0x400529
+  0x400525
+  0x400521
+  0x40052d
+  0x400529'''])
+
         # Dump the instructions of the currently selected thread
         self.expect("trace dump -i", substrs=['''Instructions:
 pid: '2', tid: '21'
-  4195629
-  4195625
-  4195621
-  4195617
-  4195629
-  4195625
-  4195621
-  4195617
-  4195629
-  4195625'''])
+  a.out`main
+  [21] 0x40052d <+32>: jle    0x400521                  ; <+20> at main.cpp:5
+  [20] 0x400529 <+28>: cmpl   $0x3, -0x8(%rbp)
+  [19] 0x400525 <+24>: addl   $0x1, -0x8(%rbp)
+  [18] 0x400521 <+20>: xorl   $0x1, -0x4(%rbp)
+  [17] 0x40052d <+32>: jle    0x400521                  ; <+20> at main.cpp:5
+  [16] 0x400529 <+28>: cmpl   $0x3, -0x8(%rbp)
+  [15] 0x400525 <+24>: addl   $0x1, -0x8(%rbp)
+  [14] 0x400521 <+20>: xorl   $0x1, -0x4(%rbp)
+  [13] 0x40052d <+32>: jle    0x400521                  ; <+20> at main.cpp:5
+  [12] 0x400529 <+28>: cmpl   $0x3, -0x8(%rbp)'''])
 
         # Dump the instructions of one specific thread
         self.expect("trace dump -i -t 22", substrs=['''Instructions:
 pid: '2', tid: '22'
-  4195629
-  4195625
-  4195621
-  4195617
-  4195629
-  4195625
-  4195621
-  4195617
-  4195629
-  4195625'''])
+  a.out`main
+  [21] 0x40052d <+32>: jle    0x400521                  ; <+20> at main.cpp:5
+  [20] 0x400529 <+28>: cmpl   $0x3, -0x8(%rbp)
+  [19] 0x400525 <+24>: addl   $0x1, -0x8(%rbp)
+  [18] 0x400521 <+20>: xorl   $0x1, -0x4(%rbp)
+  [17] 0x40052d <+32>: jle    0x400521                  ; <+20> at main.cpp:5
+  [16] 0x400529 <+28>: cmpl   $0x3, -0x8(%rbp)
+  [15] 0x400525 <+24>: addl   $0x1, -0x8(%rbp)
+  [14] 0x400521 <+20>: xorl   $0x1, -0x4(%rbp)
+  [13] 0x40052d <+32>: jle    0x400521                  ; <+20> at main.cpp:5
+  [12] 0x400529 <+28>: cmpl   $0x3, -0x8(%rbp)'''])
 
         # Dump the instructions of two threads
         self.expect("trace dump -i -t 22 -t 21", substrs=[
           'Instructions:',
-          "pid: '2', tid: '21'\n  4195629",
-          "pid: '2', tid: '22'\n  4195629"
+          "pid: '2', tid: '21'\n  a.out`main\n  [21] 0x40052d <+32>: jle    0x400521",
+          "pid: '2', tid: '22'\n  a.out`main\n  [21] 0x40052d <+32>: jle    0x400521"
         ])
 
         # Dump specific instructions using --offset and --count
         self.expect("trace dump -i -t 22 -c 3 -o 3", substrs=['''Instructions:
 pid: '2', tid: '22'
-  4195617
-  4195629
-  4195625'''])
+  a.out`main
+  [18] 0x400521 <+20>: xorl   $0x1, -0x4(%rbp)
+  [17] 0x40052d <+32>: jle    0x400521                  ; <+20> at main.cpp:5
+  [16] 0x400529 <+28>: cmpl   $0x3, -0x8(%rbp)'''])
 
         # Check errors of a failed decoding
         trace_definition_file = os.path.join(src_dir, "intelpt-trace", "trace_bad_image.json")
         self.expect("trace load " + trace_definition_file)
         self.expect("trace dump -i", substrs=['''Instructions:
 pid: '1234', tid: '3842849'
-  error -13. 'no memory mapped at this address'''])
+  [3] error -13. 'no memory mapped at this address'''])
+
+    def testDumpMultiFunctionTrace(self):
+        self.expect("trace dump", substrs=["error: no trace data in this target"])
+
+        src_dir = self.getSourceDir()
+        trace_definition_file = os.path.join(src_dir, "intelpt-trace-multi-function", "trace.json")
+        self.expect("trace load " + trace_definition_file)
+        self.expect("trace dump -i -c 30", substrs=['''Instructions:
+pid: '1234', tid: '1981309'
+  a.out`main
+  [57] 0x400549 <+13>: movl   %eax, -0x4(%rbp)
+  a.out`bar()
+  [56] 0x40053b <+46>: retq
+  [55] 0x40053a <+45>: leave
+  [54] 0x400537 <+42>: movl   -0x4(%rbp), %eax
+  [53] 0x400535 <+40>: jle    0x400525                  ; <+24> at main.cpp:7
+  [52] 0x400531 <+36>: cmpl   $0x3, -0x8(%rbp)
+  [51] 0x40052d <+32>: addl   $0x1, -0x8(%rbp)
+  [50] 0x40052a <+29>: addl   %eax, -0x4(%rbp)
+  a.out`foo()
+  [49] 0x400567 <+15>: retq
+  [48] 0x400566 <+14>: popq   %rbp
+  [47] 0x400563 <+11>: movl   -0x4(%rbp), %eax
+  [46] 0x40055c <+4>: movl   $0x2a, -0x4(%rbp)
+  [45] 0x400559 <+1>: movq   %rsp, %rbp
+  [44] 0x400558 <+0>: pushq  %rbp
+  a.out`bar()
+  [43] 0x400525 <+24>: callq  0x400558                  ; foo at other.cpp:1
+  [42] 0x400535 <+40>: jle    0x400525                  ; <+24> at main.cpp:7
+  [41] 0x400531 <+36>: cmpl   $0x3, -0x8(%rbp)
+  [40] 0x40052d <+32>: addl   $0x1, -0x8(%rbp)
+  [39] 0x40052a <+29>: addl   %eax, -0x4(%rbp)
+  a.out`foo()
+  [38] 0x400567 <+15>: retq
+  [37] 0x400566 <+14>: popq   %rbp
+  [36] 0x400563 <+11>: movl   -0x4(%rbp), %eax
+  [35] 0x40055c <+4>: movl   $0x2a, -0x4(%rbp)
+  [34] 0x400559 <+1>: movq   %rsp, %rbp
+  [33] 0x400558 <+0>: pushq  %rbp
+  a.out`bar()
+  [32] 0x400525 <+24>: callq  0x400558                  ; foo at other.cpp:1
+  [31] 0x400535 <+40>: jle    0x400525                  ; <+24> at main.cpp:7
+  [30] 0x400531 <+36>: cmpl   $0x3, -0x8(%rbp)
+  [29] 0x40052d <+32>: addl   $0x1, -0x8(%rbp)
+  [28] 0x40052a <+29>: addl   %eax, -0x4(%rbp)'''])
Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp
===================================================================
--- lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp
+++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp
@@ -9,13 +9,17 @@
 #include "TraceIntelPT.h"
 
 #include <algorithm>
+#include <sstream>
 
 #include "llvm/Support/Format.h"
 #include "llvm/Support/FormatVariadic.h"
 
 #include "TraceIntelPTSettingsParser.h"
 #include "lldb/Core/Disassembler.h"
+#include "lldb/Core/Module.h"
 #include "lldb/Core/PluginManager.h"
+#include "lldb/Interpreter/CommandInterpreter.h"
+#include "lldb/Interpreter/CommandReturnObject.h"
 #include "lldb/Target/Process.h"
 
 using namespace lldb;
@@ -56,19 +60,127 @@
 
 void TraceIntelPT::DumpSettingsFile(Stream &s) const {
   s << "Settings:\n";
-  s.Indent();
   std::string str;
   llvm::raw_string_ostream OS(str);
   json::Object obj = GetSettings();
   OS << llvm::formatv("{0:2}", json::Value(std::move(obj)));
   OS.flush();
   s << OS.str();
-  s.IndentLess();
 
   s << "\n\nSettings directory:\n";
-  s.Indent();
   s << GetSettingsDir() << "\n\n";
-  s.IndentLess();
+}
+
+void DumpInstructionSymbolHeader(Stream &s, const ModuleSP &module_sp,
+                                 Symbol *symbol) {
+  if (module_sp)
+    s.Format("{0:F}`", module_sp->GetObjectFile()->GetFileSpec());
+  else
+    s << "(unknown module)`";
+
+  if (symbol == nullptr)
+    s << "(unknown symbol)";
+  else
+    s << symbol->GetDisplayName().AsCString();
+  s << "\n";
+}
+
+/// Disassemble an instruction given an address and return a string like the
+/// following 0x400525 <+24>: addl   $0x1, -0x8(%rbp)
+///
+/// This uses \a lldb_private::Disassembler, which outputs a prefix line like
+/// "a.out`main" if there's symbol information of the address. We don't need
+/// that prefix, so we remove it if present.
+///
+/// \return
+///   The portion of the Disassembler output corresponding exactly to the
+///   instruction, or an empty string if disassembling failed.
+std::string DisassembleInstruction(Target &target, const Address &addr) {
+  ExecutionContext exe_ctx;
+  target.CalculateExecutionContext(exe_ctx);
+  CommandReturnObject result(/*colors*/ true);
+  Disassembler::Limit limit = {Disassembler::Limit::Instructions, 1};
+  if (Disassembler::Disassemble(
+          target.GetDebugger(), target.GetArchitecture(),
+          /*plugin_name*/ nullptr, /*flavor_string*/ nullptr, exe_ctx, addr,
+          limit, /*show_mixed*/ false,
+          /*context_lines*/ 0, /*options*/ 0, result.GetOutputStream())) {
+    StringRef full_disassembly = result.GetOutputData().trim();
+    return full_disassembly.substr(full_disassembly.find_last_of('\n') + 1)
+        .str();
+  }
+  return "";
+}
+
+/// Dump an instruction and print a module`symbol header if this instruction
+/// belongs to a different symbol than the previous instruction.
+///
+/// \return
+///     A pointer to the symbol containing this instruction, or nullptr in case
+///     of an error
+Symbol *DumpInstruction(Stream &s, const IntelPTInstruction &insn,
+                        Target &target, Symbol *prev_symbol, size_t index,
+                        bool raw) {
+  Symbol *symbol = nullptr;
+
+  s << "\n";
+  if (insn.IsError()) {
+    s.Indent();
+    s.Printf("[%zu] ", index);
+    s.Printf("error %d. '%s'", insn.GetErrorCode(), insn.GetErrorMessage());
+    return symbol;
+  }
+  if (raw) {
+    s.Indent();
+    s.Printf("0x%" PRIx64, insn.GetIP());
+    return symbol;
+  }
+
+  Address addr;
+  ModuleSP module_sp;
+  std::string disassembly;
+  if (target.ResolveLoadAddress(insn.GetIP(), addr)) {
+    symbol = addr.CalculateSymbolContextSymbol();
+    module_sp = addr.CalculateSymbolContextModule();
+    disassembly = DisassembleInstruction(target, addr);
+  }
+
+  if (symbol != prev_symbol) {
+    s.Indent();
+    DumpInstructionSymbolHeader(s, module_sp, symbol);
+  }
+
+  s.Indent();
+  s.Printf("[%zu] ", index);
+  if (!disassembly.empty())
+    s << disassembly;
+  else
+    s.Printf("%" PRIx64, insn.GetIP());
+  return symbol;
+}
+
+void DumpInstructions(Stream &s, IntelPTThread &thread,
+                      const TraceDumpOptions &options) {
+  if (llvm::Expected<const DecodedTrace &> trace =
+          thread.Decode(*options.process)) {
+    const std::vector<IntelPTInstruction> &instructions =
+        trace->GetInstructions();
+
+    int to = std::max((int)instructions.size() - (int)options.offset, 0);
+    int from = std::max((int)to - (int)options.count, 0);
+
+    Target &target = options.process->GetTarget();
+    Symbol *prev_symbol = nullptr;
+
+    for (int i = to - 1; i >= from; i--) {
+      prev_symbol = DumpInstruction(s, instructions[i], target, prev_symbol, i,
+                                    options.raw);
+    }
+  } else {
+    s << "\n";
+    s.Indent();
+    s << llvm::toString(trace.takeError());
+  }
 }
 
 void TraceIntelPT::Dump(Stream &s, const TraceDumpOptions &options) const {
@@ -81,36 +193,18 @@
     s << "Instructions:";
   }
 
-  auto dump_instructions = [&](IntelPTThread &thread) {
-    if (llvm::Expected<const DecodedTrace &> trace =
-            thread.Decode(*options.process)) {
-      const std::vector<IntelPTInstruction> &instructions =
-          trace->GetInstructions();
-
-      int to = std::max((int)instructions.size() - (int)options.offset, 0);
-      int from = std::max((int)to - (int)options.count, 0);
-
-      for (int i = to - 1; i >= from; i--) {
-        const IntelPTInstruction &insn = instructions[i];
-        if (insn.IsError())
-          s.Printf("\n  error %d. '%s'", insn.GetErrorCode(),
-                   insn.GetErrorMessage());
-        else
-          s.Printf("\n  %" PRIu64, insn.GetIP());
-      }
-    } else {
-      s << "\n" << llvm::toString(trace.takeError());
-    }
-  };
-
   auto dump_thread = [&](lldb::pid_t pid, IntelPTThread &thread) {
     s.Printf("\npid: '%" PRIu64 "', tid: '%" PRIu64 "'", pid,
              thread.GetThreadID());
 
-    if (options.category == TraceDumpCategory::GenericInfo)
-      s << "\n  " << thread.GetTraceFile();
-    else if (options.category == TraceDumpCategory::Instructions)
-      dump_instructions(thread);
+    s.IndentMore();
+    if (options.category == TraceDumpCategory::GenericInfo) {
+      s << "\n";
+      s.Indent();
+      s << thread.GetTraceFile();
+    } else if (options.category == TraceDumpCategory::Instructions)
+      DumpInstructions(s, thread, options);
+    s.IndentLess();
   };
 
   // We go through all the processes and threads even when there are filters as
Index: lldb/source/Commands/Options.td
===================================================================
--- lldb/source/Commands/Options.td
+++ lldb/source/Commands/Options.td
@@ -1181,7 +1181,7 @@
     Desc<"Show verbose trace information.">;
   def trace_dump_thread_id : Option<"thread-id", "t">, Groups<[1,2]>,
     Arg<"ThreadID">, Desc<"The thread id to dump trace information of. Defaults to the currently selected thread. Can be specified multiple times">;
-  def trace_dump_instructions: Option<"instructions", "i">, Group<2>,
+  def trace_dump_instructions: Option<"instructions", "i">, Group<2>, Required,
     Desc<"Display the last instructions from the trace.">;
   def trace_dump_count : Option<"count", "c">, Group<2>,
     Arg<"Count">,
@@ -1189,6 +1189,8 @@
   def trace_dump_offset: Option<"offset", "o">, Group<2>,
     Arg<"Offset">,
     Desc<"Offset from the last position to start displaying instructions from. Defaults to 0.">;
+  def trace_dump_raw: Option<"raw", "r">, Group<2>,
+    Desc<"Print raw addresses without any additional information.">;
 }
 
 let Command = "trace schema" in {
Index: lldb/source/Commands/CommandObjectTrace.cpp
===================================================================
--- lldb/source/Commands/CommandObjectTrace.cpp
+++ lldb/source/Commands/CommandObjectTrace.cpp
@@ -183,6 +183,10 @@
           m_dump_options.offset = offset;
         break;
       }
+      case 'r': {
+        m_dump_options.raw = true;
+        break;
+      }
       default:
         llvm_unreachable("Unimplemented option");
       }
Index: lldb/include/lldb/Target/Trace.h
===================================================================
--- lldb/include/lldb/Target/Trace.h
+++ lldb/include/lldb/Target/Trace.h
@@ -29,6 +29,7 @@
   Process *process = nullptr; // Must be specified
   std::set<lldb::tid_t> tids; // Thread IDs, if empty dump all threads
   bool verbose = false;
+  bool raw = false;
   TraceDumpCategory category = TraceDumpCategory::GenericInfo;
 
   size_t count = 10;
Index: lldb/include/lldb/Core/Disassembler.h
===================================================================
--- lldb/include/lldb/Core/Disassembler.h
+++ lldb/include/lldb/Core/Disassembler.h
@@ -282,16 +282,16 @@
   /// @param[in] ignore_calls
   ///     It true, then fine the first branch instruction that isn't
   ///     a function call (a branch that calls and returns to the next
-  ///     instruction). If false, find the instruction index of any 
+  ///     instruction). If false, find the instruction index of any
   ///     branch in the list.
-  ///     
+  ///
   /// @param[out] found_calls
-  ///     If non-null, this will be set to true if any calls were found in 
+  ///     If non-null, this will be set to true if any calls were found in
   ///     extending the range.
-  ///    
+  ///
   /// @return
   ///     The instruction index of the first branch that is at or past
-  ///     \a start. Returns UINT32_MAX if no matching branches are 
+  ///     \a start. Returns UINT32_MAX if no matching branches are
   ///     found.
   //------------------------------------------------------------------
   uint32_t GetIndexOfNextBranchInstruction(uint32_t start,
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
  • [Lldb-commits] [PATCH] D... walter erquinigo via Phabricator via lldb-commits

Reply via email to