[trace][intel-pt] Scaffold the 'thread trace start | stop' commands

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
This commit is contained in:
Walter Erquinigo
2020-10-26 21:22:06 -07:00
parent 25f5406f08
commit fb19f11ef4
30 changed files with 741 additions and 245 deletions

View 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.
///

View 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:

View File

@@ -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

View 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,

View 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();

View 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

View 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 {

View 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

View File

@@ -31,6 +31,7 @@ add_lldb_library(lldbCommands
CommandObjectStats.cpp
CommandObjectTarget.cpp
CommandObjectThread.cpp
CommandObjectThreadUtil.cpp
CommandObjectTrace.cpp
CommandObjectType.cpp
CommandObjectVersion.cpp

View 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;
}

View 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"
@@ -34,202 +36,6 @@
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;

View File

@@ -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;
}

View File

@@ -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

View 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.

View 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;
}

View 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() {

View 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

View 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() {}

View File

@@ -12,13 +12,13 @@
#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,

View 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() {

View 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,

View 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)

View File

@@ -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();
}

View File

@@ -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

View 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() {

View File

@@ -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.">;
}

View 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) {

View 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();

View 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):

View File

@@ -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"])