diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h --- a/lldb/include/lldb/Core/PluginManager.h +++ b/lldb/include/lldb/Core/PluginManager.h @@ -333,12 +333,17 @@ // Trace static bool RegisterPlugin(ConstString name, const char *description, TraceCreateInstance create_callback, - llvm::StringRef schema); + llvm::StringRef schema, + TraceGetStartCommand get_start_command); static bool UnregisterPlugin(TraceCreateInstance create_callback); static TraceCreateInstance GetTraceCreateCallback(ConstString plugin_name); + static lldb::CommandObjectSP + GetTraceStartCommand(llvm::StringRef plugin_name, + CommandInterpreter &interpreter); + /// Get the JSON schema for a trace session file corresponding to the given /// plugin. /// diff --git a/lldb/include/lldb/Interpreter/CommandObjectMultiword.h b/lldb/include/lldb/Interpreter/CommandObjectMultiword.h --- a/lldb/include/lldb/Interpreter/CommandObjectMultiword.h +++ b/lldb/include/lldb/Interpreter/CommandObjectMultiword.h @@ -82,6 +82,10 @@ // 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 @@ 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: diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -1392,6 +1392,8 @@ /// 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 @@ -2551,9 +2553,7 @@ /// \return /// The supported trace type or an \a llvm::Error if tracing is /// not supported for the inferior. - virtual llvm::Expected GetSupportedTraceType() { - return llvm::make_error(); - } + virtual llvm::Expected GetSupportedTraceType(); // This calls a function of the form "void * (*)(void)". bool CallVoidArgVoidPtrReturn(const Address *address, diff --git a/lldb/include/lldb/Target/ProcessTrace.h b/lldb/include/lldb/Target/ProcessTrace.h --- a/lldb/include/lldb/Target/ProcessTrace.h +++ b/lldb/include/lldb/Target/ProcessTrace.h @@ -9,13 +9,13 @@ #ifndef LLDB_TARGET_PROCESSTRACE_H #define LLDB_TARGET_PROCESSTRACE_H -#include "lldb/Target/Process.h" +#include "lldb/Target/RecordedProcess.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/Status.h" namespace lldb_private { -class ProcessTrace : public Process { +class ProcessTrace : public RecordedProcess { public: static void Initialize(); diff --git a/lldb/include/lldb/Target/RecordedProcess.h b/lldb/include/lldb/Target/RecordedProcess.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Target/RecordedProcess.h @@ -0,0 +1,28 @@ +//===-- RecordedProcess.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_RECORDEDPROCESS_H +#define LLDB_TARGET_RECORDEDPROCESS_H + +#include "lldb/Target/Process.h" + +namespace lldb_private { + +/// \class RecordedProcess +/// Base class for all processes that don't represent a live process, such as +/// coredumps or processes traced in the past. +class RecordedProcess : public Process { +public: + using Process::Process; + + bool IsLiveDebugSession() const override { return false; } +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_RECORDED_PROCESS_H diff --git a/lldb/include/lldb/Target/Trace.h b/lldb/include/lldb/Target/Trace.h --- a/lldb/include/lldb/Target/Trace.h +++ b/lldb/include/lldb/Target/Trace.h @@ -13,6 +13,7 @@ #include "lldb/Core/PluginInterface.h" #include "lldb/Utility/ArchSpec.h" +#include "lldb/Utility/UnimplementedError.h" #include "lldb/lldb-private.h" namespace lldb_private { @@ -175,6 +176,18 @@ std::function 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(); + } + /// Get the number of available instructions in the trace of the given thread. /// /// \param[in] thread diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -1081,7 +1081,12 @@ /// /// Verifies that there is a paused process in m_exe_ctx, if there isn't, /// the command will fail with an appropriate error message. - eCommandProcessMustBePaused = (1u << 7)}; + eCommandProcessMustBePaused = (1u << 7), + /// eCommandProcessMustBeTraced + /// + /// Verifies that the process is being traced by a Trace plug-in, if it + /// isn't the command will fail with an appropriate error message. + eCommandProcessMustBeTraced = (1u << 8)}; /// Whether a summary should cap how much data it returns to users or not. enum TypeSummaryCapping { diff --git a/lldb/include/lldb/lldb-private-interfaces.h b/lldb/include/lldb/lldb-private-interfaces.h --- a/lldb/include/lldb/lldb-private-interfaces.h +++ b/lldb/include/lldb/lldb-private-interfaces.h @@ -114,6 +114,8 @@ typedef llvm::Expected (*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 diff --git a/lldb/source/Commands/CMakeLists.txt b/lldb/source/Commands/CMakeLists.txt --- a/lldb/source/Commands/CMakeLists.txt +++ b/lldb/source/Commands/CMakeLists.txt @@ -31,6 +31,7 @@ CommandObjectStats.cpp CommandObjectTarget.cpp CommandObjectThread.cpp + CommandObjectThreadUtil.cpp CommandObjectTrace.cpp CommandObjectType.cpp CommandObjectVersion.cpp diff --git a/lldb/source/Commands/CommandObjectMultiword.cpp b/lldb/source/Commands/CommandObjectMultiword.cpp --- a/lldb/source/Commands/CommandObjectMultiword.cpp +++ b/lldb/source/Commands/CommandObjectMultiword.cpp @@ -261,11 +261,32 @@ 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 @@ 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 @@ 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,21 @@ 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); - return false; + bool handled = false; + if (CheckRequirements(result)) { + CommandObject *proxy_command = GetProxyCommandObject(); + if (proxy_command) + handled = proxy_command->Execute(args_string, result); + else + result.SetError(GetUnsupportedError()); + } + Cleanup(); + + return handled; } diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp --- a/lldb/source/Commands/CommandObjectThread.cpp +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -10,6 +10,8 @@ #include +#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 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 &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 m_thread_index_ids; - std::stack 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 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 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 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 &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 &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 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::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 @@ // 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 []", + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused) {} + +protected: + llvm::Expected DoGetProxyCommandObject() { + ProcessSP process_sp = m_interpreter.GetExecutionContext().GetProcessSP(); + + if (!process_sp) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "no process available"); + + llvm::Expected 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 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 [ ...]", + 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 @@ "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 @@ 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 @@ "thread trace []") { LoadSubCommand("dump", CommandObjectSP(new CommandObjectMultiwordTraceDump( interpreter))); + LoadSubCommand("start", + CommandObjectSP(new CommandObjectTraceStart(interpreter))); + LoadSubCommand("stop", + CommandObjectSP(new CommandObjectTraceStop(interpreter))); } ~CommandObjectMultiwordTrace() override = default; diff --git a/lldb/source/Commands/CommandObjectThreadUtil.h b/lldb/source/Commands/CommandObjectThreadUtil.h new file mode 100644 --- /dev/null +++ b/lldb/source/Commands/CommandObjectThreadUtil.h @@ -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 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 &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 m_thread_index_ids; + std::stack 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 &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 diff --git a/lldb/source/Commands/CommandObjectThreadUtil.cpp b/lldb/source/Commands/CommandObjectThreadUtil.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Commands/CommandObjectThreadUtil.cpp @@ -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 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 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 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 &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 &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 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::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; +} diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp --- a/lldb/source/Core/PluginManager.cpp +++ b/lldb/source/Core/PluginManager.cpp @@ -1010,46 +1010,59 @@ struct TraceInstance : public PluginInstance { TraceInstance(ConstString name, std::string description, - CallbackType create_callback, llvm::StringRef schema) + CallbackType create_callback, llvm::StringRef schema, + TraceGetStartCommand get_start_command) : PluginInstance(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 TraceInstances; -static TraceInstances &GetTraceInstances() { +static TraceInstances &GetTracePluginInstances() { static TraceInstances g_instances; return g_instances; } bool PluginManager::RegisterPlugin(ConstString name, const char *description, TraceCreateInstance create_callback, - llvm::StringRef schema) { - return GetTraceInstances().RegisterPlugin(name, description, create_callback, - schema); + llvm::StringRef schema, + TraceGetStartCommand get_start_command) { + return GetTracePluginInstances().RegisterPlugin( + name, description, create_callback, schema, get_start_command); } bool PluginManager::UnregisterPlugin(TraceCreateInstance create_callback) { - return GetTraceInstances().UnregisterPlugin(create_callback); + return GetTracePluginInstances().UnregisterPlugin(create_callback); } TraceCreateInstance PluginManager::GetTraceCreateCallback(ConstString plugin_name) { - return GetTraceInstances().GetCallbackForName(plugin_name); + return GetTracePluginInstances().GetCallbackForName(plugin_name); } llvm::StringRef PluginManager::GetTraceSchema(ConstString plugin_name) { - for (const TraceInstance &instance : GetTraceInstances().GetInstances()) + for (const TraceInstance &instance : GetTracePluginInstances().GetInstances()) if (instance.name == plugin_name) return instance.schema; return llvm::StringRef(); } +CommandObjectSP +PluginManager::GetTraceStartCommand(llvm::StringRef plugin_name, + CommandInterpreter &interpreter) { + for (const TraceInstance &instance : GetTracePluginInstances().GetInstances()) + if (instance.name.GetStringRef() == plugin_name) + return instance.get_start_command(interpreter); + return CommandObjectSP(); +} + llvm::StringRef PluginManager::GetTraceSchema(size_t index) { - if (TraceInstance *instance = GetTraceInstances().GetInstanceAtIndex(index)) + if (TraceInstance *instance = + GetTracePluginInstances().GetInstanceAtIndex(index)) return instance->schema; return llvm::StringRef(); } @@ -1267,6 +1280,7 @@ GetSymbolFileInstances().PerformDebuggerCallback(debugger); GetOperatingSystemInstances().PerformDebuggerCallback(debugger); GetStructuredDataPluginInstances().PerformDebuggerCallback(debugger); + GetTracePluginInstances().PerformDebuggerCallback(debugger); } // This is the preferred new way to register plugin specific settings. e.g. diff --git a/lldb/source/Interpreter/CommandObject.cpp b/lldb/source/Interpreter/CommandObject.cpp --- a/lldb/source/Interpreter/CommandObject.cpp +++ b/lldb/source/Interpreter/CommandObject.cpp @@ -258,6 +258,15 @@ } } } + + 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; } diff --git a/lldb/source/Plugins/Process/elf-core/ProcessElfCore.h b/lldb/source/Plugins/Process/elf-core/ProcessElfCore.h --- a/lldb/source/Plugins/Process/elf-core/ProcessElfCore.h +++ b/lldb/source/Plugins/Process/elf-core/ProcessElfCore.h @@ -19,7 +19,7 @@ #include #include -#include "lldb/Target/Process.h" +#include "lldb/Target/RecordedProcess.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::RecordedProcess { public: // Constructors and Destructors static lldb::ProcessSP diff --git a/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp b/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp --- a/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp +++ b/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp @@ -97,7 +97,7 @@ ProcessElfCore::ProcessElfCore(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp, const FileSpec &core_file) - : Process(target_sp, listener_sp), m_core_file(core_file) {} + : RecordedProcess(target_sp, listener_sp), m_core_file(core_file) {} // Destructor ProcessElfCore::~ProcessElfCore() { @@ -206,7 +206,7 @@ ArchSpec core_arch(m_core_module_sp->GetArchitecture()); target_arch.MergeFrom(core_arch); GetTarget().SetArchitecture(target_arch); - + SetUnixSignals(UnixSignals::Create(GetArchitecture())); // Ensure we found at least one thread that was stopped on a signal. @@ -243,8 +243,8 @@ exe_module_spec.GetFileSpec().SetFile( m_nt_file_entries[0].path.GetCString(), FileSpec::Style::native); if (exe_module_spec.GetFileSpec()) { - exe_module_sp = GetTarget().GetOrCreateModule(exe_module_spec, - true /* notify */); + exe_module_sp = + GetTarget().GetOrCreateModule(exe_module_spec, true /* notify */); if (exe_module_sp) GetTarget().SetExecutableModule(exe_module_sp, eLoadDependentsNo); } @@ -763,7 +763,7 @@ /// - NT_SIGINFO - Information about the signal that terminated the process /// - NT_AUXV - Process auxiliary vector /// - NT_FILE - Files mapped into memory -/// +/// /// Additionally, for each thread in the process the core file will contain at /// least the NT_PRSTATUS note, containing the thread id and general purpose /// registers. It may include additional notes for other register sets (floating diff --git a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.h b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.h --- a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.h +++ b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.h @@ -12,13 +12,13 @@ #include #include -#include "lldb/Target/Process.h" +#include "lldb/Target/RecordedProcess.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/Status.h" class ThreadKDP; -class ProcessMachCore : public lldb_private::Process { +class ProcessMachCore : public lldb_private::RecordedProcess { public: // Constructors and Destructors ProcessMachCore(lldb::TargetSP target_sp, lldb::ListenerSP listener, diff --git a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp --- a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp +++ b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp @@ -110,8 +110,8 @@ 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), + : RecordedProcess(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() {} diff --git a/lldb/source/Plugins/Process/minidump/ProcessMinidump.h b/lldb/source/Plugins/Process/minidump/ProcessMinidump.h --- a/lldb/source/Plugins/Process/minidump/ProcessMinidump.h +++ b/lldb/source/Plugins/Process/minidump/ProcessMinidump.h @@ -12,7 +12,7 @@ #include "MinidumpParser.h" #include "MinidumpTypes.h" -#include "lldb/Target/Process.h" +#include "lldb/Target/RecordedProcess.h" #include "lldb/Target/StopInfo.h" #include "lldb/Target/Target.h" #include "lldb/Utility/ConstString.h" @@ -26,7 +26,7 @@ namespace minidump { -class ProcessMinidump : public Process { +class ProcessMinidump : public RecordedProcess { public: static lldb::ProcessSP CreateInstance(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp, diff --git a/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp b/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp --- a/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp +++ b/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp @@ -234,7 +234,7 @@ lldb::ListenerSP listener_sp, const FileSpec &core_file, DataBufferSP core_data) - : Process(target_sp, listener_sp), m_core_file(core_file), + : RecordedProcess(target_sp, listener_sp), m_core_file(core_file), m_core_data(std::move(core_data)), m_is_wow64(false) {} ProcessMinidump::~ProcessMinidump() { diff --git a/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt b/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt --- a/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt +++ b/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt @@ -9,7 +9,12 @@ find_library(LIBIPT_LIBRARY ipt PATHS ${LIBIPT_LIBRARY_PATH} REQUIRED) +lldb_tablegen(TraceIntelPTCommandOptions.inc -gen-lldb-option-defs + SOURCE TraceIntelPTOptions.td + TARGET TraceIntelPTOptionsGen) + add_lldb_library(lldbPluginTraceIntelPT PLUGIN + CommandObjectTraceStartIntelPT.cpp DecodedThread.cpp IntelPTDecoder.cpp TraceIntelPT.cpp @@ -23,3 +28,6 @@ LINK_COMPONENTS Support ) + + +add_dependencies(lldbPluginTraceIntelPT TraceIntelPTOptionsGen) diff --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h @@ -0,0 +1,65 @@ +//===-- CommandObjectTraceStartIntelPT.h ----------------------*- C++ //-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_COMMANDOBJECTTRACESTARTINTELPT_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_COMMANDOBJECTTRACESTARTINTELPT_H + +#include "../../../../source/Commands/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 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 [ ...] " + "[]", + 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 diff --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp @@ -0,0 +1,73 @@ +//===-- CommandObjectTraceStartIntelPT.cpp --------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "CommandObjectTraceStartIntelPT.h" + +#include "lldb/Host/OptionParser.h" +#include "lldb/Target/Trace.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +#define LLDB_OPTIONS_thread_trace_start_intel_pt +#include "TraceIntelPTCommandOptions.inc" + +Status CommandObjectTraceStartIntelPT::CommandOptions::SetOptionValue( + uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 's': { + int32_t size_in_kb; + if (option_arg.empty() || option_arg.getAsInteger(0, size_in_kb) || + size_in_kb < 0) + error.SetErrorStringWithFormat("invalid integer value for option '%s'", + option_arg.str().c_str()); + else + m_size_in_kb = size_in_kb; + break; + } + case 'c': { + int32_t custom_config; + if (option_arg.empty() || option_arg.getAsInteger(0, custom_config) || + custom_config < 0) + error.SetErrorStringWithFormat("invalid integer value for option '%s'", + option_arg.str().c_str()); + else + m_custom_config = custom_config; + break; + } + default: + llvm_unreachable("Unimplemented option"); + } + return error; +} + +void CommandObjectTraceStartIntelPT::CommandOptions::OptionParsingStarting( + ExecutionContext *execution_context) { + m_size_in_kb = 4; + m_custom_config = 0; +} + +llvm::ArrayRef +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(); +} diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -8,6 +8,7 @@ #include "TraceIntelPT.h" +#include "CommandObjectTraceStartIntelPT.h" #include "TraceIntelPTSessionFileParser.h" #include "lldb/Core/PluginManager.h" #include "lldb/Target/Process.h" @@ -21,10 +22,14 @@ LLDB_PLUGIN_DEFINE(TraceIntelPT) +CommandObjectSP GetStartCommand(CommandInterpreter &interpreter) { + return CommandObjectSP(new CommandObjectTraceStartIntelPT(interpreter)); +} + void TraceIntelPT::Initialize() { - PluginManager::RegisterPlugin(GetPluginNameStatic(), "Intel Processor Trace", - CreateInstance, - TraceIntelPTSessionFileParser::GetSchema()); + PluginManager::RegisterPlugin( + GetPluginNameStatic(), "Intel Processor Trace", CreateInstance, + TraceIntelPTSessionFileParser::GetSchema(), GetStartCommand); } void TraceIntelPT::Terminate() { diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td @@ -0,0 +1,16 @@ +include "../../../../source/Commands/OptionsBase.td" + +let Command = "thread trace start intel pt" in { + def thread_trace_start_intel_pt_size: Option<"size", "s">, + Group<1>, + Arg<"Value">, + Desc<"The size of the trace in KB. The kernel rounds it down to the nearest" + " multiple of 4. Defaults to 4.">; + def thread_trace_start_intel_pt_custom_config: Option<"custom-config", "c">, + Group<1>, + Arg<"Value">, + Desc<"Low level bitmask configuration for the kernel based on the values " + "in `grep -H /sys/bus/event_source/devices/intel_pt/format/*`. " + "See https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/perf-intel-pt.txt" + " for more information. Defaults to 0.">; +} diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -6128,6 +6128,13 @@ return m_dlopen_utility_func_up.get(); } +llvm::Expected Process::GetSupportedTraceType() { + if (!IsLiveDebugSession()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Can't trace a non-live process."); + return llvm::make_error(); +} + bool Process::CallVoidArgVoidPtrReturn(const Address *address, addr_t &returned_func, bool trap_exceptions) { diff --git a/lldb/source/Target/ProcessTrace.cpp b/lldb/source/Target/ProcessTrace.cpp --- a/lldb/source/Target/ProcessTrace.cpp +++ b/lldb/source/Target/ProcessTrace.cpp @@ -43,7 +43,7 @@ } ProcessTrace::ProcessTrace(TargetSP target_sp, ListenerSP listener_sp) - : Process(target_sp, listener_sp) {} + : RecordedProcess(target_sp, listener_sp) {} ProcessTrace::~ProcessTrace() { Clear(); diff --git a/lldb/test/API/commands/trace/TestTraceDumpInstructions.py b/lldb/test/API/commands/trace/TestTraceDumpInstructions.py --- a/lldb/test/API/commands/trace/TestTraceDumpInstructions.py +++ b/lldb/test/API/commands/trace/TestTraceDumpInstructions.py @@ -32,7 +32,7 @@ self.expect("run") self.expect("thread trace dump instructions", - substrs=["error: this thread is not being traced"], + substrs=["error: Process is not being traced"], error=True) def testRawDumpInstructions(self): diff --git a/lldb/test/API/commands/trace/TestTraceStartStop.py b/lldb/test/API/commands/trace/TestTraceStartStop.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/trace/TestTraceStartStop.py @@ -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 []"]) + + 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: invalid target, create a target using the 'target create' command"]) + + 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: invalid process"]) + + # 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 [ ...] []"]) + + 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."])