wallace created this revision.
wallace added a reviewer: clayborg.
Herald added subscribers: lldb-commits, dang, mgorny.
Herald added a reviewer: JDevlieghere.
Herald added a project: LLDB.
wallace requested review of this revision.
Depends on D90490 <https://reviews.llvm.org/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
<https://reviews.llvm.org/D90490> to quickly infer what's the trace plug-in
that works for the current process.
Repository:
rG LLVM Github Monorepo
https://reviews.llvm.org/D90729
Files:
lldb/include/lldb/Core/PluginManager.h
lldb/include/lldb/Target/Trace.h
lldb/include/lldb/lldb-enumerations.h
lldb/include/lldb/lldb-private-interfaces.h
lldb/source/Commands/CommandObjectThread.cpp
lldb/source/Commands/CommandObjectThread.h
lldb/source/Core/PluginManager.cpp
lldb/source/Interpreter/CommandObject.cpp
lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt
lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp
lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h
lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp
lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td
lldb/test/API/commands/trace/TestTraceDumpInstructions.py
lldb/test/API/commands/trace/TestTraceStartStop.py
Index: lldb/test/API/commands/trace/TestTraceStartStop.py
===================================================================
--- /dev/null
+++ lldb/test/API/commands/trace/TestTraceStartStop.py
@@ -0,0 +1,67 @@
+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 available for the current process. Not implemented"])
+
+ # 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("file " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out"))
+ self.expect("b main")
+
+ # 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."])
Index: lldb/test/API/commands/trace/TestTraceDumpInstructions.py
===================================================================
--- lldb/test/API/commands/trace/TestTraceDumpInstructions.py
+++ lldb/test/API/commands/trace/TestTraceDumpInstructions.py
@@ -32,7 +32,7 @@
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):
Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td
===================================================================
--- /dev/null
+++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td
@@ -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: lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp
===================================================================
--- lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp
+++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp
@@ -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 @@
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() {
Index: lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h
===================================================================
--- /dev/null
+++ lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h
@@ -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/CommandObjectThread.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: lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp
===================================================================
--- /dev/null
+++ lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp
@@ -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();
+}
Index: lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt
===================================================================
--- lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt
+++ lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt
@@ -9,7 +9,12 @@
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 @@
LINK_COMPONENTS
Support
)
+
+
+add_dependencies(lldbPluginTraceIntelPT TraceIntelPTOptionsGen)
Index: lldb/source/Interpreter/CommandObject.cpp
===================================================================
--- lldb/source/Interpreter/CommandObject.cpp
+++ lldb/source/Interpreter/CommandObject.cpp
@@ -258,6 +258,18 @@
}
}
}
+
+ if (GetFlags().Test(eCommandProcessMustBeTraced)) {
+ if (!m_exe_ctx.GetProcessPtr()) {
+ result.SetError("Process must exist.");
+ return false;
+ }
+ if (!m_exe_ctx.GetTargetPtr()->GetTrace()) {
+ result.SetError("Process is not being traced.");
+ return false;
+ }
+ }
+
return true;
}
Index: lldb/source/Core/PluginManager.cpp
===================================================================
--- lldb/source/Core/PluginManager.cpp
+++ lldb/source/Core/PluginManager.cpp
@@ -1010,46 +1010,59 @@
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 @@
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: lldb/source/Commands/CommandObjectThread.h
===================================================================
--- lldb/source/Commands/CommandObjectThread.h
+++ lldb/source/Commands/CommandObjectThread.h
@@ -13,6 +13,69 @@
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;
+};
+
class CommandObjectMultiwordThread : public CommandObjectMultiword {
public:
CommandObjectMultiwordThread(CommandInterpreter &interpreter);
Index: lldb/source/Commands/CommandObjectThread.cpp
===================================================================
--- lldb/source/Commands/CommandObjectThread.cpp
+++ lldb/source/Commands/CommandObjectThread.cpp
@@ -10,6 +10,7 @@
#include <sstream>
+#include "lldb/Core/PluginManager.h"
#include "lldb/Core/ValueObject.h"
#include "lldb/Host/OptionParser.h"
#include "lldb/Interpreter/CommandInterpreter.h"
@@ -33,202 +34,150 @@
using namespace lldb;
using namespace lldb_private;
+using namespace llvm;
// CommandObjectIterateOverThreads
-class CommandObjectIterateOverThreads : public CommandObjectParsed {
+CommandObjectIterateOverThreads::CommandObjectIterateOverThreads(
+ CommandInterpreter &interpreter, const char *name, const char *help,
+ const char *syntax, uint32_t flags)
+ : CommandObjectParsed(interpreter, name, help, syntax, flags) {}
- class UniqueStack {
+bool CommandObjectIterateOverThreads::DoExecute(Args &command,
+ CommandReturnObject &result) {
+ result.SetStatus(m_success_return);
- 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;
- }
+ 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;
+ }
- 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;
- };
+ // 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;
-public:
- CommandObjectIterateOverThreads(CommandInterpreter &interpreter,
- const char *name, const char *help,
- const char *syntax, uint32_t flags)
- : CommandObjectParsed(interpreter, name, help, syntax, flags) {}
+ if (all_threads || m_unique_stacks) {
+ Process *process = m_exe_ctx.GetProcessPtr();
- ~CommandObjectIterateOverThreads() override = default;
+ 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();
- bool DoExecute(Args &command, CommandReturnObject &result) override {
- result.SetStatus(m_success_return);
+ std::lock_guard<std::recursive_mutex> guard(
+ process->GetThreadList().GetMutex());
- bool all_threads = false;
- if (command.GetArgumentCount() == 0) {
- Thread *thread = m_exe_ctx.GetThreadPtr();
- if (!thread || !HandleOneThread(thread->GetID(), result))
+ 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;
- 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);
- 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;
+ }
- if (!thread) {
- result.AppendErrorWithFormat("no thread with index: \"%s\"\n",
- command.GetArgumentAtIndex(i));
- result.SetStatus(eReturnStatusFailed);
- return false;
- }
+ tids.push_back(thread->GetID());
+ }
+ }
- 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;
}
}
- 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();
- // 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;
- }
+ // 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("");
+ }
+ } 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;
+ if (!HandleOneThread(tid, result))
+ return false;
- ++idx;
- }
+ ++idx;
}
- return result.Succeeded();
}
+ 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;
- }
+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);
- }
+ // 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);
+ 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;
+ // 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);
}
-
- ReturnStatus m_success_return = eReturnStatusSuccessFinishResult;
- bool m_unique_stacks = false;
- bool m_add_return = true;
-};
+ return true;
+}
// CommandObjectThreadBacktrace
#define LLDB_OPTIONS_thread_backtrace
@@ -2170,6 +2119,150 @@
// 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 CommandObjectParsed {
+public:
+ CommandObjectTraceStart(CommandInterpreter &interpreter)
+ : CommandObjectParsed(
+ interpreter, "thread trace start",
+ "Start tracing threads with the corresponding trace "
+ "plug-in for the current process.",
+ "thread trace start [<trace-options>]",
+ eCommandRequiresProcess | eCommandTryTargetAPILock |
+ eCommandProcessMustBeLaunched | eCommandProcessMustBePaused) {}
+
+ ~CommandObjectTraceStart() override = default;
+
+ Options *GetOptions() override {
+ if (CommandObject *trace_plugin_command =
+ GetTracePluginCommand().command.get())
+ return trace_plugin_command->GetOptions();
+ return CommandObjectParsed::GetOptions();
+ }
+
+ llvm::StringRef GetSyntax() override {
+ if (CommandObject *trace_plugin_command =
+ GetTracePluginCommand().command.get())
+ return trace_plugin_command->GetSyntax();
+ return CommandObjectParsed::GetSyntax();
+ }
+
+ llvm::StringRef GetHelp() override {
+ if (CommandObject *trace_plugin_command =
+ GetTracePluginCommand().command.get())
+ return trace_plugin_command->GetHelp();
+ return CommandObjectParsed::GetHelp();
+ }
+
+ llvm::StringRef GetHelpLong() override {
+ if (CommandObject *trace_plugin_command =
+ GetTracePluginCommand().command.get())
+ return trace_plugin_command->GetHelpLong();
+ return CommandObjectParsed::GetHelpLong();
+ }
+
+protected:
+ struct TracePluginCommandImplementation;
+
+ bool Execute(const char *args_string, CommandReturnObject &result) override {
+ TracePluginCommandImplementation &trace_plugin_command =
+ GetTracePluginCommand();
+ if (!trace_plugin_command.command || !trace_plugin_command.process)
+ return CommandObjectParsed::Execute(args_string, result);
+
+ if (const TraceSP &trace_sp =
+ trace_plugin_command.process->GetTarget().GetTrace()) {
+ ConstString plugin_name = trace_sp->GetPluginName();
+ if (plugin_name.GetStringRef() != trace_plugin_command.plugin_name) {
+ result.AppendErrorWithFormat(
+ "error: attempted to trace with the '%s' plug-in, but this process "
+ "is already being traced with the '%s' plug-in. Stop tracing "
+ "first.",
+ trace_plugin_command.plugin_name.c_str(), plugin_name.AsCString());
+ return false;
+ }
+ }
+
+ return trace_plugin_command.command->Execute(args_string, result);
+ }
+
+ bool DoExecute(Args &command, CommandReturnObject &result) override {
+ result.AppendErrorWithFormat(
+ "tracing is not available for the current process. %s",
+ m_trace_plugin_command.error.c_str());
+ result.SetStatus(eReturnStatusFailed);
+ return false;
+ }
+
+ /// Find the implementation of the "thread trace start" command provided by
+ /// the trace plug-in that supports the current process.
+ TracePluginCommandImplementation &GetTracePluginCommand() {
+ ExecutionContext exe_ctx = m_interpreter.GetExecutionContext();
+ Process *process = exe_ctx.GetProcessPtr();
+
+ if (process != m_trace_plugin_command.process) {
+ m_trace_plugin_command = {process, /*plugin_name*/ "",
+ /*command*/ nullptr, /*error*/ ""};
+ if (process) {
+ if (llvm::Expected<TraceTypeInfo> trace_type =
+ process->GetSupportedTraceType()) {
+ m_trace_plugin_command.command = PluginManager::GetTraceStartCommand(
+ trace_type->name, m_interpreter);
+ } else
+ m_trace_plugin_command.error = toString(trace_type.takeError());
+ }
+ }
+ return m_trace_plugin_command;
+ }
+
+ /// The last value computed by \a GetTracePluginCommand to avoid
+ /// recalculations.
+ struct TracePluginCommandImplementation {
+ Process *process;
+ std::string plugin_name;
+ CommandObjectSP command;
+ std::string error;
+ } m_trace_plugin_command;
+};
+
+// 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 (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 +2340,8 @@
"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 +2372,6 @@
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 +2422,10 @@
"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;
Index: lldb/include/lldb/lldb-private-interfaces.h
===================================================================
--- lldb/include/lldb/lldb-private-interfaces.h
+++ lldb/include/lldb/lldb-private-interfaces.h
@@ -114,6 +114,8 @@
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: lldb/include/lldb/lldb-enumerations.h
===================================================================
--- lldb/include/lldb/lldb-enumerations.h
+++ lldb/include/lldb/lldb-enumerations.h
@@ -1080,7 +1080,12 @@
///
/// 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: lldb/include/lldb/Target/Trace.h
===================================================================
--- lldb/include/lldb/Target/Trace.h
+++ lldb/include/lldb/Target/Trace.h
@@ -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 {
@@ -171,6 +172,18 @@
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: lldb/include/lldb/Core/PluginManager.h
===================================================================
--- lldb/include/lldb/Core/PluginManager.h
+++ lldb/include/lldb/Core/PluginManager.h
@@ -333,12 +333,17 @@
// 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.
///
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits