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
// 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.
///
// for this object.
virtual CommandObject *GetProxyCommandObject() = 0;
+ llvm::StringRef GetSyntax() override;
+
+ llvm::StringRef GetHelp() override;
+
llvm::StringRef GetHelpLong() override;
bool IsRemovable() const override;
const char *GetRepeatCommand(Args ¤t_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:
--- /dev/null
+//===-- 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
/// 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
/// \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,
#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();
#include "lldb/Core/PluginInterface.h"
#include "lldb/Utility/ArchSpec.h"
+#include "lldb/Utility/UnimplementedError.h"
#include "lldb/lldb-private.h"
namespace lldb_private {
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
///
/// 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 {
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
CommandObjectStats.cpp
CommandObjectTarget.cpp
CommandObjectThread.cpp
+ CommandObjectThreadUtil.cpp
CommandObjectTrace.cpp
CommandObjectType.cpp
CommandObjectVersion.cpp
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 {
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
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)
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;
}
#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"
// 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"
"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;
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);
"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;
--- /dev/null
+//===-- 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;
+}
--- /dev/null
+//===-- 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
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();
}
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.
}
}
}
+
+ 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;
}
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() {
#include <list>
#include <vector>
-#include "lldb/Target/Process.h"
+#include "lldb/Target/PostMortemProcess.h"
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/Status.h"
struct ThreadData;
-class ProcessElfCore : public lldb_private::Process {
+class ProcessElfCore : public lldb_private::PostMortemProcess {
public:
// Constructors and Destructors
static lldb::ProcessSP
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() {}
#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,
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() {
#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"
namespace minidump {
-class ProcessMinidump : public Process {
+class ProcessMinidump : public PostMortemProcess {
public:
static lldb::ProcessSP CreateInstance(lldb::TargetSP target_sp,
lldb::ListenerSP listener_sp,
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
LINK_COMPONENTS
Support
)
+
+
+add_dependencies(lldbPluginTraceIntelPT TraceIntelPTOptionsGen)
--- /dev/null
+//===-- 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();
+}
--- /dev/null
+//===-- 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
#include "TraceIntelPT.h"
+#include "CommandObjectTraceStartIntelPT.h"
#include "TraceIntelPTSessionFileParser.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Target/Process.h"
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() {
--- /dev/null
+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.">;
+}
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) {
}
ProcessTrace::ProcessTrace(TargetSP target_sp, ListenerSP listener_sp)
- : Process(target_sp, listener_sp) {}
+ : PostMortemProcess(target_sp, listener_sp) {}
ProcessTrace::~ProcessTrace() {
Clear();
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):
--- /dev/null
+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"])