forked from OSchip/llvm-project
[trace] Scaffold "thread trace dump instructions"
Depends on D88841 As per the discussion in the RFC, we'll implement both thread trace dump [instructions | functions] This is the first step in implementing the "instructions" dumping command. It includes: - A minimal ProcessTrace plugin for representing processes from a trace file. I noticed that it was a required step to mimic how core-based processes are initialized, e.g. ProcessElfCore and ProcessMinidump. I haven't had the need to create ThreadTrace yet, though. So far HistoryThread seems good enough. - The command handling itself in CommandObjectThread, which outputs a placeholder text instead of the actual instructions. I'll do that part in the next diff. - Tests {F13132325} Differential Revision: https://reviews.llvm.org/D88769
This commit is contained in:
parent
388d373294
commit
26d861cbbd
|
@ -1105,6 +1105,20 @@ public:
|
|||
|
||||
void ClearAllLoadedSections();
|
||||
|
||||
/// Set the \a Trace object containing processor trace information of this
|
||||
/// target.
|
||||
///
|
||||
/// \param[in] trace_sp
|
||||
/// The trace object.
|
||||
void SetTrace(const lldb::TraceSP &trace_sp);
|
||||
|
||||
/// Get the \a Trace object containing processor trace information of this
|
||||
/// target.
|
||||
///
|
||||
/// \return
|
||||
/// The trace object. It might be undefined.
|
||||
const lldb::TraceSP &GetTrace();
|
||||
|
||||
// Since expressions results can persist beyond the lifetime of a process,
|
||||
// and the const expression results are available after a process is gone, we
|
||||
// provide a way for expressions to be evaluated from the Target itself. If
|
||||
|
@ -1402,6 +1416,9 @@ protected:
|
|||
bool m_suppress_stop_hooks;
|
||||
bool m_is_dummy_target;
|
||||
unsigned m_next_persistent_variable_index = 0;
|
||||
/// An optional \a lldb_private::Trace object containing processor trace
|
||||
/// information of this target.
|
||||
lldb::TraceSP m_trace_sp;
|
||||
/// Stores the frame recognizers of this target.
|
||||
lldb::StackFrameRecognizerManagerUP m_frame_recognizer_manager_up;
|
||||
|
||||
|
|
|
@ -469,6 +469,24 @@ public:
|
|||
// the backing thread for all memory threads each time we stop.
|
||||
}
|
||||
|
||||
/// Dump \a count instructions of the thread's \a Trace starting at the \a
|
||||
/// start_position position in reverse order.
|
||||
///
|
||||
/// The instructions are indexed in reverse order, which means that the \a
|
||||
/// start_position 0 represents the last instruction of the trace
|
||||
/// chronologically.
|
||||
///
|
||||
/// \param[in] s
|
||||
/// The stream object where the instructions are printed.
|
||||
///
|
||||
/// \param[in] count
|
||||
/// The number of instructions to print.
|
||||
///
|
||||
/// \param[in] start_position
|
||||
/// The position of the first instruction to print.
|
||||
void DumpTraceInstructions(Stream &s, size_t count,
|
||||
size_t start_position = 0) const;
|
||||
|
||||
// If stop_format is true, this will be the form used when we print stop
|
||||
// info. If false, it will be the form we use for thread list and co.
|
||||
void DumpUsingSettingsFormat(Stream &strm, uint32_t frame_idx,
|
||||
|
|
|
@ -32,7 +32,8 @@ namespace lldb_private {
|
|||
/// Processor trace information can also be fetched through the process
|
||||
/// interfaces during a live debug session if your process supports gathering
|
||||
/// this information.
|
||||
class Trace : public PluginInterface {
|
||||
class Trace : public PluginInterface,
|
||||
public std::enable_shared_from_this<Trace> {
|
||||
public:
|
||||
/// Dump the trace data that this plug-in has access to.
|
||||
///
|
||||
|
@ -96,6 +97,27 @@ public:
|
|||
/// \return
|
||||
/// The JSON schema of this Trace plug-in.
|
||||
virtual llvm::StringRef GetSchema() = 0;
|
||||
|
||||
/// Dump \a count instructions of the given thread's \a Trace starting at the
|
||||
/// \a start_position position in reverse order.
|
||||
///
|
||||
/// The instructions are indexed in reverse order, which means that the \a
|
||||
/// start_position 0 represents the last instruction of the trace
|
||||
/// chronologically.
|
||||
///
|
||||
/// \param[in] thread
|
||||
/// The thread whose trace will be dumped.
|
||||
///
|
||||
/// \param[in] s
|
||||
/// The stream object where the instructions are printed.
|
||||
///
|
||||
/// \param[in] count
|
||||
/// The number of instructions to print.
|
||||
///
|
||||
/// \param[in] start_position
|
||||
/// The position of the first instruction to print.
|
||||
void DumpTraceInstructions(Thread &thread, Stream &s, size_t count,
|
||||
size_t start_position) const;
|
||||
};
|
||||
|
||||
} // namespace lldb_private
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include "CommandObjectThread.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "lldb/Core/ValueObject.h"
|
||||
#include "lldb/Host/OptionParser.h"
|
||||
#include "lldb/Interpreter/CommandInterpreter.h"
|
||||
|
@ -26,6 +28,7 @@
|
|||
#include "lldb/Target/Thread.h"
|
||||
#include "lldb/Target/ThreadPlan.h"
|
||||
#include "lldb/Target/ThreadPlanStepInRange.h"
|
||||
#include "lldb/Target/Trace.h"
|
||||
#include "lldb/Utility/State.h"
|
||||
|
||||
using namespace lldb;
|
||||
|
@ -2165,6 +2168,170 @@ public:
|
|||
~CommandObjectMultiwordThreadPlan() override = default;
|
||||
};
|
||||
|
||||
// Next are the subcommands of CommandObjectMultiwordTrace
|
||||
|
||||
// CommandObjectTraceDumpInstructions
|
||||
#define LLDB_OPTIONS_thread_trace_dump_instructions
|
||||
#include "CommandOptions.inc"
|
||||
|
||||
class CommandObjectTraceDumpInstructions
|
||||
: 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 {
|
||||
Status error;
|
||||
const int short_option = m_getopt_table[option_idx].val;
|
||||
|
||||
switch (short_option) {
|
||||
case 'c': {
|
||||
int32_t count;
|
||||
if (option_arg.empty() || option_arg.getAsInteger(0, count) ||
|
||||
count < 0)
|
||||
error.SetErrorStringWithFormat(
|
||||
"invalid integer value for option '%s'",
|
||||
option_arg.str().c_str());
|
||||
else
|
||||
m_count = count;
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
int32_t start_position;
|
||||
if (option_arg.empty() || option_arg.getAsInteger(0, start_position) ||
|
||||
start_position < 0)
|
||||
error.SetErrorStringWithFormat(
|
||||
"invalid integer value for option '%s'",
|
||||
option_arg.str().c_str());
|
||||
else
|
||||
m_start_position = start_position;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
llvm_unreachable("Unimplemented option");
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
||||
m_count = kDefaultCount;
|
||||
m_start_position = kDefaultStartPosition;
|
||||
}
|
||||
|
||||
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
|
||||
return llvm::makeArrayRef(g_thread_trace_dump_instructions_options);
|
||||
}
|
||||
|
||||
static const uint32_t kDefaultCount = 20;
|
||||
static const uint32_t kDefaultStartPosition = 0;
|
||||
|
||||
// Instance variables to hold the values for command options.
|
||||
uint32_t m_count;
|
||||
uint32_t m_start_position;
|
||||
};
|
||||
|
||||
CommandObjectTraceDumpInstructions(CommandInterpreter &interpreter)
|
||||
: CommandObjectIterateOverThreads(
|
||||
interpreter, "thread trace dump instructions",
|
||||
"Dump the traced instructions for one or more threads. If no "
|
||||
"threads are specified, show the current thread. Use the "
|
||||
"thread-index \"all\" to see all threads.",
|
||||
nullptr,
|
||||
eCommandRequiresProcess | eCommandTryTargetAPILock |
|
||||
eCommandProcessMustBeLaunched | eCommandProcessMustBePaused),
|
||||
m_options(), m_create_repeat_command_just_invoked(false) {}
|
||||
|
||||
~CommandObjectTraceDumpInstructions() override = default;
|
||||
|
||||
Options *GetOptions() override { return &m_options; }
|
||||
|
||||
const char *GetRepeatCommand(Args ¤t_command_args,
|
||||
uint32_t index) override {
|
||||
current_command_args.GetCommandString(m_repeat_command);
|
||||
m_create_repeat_command_just_invoked = true;
|
||||
return m_repeat_command.c_str();
|
||||
}
|
||||
|
||||
protected:
|
||||
bool DoExecute(Args &args, CommandReturnObject &result) override {
|
||||
bool status = CommandObjectIterateOverThreads::DoExecute(args, result);
|
||||
PrepareRepeatArguments();
|
||||
return status;
|
||||
}
|
||||
|
||||
void PrepareRepeatArguments() {
|
||||
m_repeat_start_position = m_options.m_count + GetStartPosition();
|
||||
m_create_repeat_command_just_invoked = false;
|
||||
}
|
||||
|
||||
bool IsRepeatCommand() {
|
||||
return !m_repeat_command.empty() && !m_create_repeat_command_just_invoked;
|
||||
}
|
||||
|
||||
uint32_t GetStartPosition() {
|
||||
return IsRepeatCommand() ? m_repeat_start_position
|
||||
: m_options.m_start_position;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
trace_sp->DumpTraceInstructions(*thread_sp, result.GetOutputStream(),
|
||||
m_options.m_count, GetStartPosition());
|
||||
return true;
|
||||
}
|
||||
|
||||
CommandOptions m_options;
|
||||
|
||||
// Repeat command helpers
|
||||
std::string m_repeat_command;
|
||||
bool m_create_repeat_command_just_invoked;
|
||||
uint32_t m_repeat_start_position;
|
||||
};
|
||||
|
||||
// CommandObjectMultiwordTraceDump
|
||||
class CommandObjectMultiwordTraceDump : public CommandObjectMultiword {
|
||||
public:
|
||||
CommandObjectMultiwordTraceDump(CommandInterpreter &interpreter)
|
||||
: CommandObjectMultiword(
|
||||
interpreter, "dump",
|
||||
"Commands for displaying trace information of the threads "
|
||||
"in the current process.",
|
||||
"thread trace dump <subcommand> [<subcommand objects>]") {
|
||||
LoadSubCommand(
|
||||
"instructions",
|
||||
CommandObjectSP(new CommandObjectTraceDumpInstructions(interpreter)));
|
||||
}
|
||||
~CommandObjectMultiwordTraceDump() override = default;
|
||||
};
|
||||
|
||||
// CommandObjectMultiwordTrace
|
||||
class CommandObjectMultiwordTrace : public CommandObjectMultiword {
|
||||
public:
|
||||
CommandObjectMultiwordTrace(CommandInterpreter &interpreter)
|
||||
: CommandObjectMultiword(
|
||||
interpreter, "trace",
|
||||
"Commands for operating on traces of the threads in the current "
|
||||
"process.",
|
||||
"thread trace <subcommand> [<subcommand objects>]") {
|
||||
LoadSubCommand("dump", CommandObjectSP(new CommandObjectMultiwordTraceDump(
|
||||
interpreter)));
|
||||
}
|
||||
|
||||
~CommandObjectMultiwordTrace() override = default;
|
||||
};
|
||||
|
||||
// CommandObjectMultiwordThread
|
||||
|
||||
CommandObjectMultiwordThread::CommandObjectMultiwordThread(
|
||||
|
@ -2240,6 +2407,8 @@ CommandObjectMultiwordThread::CommandObjectMultiwordThread(
|
|||
|
||||
LoadSubCommand("plan", CommandObjectSP(new CommandObjectMultiwordThreadPlan(
|
||||
interpreter)));
|
||||
LoadSubCommand("trace",
|
||||
CommandObjectSP(new CommandObjectMultiwordTrace(interpreter)));
|
||||
}
|
||||
|
||||
CommandObjectMultiwordThread::~CommandObjectMultiwordThread() = default;
|
||||
|
|
|
@ -1005,6 +1005,21 @@ let Command = "thread plan list" in {
|
|||
Desc<"Display thread plans for unreported threads">;
|
||||
}
|
||||
|
||||
let Command = "thread trace dump instructions" in {
|
||||
def thread_trace_dump_instructions_count : Option<"count", "c">, Group<1>,
|
||||
Arg<"Count">,
|
||||
Desc<"The number of instructions to display starting at the current "
|
||||
"position in reverse order chronologically.">;
|
||||
def thread_trace_dump_instructions_start_position:
|
||||
Option<"start-position", "s">,
|
||||
Group<1>,
|
||||
Arg<"Index">,
|
||||
Desc<"The position of the first instruction to print. Defaults to the "
|
||||
"current position, i.e. where the thread is stopped. The instructions are "
|
||||
"indexed in reverse order, which means that a start position of 0 refers "
|
||||
"to the last instruction chronologically.">;
|
||||
}
|
||||
|
||||
let Command = "type summary add" in {
|
||||
def type_summary_add_category : Option<"category", "w">, Arg<"Name">,
|
||||
Desc<"Add this to the given category instead of the default one.">;
|
||||
|
|
|
@ -18,3 +18,4 @@ add_subdirectory(Utility)
|
|||
add_subdirectory(elf-core)
|
||||
add_subdirectory(mach-core)
|
||||
add_subdirectory(minidump)
|
||||
add_subdirectory(Trace)
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
add_lldb_library(lldbPluginProcessTrace PLUGIN
|
||||
ProcessTrace.cpp
|
||||
|
||||
LINK_LIBS
|
||||
lldbCore
|
||||
lldbTarget
|
||||
lldbUtility
|
||||
lldbPluginProcessUtility
|
||||
LINK_COMPONENTS
|
||||
BinaryFormat
|
||||
Object
|
||||
Support
|
||||
)
|
|
@ -0,0 +1,128 @@
|
|||
//===-- ProcessTrace.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 "ProcessTrace.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "lldb/Core/Module.h"
|
||||
#include "lldb/Core/PluginManager.h"
|
||||
#include "lldb/Target/Target.h"
|
||||
|
||||
using namespace lldb;
|
||||
using namespace lldb_private;
|
||||
using namespace lldb_private::process_trace;
|
||||
|
||||
LLDB_PLUGIN_DEFINE(ProcessTrace)
|
||||
|
||||
ConstString ProcessTrace::GetPluginNameStatic() {
|
||||
static ConstString g_name("trace");
|
||||
return g_name;
|
||||
}
|
||||
|
||||
const char *ProcessTrace::GetPluginDescriptionStatic() {
|
||||
return "Trace process plug-in.";
|
||||
}
|
||||
|
||||
void ProcessTrace::Terminate() {
|
||||
PluginManager::UnregisterPlugin(ProcessTrace::CreateInstance);
|
||||
}
|
||||
|
||||
ProcessSP ProcessTrace::CreateInstance(TargetSP target_sp,
|
||||
ListenerSP listener_sp,
|
||||
const FileSpec *crash_file) {
|
||||
return std::make_shared<ProcessTrace>(target_sp, listener_sp);
|
||||
}
|
||||
|
||||
bool ProcessTrace::CanDebug(TargetSP target_sp, bool plugin_specified_by_name) {
|
||||
return plugin_specified_by_name;
|
||||
}
|
||||
|
||||
ProcessTrace::ProcessTrace(TargetSP target_sp, ListenerSP listener_sp)
|
||||
: Process(target_sp, listener_sp) {}
|
||||
|
||||
ProcessTrace::~ProcessTrace() {
|
||||
Clear();
|
||||
// We need to call finalize on the process before destroying ourselves to
|
||||
// make sure all of the broadcaster cleanup goes as planned. If we destruct
|
||||
// this class, then Process::~Process() might have problems trying to fully
|
||||
// destroy the broadcaster.
|
||||
Finalize();
|
||||
}
|
||||
|
||||
ConstString ProcessTrace::GetPluginName() { return GetPluginNameStatic(); }
|
||||
|
||||
uint32_t ProcessTrace::GetPluginVersion() { return 1; }
|
||||
|
||||
void ProcessTrace::DidAttach(ArchSpec &process_arch) {
|
||||
ListenerSP listener_sp(
|
||||
Listener::MakeListener("lldb.process_trace.did_attach_listener"));
|
||||
HijackProcessEvents(listener_sp);
|
||||
|
||||
SetCanJIT(false);
|
||||
StartPrivateStateThread();
|
||||
SetPrivateState(eStateStopped);
|
||||
|
||||
EventSP event_sp;
|
||||
WaitForProcessToStop(llvm::None, &event_sp, true, listener_sp);
|
||||
|
||||
RestoreProcessEvents();
|
||||
|
||||
Process::DidAttach(process_arch);
|
||||
}
|
||||
|
||||
bool ProcessTrace::UpdateThreadList(ThreadList &old_thread_list,
|
||||
ThreadList &new_thread_list) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ProcessTrace::RefreshStateAfterStop() {}
|
||||
|
||||
Status ProcessTrace::DoDestroy() { return Status(); }
|
||||
|
||||
bool ProcessTrace::IsAlive() { return true; }
|
||||
|
||||
size_t ProcessTrace::ReadMemory(addr_t addr, void *buf, size_t size,
|
||||
Status &error) {
|
||||
// Don't allow the caching that lldb_private::Process::ReadMemory does since
|
||||
// we have it all cached in the trace files.
|
||||
return DoReadMemory(addr, buf, size, error);
|
||||
}
|
||||
|
||||
void ProcessTrace::Clear() { m_thread_list.Clear(); }
|
||||
|
||||
void ProcessTrace::Initialize() {
|
||||
static llvm::once_flag g_once_flag;
|
||||
|
||||
llvm::call_once(g_once_flag, []() {
|
||||
PluginManager::RegisterPlugin(GetPluginNameStatic(),
|
||||
GetPluginDescriptionStatic(), CreateInstance);
|
||||
});
|
||||
}
|
||||
|
||||
ArchSpec ProcessTrace::GetArchitecture() {
|
||||
return GetTarget().GetArchitecture();
|
||||
}
|
||||
|
||||
bool ProcessTrace::GetProcessInfo(ProcessInstanceInfo &info) {
|
||||
info.Clear();
|
||||
info.SetProcessID(GetID());
|
||||
info.SetArchitecture(GetArchitecture());
|
||||
ModuleSP module_sp = GetTarget().GetExecutableModule();
|
||||
if (module_sp) {
|
||||
const bool add_exe_file_as_first_arg = false;
|
||||
info.SetExecutableFile(GetTarget().GetExecutableModule()->GetFileSpec(),
|
||||
add_exe_file_as_first_arg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t ProcessTrace::DoReadMemory(addr_t addr, void *buf, size_t size,
|
||||
Status &error) {
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
//===-- ProcessTrace.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_PROCESS_TRACE_PROCESSTRACE_H
|
||||
#define LLDB_SOURCE_PLUGINS_PROCESS_TRACE_PROCESSTRACE_H
|
||||
|
||||
#include "lldb/Target/Process.h"
|
||||
#include "lldb/Utility/ConstString.h"
|
||||
#include "lldb/Utility/Status.h"
|
||||
|
||||
namespace lldb_private {
|
||||
namespace process_trace {
|
||||
|
||||
class ProcessTrace : public Process {
|
||||
public:
|
||||
static lldb::ProcessSP CreateInstance(lldb::TargetSP target_sp,
|
||||
lldb::ListenerSP listener_sp,
|
||||
const FileSpec *crash_file_path);
|
||||
|
||||
static void Initialize();
|
||||
|
||||
static void Terminate();
|
||||
|
||||
static ConstString GetPluginNameStatic();
|
||||
|
||||
static const char *GetPluginDescriptionStatic();
|
||||
|
||||
ProcessTrace(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp);
|
||||
|
||||
~ProcessTrace() override;
|
||||
|
||||
bool CanDebug(lldb::TargetSP target_sp,
|
||||
bool plugin_specified_by_name) override;
|
||||
|
||||
void DidAttach(ArchSpec &process_arch) override;
|
||||
|
||||
DynamicLoader *GetDynamicLoader() override { return nullptr; }
|
||||
|
||||
SystemRuntime *GetSystemRuntime() override { return nullptr; }
|
||||
|
||||
ConstString GetPluginName() override;
|
||||
|
||||
uint32_t GetPluginVersion() override;
|
||||
|
||||
Status DoDestroy() override;
|
||||
|
||||
void RefreshStateAfterStop() override;
|
||||
|
||||
Status WillResume() override {
|
||||
Status error;
|
||||
error.SetErrorStringWithFormat(
|
||||
"error: %s does not support resuming processes",
|
||||
GetPluginName().GetCString());
|
||||
return error;
|
||||
}
|
||||
|
||||
bool IsAlive() override;
|
||||
|
||||
bool WarnBeforeDetach() const override { return false; }
|
||||
|
||||
size_t ReadMemory(lldb::addr_t addr, void *buf, size_t size,
|
||||
Status &error) override;
|
||||
|
||||
size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
|
||||
Status &error) override;
|
||||
|
||||
ArchSpec GetArchitecture();
|
||||
|
||||
bool GetProcessInfo(ProcessInstanceInfo &info) override;
|
||||
|
||||
protected:
|
||||
void Clear();
|
||||
|
||||
bool UpdateThreadList(ThreadList &old_thread_list,
|
||||
ThreadList &new_thread_list) override;
|
||||
};
|
||||
|
||||
} // namespace process_trace
|
||||
} // namespace lldb_private
|
||||
|
||||
#endif // LLDB_SOURCE_PLUGINS_PROCESS_TRACE_PROCESSTRACE_H
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include "TraceIntelPTSessionFileParser.h"
|
||||
#include "lldb/Core/PluginManager.h"
|
||||
#include "lldb/Target/Target.h"
|
||||
|
||||
using namespace lldb;
|
||||
using namespace lldb_private;
|
||||
|
@ -45,12 +46,21 @@ ConstString TraceIntelPT::GetPluginName() { return GetPluginNameStatic(); }
|
|||
|
||||
uint32_t TraceIntelPT::GetPluginVersion() { return 1; }
|
||||
|
||||
void TraceIntelPT::Dump(lldb_private::Stream *s) const {}
|
||||
void TraceIntelPT::Dump(Stream *s) const {}
|
||||
|
||||
Expected<lldb::TraceSP>
|
||||
Expected<TraceSP>
|
||||
TraceIntelPT::CreateInstance(const json::Value &trace_session_file,
|
||||
StringRef session_file_dir, Debugger &debugger) {
|
||||
return TraceIntelPTSessionFileParser(debugger, trace_session_file,
|
||||
session_file_dir)
|
||||
.Parse();
|
||||
}
|
||||
|
||||
TraceSP TraceIntelPT::CreateInstance(const pt_cpu &pt_cpu,
|
||||
const std::vector<TargetSP> &targets) {
|
||||
TraceSP trace_instance(new TraceIntelPT(pt_cpu, targets));
|
||||
for (const TargetSP &target_sp : targets)
|
||||
target_sp->SetTrace(trace_instance);
|
||||
|
||||
return trace_instance;
|
||||
}
|
||||
|
|
|
@ -52,6 +52,21 @@ public:
|
|||
CreateInstance(const llvm::json::Value &trace_session_file,
|
||||
llvm::StringRef session_file_dir, Debugger &debugger);
|
||||
|
||||
/// Create an instance of this class.
|
||||
///
|
||||
/// \param[in] pt_cpu
|
||||
/// The libipt.h cpu information needed for decoding correctling the
|
||||
/// traces.
|
||||
///
|
||||
/// \param[in] targets
|
||||
/// The list of targets to associate with this trace instance
|
||||
///
|
||||
/// \return
|
||||
/// An intel-pt trace instance.
|
||||
static lldb::TraceSP
|
||||
CreateInstance(const pt_cpu &pt_cpu,
|
||||
const std::vector<lldb::TargetSP> &targets);
|
||||
|
||||
static ConstString GetPluginNameStatic();
|
||||
|
||||
uint32_t GetPluginVersion() override;
|
||||
|
@ -59,13 +74,13 @@ public:
|
|||
|
||||
llvm::StringRef GetSchema() override;
|
||||
|
||||
private:
|
||||
TraceIntelPT(const pt_cpu &pt_cpu, const std::vector<lldb::TargetSP> &targets)
|
||||
: m_pt_cpu(pt_cpu) {
|
||||
for (const lldb::TargetSP &target_sp : targets)
|
||||
m_targets.push_back(target_sp);
|
||||
}
|
||||
|
||||
private:
|
||||
pt_cpu m_pt_cpu;
|
||||
std::vector<std::weak_ptr<Target>> m_targets;
|
||||
};
|
||||
|
|
|
@ -41,7 +41,8 @@ void TraceIntelPTSessionFileParser::ParseThread(
|
|||
FileSpec trace_file(thread.trace_file);
|
||||
NormalizePath(trace_file);
|
||||
|
||||
ThreadSP thread_sp(new ThreadIntelPT(*process_sp, tid, trace_file));
|
||||
ThreadSP thread_sp =
|
||||
std::make_shared<ThreadIntelPT>(*process_sp, tid, trace_file);
|
||||
process_sp->GetThreadList().AddThread(thread_sp);
|
||||
}
|
||||
|
||||
|
@ -60,7 +61,7 @@ Error TraceIntelPTSessionFileParser::ParseProcess(
|
|||
m_debugger.GetTargetList().SetSelectedTarget(target_sp.get());
|
||||
|
||||
ProcessSP process_sp(target_sp->CreateProcess(
|
||||
/*listener*/ nullptr, /*plugin_name*/ StringRef(),
|
||||
/*listener*/ nullptr, "trace",
|
||||
/*crash_file*/ nullptr));
|
||||
process_sp->SetID(static_cast<lldb::pid_t>(process.pid));
|
||||
|
||||
|
@ -71,7 +72,16 @@ Error TraceIntelPTSessionFileParser::ParseProcess(
|
|||
if (Error err = ParseModule(target_sp, module))
|
||||
return err;
|
||||
}
|
||||
return Error::success();
|
||||
|
||||
if (!process.threads.empty())
|
||||
process_sp->GetThreadList().SetSelectedThreadByIndexID(0);
|
||||
|
||||
// We invoke DidAttach to create a correct stopped state for the process and
|
||||
// its threads.
|
||||
ArchSpec process_arch;
|
||||
process_sp->DidAttach(process_arch);
|
||||
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
void TraceIntelPTSessionFileParser::ParsePTCPU(const JSONPTCPU &pt_cpu) {
|
||||
|
@ -105,7 +115,7 @@ Expected<TraceSP> TraceIntelPTSessionFileParser::Parse() {
|
|||
return std::move(err);
|
||||
}
|
||||
|
||||
return std::make_shared<TraceIntelPT>(m_pt_cpu, m_targets);
|
||||
return TraceIntelPT::CreateInstance(m_pt_cpu, m_targets);
|
||||
}
|
||||
|
||||
namespace llvm {
|
||||
|
|
|
@ -2997,6 +2997,10 @@ Status Target::Launch(ProcessLaunchInfo &launch_info, Stream *stream) {
|
|||
return error;
|
||||
}
|
||||
|
||||
void Target::SetTrace(const TraceSP &trace_sp) { m_trace_sp = trace_sp; }
|
||||
|
||||
const TraceSP &Target::GetTrace() { return m_trace_sp; }
|
||||
|
||||
Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) {
|
||||
auto state = eStateInvalid;
|
||||
auto process_sp = GetProcessSP();
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include "llvm/Support/Format.h"
|
||||
|
||||
#include "lldb/Core/PluginManager.h"
|
||||
#include "lldb/Target/Thread.h"
|
||||
#include "lldb/Utility/Stream.h"
|
||||
|
||||
using namespace lldb;
|
||||
using namespace lldb_private;
|
||||
|
@ -76,3 +78,11 @@ Expected<StringRef> Trace::FindPluginSchema(StringRef name) {
|
|||
|
||||
return createInvalidPlugInError(name);
|
||||
}
|
||||
|
||||
void Trace::DumpTraceInstructions(Thread &thread, Stream &s, size_t count,
|
||||
size_t start_position) const {
|
||||
s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = 1000\n",
|
||||
thread.GetIndexID(), thread.GetID());
|
||||
s.Printf(" would print %zu instructions from position %zu\n", count,
|
||||
start_position);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
import lldb
|
||||
from lldbsuite.test.lldbtest import *
|
||||
from lldbsuite.test import lldbutil
|
||||
from lldbsuite.test.decorators import *
|
||||
|
||||
class TestTraceDumpInstructions(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 testErrorMessages(self):
|
||||
# We first check the output when there are no targets
|
||||
self.expect("thread trace dump instructions",
|
||||
substrs=["error: invalid target, create a target using the 'target create' command"],
|
||||
error=True)
|
||||
|
||||
# We now check the output when there's a non-running target
|
||||
self.expect("target create " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out"))
|
||||
|
||||
self.expect("thread trace dump instructions",
|
||||
substrs=["error: invalid process"],
|
||||
error=True)
|
||||
|
||||
# Now we check the output when there's a running target without a trace
|
||||
self.expect("b main")
|
||||
self.expect("run")
|
||||
|
||||
self.expect("thread trace dump instructions",
|
||||
substrs=["error: this thread is not being traced"],
|
||||
error=True)
|
||||
|
||||
def testDumpInstructions(self):
|
||||
self.expect("trace load -v " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
|
||||
substrs=["intel-pt"])
|
||||
|
||||
self.expect("thread trace dump instructions",
|
||||
substrs=['thread #1: tid = 3842849, total instructions = 1000',
|
||||
'would print 20 instructions from position 0'])
|
||||
|
||||
# We check if we can pass count and offset
|
||||
self.expect("thread trace dump instructions --count 5 --start-position 10",
|
||||
substrs=['thread #1: tid = 3842849, total instructions = 1000',
|
||||
'would print 5 instructions from position 10'])
|
||||
|
||||
# We check if we can access the thread by index id
|
||||
self.expect("thread trace dump instructions 1",
|
||||
substrs=['thread #1: tid = 3842849, total instructions = 1000',
|
||||
'would print 20 instructions from position 0'])
|
||||
|
||||
# We check that we get an error when using an invalid thread index id
|
||||
self.expect("thread trace dump instructions 10", error=True,
|
||||
substrs=['error: no thread with index: "10"'])
|
||||
|
||||
def testDumpInstructionsWithMultipleThreads(self):
|
||||
# We load a trace with two threads
|
||||
self.expect("trace load -v " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_2threads.json"))
|
||||
|
||||
# We print the instructions of two threads simultaneously
|
||||
self.expect("thread trace dump instructions 1 2",
|
||||
substrs=['''thread #1: tid = 3842849, total instructions = 1000
|
||||
would print 20 instructions from position 0
|
||||
thread #2: tid = 3842850, total instructions = 1000
|
||||
would print 20 instructions from position 0'''])
|
||||
|
||||
# We use custom --count and --start-position, saving the command to history for later
|
||||
ci = self.dbg.GetCommandInterpreter()
|
||||
|
||||
result = lldb.SBCommandReturnObject()
|
||||
ci.HandleCommand("thread trace dump instructions 1 2 --count 12 --start-position 5", result, True)
|
||||
self.assertIn('''thread #1: tid = 3842849, total instructions = 1000
|
||||
would print 12 instructions from position 5
|
||||
thread #2: tid = 3842850, total instructions = 1000
|
||||
would print 12 instructions from position 5''', result.GetOutput())
|
||||
|
||||
# We use a repeat command and ensure the previous count is used and the start-position has moved to the next position
|
||||
result = lldb.SBCommandReturnObject()
|
||||
ci.HandleCommand("", result)
|
||||
self.assertIn('''thread #1: tid = 3842849, total instructions = 1000
|
||||
would print 12 instructions from position 17
|
||||
thread #2: tid = 3842850, total instructions = 1000
|
||||
would print 12 instructions from position 17''', result.GetOutput())
|
||||
|
||||
ci.HandleCommand("", result)
|
||||
self.assertIn('''thread #1: tid = 3842849, total instructions = 1000
|
||||
would print 12 instructions from position 29
|
||||
thread #2: tid = 3842850, total instructions = 1000
|
||||
would print 12 instructions from position 29''', result.GetOutput())
|
|
@ -35,6 +35,10 @@ class TestTraceLoad(TestBase):
|
|||
|
||||
self.assertEqual("6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A", module.GetUUIDString())
|
||||
|
||||
# check that the Process and Thread objects were created correctly
|
||||
self.expect("thread info", substrs=["tid = 3842849"])
|
||||
self.expect("thread list", substrs=["Process 1234 stopped", "tid = 3842849"])
|
||||
|
||||
|
||||
def testLoadInvalidTraces(self):
|
||||
src_dir = self.getSourceDir()
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"trace": {
|
||||
"type": "intel-pt",
|
||||
"pt_cpu": {
|
||||
"vendor": "intel",
|
||||
"family": 6,
|
||||
"model": 79,
|
||||
"stepping": 1
|
||||
}
|
||||
},
|
||||
"processes": [
|
||||
{
|
||||
"pid": 1234,
|
||||
"triple": "x86_64-*-linux",
|
||||
"threads": [
|
||||
{
|
||||
"tid": 3842849,
|
||||
"traceFile": "3842849.trace"
|
||||
},
|
||||
{
|
||||
"tid": 3842850,
|
||||
"traceFile": "3842849.trace"
|
||||
}
|
||||
],
|
||||
"modules": [
|
||||
{
|
||||
"file": "a.out",
|
||||
"systemPath": "a.out",
|
||||
"loadAddress": "0x0000000000400000",
|
||||
"uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue