mirror of
https://github.com/intel/llvm.git
synced 2026-01-13 11:02:04 +08:00
[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:
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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 ¤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:
|
||||
|
||||
32
lldb/include/lldb/Target/PostMortemProcess.h
Normal file
32
lldb/include/lldb/Target/PostMortemProcess.h
Normal 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
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ add_lldb_library(lldbCommands
|
||||
CommandObjectStats.cpp
|
||||
CommandObjectTarget.cpp
|
||||
CommandObjectThread.cpp
|
||||
CommandObjectThreadUtil.cpp
|
||||
CommandObjectTrace.cpp
|
||||
CommandObjectType.cpp
|
||||
CommandObjectVersion.cpp
|
||||
|
||||
@@ -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 ¤t_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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
158
lldb/source/Commands/CommandObjectThreadUtil.cpp
Normal file
158
lldb/source/Commands/CommandObjectThreadUtil.cpp
Normal 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;
|
||||
}
|
||||
81
lldb/source/Commands/CommandObjectThreadUtil.h
Normal file
81
lldb/source/Commands/CommandObjectThreadUtil.h
Normal 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
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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() {
|
||||
|
||||
16
lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td
Normal file
16
lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td
Normal 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.">;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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):
|
||||
|
||||
73
lldb/test/API/commands/trace/TestTraceStartStop.py
Normal file
73
lldb/test/API/commands/trace/TestTraceStartStop.py
Normal 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"])
|
||||
Reference in New Issue
Block a user