[trace][intel-pt] Scaffold the 'thread trace start | stop' commands
authorWalter Erquinigo <a20012251@gmail.com>
Tue, 27 Oct 2020 04:22:06 +0000 (21:22 -0700)
committerWalter Erquinigo <a20012251@gmail.com>
Thu, 19 Nov 2020 02:24:36 +0000 (18:24 -0800)
Depends on D90490.

The stop command is simple and invokes the new method Trace::StopTracingThread(thread).

On the other hand, the start command works by delegating its implementation to a CommandObject provided by the Trace plugin. This is necessary because each trace plugin needs different options for this command. There's even the chance that a Trace plugin can't support live tracing, but instead supports offline decoding and analysis, which means that "thread trace dump instructions" works but "thread trace start" doest. Because of this and a few other reasons, it's better to have each plugin provide this implementation.

Besides, I'm using the GetSupportedTraceType method introduced in D90490 to quickly infer what's the trace plug-in that works for the current process.

As an implementation note, I moved CommandObjectIterateOverThreads to its header so that I can use it from the IntelPT plugin. Besides, the actual start and stop logic for intel-pt is not part of this diff.

Reviewed By: clayborg

Differential Revision: https://reviews.llvm.org/D90729

30 files changed:
lldb/include/lldb/Core/PluginManager.h
lldb/include/lldb/Interpreter/CommandObjectMultiword.h
lldb/include/lldb/Target/PostMortemProcess.h [new file with mode: 0644]
lldb/include/lldb/Target/Process.h
lldb/include/lldb/Target/ProcessTrace.h
lldb/include/lldb/Target/Trace.h
lldb/include/lldb/lldb-enumerations.h
lldb/include/lldb/lldb-private-interfaces.h
lldb/source/Commands/CMakeLists.txt
lldb/source/Commands/CommandObjectMultiword.cpp
lldb/source/Commands/CommandObjectThread.cpp
lldb/source/Commands/CommandObjectThreadUtil.cpp [new file with mode: 0644]
lldb/source/Commands/CommandObjectThreadUtil.h [new file with mode: 0644]
lldb/source/Core/PluginManager.cpp
lldb/source/Interpreter/CommandObject.cpp
lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp
lldb/source/Plugins/Process/elf-core/ProcessElfCore.h
lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp
lldb/source/Plugins/Process/mach-core/ProcessMachCore.h
lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp
lldb/source/Plugins/Process/minidump/ProcessMinidump.h
lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt
lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp [new file with mode: 0644]
lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h [new file with mode: 0644]
lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp
lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td [new file with mode: 0644]
lldb/source/Target/Process.cpp
lldb/source/Target/ProcessTrace.cpp
lldb/test/API/commands/trace/TestTraceDumpInstructions.py
lldb/test/API/commands/trace/TestTraceStartStop.py [new file with mode: 0644]

index 77ace59..0ac8308 100644 (file)
@@ -333,12 +333,17 @@ public:
   // Trace
   static bool RegisterPlugin(ConstString name, const char *description,
                              TraceCreateInstance create_callback,
-                             llvm::StringRef schema);
+                             llvm::StringRef schema,
+                             TraceGetStartCommand get_start_command);
 
   static bool UnregisterPlugin(TraceCreateInstance create_callback);
 
   static TraceCreateInstance GetTraceCreateCallback(ConstString plugin_name);
 
+  static lldb::CommandObjectSP
+  GetTraceStartCommand(llvm::StringRef plugin_name,
+                       CommandInterpreter &interpreter);
+
   /// Get the JSON schema for a trace session file corresponding to the given
   /// plugin.
   ///
index 6b383f8..f330a74 100644 (file)
@@ -82,6 +82,10 @@ public:
   // for this object.
   virtual CommandObject *GetProxyCommandObject() = 0;
 
+  llvm::StringRef GetSyntax() override;
+
+  llvm::StringRef GetHelp() override;
+
   llvm::StringRef GetHelpLong() override;
 
   bool IsRemovable() const override;
@@ -121,6 +125,11 @@ public:
   const char *GetRepeatCommand(Args &current_command_args,
                                uint32_t index) override;
 
+  /// \return
+  ///     An error message to be displayed when the command is executed (i.e.
+  ///     Execute is called) and \a GetProxyCommandObject returned null.
+  virtual llvm::StringRef GetUnsupportedError();
+
   bool Execute(const char *args_string, CommandReturnObject &result) override;
 
 protected:
diff --git a/lldb/include/lldb/Target/PostMortemProcess.h b/lldb/include/lldb/Target/PostMortemProcess.h
new file mode 100644 (file)
index 0000000..353bfc0
--- /dev/null
@@ -0,0 +1,32 @@
+//===-- PostMortemProcess.h -------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TARGET_POSTMORTEMPROCESS_H
+#define LLDB_TARGET_POSTMORTEMPROCESS_H
+
+#include "lldb/Target/Process.h"
+
+namespace lldb_private {
+
+/// \class PostMortemProcess
+/// Base class for all processes that don't represent a live process, such as
+/// coredumps or processes traced in the past.
+///
+/// \a lldb_private::Process virtual functions overrides that are common
+/// between these kinds of processes can have default implementations in this
+/// class.
+class PostMortemProcess : public Process {
+public:
+  using Process::Process;
+
+  bool IsLiveDebugSession() const override { return false; }
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_TARGET_POSTMORTEMPROCESS_H
index 2c21b80..7f1e1e8 100644 (file)
@@ -1400,6 +1400,8 @@ public:
   ///     otherwise.
   virtual bool IsAlive();
 
+  virtual bool IsLiveDebugSession() const { return true; };
+
   /// Before lldb detaches from a process, it warns the user that they are
   /// about to lose their debug session. In some cases, this warning doesn't
   /// need to be emitted -- for instance, with core file debugging where the
@@ -2559,9 +2561,7 @@ void PruneThreadPlans();
   ///  \return
   ///     The supported trace type or an \a llvm::Error if tracing is
   ///     not supported for the inferior.
-  virtual llvm::Expected<TraceTypeInfo> GetSupportedTraceType() {
-    return llvm::make_error<UnimplementedError>();
-  }
+  virtual llvm::Expected<TraceTypeInfo> GetSupportedTraceType();
 
   // This calls a function of the form "void * (*)(void)".
   bool CallVoidArgVoidPtrReturn(const Address *address,
index d5a052c..f947fbe 100644 (file)
@@ -9,13 +9,13 @@
 #ifndef LLDB_TARGET_PROCESSTRACE_H
 #define LLDB_TARGET_PROCESSTRACE_H
 
-#include "lldb/Target/Process.h"
+#include "lldb/Target/PostMortemProcess.h"
 #include "lldb/Utility/ConstString.h"
 #include "lldb/Utility/Status.h"
 
 namespace lldb_private {
 
-class ProcessTrace : public Process {
+class ProcessTrace : public PostMortemProcess {
 public:
   static void Initialize();
 
index 632a7b8..3b12791 100644 (file)
@@ -13,6 +13,7 @@
 
 #include "lldb/Core/PluginInterface.h"
 #include "lldb/Utility/ArchSpec.h"
+#include "lldb/Utility/UnimplementedError.h"
 #include "lldb/lldb-private.h"
 
 namespace lldb_private {
@@ -175,6 +176,18 @@ public:
       std::function<bool(size_t index, llvm::Expected<lldb::addr_t> load_addr)>
           callback) = 0;
 
+  /// Stop tracing a live thread
+  ///
+  /// \param[in] thread
+  ///     The thread object to stop tracing.
+  ///
+  /// \return
+  ///     An \a llvm::Error if stopping tracing failed, or \b
+  ///     llvm::Error::success() otherwise.
+  virtual llvm::Error StopTracingThread(const Thread &thread) {
+    return llvm::make_error<UnimplementedError>();
+  }
+
   /// Get the number of available instructions in the trace of the given thread.
   ///
   /// \param[in] thread
index 061f385..2679ee5 100644 (file)
@@ -1081,7 +1081,12 @@ FLAGS_ENUM(CommandFlags){
     ///
     /// Verifies that there is a paused process in m_exe_ctx, if there isn't,
     /// the command will fail with an appropriate error message.
-    eCommandProcessMustBePaused = (1u << 7)};
+    eCommandProcessMustBePaused = (1u << 7),
+    /// eCommandProcessMustBeTraced
+    ///
+    /// Verifies that the process is being traced by a Trace plug-in, if it
+    /// isn't the command will fail with an appropriate error message.
+    eCommandProcessMustBeTraced = (1u << 8)};
 
 /// Whether a summary should cap how much data it returns to users or not.
 enum TypeSummaryCapping {
index 1938de5..1a4eaba 100644 (file)
@@ -114,6 +114,8 @@ typedef void (*DebuggerInitializeCallback)(Debugger &debugger);
 typedef llvm::Expected<lldb::TraceSP> (*TraceCreateInstance)(
     const llvm::json::Value &trace_session_file,
     llvm::StringRef session_file_dir, lldb_private::Debugger &debugger);
+typedef lldb::CommandObjectSP (*TraceGetStartCommand)(
+    CommandInterpreter &interpreter);
 
 } // namespace lldb_private
 
index e29a241..4f10516 100644 (file)
@@ -31,6 +31,7 @@ add_lldb_library(lldbCommands
   CommandObjectStats.cpp
   CommandObjectTarget.cpp
   CommandObjectThread.cpp
+  CommandObjectThreadUtil.cpp
   CommandObjectTrace.cpp
   CommandObjectType.cpp
   CommandObjectVersion.cpp
index 9033cfe..0f20a1d 100644 (file)
@@ -261,11 +261,32 @@ CommandObjectProxy::CommandObjectProxy(CommandInterpreter &interpreter,
 
 CommandObjectProxy::~CommandObjectProxy() = default;
 
+Options *CommandObjectProxy::GetOptions() {
+  CommandObject *proxy_command = GetProxyCommandObject();
+  if (proxy_command)
+    return proxy_command->GetOptions();
+  return CommandObject::GetOptions();
+}
+
+llvm::StringRef CommandObjectProxy::GetHelp() {
+  CommandObject *proxy_command = GetProxyCommandObject();
+  if (proxy_command)
+    return proxy_command->GetHelp();
+  return CommandObject::GetHelp();
+}
+
+llvm::StringRef CommandObjectProxy::GetSyntax() {
+  CommandObject *proxy_command = GetProxyCommandObject();
+  if (proxy_command)
+    return proxy_command->GetSyntax();
+  return CommandObject::GetSyntax();
+}
+
 llvm::StringRef CommandObjectProxy::GetHelpLong() {
   CommandObject *proxy_command = GetProxyCommandObject();
   if (proxy_command)
     return proxy_command->GetHelpLong();
-  return llvm::StringRef();
+  return CommandObject::GetHelpLong();
 }
 
 bool CommandObjectProxy::IsRemovable() const {
@@ -293,7 +314,9 @@ CommandObjectMultiword *CommandObjectProxy::GetAsMultiwordCommand() {
 void CommandObjectProxy::GenerateHelpText(Stream &result) {
   CommandObject *proxy_command = GetProxyCommandObject();
   if (proxy_command)
-    return proxy_command->GenerateHelpText(result);
+    proxy_command->GenerateHelpText(result);
+  else
+    CommandObject::GenerateHelpText(result);
 }
 
 lldb::CommandObjectSP
@@ -345,13 +368,6 @@ bool CommandObjectProxy::WantsCompletion() {
   return false;
 }
 
-Options *CommandObjectProxy::GetOptions() {
-  CommandObject *proxy_command = GetProxyCommandObject();
-  if (proxy_command)
-    return proxy_command->GetOptions();
-  return nullptr;
-}
-
 void CommandObjectProxy::HandleCompletion(CompletionRequest &request) {
   CommandObject *proxy_command = GetProxyCommandObject();
   if (proxy_command)
@@ -373,12 +389,15 @@ const char *CommandObjectProxy::GetRepeatCommand(Args &current_command_args,
   return nullptr;
 }
 
+llvm::StringRef CommandObjectProxy::GetUnsupportedError() {
+  return "command is not implemented";
+}
+
 bool CommandObjectProxy::Execute(const char *args_string,
                                  CommandReturnObject &result) {
   CommandObject *proxy_command = GetProxyCommandObject();
   if (proxy_command)
     return proxy_command->Execute(args_string, result);
-  result.AppendError("command is not implemented");
-  result.SetStatus(eReturnStatusFailed);
+  result.SetError(GetUnsupportedError());
   return false;
 }
index 5aa77de..f4ce5cc 100644 (file)
@@ -10,6 +10,8 @@
 
 #include <sstream>
 
+#include "CommandObjectThreadUtil.h"
+#include "lldb/Core/PluginManager.h"
 #include "lldb/Core/ValueObject.h"
 #include "lldb/Host/OptionParser.h"
 #include "lldb/Interpreter/CommandInterpreter.h"
 using namespace lldb;
 using namespace lldb_private;
 
-// CommandObjectIterateOverThreads
-
-class CommandObjectIterateOverThreads : public CommandObjectParsed {
-
-  class UniqueStack {
-
-  public:
-    UniqueStack(std::stack<lldb::addr_t> stack_frames, uint32_t thread_index_id)
-        : m_stack_frames(stack_frames) {
-      m_thread_index_ids.push_back(thread_index_id);
-    }
-
-    void AddThread(uint32_t thread_index_id) const {
-      m_thread_index_ids.push_back(thread_index_id);
-    }
-
-    const std::vector<uint32_t> &GetUniqueThreadIndexIDs() const {
-      return m_thread_index_ids;
-    }
-
-    lldb::tid_t GetRepresentativeThread() const {
-      return m_thread_index_ids.front();
-    }
-
-    friend bool inline operator<(const UniqueStack &lhs,
-                                 const UniqueStack &rhs) {
-      return lhs.m_stack_frames < rhs.m_stack_frames;
-    }
-
-  protected:
-    // Mark the thread index as mutable, as we don't care about it from a const
-    // perspective, we only care about m_stack_frames so we keep our std::set
-    // sorted.
-    mutable std::vector<uint32_t> m_thread_index_ids;
-    std::stack<lldb::addr_t> m_stack_frames;
-  };
-
-public:
-  CommandObjectIterateOverThreads(CommandInterpreter &interpreter,
-                                  const char *name, const char *help,
-                                  const char *syntax, uint32_t flags)
-      : CommandObjectParsed(interpreter, name, help, syntax, flags) {}
-
-  ~CommandObjectIterateOverThreads() override = default;
-
-  bool DoExecute(Args &command, CommandReturnObject &result) override {
-    result.SetStatus(m_success_return);
-
-    bool all_threads = false;
-    if (command.GetArgumentCount() == 0) {
-      Thread *thread = m_exe_ctx.GetThreadPtr();
-      if (!thread || !HandleOneThread(thread->GetID(), result))
-        return false;
-      return result.Succeeded();
-    } else if (command.GetArgumentCount() == 1) {
-      all_threads = ::strcmp(command.GetArgumentAtIndex(0), "all") == 0;
-      m_unique_stacks = ::strcmp(command.GetArgumentAtIndex(0), "unique") == 0;
-    }
-
-    // Use tids instead of ThreadSPs to prevent deadlocking problems which
-    // result from JIT-ing code while iterating over the (locked) ThreadSP
-    // list.
-    std::vector<lldb::tid_t> tids;
-
-    if (all_threads || m_unique_stacks) {
-      Process *process = m_exe_ctx.GetProcessPtr();
-
-      for (ThreadSP thread_sp : process->Threads())
-        tids.push_back(thread_sp->GetID());
-    } else {
-      const size_t num_args = command.GetArgumentCount();
-      Process *process = m_exe_ctx.GetProcessPtr();
-
-      std::lock_guard<std::recursive_mutex> guard(
-          process->GetThreadList().GetMutex());
-
-      for (size_t i = 0; i < num_args; i++) {
-        uint32_t thread_idx;
-        if (!llvm::to_integer(command.GetArgumentAtIndex(i), thread_idx)) {
-          result.AppendErrorWithFormat("invalid thread specification: \"%s\"\n",
-                                       command.GetArgumentAtIndex(i));
-          result.SetStatus(eReturnStatusFailed);
-          return false;
-        }
-
-        ThreadSP thread =
-            process->GetThreadList().FindThreadByIndexID(thread_idx);
-
-        if (!thread) {
-          result.AppendErrorWithFormat("no thread with index: \"%s\"\n",
-                                       command.GetArgumentAtIndex(i));
-          result.SetStatus(eReturnStatusFailed);
-          return false;
-        }
-
-        tids.push_back(thread->GetID());
-      }
-    }
-
-    if (m_unique_stacks) {
-      // Iterate over threads, finding unique stack buckets.
-      std::set<UniqueStack> unique_stacks;
-      for (const lldb::tid_t &tid : tids) {
-        if (!BucketThread(tid, unique_stacks, result)) {
-          return false;
-        }
-      }
-
-      // Write the thread id's and unique call stacks to the output stream
-      Stream &strm = result.GetOutputStream();
-      Process *process = m_exe_ctx.GetProcessPtr();
-      for (const UniqueStack &stack : unique_stacks) {
-        // List the common thread ID's
-        const std::vector<uint32_t> &thread_index_ids =
-            stack.GetUniqueThreadIndexIDs();
-        strm.Format("{0} thread(s) ", thread_index_ids.size());
-        for (const uint32_t &thread_index_id : thread_index_ids) {
-          strm.Format("#{0} ", thread_index_id);
-        }
-        strm.EOL();
-
-        // List the shared call stack for this set of threads
-        uint32_t representative_thread_id = stack.GetRepresentativeThread();
-        ThreadSP thread = process->GetThreadList().FindThreadByIndexID(
-            representative_thread_id);
-        if (!HandleOneThread(thread->GetID(), result)) {
-          return false;
-        }
-      }
-    } else {
-      uint32_t idx = 0;
-      for (const lldb::tid_t &tid : tids) {
-        if (idx != 0 && m_add_return)
-          result.AppendMessage("");
-
-        if (!HandleOneThread(tid, result))
-          return false;
-
-        ++idx;
-      }
-    }
-    return result.Succeeded();
-  }
-
-protected:
-  // Override this to do whatever you need to do for one thread.
-  //
-  // If you return false, the iteration will stop, otherwise it will proceed.
-  // The result is set to m_success_return (defaults to
-  // eReturnStatusSuccessFinishResult) before the iteration, so you only need
-  // to set the return status in HandleOneThread if you want to indicate an
-  // error. If m_add_return is true, a blank line will be inserted between each
-  // of the listings (except the last one.)
-
-  virtual bool HandleOneThread(lldb::tid_t, CommandReturnObject &result) = 0;
-
-  bool BucketThread(lldb::tid_t tid, std::set<UniqueStack> &unique_stacks,
-                    CommandReturnObject &result) {
-    // Grab the corresponding thread for the given thread id.
-    Process *process = m_exe_ctx.GetProcessPtr();
-    Thread *thread = process->GetThreadList().FindThreadByID(tid).get();
-    if (thread == nullptr) {
-      result.AppendErrorWithFormatv("Failed to process thread #{0}.\n", tid);
-      result.SetStatus(eReturnStatusFailed);
-      return false;
-    }
-
-    // Collect the each frame's address for this call-stack
-    std::stack<lldb::addr_t> stack_frames;
-    const uint32_t frame_count = thread->GetStackFrameCount();
-    for (uint32_t frame_index = 0; frame_index < frame_count; frame_index++) {
-      const lldb::StackFrameSP frame_sp =
-          thread->GetStackFrameAtIndex(frame_index);
-      const lldb::addr_t pc = frame_sp->GetStackID().GetPC();
-      stack_frames.push(pc);
-    }
-
-    uint32_t thread_index_id = thread->GetIndexID();
-    UniqueStack new_unique_stack(stack_frames, thread_index_id);
-
-    // Try to match the threads stack to and existing entry.
-    std::set<UniqueStack>::iterator matching_stack =
-        unique_stacks.find(new_unique_stack);
-    if (matching_stack != unique_stacks.end()) {
-      matching_stack->AddThread(thread_index_id);
-    } else {
-      unique_stacks.insert(new_unique_stack);
-    }
-    return true;
-  }
-
-  ReturnStatus m_success_return = eReturnStatusSuccessFinishResult;
-  bool m_unique_stacks = false;
-  bool m_add_return = true;
-};
-
 // CommandObjectThreadBacktrace
 #define LLDB_OPTIONS_thread_backtrace
 #include "CommandOptions.inc"
@@ -2170,6 +1976,101 @@ public:
 
 // Next are the subcommands of CommandObjectMultiwordTrace
 
+// CommandObjectTraceStart
+
+/// This class works by delegating the logic to the actual trace plug-in that
+/// can support the current process.
+class CommandObjectTraceStart : public CommandObjectProxy {
+public:
+  CommandObjectTraceStart(CommandInterpreter &interpreter)
+      : CommandObjectProxy(interpreter, "thread trace start",
+                           "Start tracing threads with the corresponding trace "
+                           "plug-in for the current process.",
+                           "thread trace start [<trace-options>]") {}
+
+protected:
+  llvm::Expected<CommandObjectSP> DoGetProxyCommandObject() {
+    ProcessSP process_sp = m_interpreter.GetExecutionContext().GetProcessSP();
+
+    if (!process_sp)
+      return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                     "Process not available.");
+    if (!process_sp->IsAlive())
+      return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                     "Process must be launched.");
+
+    llvm::Expected<TraceTypeInfo> trace_type =
+        process_sp->GetSupportedTraceType();
+
+    if (!trace_type)
+      return llvm::createStringError(
+          llvm::inconvertibleErrorCode(), "Tracing is not supported. %s",
+          llvm::toString(trace_type.takeError()).c_str());
+
+    CommandObjectSP delegate_sp =
+        PluginManager::GetTraceStartCommand(trace_type->name, m_interpreter);
+    if (!delegate_sp)
+      return llvm::createStringError(
+          llvm::inconvertibleErrorCode(),
+          "No trace plug-in matches the specified type: \"%s\"",
+          trace_type->name.c_str());
+    return delegate_sp;
+  }
+
+  CommandObject *GetProxyCommandObject() override {
+    if (llvm::Expected<CommandObjectSP> delegate = DoGetProxyCommandObject()) {
+      m_delegate_sp = *delegate;
+      m_delegate_error.clear();
+      return m_delegate_sp.get();
+    } else {
+      m_delegate_sp.reset();
+      m_delegate_error = llvm::toString(delegate.takeError());
+      return nullptr;
+    }
+  }
+
+private:
+  llvm::StringRef GetUnsupportedError() override { return m_delegate_error; }
+
+  CommandObjectSP m_delegate_sp;
+  std::string m_delegate_error;
+};
+
+// CommandObjectTraceStop
+
+class CommandObjectTraceStop : public CommandObjectIterateOverThreads {
+public:
+  CommandObjectTraceStop(CommandInterpreter &interpreter)
+      : CommandObjectIterateOverThreads(
+            interpreter, "thread trace stop",
+            "Stop tracing threads. "
+            "Defaults to the current thread. Thread indices can be "
+            "specified as arguments.\n Use the thread-index \"all\" to trace "
+            "all threads.",
+            "thread trace stop [<thread-index> <thread-index> ...]",
+            eCommandRequiresProcess | eCommandTryTargetAPILock |
+                eCommandProcessMustBeLaunched | eCommandProcessMustBePaused |
+                eCommandProcessMustBeTraced) {}
+
+  ~CommandObjectTraceStop() override = default;
+
+  bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override {
+    const Thread &thread =
+        *m_exe_ctx.GetProcessPtr()->GetThreadList().FindThreadByID(tid);
+    Trace &trace = *m_exe_ctx.GetTargetSP()->GetTrace();
+
+    if (llvm::Error err = trace.StopTracingThread(thread)) {
+      result.AppendErrorWithFormat("Failed stopping thread %" PRIu64 ": %s\n",
+                                   tid, toString(std::move(err)).c_str());
+      result.SetStatus(eReturnStatusFailed);
+    }
+
+    // We don't return false on errors to try to stop as many threads as
+    // possible.
+    return true;
+  }
+};
+
 // CommandObjectTraceDumpInstructions
 #define LLDB_OPTIONS_thread_trace_dump_instructions
 #include "CommandOptions.inc"
@@ -2247,7 +2148,8 @@ public:
             "thread-index \"all\" to see all threads.",
             nullptr,
             eCommandRequiresProcess | eCommandTryTargetAPILock |
-                eCommandProcessMustBeLaunched | eCommandProcessMustBePaused),
+                eCommandProcessMustBeLaunched | eCommandProcessMustBePaused |
+                eCommandProcessMustBeTraced),
         m_options(), m_create_repeat_command_just_invoked(false) {}
 
   ~CommandObjectTraceDumpInstructions() override = default;
@@ -2278,11 +2180,6 @@ protected:
 
   bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override {
     const TraceSP &trace_sp = m_exe_ctx.GetTargetSP()->GetTrace();
-    if (!trace_sp) {
-      result.SetError("error: this thread is not being traced");
-      return false;
-    }
-
     ThreadSP thread_sp =
         m_exe_ctx.GetProcessPtr()->GetThreadList().FindThreadByID(tid);
 
@@ -2333,6 +2230,10 @@ public:
             "thread trace <subcommand> [<subcommand objects>]") {
     LoadSubCommand("dump", CommandObjectSP(new CommandObjectMultiwordTraceDump(
                                interpreter)));
+    LoadSubCommand("start",
+                   CommandObjectSP(new CommandObjectTraceStart(interpreter)));
+    LoadSubCommand("stop",
+                   CommandObjectSP(new CommandObjectTraceStop(interpreter)));
   }
 
   ~CommandObjectMultiwordTrace() override = default;
diff --git a/lldb/source/Commands/CommandObjectThreadUtil.cpp b/lldb/source/Commands/CommandObjectThreadUtil.cpp
new file mode 100644 (file)
index 0000000..b93698c
--- /dev/null
@@ -0,0 +1,158 @@
+//===-- CommandObjectThreadUtil.cpp -----------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "CommandObjectThreadUtil.h"
+
+#include "lldb/Interpreter/CommandReturnObject.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Target/Thread.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace llvm;
+
+CommandObjectIterateOverThreads::CommandObjectIterateOverThreads(
+    CommandInterpreter &interpreter, const char *name, const char *help,
+    const char *syntax, uint32_t flags)
+    : CommandObjectParsed(interpreter, name, help, syntax, flags) {}
+
+bool CommandObjectIterateOverThreads::DoExecute(Args &command,
+                                                CommandReturnObject &result) {
+  result.SetStatus(m_success_return);
+
+  bool all_threads = false;
+  if (command.GetArgumentCount() == 0) {
+    Thread *thread = m_exe_ctx.GetThreadPtr();
+    if (!thread || !HandleOneThread(thread->GetID(), result))
+      return false;
+    return result.Succeeded();
+  } else if (command.GetArgumentCount() == 1) {
+    all_threads = ::strcmp(command.GetArgumentAtIndex(0), "all") == 0;
+    m_unique_stacks = ::strcmp(command.GetArgumentAtIndex(0), "unique") == 0;
+  }
+
+  // Use tids instead of ThreadSPs to prevent deadlocking problems which
+  // result from JIT-ing code while iterating over the (locked) ThreadSP
+  // list.
+  std::vector<lldb::tid_t> tids;
+
+  if (all_threads || m_unique_stacks) {
+    Process *process = m_exe_ctx.GetProcessPtr();
+
+    for (ThreadSP thread_sp : process->Threads())
+      tids.push_back(thread_sp->GetID());
+  } else {
+    const size_t num_args = command.GetArgumentCount();
+    Process *process = m_exe_ctx.GetProcessPtr();
+
+    std::lock_guard<std::recursive_mutex> guard(
+        process->GetThreadList().GetMutex());
+
+    for (size_t i = 0; i < num_args; i++) {
+      uint32_t thread_idx;
+      if (!llvm::to_integer(command.GetArgumentAtIndex(i), thread_idx)) {
+        result.AppendErrorWithFormat("invalid thread specification: \"%s\"\n",
+                                     command.GetArgumentAtIndex(i));
+        result.SetStatus(eReturnStatusFailed);
+        return false;
+      }
+
+      ThreadSP thread =
+          process->GetThreadList().FindThreadByIndexID(thread_idx);
+
+      if (!thread) {
+        result.AppendErrorWithFormat("no thread with index: \"%s\"\n",
+                                     command.GetArgumentAtIndex(i));
+        result.SetStatus(eReturnStatusFailed);
+        return false;
+      }
+
+      tids.push_back(thread->GetID());
+    }
+  }
+
+  if (m_unique_stacks) {
+    // Iterate over threads, finding unique stack buckets.
+    std::set<UniqueStack> unique_stacks;
+    for (const lldb::tid_t &tid : tids) {
+      if (!BucketThread(tid, unique_stacks, result)) {
+        return false;
+      }
+    }
+
+    // Write the thread id's and unique call stacks to the output stream
+    Stream &strm = result.GetOutputStream();
+    Process *process = m_exe_ctx.GetProcessPtr();
+    for (const UniqueStack &stack : unique_stacks) {
+      // List the common thread ID's
+      const std::vector<uint32_t> &thread_index_ids =
+          stack.GetUniqueThreadIndexIDs();
+      strm.Format("{0} thread(s) ", thread_index_ids.size());
+      for (const uint32_t &thread_index_id : thread_index_ids) {
+        strm.Format("#{0} ", thread_index_id);
+      }
+      strm.EOL();
+
+      // List the shared call stack for this set of threads
+      uint32_t representative_thread_id = stack.GetRepresentativeThread();
+      ThreadSP thread = process->GetThreadList().FindThreadByIndexID(
+          representative_thread_id);
+      if (!HandleOneThread(thread->GetID(), result)) {
+        return false;
+      }
+    }
+  } else {
+    uint32_t idx = 0;
+    for (const lldb::tid_t &tid : tids) {
+      if (idx != 0 && m_add_return)
+        result.AppendMessage("");
+
+      if (!HandleOneThread(tid, result))
+        return false;
+
+      ++idx;
+    }
+  }
+  return result.Succeeded();
+}
+
+bool CommandObjectIterateOverThreads::BucketThread(
+    lldb::tid_t tid, std::set<UniqueStack> &unique_stacks,
+    CommandReturnObject &result) {
+  // Grab the corresponding thread for the given thread id.
+  Process *process = m_exe_ctx.GetProcessPtr();
+  Thread *thread = process->GetThreadList().FindThreadByID(tid).get();
+  if (thread == nullptr) {
+    result.AppendErrorWithFormatv("Failed to process thread #{0}.\n", tid);
+    result.SetStatus(eReturnStatusFailed);
+    return false;
+  }
+
+  // Collect the each frame's address for this call-stack
+  std::stack<lldb::addr_t> stack_frames;
+  const uint32_t frame_count = thread->GetStackFrameCount();
+  for (uint32_t frame_index = 0; frame_index < frame_count; frame_index++) {
+    const lldb::StackFrameSP frame_sp =
+        thread->GetStackFrameAtIndex(frame_index);
+    const lldb::addr_t pc = frame_sp->GetStackID().GetPC();
+    stack_frames.push(pc);
+  }
+
+  uint32_t thread_index_id = thread->GetIndexID();
+  UniqueStack new_unique_stack(stack_frames, thread_index_id);
+
+  // Try to match the threads stack to and existing entry.
+  std::set<UniqueStack>::iterator matching_stack =
+      unique_stacks.find(new_unique_stack);
+  if (matching_stack != unique_stacks.end()) {
+    matching_stack->AddThread(thread_index_id);
+  } else {
+    unique_stacks.insert(new_unique_stack);
+  }
+  return true;
+}
diff --git a/lldb/source/Commands/CommandObjectThreadUtil.h b/lldb/source/Commands/CommandObjectThreadUtil.h
new file mode 100644 (file)
index 0000000..7122982
--- /dev/null
@@ -0,0 +1,81 @@
+//===-- CommandObjectThreadUtil.h -------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_COMMANDS_COMMANDOBJECTTHREADUTIL_H
+#define LLDB_SOURCE_COMMANDS_COMMANDOBJECTTHREADUTIL_H
+
+#include "lldb/Interpreter/CommandObjectMultiword.h"
+
+namespace lldb_private {
+
+class CommandObjectIterateOverThreads : public CommandObjectParsed {
+
+  class UniqueStack {
+  public:
+    UniqueStack(std::stack<lldb::addr_t> stack_frames, uint32_t thread_index_id)
+        : m_stack_frames(stack_frames) {
+      m_thread_index_ids.push_back(thread_index_id);
+    }
+
+    void AddThread(uint32_t thread_index_id) const {
+      m_thread_index_ids.push_back(thread_index_id);
+    }
+
+    const std::vector<uint32_t> &GetUniqueThreadIndexIDs() const {
+      return m_thread_index_ids;
+    }
+
+    lldb::tid_t GetRepresentativeThread() const {
+      return m_thread_index_ids.front();
+    }
+
+    friend bool inline operator<(const UniqueStack &lhs,
+                                 const UniqueStack &rhs) {
+      return lhs.m_stack_frames < rhs.m_stack_frames;
+    }
+
+  protected:
+    // Mark the thread index as mutable, as we don't care about it from a const
+    // perspective, we only care about m_stack_frames so we keep our std::set
+    // sorted.
+    mutable std::vector<uint32_t> m_thread_index_ids;
+    std::stack<lldb::addr_t> m_stack_frames;
+  };
+
+public:
+  CommandObjectIterateOverThreads(CommandInterpreter &interpreter,
+                                  const char *name, const char *help,
+                                  const char *syntax, uint32_t flags);
+
+  ~CommandObjectIterateOverThreads() override = default;
+
+  bool DoExecute(Args &command, CommandReturnObject &result) override;
+
+protected:
+  // Override this to do whatever you need to do for one thread.
+  //
+  // If you return false, the iteration will stop, otherwise it will proceed.
+  // The result is set to m_success_return (defaults to
+  // eReturnStatusSuccessFinishResult) before the iteration, so you only need
+  // to set the return status in HandleOneThread if you want to indicate an
+  // error. If m_add_return is true, a blank line will be inserted between each
+  // of the listings (except the last one.)
+
+  virtual bool HandleOneThread(lldb::tid_t, CommandReturnObject &result) = 0;
+
+  bool BucketThread(lldb::tid_t tid, std::set<UniqueStack> &unique_stacks,
+                    CommandReturnObject &result);
+
+  lldb::ReturnStatus m_success_return = lldb::eReturnStatusSuccessFinishResult;
+  bool m_unique_stacks = false;
+  bool m_add_return = true;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_COMMANDS_COMMANDOBJECTTHREADUTIL_H
index 1f6487c..97e1e8d 100644 (file)
@@ -1010,46 +1010,59 @@ PluginManager::GetSymbolVendorCreateCallbackAtIndex(uint32_t idx) {
 
 struct TraceInstance : public PluginInstance<TraceCreateInstance> {
   TraceInstance(ConstString name, std::string description,
-                CallbackType create_callback, llvm::StringRef schema)
+                CallbackType create_callback, llvm::StringRef schema,
+                TraceGetStartCommand get_start_command)
       : PluginInstance<TraceCreateInstance>(name, std::move(description),
                                             create_callback),
-        schema(schema) {}
+        schema(schema), get_start_command(get_start_command) {}
 
   llvm::StringRef schema;
+  TraceGetStartCommand get_start_command;
 };
 
 typedef PluginInstances<TraceInstance> TraceInstances;
 
-static TraceInstances &GetTraceInstances() {
+static TraceInstances &GetTracePluginInstances() {
   static TraceInstances g_instances;
   return g_instances;
 }
 
 bool PluginManager::RegisterPlugin(ConstString name, const char *description,
                                    TraceCreateInstance create_callback,
-                                   llvm::StringRef schema) {
-  return GetTraceInstances().RegisterPlugin(name, description, create_callback,
-                                            schema);
+                                   llvm::StringRef schema,
+                                   TraceGetStartCommand get_start_command) {
+  return GetTracePluginInstances().RegisterPlugin(
+      name, description, create_callback, schema, get_start_command);
 }
 
 bool PluginManager::UnregisterPlugin(TraceCreateInstance create_callback) {
-  return GetTraceInstances().UnregisterPlugin(create_callback);
+  return GetTracePluginInstances().UnregisterPlugin(create_callback);
 }
 
 TraceCreateInstance
 PluginManager::GetTraceCreateCallback(ConstString plugin_name) {
-  return GetTraceInstances().GetCallbackForName(plugin_name);
+  return GetTracePluginInstances().GetCallbackForName(plugin_name);
 }
 
 llvm::StringRef PluginManager::GetTraceSchema(ConstString plugin_name) {
-  for (const TraceInstance &instance : GetTraceInstances().GetInstances())
+  for (const TraceInstance &instance : GetTracePluginInstances().GetInstances())
     if (instance.name == plugin_name)
       return instance.schema;
   return llvm::StringRef();
 }
 
+CommandObjectSP
+PluginManager::GetTraceStartCommand(llvm::StringRef plugin_name,
+                                    CommandInterpreter &interpreter) {
+  for (const TraceInstance &instance : GetTracePluginInstances().GetInstances())
+    if (instance.name.GetStringRef() == plugin_name)
+      return instance.get_start_command(interpreter);
+  return CommandObjectSP();
+}
+
 llvm::StringRef PluginManager::GetTraceSchema(size_t index) {
-  if (TraceInstance *instance = GetTraceInstances().GetInstanceAtIndex(index))
+  if (TraceInstance *instance =
+          GetTracePluginInstances().GetInstanceAtIndex(index))
     return instance->schema;
   return llvm::StringRef();
 }
@@ -1267,6 +1280,7 @@ void PluginManager::DebuggerInitialize(Debugger &debugger) {
   GetSymbolFileInstances().PerformDebuggerCallback(debugger);
   GetOperatingSystemInstances().PerformDebuggerCallback(debugger);
   GetStructuredDataPluginInstances().PerformDebuggerCallback(debugger);
+  GetTracePluginInstances().PerformDebuggerCallback(debugger);
 }
 
 // This is the preferred new way to register plugin specific settings.  e.g.
index 7e89faa..71adf8c 100644 (file)
@@ -258,6 +258,15 @@ bool CommandObject::CheckRequirements(CommandReturnObject &result) {
       }
     }
   }
+
+  if (GetFlags().Test(eCommandProcessMustBeTraced)) {
+    Target *target = m_exe_ctx.GetTargetPtr();
+    if (target && !target->GetTrace()) {
+      result.SetError("Process is not being traced.");
+      return false;
+    }
+  }
+
   return true;
 }
 
index aa95e92..dbc1a01 100644 (file)
@@ -97,7 +97,7 @@ bool ProcessElfCore::CanDebug(lldb::TargetSP target_sp,
 ProcessElfCore::ProcessElfCore(lldb::TargetSP target_sp,
                                lldb::ListenerSP listener_sp,
                                const FileSpec &core_file)
-    : Process(target_sp, listener_sp), m_core_file(core_file) {}
+    : PostMortemProcess(target_sp, listener_sp), m_core_file(core_file) {}
 
 // Destructor
 ProcessElfCore::~ProcessElfCore() {
index ddde3d3..9f796d6 100644 (file)
@@ -19,7 +19,7 @@
 #include <list>
 #include <vector>
 
-#include "lldb/Target/Process.h"
+#include "lldb/Target/PostMortemProcess.h"
 #include "lldb/Utility/ConstString.h"
 #include "lldb/Utility/Status.h"
 
@@ -28,7 +28,7 @@
 
 struct ThreadData;
 
-class ProcessElfCore : public lldb_private::Process {
+class ProcessElfCore : public lldb_private::PostMortemProcess {
 public:
   // Constructors and Destructors
   static lldb::ProcessSP
index 7449421..c1a8d16 100644 (file)
@@ -110,8 +110,8 @@ bool ProcessMachCore::CanDebug(lldb::TargetSP target_sp,
 ProcessMachCore::ProcessMachCore(lldb::TargetSP target_sp,
                                  ListenerSP listener_sp,
                                  const FileSpec &core_file)
-    : Process(target_sp, listener_sp), m_core_aranges(), m_core_range_infos(),
-      m_core_module_sp(), m_core_file(core_file),
+    : PostMortemProcess(target_sp, listener_sp), m_core_aranges(),
+      m_core_range_infos(), m_core_module_sp(), m_core_file(core_file),
       m_dyld_addr(LLDB_INVALID_ADDRESS),
       m_mach_kernel_addr(LLDB_INVALID_ADDRESS), m_dyld_plugin_name() {}
 
index 4bda62b..9c85139 100644 (file)
 #include <list>
 #include <vector>
 
-#include "lldb/Target/Process.h"
+#include "lldb/Target/PostMortemProcess.h"
 #include "lldb/Utility/ConstString.h"
 #include "lldb/Utility/Status.h"
 
 class ThreadKDP;
 
-class ProcessMachCore : public lldb_private::Process {
+class ProcessMachCore : public lldb_private::PostMortemProcess {
 public:
   // Constructors and Destructors
   ProcessMachCore(lldb::TargetSP target_sp, lldb::ListenerSP listener,
index 26c56e3..1850a36 100644 (file)
@@ -234,7 +234,7 @@ ProcessMinidump::ProcessMinidump(lldb::TargetSP target_sp,
                                  lldb::ListenerSP listener_sp,
                                  const FileSpec &core_file,
                                  DataBufferSP core_data)
-    : Process(target_sp, listener_sp), m_core_file(core_file),
+    : PostMortemProcess(target_sp, listener_sp), m_core_file(core_file),
       m_core_data(std::move(core_data)), m_is_wow64(false) {}
 
 ProcessMinidump::~ProcessMinidump() {
index bfdace7..1d4d535 100644 (file)
@@ -12,7 +12,7 @@
 #include "MinidumpParser.h"
 #include "MinidumpTypes.h"
 
-#include "lldb/Target/Process.h"
+#include "lldb/Target/PostMortemProcess.h"
 #include "lldb/Target/StopInfo.h"
 #include "lldb/Target/Target.h"
 #include "lldb/Utility/ConstString.h"
@@ -26,7 +26,7 @@ namespace lldb_private {
 
 namespace minidump {
 
-class ProcessMinidump : public Process {
+class ProcessMinidump : public PostMortemProcess {
 public:
   static lldb::ProcessSP CreateInstance(lldb::TargetSP target_sp,
                                         lldb::ListenerSP listener_sp,
index 1405844..a75d967 100644 (file)
@@ -9,7 +9,12 @@ include_directories(${LIBIPT_INCLUDE_PATH})
 
 find_library(LIBIPT_LIBRARY ipt PATHS ${LIBIPT_LIBRARY_PATH} REQUIRED)
 
+lldb_tablegen(TraceIntelPTCommandOptions.inc -gen-lldb-option-defs
+  SOURCE TraceIntelPTOptions.td
+  TARGET TraceIntelPTOptionsGen)
+
 add_lldb_library(lldbPluginTraceIntelPT PLUGIN
+  CommandObjectTraceStartIntelPT.cpp
   DecodedThread.cpp
   IntelPTDecoder.cpp
   TraceIntelPT.cpp
@@ -23,3 +28,6 @@ add_lldb_library(lldbPluginTraceIntelPT PLUGIN
   LINK_COMPONENTS
     Support
   )
+
+
+add_dependencies(lldbPluginTraceIntelPT TraceIntelPTOptionsGen)
diff --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp
new file mode 100644 (file)
index 0000000..e1758df
--- /dev/null
@@ -0,0 +1,73 @@
+//===-- CommandObjectTraceStartIntelPT.cpp --------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "CommandObjectTraceStartIntelPT.h"
+
+#include "lldb/Host/OptionParser.h"
+#include "lldb/Target/Trace.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::trace_intel_pt;
+using namespace llvm;
+
+#define LLDB_OPTIONS_thread_trace_start_intel_pt
+#include "TraceIntelPTCommandOptions.inc"
+
+Status CommandObjectTraceStartIntelPT::CommandOptions::SetOptionValue(
+    uint32_t option_idx, llvm::StringRef option_arg,
+    ExecutionContext *execution_context) {
+  Status error;
+  const int short_option = m_getopt_table[option_idx].val;
+
+  switch (short_option) {
+  case 's': {
+    int32_t size_in_kb;
+    if (option_arg.empty() || option_arg.getAsInteger(0, size_in_kb) ||
+        size_in_kb < 0)
+      error.SetErrorStringWithFormat("invalid integer value for option '%s'",
+                                     option_arg.str().c_str());
+    else
+      m_size_in_kb = size_in_kb;
+    break;
+  }
+  case 'c': {
+    int32_t custom_config;
+    if (option_arg.empty() || option_arg.getAsInteger(0, custom_config) ||
+        custom_config < 0)
+      error.SetErrorStringWithFormat("invalid integer value for option '%s'",
+                                     option_arg.str().c_str());
+    else
+      m_custom_config = custom_config;
+    break;
+  }
+  default:
+    llvm_unreachable("Unimplemented option");
+  }
+  return error;
+}
+
+void CommandObjectTraceStartIntelPT::CommandOptions::OptionParsingStarting(
+    ExecutionContext *execution_context) {
+  m_size_in_kb = 4;
+  m_custom_config = 0;
+}
+
+llvm::ArrayRef<OptionDefinition>
+CommandObjectTraceStartIntelPT::CommandOptions::GetDefinitions() {
+  return llvm::makeArrayRef(g_thread_trace_start_intel_pt_options);
+}
+
+bool CommandObjectTraceStartIntelPT::HandleOneThread(
+    lldb::tid_t tid, CommandReturnObject &result) {
+  result.AppendMessageWithFormat(
+      "would trace tid %" PRIu64 " with size_in_kb %zu and custom_config %d\n",
+      tid, m_options.m_size_in_kb, m_options.m_custom_config);
+  result.SetStatus(eReturnStatusSuccessFinishResult);
+  return result.Succeeded();
+}
diff --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h
new file mode 100644 (file)
index 0000000..265569c
--- /dev/null
@@ -0,0 +1,65 @@
+//===-- CommandObjectTraceStartIntelPT.h ----------------------*- C++ //-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_COMMANDOBJECTTRACESTARTINTELPT_H
+#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_COMMANDOBJECTTRACESTARTINTELPT_H
+
+#include "../../../../source/Commands/CommandObjectThreadUtil.h"
+#include "lldb/Interpreter/CommandInterpreter.h"
+#include "lldb/Interpreter/CommandReturnObject.h"
+
+namespace lldb_private {
+namespace trace_intel_pt {
+
+class CommandObjectTraceStartIntelPT : public CommandObjectIterateOverThreads {
+public:
+  class CommandOptions : public Options {
+  public:
+    CommandOptions() : Options() { OptionParsingStarting(nullptr); }
+
+    ~CommandOptions() override = default;
+
+    Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+                          ExecutionContext *execution_context) override;
+
+    void OptionParsingStarting(ExecutionContext *execution_context) override;
+
+    llvm::ArrayRef<OptionDefinition> GetDefinitions() override;
+
+    size_t m_size_in_kb;
+    uint32_t m_custom_config;
+  };
+
+  CommandObjectTraceStartIntelPT(CommandInterpreter &interpreter)
+      : CommandObjectIterateOverThreads(
+            interpreter, "thread trace start",
+            "Start tracing one or more threads with intel-pt. "
+            "Defaults to the current thread. Thread indices can be "
+            "specified as arguments.\n Use the thread-index \"all\" to trace "
+            "all threads.",
+            "thread trace start [<thread-index> <thread-index> ...] "
+            "[<intel-pt-options>]",
+            lldb::eCommandRequiresProcess | lldb::eCommandTryTargetAPILock |
+                lldb::eCommandProcessMustBeLaunched |
+                lldb::eCommandProcessMustBePaused),
+        m_options() {}
+
+  ~CommandObjectTraceStartIntelPT() override = default;
+
+  Options *GetOptions() override { return &m_options; }
+
+protected:
+  bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override;
+
+  CommandOptions m_options;
+};
+
+} // namespace trace_intel_pt
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_COMMANDOBJECTTRACESTARTINTELPT_H
index 12bbf8d..63a8b2d 100644 (file)
@@ -8,6 +8,7 @@
 
 #include "TraceIntelPT.h"
 
+#include "CommandObjectTraceStartIntelPT.h"
 #include "TraceIntelPTSessionFileParser.h"
 #include "lldb/Core/PluginManager.h"
 #include "lldb/Target/Process.h"
@@ -21,10 +22,14 @@ using namespace llvm;
 
 LLDB_PLUGIN_DEFINE(TraceIntelPT)
 
+CommandObjectSP GetStartCommand(CommandInterpreter &interpreter) {
+  return CommandObjectSP(new CommandObjectTraceStartIntelPT(interpreter));
+}
+
 void TraceIntelPT::Initialize() {
-  PluginManager::RegisterPlugin(GetPluginNameStatic(), "Intel Processor Trace",
-                                CreateInstance,
-                                TraceIntelPTSessionFileParser::GetSchema());
+  PluginManager::RegisterPlugin(
+      GetPluginNameStatic(), "Intel Processor Trace", CreateInstance,
+      TraceIntelPTSessionFileParser::GetSchema(), GetStartCommand);
 }
 
 void TraceIntelPT::Terminate() {
diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td
new file mode 100644 (file)
index 0000000..6ffe949
--- /dev/null
@@ -0,0 +1,16 @@
+include "../../../../source/Commands/OptionsBase.td"
+
+let Command = "thread trace start intel pt" in {
+  def thread_trace_start_intel_pt_size: Option<"size", "s">,
+    Group<1>,
+    Arg<"Value">,
+    Desc<"The size of the trace in KB. The kernel rounds it down to the nearest"
+         " multiple of 4. Defaults to 4.">;
+  def thread_trace_start_intel_pt_custom_config: Option<"custom-config", "c">,
+    Group<1>,
+    Arg<"Value">,
+    Desc<"Low level bitmask configuration for the kernel based on the values "
+         "in `grep -H  /sys/bus/event_source/devices/intel_pt/format/*`. "
+         "See https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/perf-intel-pt.txt"
+         " for more information. Defaults to 0.">;
+}
index d32d3df..c2effda 100644 (file)
@@ -6128,6 +6128,13 @@ UtilityFunction *Process::GetLoadImageUtilityFunction(
   return m_dlopen_utility_func_up.get();
 }
 
+llvm::Expected<TraceTypeInfo> Process::GetSupportedTraceType() {
+  if (!IsLiveDebugSession())
+    return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                   "Can't trace a non-live process.");
+  return llvm::make_error<UnimplementedError>();
+}
+
 bool Process::CallVoidArgVoidPtrReturn(const Address *address,
                                        addr_t &returned_func,
                                        bool trap_exceptions) {
index 4b8dd37..85e5380 100644 (file)
@@ -43,7 +43,7 @@ bool ProcessTrace::CanDebug(TargetSP target_sp, bool plugin_specified_by_name) {
 }
 
 ProcessTrace::ProcessTrace(TargetSP target_sp, ListenerSP listener_sp)
-    : Process(target_sp, listener_sp) {}
+    : PostMortemProcess(target_sp, listener_sp) {}
 
 ProcessTrace::~ProcessTrace() {
   Clear();
index 2a012dd..4a459f0 100644 (file)
@@ -32,7 +32,7 @@ class TestTraceDumpInstructions(TestBase):
         self.expect("run")
 
         self.expect("thread trace dump instructions",
-            substrs=["error: this thread is not being traced"],
+            substrs=["error: Process is not being traced"],
             error=True)
 
     def testRawDumpInstructions(self):
diff --git a/lldb/test/API/commands/trace/TestTraceStartStop.py b/lldb/test/API/commands/trace/TestTraceStartStop.py
new file mode 100644 (file)
index 0000000..4984365
--- /dev/null
@@ -0,0 +1,73 @@
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+from lldbsuite.test.decorators import *
+
+class TestTraceLoad(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def setUp(self):
+        TestBase.setUp(self)
+        if 'intel-pt' not in configuration.enabled_plugins:
+            self.skipTest("The intel-pt test plugin is not enabled")
+
+    def expectGenericHelpMessageForStartCommand(self):
+        self.expect("help thread trace start",
+            substrs=["Syntax: thread trace start [<trace-options>]"])
+
+    def testStartStopSessionFileThreads(self):
+        # it should fail for processes from json session files
+        self.expect("trace load -v " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"))
+        self.expect("thread trace start", error=True,
+            substrs=["error: Tracing is not supported. Can't trace a non-live process"])
+
+        # the help command should be the generic one, as it's not a live process
+        self.expectGenericHelpMessageForStartCommand()
+
+        # this should fail because 'trace stop' is not yet implemented
+        self.expect("thread trace stop", error=True,
+            substrs=["error: Failed stopping thread 3842849"])
+
+    @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64']))
+    def testStartStopLiveThreads(self):
+        # The help command should be the generic one if there's no process running
+        self.expectGenericHelpMessageForStartCommand()
+
+        self.expect("thread trace start", error=True,
+            substrs=["error: Process not available"])
+
+        self.expect("file " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out"))
+        self.expect("b main")
+
+        self.expect("thread trace start", error=True,
+            substrs=["error: Process not available"])
+
+        # The help command should be the generic one if there's still no process running
+        self.expectGenericHelpMessageForStartCommand()
+
+        self.expect("r")
+
+        # the help command should be the intel-pt one now
+        self.expect("help thread trace start",
+            substrs=["Start tracing one or more threads with intel-pt.",
+                     "Syntax: thread trace start [<thread-index> <thread-index> ...] [<intel-pt-options>]"])
+
+        self.expect("thread trace start",
+            patterns=["would trace tid .* with size_in_kb 4 and custom_config 0"])
+
+        self.expect("thread trace start --size 20 --custom-config 1",
+            patterns=["would trace tid .* with size_in_kb 20 and custom_config 1"])
+
+        # This fails because "trace stop" is not yet implemented.
+        self.expect("thread trace stop", error=True,
+            substrs=["error: Process is not being traced"])
+
+        self.expect("c")
+        # Now the process has finished, so the commands should fail
+        self.expect("thread trace start", error=True,
+            substrs=["error: Process must be launched"])
+
+        self.expect("thread trace stop", error=True,
+            substrs=["error: Process must be launched"])