diff --git a/lldb/docs/lldb-gdb-remote.txt b/lldb/docs/lldb-gdb-remote.txt --- a/lldb/docs/lldb-gdb-remote.txt +++ b/lldb/docs/lldb-gdb-remote.txt @@ -261,6 +261,159 @@ send packet: jLLDBTraceSupportedType read packet: {"name": , "description", }/E;AAAAAAAAA +//---------------------------------------------------------------------- +// jLLDBTraceStart +// +// BRIEF +// Start tracing a process or its threads using a provided tracing technology. +// The input is specified as a JSON object. If tracing started succesfully, an +// OK response is returned, or an error otherwise. +// +// SCHEMA +// The schema for the input is +// +// { +// "type": , +// "tids": [], +// "variant": "specificThreads" | "currentAndFutureThreads" | "process", +// "params": { +// +// } +// } +// +// Notes: +// - "tids" is only used when "variant" is "specificThreads". +// - Tracing a thread for a second time will stop the first trace and start a +// new one. This is done to allow the user to easily change tracing params +// without requiring to stop it first. +// +// VARIANTS +// +// If "variant" is "specificThreads", then "tids" is required and each +// specified thread will be traced using the same "params". +// +// If "variant" is "currentAndFutureThreads", then all threads including future +// threads, will be individually traced using the same "params". +// +// If "variant" is "process", then the process will be traced as a single +// trace instance instead of tracing each individual thread. +// +// Note: +// Each trace technology can have different levels of support for each +// variant. +// +// INTEL-PT +// +// The schema for the "params" field for the "intel-pt" tracing technology is +// +// { +// "bufferSizeInKB": +// "perfConfig": +// } +// +// intel-pt only supports the variants "specificThreads" and +// "currentAndFutureThreads". +//---------------------------------------------------------------------- + +send packet: jLLDBTraceStart:{"type":,"variant":,"tids",,"params":}] +read packet: OK/E;AAAAAAAAA + +//---------------------------------------------------------------------- +// jLLDBTraceStop +// +// BRIEF +// Stop tracing a process or its threads using a provided tracing technology. +// The input is specified as a JSON object similar to the jLLDBTraceStart input. +// If tracing was stopped succesfully, an OK response is returned, or an error +// otherwise. +// +// Note: +// When stopping multiple threads, it will try to stop as many threads as +// many threads as possible and then returning an error in case of failures. +// +// SCHEMA +// The schema for the input is +// +// { +// "type": , +// "tids": [], +// "variant": "specificThreads" | "currentAndFutureThreads" | "process", +// } +// +// Notes: +// - "tids" is only used when "variant" is "specificThreads". +// - Stopping threads with "specificThreads" won't stop tracing of future +// threads if "currentAndFutureThreads" is enabled. +// - "currentAndFutureThreads" effectively stops thread traces started with +// "specificThreads". +//---------------------------------------------------------------------- + +send packet: jLLDBTraceStop:{"type":,"variant":,"tids",,"params":}] +read packet: OK/E;AAAAAAAAA + +//---------------------------------------------------------------------- +// jLLDBTraceGetState +// +// BRIEF +// Get the current state of the process and its threads being traced by +// a given trace technology. The response is a JSON object with custom +// information depending on the trace technology. +// +// SCHEMA +// The schema for the input is +// +// { +// "type": +// } +// +// INTEL-PT +// +// The schema for the output is +// +// { +// "threads": [ +// "tid": +// "bufferSizeInBytes": +// ], +// "pt_cpu": { +// "vendor": "intel" | "unknown", +// "family": , +// "model": , +// "stepping": +// } +// } +//---------------------------------------------------------------------- + +send packet: jLLDBTraceGetState:{"type":}] +read packet: {return object}/E;AAAAAAAAA + +//---------------------------------------------------------------------- +// jLLDBTraceGetBinaryData +// +// BRIEF +// Get binary data given a trace technology and a data identifier. +// The input is specified as a JSON object and the response has the same format +// as the "binary memory read" (aka "x") packet. +// +// SCHEMA +// The schema for the input is +// +// { +// "type": +// "label": +// "tid": +// "offset": +// "size": +// } +// +// SUPPORTED DATA OBJECTS +// +// - intel-pt "thread_trace_buffer": trace buffer for a given thread +//---------------------------------------------------------------------- + +send packet: jLLDBTraceGetBinaryData:{"type":,"label":,"tid":,"offset":,"size":}] +read packet: /E;AAAAAAAAA + //---------------------------------------------------------------------- // jTraceStart: // 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 @@ -331,14 +331,20 @@ GetSymbolVendorCreateCallbackAtIndex(uint32_t idx); // Trace - static bool RegisterPlugin(ConstString name, const char *description, - TraceCreateInstance create_callback, - llvm::StringRef schema, - TraceGetStartCommand get_start_command); + static bool RegisterPlugin( + ConstString name, const char *description, + TraceCreateInstanceForSessionFile create_callback_for_session_file, + TraceCreateInstanceForLiveProcess create_callback_for_live_process, + llvm::StringRef schema, TraceGetStartCommand get_start_command); + + static bool + UnregisterPlugin(TraceCreateInstanceForSessionFile create_callback); - static bool UnregisterPlugin(TraceCreateInstance create_callback); + static TraceCreateInstanceForSessionFile + GetTraceCreateCallback(ConstString plugin_name); - static TraceCreateInstance GetTraceCreateCallback(ConstString plugin_name); + static TraceCreateInstanceForLiveProcess + GetTraceCreateCallbackForLiveProcess(ConstString plugin_name); static lldb::CommandObjectSP GetTraceStartCommand(llvm::StringRef plugin_name, diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -306,6 +306,81 @@ MainLoop &mainloop) const = 0; }; + /// Start tracing a process or its threads. + /// + /// \param[in] json_params + /// JSON object with the information of what and how to trace. + /// In the case of gdb-remote, this object should conform to the + /// jLLDBTraceStart packet. + /// + /// This object should have a string entry called "type", which is the + /// tracing technology name. + /// + /// \param[in] type + /// Tracing technology type, as described in the \a json_params. + /// + /// \return + /// \a llvm::Error::success if the operation was successful, or an + /// \a llvm::Error otherwise. + virtual llvm::Error TraceStart(llvm::StringRef json_params, + llvm::StringRef type) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Unsupported tracing type '%s'", + type.data()); + } + + /// Stop tracing a process or its threads. + /// + /// \param[in] params + /// The information determining which threads or process to stop tracing. + /// + /// \return + /// \a llvm::Error::success if stopping was successful, or an + /// \a llvm::Error otherwise. + virtual llvm::Error TraceStop(const TraceStopPacket ¶ms) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Unsupported tracing type '%s'", + params.type.data()); + } + + /// Get the current tracing state of the process and its threads. + /// + /// \param[in] type + /// Tracing technology type to consider. + /// + /// \return + /// A JSON object with custom data depending on the trace technology, or + /// an \a llvm::Error in case of errors. + virtual llvm::Expected + TraceGetState(llvm::StringRef type) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Unsupported tracing type '%s'", + type.data()); + } + + /// Get binary data given a trace technology and a data identifier. + /// + /// \param[in] params + /// Object with the params of the requested data. + /// + /// \return + /// A vector of bytes with the requested data, or an \a llvm::Error in + /// case of failures. + virtual llvm::Expected> + TraceGetBinaryData(const TraceGetBinaryDataPacket ¶ms) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Unsupported data type '%s' for the '%s' tracing technology", + params.label.c_str(), params.type.c_str()); + } + + /// \copydoc Process::GetSupportedTraceType() + virtual llvm::Expected GetSupportedTraceType() { + return llvm::make_error(); + } + + /// Deprecated + /// /// StartTracing API for starting a tracing instance with the /// TraceOptions on a specific thread or process. /// @@ -327,6 +402,8 @@ return LLDB_INVALID_UID; } + /// Deprecated + /// /// StopTracing API as the name suggests stops a tracing instance. /// /// \param[in] traceid @@ -346,6 +423,8 @@ return Status("Not implemented"); } + /// Deprecated + /// /// This API provides the trace data collected in the form of raw /// data. /// @@ -371,6 +450,8 @@ return Status("Not implemented"); } + /// Deprecated + /// /// Similar API as above except it aims to provide any extra data /// useful for decoding the actual trace data. virtual Status GetMetaData(lldb::user_id_t traceid, lldb::tid_t thread, @@ -379,6 +460,8 @@ return Status("Not implemented"); } + /// Deprecated + /// /// API to query the TraceOptions for a given user id /// /// \param[in] traceid @@ -392,11 +475,6 @@ return Status("Not implemented"); } - /// \copydoc Process::GetSupportedTraceType() - virtual llvm::Expected GetSupportedTraceType() { - return llvm::make_error(); - } - protected: struct SoftwareBreakpoint { uint32_t ref_count; 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 @@ -2563,6 +2563,60 @@ /// not supported for the inferior. virtual llvm::Expected GetSupportedTraceType(); + /// Start tracing a process or its threads. + /// + /// \param[in] request + /// JSON object with the information necessary to start tracing. In the + /// case of gdb-remote processes, this JSON object should conform to the + /// jLLDBTraceStart packet. + /// + /// \return + /// \a llvm::Error::success if the operation was successful, or + /// \a llvm::Error otherwise. + virtual llvm::Error StartTracing(const llvm::json::Value ¶ms) { + return llvm::make_error(); + } + + /// Get the current tracing state of the process and its threads. + /// + /// \param[in] type + /// Tracing technology type to consider. + /// + /// \return + /// A JSON object string with custom data depending on the trace + /// technology, or an \a llvm::Error in case of errors. + virtual llvm::Expected GetTracingState(llvm::StringRef type) { + return llvm::make_error(); + } + + /// Stop tracing a live process or its threads. + /// + /// \param[in] process + /// Process that will be affected. + /// + /// \param[in] params + /// The information determining which threads or process to stop tracing. + /// + /// \return + /// An \a llvm::Error if stopping tracing failed, or an \b + /// llvm::Error::success() otherwise. + virtual llvm::Error StopTracing(const TraceStopPacket ¶ms) { + return llvm::make_error(); + } + + /// Get binary data given a trace technology and a data identifier. + /// + /// \param[in] params + /// Object with the params of the requested data. + /// + /// \return + /// A vector of bytes with the requested data, or an \a llvm::Error in + /// case of failures. + virtual llvm::Expected> + TraceGetBinaryData(const TraceGetBinaryDataPacket ¶ms) { + return llvm::make_error(); + } + // This calls a function of the form "void * (*)(void)". bool CallVoidArgVoidPtrReturn(const Address *address, lldb::addr_t &returned_func, 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 @@ -12,7 +12,9 @@ #include "llvm/Support/JSON.h" #include "lldb/Core/PluginInterface.h" +#include "lldb/Target/Thread.h" #include "lldb/Utility/ArchSpec.h" +#include "lldb/Utility/TraceOptions.h" #include "lldb/Utility/UnimplementedError.h" #include "lldb/lldb-private.h" @@ -97,6 +99,21 @@ FindPlugin(Debugger &debugger, const llvm::json::Value &trace_session_file, llvm::StringRef session_file_dir); + /// Find a trace plug-in to trace a live process. + /// + /// \param[in] plugin_name + /// Plug-in name to search. + /// + /// \param[in] process + /// Live process to trace. + /// + /// \return + /// A \a TraceSP instance, or an \a llvm::Error if the plug-in name + /// doesn't match any registered plug-ins or tracing couldn't be + /// started. + static llvm::Expected FindPlugin(llvm::StringRef plugin_name, + Process &process); + /// Get the schema of a Trace plug-in given its name. /// /// \param[in] plugin_name @@ -118,7 +135,7 @@ /// /// \return /// The current position of the thread's trace or \b 0 if empty. - virtual size_t GetCursorPosition(const Thread &thread) = 0; + virtual size_t GetCursorPosition(Thread &thread) = 0; /// Dump \a count instructions of the given thread's trace ending at the /// given \a end_position position. @@ -172,30 +189,27 @@ /// The callback to execute on each instruction. If it returns \b false, /// the iteration stops. virtual void TraverseInstructions( - const Thread &thread, size_t position, TraceDirection direction, + Thread &thread, size_t position, TraceDirection direction, std::function load_addr)> callback) = 0; - /// Stop tracing a live thread + /// Get the number of available instructions in the trace of the given thread. /// /// \param[in] thread - /// The thread object to stop tracing. + /// The thread whose trace will be inspected. /// /// \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(); - } + /// The total number of instructions in the trace. + virtual size_t GetInstructionCount(Thread &thread) = 0; - /// Get the number of available instructions in the trace of the given thread. + /// Check if a thread is currently traced by this object. /// /// \param[in] thread - /// The thread whose trace will be inspected. + /// The thread in question. /// /// \return - /// The total number of instructions in the trace. - virtual size_t GetInstructionCount(const Thread &thread) = 0; + /// \b true if the thread is traced by this instance, \b false otherwise. + virtual bool IsTraced(const Thread &thread) = 0; }; } // namespace lldb_private diff --git a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h --- a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h +++ b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h @@ -169,6 +169,10 @@ eServerPacketType_jTraceConfigRead, // deprecated eServerPacketType_jLLDBTraceSupportedType, + eServerPacketType_jLLDBTraceStart, + eServerPacketType_jLLDBTraceStop, + eServerPacketType_jLLDBTraceGetState, + eServerPacketType_jLLDBTraceGetBinaryData, }; ServerPacketType GetServerPacketType() const; diff --git a/lldb/include/lldb/Utility/TraceOptions.h b/lldb/include/lldb/Utility/TraceOptions.h --- a/lldb/include/lldb/Utility/TraceOptions.h +++ b/lldb/include/lldb/Utility/TraceOptions.h @@ -16,18 +16,7 @@ namespace lldb_private { -/// This struct represents a tracing technology. -struct TraceTypeInfo { - /// The name of the technology, e.g. intel-pt or arm-coresight. - /// - /// In order for a Trace plug-in (see \a lldb_private::Trace.h) to support the - /// trace technology given by this struct, it should match its name with this - /// field. - std::string name; - /// A description for the technology. - std::string description; -}; - +/// Deprecated class TraceOptions { public: TraceOptions() : m_trace_params(new StructuredData::Dictionary()) {} @@ -68,12 +57,176 @@ /// the lldb-server. StructuredData::DictionarySP m_trace_params; }; -} + +/// jLLDBTraceGetSupportedType gdb-remote packet structures +/// \{ +/// This struct represents a tracing technology. +struct TraceTypeInfo { + /// The name of the technology, e.g. intel-pt or arm-coresight. + /// + /// In order for a Trace plug-in (see \a lldb_private::Trace.h) to support the + /// trace technology given by this struct, it should match its name with this + /// field. + std::string name; + /// An optional description for the technology. + std::string description; +}; +/// \} + +/// jLLDBTraceStop gdb-remote packet structures +/// \{ +struct TraceStopPacket { + std::string type; + lldb::TraceOperationVariant variant; + std::vector tids; +}; +///} + +/// jLLDBTraceStart gdb-remote packet structures +/// \{ +struct TraceStartBasePacket { + TraceStartBasePacket() {} + + TraceStartBasePacket(llvm::StringRef type, + lldb::TraceOperationVariant variant, + const std::vector &tids) + : type(type), variant(variant), tids(tids) {} + + std::string type; + lldb::TraceOperationVariant variant; + std::vector tids; +}; + +template struct TraceStartPacket : TraceStartBasePacket { + TraceStartPacket() {} + + TraceStartPacket(llvm::StringRef type, lldb::TraceOperationVariant variant, + const std::vector &tids, const TParams ¶ms) + : TraceStartBasePacket(type, variant, tids), params(params) {} + + TParams params; +}; + +struct TraceIntelPTStartPacketParams { + size_t buffer_size_in_kb; + uint32_t perf_config; +}; + +using TraceIntelPTStartPacket = TraceStartPacket; +/// \} + +/// jLLDBTraceGetState gdb-remote packet structures +/// \{ +struct TraceGetStatePacket { + std::string type; +}; + +struct TraceIntelPTThreadState { + lldb::tid_t tid; + size_t buffer_size_in_bytes; +}; + +struct TraceIntelPTCPUConfig { + uint16_t family; + uint8_t model; + uint8_t stepping; + std::string vendor; +}; + +struct TraceIntelPTState { + std::vector threads; + TraceIntelPTCPUConfig cpu_config; +}; +/// \} + +/// jLLDBTraceGetBinaryData gdb-remote packet structures +/// \{ +struct TraceGetBinaryDataPacket { + std::string type; + std::string label; + lldb::tid_t tid; + size_t offset; + size_t size; +}; +/// \} + +} // namespace lldb_private namespace llvm { namespace json { +/// jLLDBTraceStop +/// \{ +bool fromJSON(const Value &value, lldb_private::TraceStopPacket &packet, + Path path); + +Value toJSON(const lldb_private::TraceStopPacket &packet); +///} + +/// jLLDBTraceStart +/// \{ +bool fromJSON(const Value &value, lldb_private::TraceStartBasePacket &packet, + Path path); + +bool fromJSON(const Value &value, + lldb_private::TraceIntelPTStartPacketParams &packet, Path path); + +Value toJSON(const lldb_private::TraceIntelPTStartPacketParams &packet); + +template +bool fromJSON(const Value &value, + lldb_private::TraceStartPacket &packet, Path path) { + ObjectMapper o(value, path); + return o && + fromJSON(value, (lldb_private::TraceStartBasePacket &)packet, path) && + o.map("params", packet.params); +} + +Value toJSON(const lldb_private::TraceStartBasePacket &packet); + +template +Value toJSON(const lldb_private::TraceStartPacket &packet) { + Value base_json = toJSON((const lldb_private::TraceStartBasePacket &)packet); + base_json.getAsObject()->try_emplace("params", toJSON(packet.params)); + return base_json; +} +/// \} + +/// jLLDBTraceGetState +/// \{ +bool fromJSON(const Value &value, lldb_private::TraceGetStatePacket &packet, + Path path); + +Value toJSON(const lldb_private::TraceGetStatePacket &packet); + +bool fromJSON(const Value &value, lldb_private::TraceIntelPTThreadState &state, + Path path); + +Value toJSON(const lldb_private::TraceIntelPTThreadState &state); + +bool fromJSON(const Value &value, + lldb_private::TraceIntelPTCPUConfig &cpu_config, Path path); + +Value toJSON(const lldb_private::TraceIntelPTCPUConfig &cpu_config); + +bool fromJSON(const Value &value, lldb_private::TraceIntelPTState &state, + Path path); + +Value toJSON(const lldb_private::TraceIntelPTState &state); +/// \} + +/// jLLDBTraceGetBinaryData +/// \{ +bool fromJSON(const Value &value, + lldb_private::TraceGetBinaryDataPacket &packet, Path path); + +Value toJSON(const lldb_private::TraceGetBinaryDataPacket &packet); +/// \} + +/// jLLDBTraceGetSupportedType +/// \{ bool fromJSON(const Value &value, lldb_private::TraceTypeInfo &info, Path path); +/// \} } // namespace json } // namespace llvm 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 @@ -769,6 +769,18 @@ eBasicTypeOther }; +enum TraceOperationVariant { + /// Operate on specific existing threads, each one as an independent trace + /// instance. + eTraceOperationVariantSpecificThreads = 0, + /// Operate on all existing threads along with future threads, each one as an + /// independent trace instance. + eTraceOperationVariantCurrentAndFutureThreads, + /// Operate on a whole process as a single trace instance, instead of handling + /// each thread independently. + eTraceOperationVariantProcess, +}; + /// Deprecated enum TraceType { eTraceTypeNone = 0, 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 @@ -111,9 +111,11 @@ const char *repl_options); typedef int (*ComparisonFunction)(const void *, const void *); typedef void (*DebuggerInitializeCallback)(Debugger &debugger); -typedef llvm::Expected (*TraceCreateInstance)( +typedef llvm::Expected (*TraceCreateInstanceForSessionFile)( const llvm::json::Value &trace_session_file, llvm::StringRef session_file_dir, lldb_private::Debugger &debugger); +typedef llvm::Expected (*TraceCreateInstanceForLiveProcess)( + Process &process); typedef lldb::CommandObjectSP (*TraceGetStartCommand)( CommandInterpreter &interpreter); 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 @@ -2038,15 +2038,15 @@ // CommandObjectTraceStop -class CommandObjectTraceStop : public CommandObjectIterateOverThreads { +class CommandObjectTraceStop : public CommandObjectThreadTraceLifeCycle { public: CommandObjectTraceStop(CommandInterpreter &interpreter) - : CommandObjectIterateOverThreads( + : CommandObjectThreadTraceLifeCycle( 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.", + "all threads including future threads.", "thread trace stop [ ...]", eCommandRequiresProcess | eCommandTryTargetAPILock | eCommandProcessMustBeLaunched | eCommandProcessMustBePaused | @@ -2054,20 +2054,22 @@ ~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(); + bool HandleTraceCommand(Args &command, CommandReturnObject &result, + TraceOperationVariant &variant, + const std::vector &tids) override { + ProcessSP process_sp = m_exe_ctx.GetProcessSP(); - 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); - } + TraceSP trace_sp = process_sp->GetTarget().GetTrace(); - // We don't return false on errors to try to stop as many threads as - // possible. - return true; + TraceStopPacket params{trace_sp->GetPluginName().GetStringRef().str(), + variant, tids}; + + if (llvm::Error err = process_sp->StopTracing(params)) + result.SetError(toString(std::move(err))); + else + result.SetStatus(eReturnStatusSuccessFinishResult); + + return result.Succeeded(); } }; diff --git a/lldb/source/Commands/CommandObjectThreadUtil.h b/lldb/source/Commands/CommandObjectThreadUtil.h --- a/lldb/source/Commands/CommandObjectThreadUtil.h +++ b/lldb/source/Commands/CommandObjectThreadUtil.h @@ -76,6 +76,69 @@ bool m_add_return = true; }; +/// Class that should be extended to provide life-cycle operations on traces, +/// such as start and stop. +/// +/// The arguments of this class have the following format +/// +/// command [all | thread index 1, thread index 2, ...] --other_args +/// +/// where "all" refers to all existing and future threads, as tracing can +/// operate that way. Thread indices work the same way as other thread commands. +class CommandObjectThreadTraceLifeCycle : public CommandObjectParsed { +public: + using CommandObjectParsed::CommandObjectParsed; + + bool DoExecute(Args &command, CommandReturnObject &result) override; + +protected: + /// Method that handles the command after the main arguments have been parsed. + /// + /// \param[in] variant + /// The trace variant depending on whether "all" was passed or not. + /// + /// \param[in] tids + /// The thread ids passed as arguments. + /// + /// \return + /// A boolean result similar to the one expected from \a DoExecute. + virtual bool HandleTraceCommand(Args &command, CommandReturnObject &result, + lldb::TraceOperationVariant &variant, + const std::vector &tids) = 0; +}; + +class CommandObjecThreadTraceStart : public CommandObjectThreadTraceLifeCycle { +public: + CommandObjecThreadTraceStart(llvm::StringRef trace_plugin_name, + CommandInterpreter &interpreter, + const char *name, const char *help, + const char *syntax, uint32_t flags) + : CommandObjectThreadTraceLifeCycle(interpreter, name, help, syntax, + flags), + m_trace_plugin_name(trace_plugin_name) {} + +protected: + bool HandleTraceCommand(Args &command, CommandReturnObject &result, + lldb::TraceOperationVariant &variant, + const std::vector &tids); + + /// Create a JSON params object readable by \a Process::StartTracing. + /// + /// \param[in] variant + /// Trace start variant. + /// + /// \param[in] tids + /// Threads to trace. + /// + /// \return + /// The JSON params, or an \a llvm::Error in case of failures. + virtual llvm::Expected + CreateTraceStartParams(lldb::TraceOperationVariant variant, + const std::vector &tids) = 0; + + std::string m_trace_plugin_name; +}; + } // namespace lldb_private #endif // LLDB_SOURCE_COMMANDS_COMMANDOBJECTTHREADUTIL_H diff --git a/lldb/source/Commands/CommandObjectThreadUtil.cpp b/lldb/source/Commands/CommandObjectThreadUtil.cpp --- a/lldb/source/Commands/CommandObjectThreadUtil.cpp +++ b/lldb/source/Commands/CommandObjectThreadUtil.cpp @@ -156,3 +156,78 @@ } return true; } + +bool CommandObjectThreadTraceLifeCycle::DoExecute(Args &command, + CommandReturnObject &result) { + Process &process = m_exe_ctx.GetProcessRef(); + + std::vector tids; + const size_t num_args = command.GetArgumentCount(); + + std::lock_guard guard( + process.GetThreadList().GetMutex()); + + TraceOperationVariant variant = eTraceOperationVariantSpecificThreads; + + if (num_args > 0 && ::strcmp(command.GetArgumentAtIndex(0), "all") == 0) + variant = eTraceOperationVariantCurrentAndFutureThreads; + else { + if (num_args == 0) { + Thread &thread = m_exe_ctx.GetThreadRef(); + tids.push_back(thread.GetID()); + } + + 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()); + } + } + + return HandleTraceCommand(command, result, variant, tids); +} + +bool CommandObjecThreadTraceStart::HandleTraceCommand( + Args &command, CommandReturnObject &result, TraceOperationVariant &variant, + const std::vector &tids) { + ProcessSP process_sp = m_exe_ctx.GetProcessSP(); + TraceSP trace_sp = process_sp->GetTarget().GetTrace(); + /// We create a new Trace instance so that plug-ins can assume it exists. + if (!trace_sp) { + Expected trace_or_err = + Trace::FindPlugin(m_trace_plugin_name, *process_sp); + if (!trace_or_err) { + result.SetStatus(eReturnStatusFailed); + result.AppendErrorWithFormat("couldn't start tracing the process. %s", + toString(trace_or_err.takeError()).c_str()); + return false; + } + trace_sp = *trace_or_err; + } + + if (Expected params = CreateTraceStartParams(variant, tids)) { + if (Error err = process_sp->StartTracing(*params)) + result.SetError(toString(std::move(err))); + else + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.SetError(toString(params.takeError())); + } + + return result.Succeeded(); +} 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 @@ -1008,16 +1008,21 @@ #pragma mark Trace -struct TraceInstance : public PluginInstance { - TraceInstance(ConstString name, std::string description, - CallbackType create_callback, llvm::StringRef schema, - TraceGetStartCommand get_start_command) - : PluginInstance(name, std::move(description), - create_callback), - schema(schema), get_start_command(get_start_command) {} +struct TraceInstance + : public PluginInstance { + TraceInstance( + ConstString name, std::string description, + CallbackType create_callback_for_session_file, + TraceCreateInstanceForLiveProcess create_callback_for_live_process, + llvm::StringRef schema, TraceGetStartCommand get_start_command) + : PluginInstance( + name, std::move(description), create_callback_for_session_file), + schema(schema), get_start_command(get_start_command), + create_callback_for_live_process(create_callback_for_live_process) {} llvm::StringRef schema; TraceGetStartCommand get_start_command; + TraceCreateInstanceForLiveProcess create_callback_for_live_process; }; typedef PluginInstances TraceInstances; @@ -1027,23 +1032,35 @@ return g_instances; } -bool PluginManager::RegisterPlugin(ConstString name, const char *description, - TraceCreateInstance create_callback, - llvm::StringRef schema, - TraceGetStartCommand get_start_command) { +bool PluginManager::RegisterPlugin( + ConstString name, const char *description, + TraceCreateInstanceForSessionFile create_callback_for_session_file, + TraceCreateInstanceForLiveProcess create_callback_for_live_process, + llvm::StringRef schema, TraceGetStartCommand get_start_command) { return GetTracePluginInstances().RegisterPlugin( - name, description, create_callback, schema, get_start_command); + name, description, create_callback_for_session_file, + create_callback_for_live_process, schema, get_start_command); } -bool PluginManager::UnregisterPlugin(TraceCreateInstance create_callback) { - return GetTracePluginInstances().UnregisterPlugin(create_callback); +bool PluginManager::UnregisterPlugin( + TraceCreateInstanceForSessionFile create_callback_for_session_file) { + return GetTracePluginInstances().UnregisterPlugin( + create_callback_for_session_file); } -TraceCreateInstance +TraceCreateInstanceForSessionFile PluginManager::GetTraceCreateCallback(ConstString plugin_name) { return GetTracePluginInstances().GetCallbackForName(plugin_name); } +TraceCreateInstanceForLiveProcess +PluginManager::GetTraceCreateCallbackForLiveProcess(ConstString plugin_name) { + for (const TraceInstance &instance : GetTracePluginInstances().GetInstances()) + if (instance.name == plugin_name) + return instance.create_callback_for_live_process; + return nullptr; +} + llvm::StringRef PluginManager::GetTraceSchema(ConstString plugin_name) { for (const TraceInstance &instance : GetTracePluginInstances().GetInstances()) if (instance.name == plugin_name) diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h @@ -101,6 +101,59 @@ return getProcFile(GetID(), "auxv"); } + llvm::Error TraceStart(llvm::StringRef json_params, + llvm::StringRef type) override; + + /// Start tracing a thread with Intel PT. + /// + /// \param[in] tid + /// The thread id to start tracing. + /// + /// \param[in] buffer_size_in_kb + /// The size of the circular trace buffer. It is rounded up to the nearest + /// power of 2. + /// + /// \param[in] perf_config + /// Low level perf event config. See + /// https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/perf-intel-pt.txt + /// for more information + /// + /// \return + /// \a llvm::Error::success if the operation was successful, or an + /// \a llvm::Error otherwise. + llvm::Error StartIntelPTTrace(lldb::tid_t tid, size_t buffer_size_in_kb, + uint32_t perf_config); + + /// Start tracing a process or its threads with Intel PT. + /// + /// \param[in] params + /// The params that specify how to start tracing. + /// + /// \return + /// \a llvm::Error::success if the operation was successful, or an + /// \a llvm::Error otherwise. + llvm::Error StartIntelPTTrace(const TraceIntelPTStartPacket ¶ms); + + llvm::Error TraceStop(const TraceStopPacket ¶ms) override; + + /// Stop tracing a process or its threads traced with Intel PT. + /// + /// \param[in] params + /// The params that specify what to stop tracing. + /// + /// \return + /// \a llvm::Error::success if the operation was successful, or an + /// \a llvm::Error otherwise. + llvm::Error StopIntelPTTrace(const TraceStopPacket ¶ms); + + llvm::Expected + TraceGetState(llvm::StringRef type) override; + + llvm::Expected GetIntelPTTraceState(); + + llvm::Expected> + TraceGetBinaryData(const TraceGetBinaryDataPacket ¶ms) override; + lldb::user_id_t StartTrace(const TraceOptions &config, Status &error) override; @@ -181,6 +234,12 @@ NativeThreadLinux &AddThread(lldb::tid_t thread_id); + /// Start tracing a new thread if possible. + /// + /// Trace mechanisms should modify this method to provide automatic tracing + /// for new threads. + void TryTraceNewThread(lldb::tid_t thread_id); + /// Writes a siginfo_t structure corresponding to the given thread ID to the /// memory region pointed to by \p siginfo. Status GetSignalInfo(lldb::tid_t tid, void *siginfo); @@ -252,6 +311,10 @@ lldb::user_id_t m_pt_proces_trace_id = LLDB_INVALID_UID; TraceOptions m_pt_process_trace_config; + /// Params to use when automatically tracing new threads with Intel PT. + /// When \a llvm::None, then no automatic tracing will be performed. + llvm::Optional m_intel_pt_trace_new_threads_params = + llvm::None; }; } // namespace process_linux diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -1671,6 +1671,21 @@ return found; } +void NativeProcessLinux::TryTraceNewThread(lldb::tid_t thread_id) { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_THREAD)); + + if (m_intel_pt_trace_new_threads_params) { + if (Error err = StartIntelPTTrace( + thread_id, + m_intel_pt_trace_new_threads_params->params.buffer_size_in_kb, + m_intel_pt_trace_new_threads_params->params.perf_config)) { + LLDB_LOG(log, "failed to start intel pt trace on thread {0}", thread_id); + Status error(std::move(err)); + LLDB_LOG(log, "error {0}", error); + } + } +} + NativeThreadLinux &NativeProcessLinux::AddThread(lldb::tid_t thread_id) { Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_THREAD)); LLDB_LOG(log, "pid {0} adding thread with tid {1}", GetID(), thread_id); @@ -1683,20 +1698,7 @@ SetCurrentThreadID(thread_id); m_threads.push_back(std::make_unique(*this, thread_id)); - - if (m_pt_proces_trace_id != LLDB_INVALID_UID) { - auto traceMonitor = ProcessorTraceMonitor::Create( - GetID(), thread_id, m_pt_process_trace_config, true); - if (traceMonitor) { - m_pt_traced_thread_group.insert(thread_id); - m_processor_trace_monitor.insert( - std::make_pair(thread_id, std::move(*traceMonitor))); - } else { - LLDB_LOG(log, "failed to start trace on thread {0}", thread_id); - Status error(traceMonitor.takeError()); - LLDB_LOG(log, "error {0}", error); - } - } + TryTraceNewThread(thread_id); return static_cast(*m_threads.back()); } @@ -2033,6 +2035,140 @@ return m_pt_proces_trace_id; } +Error NativeProcessLinux::TraceStop(const TraceStopPacket ¶ms) { + if (params.type == "intel-pt") + return StopIntelPTTrace(params); + return NativeProcessProtocol::TraceStop(params); +} + +Error NativeProcessLinux::StopIntelPTTrace(const TraceStopPacket ¶ms) { + switch (params.variant) { + case lldb::eTraceOperationVariantProcess: + return createStringError( + inconvertibleErrorCode(), + "whole process (non-thread) tracing is not supported"); + case lldb::eTraceOperationVariantSpecificThreads: { + Error result = Error::success(); + for (lldb::tid_t tid : params.tids) { + const auto &trace_instance = m_processor_trace_monitor.find(tid); + if (trace_instance == m_processor_trace_monitor.end()) + result = joinErrors( + std::move(result), + createStringError(inconvertibleErrorCode(), "thread not traced")); + else + result = joinErrors(std::move(result), + StopProcessorTracingOnThread( + trace_instance->second->GetTraceID(), tid) + .ToError()); + } + return result; + } + case lldb::eTraceOperationVariantCurrentAndFutureThreads: + m_intel_pt_trace_new_threads_params = llvm::None; + m_processor_trace_monitor.clear(); + return Error::success(); + } +} + +Error NativeProcessLinux::TraceStart(StringRef json_params, StringRef type) { + if (type == "intel-pt") { + if (Expected params = + json::parse(json_params)) + return StartIntelPTTrace(*params); + else + return params.takeError(); + } + + return NativeProcessProtocol::TraceStart(json_params, type); +} + +Error NativeProcessLinux::StartIntelPTTrace( + const TraceIntelPTStartPacket ¶ms) { + switch (params.variant) { + case lldb::eTraceOperationVariantProcess: + return createStringError( + inconvertibleErrorCode(), + "whole process (non-thread) tracing is not supported"); + case lldb::eTraceOperationVariantSpecificThreads: { + Error result = Error::success(); + for (lldb::tid_t tid : params.tids) { + if (Error err = StartIntelPTTrace(tid, params.params.buffer_size_in_kb, + params.params.perf_config)) + result = joinErrors(std::move(result), std::move(err)); + } + return result; + } + case lldb::eTraceOperationVariantCurrentAndFutureThreads: { + m_intel_pt_trace_new_threads_params = params; + Error result = Error::success(); + for (const auto &thread : m_threads) { + if (Error err = StartIntelPTTrace(thread->GetID(), + params.params.buffer_size_in_kb, + params.params.perf_config)) + result = joinErrors(std::move(result), std::move(err)); + } + return result; + } + } +} + +Error NativeProcessLinux::StartIntelPTTrace(lldb::tid_t tid, + size_t buffer_size_in_kb, + uint32_t perf_config) { + if (!GetThreadByID(tid)) + return createStringError(inconvertibleErrorCode(), "invalid thread id"); + + // We clear the previous trace if we are tracing the thread for a second time. + const auto &trace_instance = m_processor_trace_monitor.find(tid); + if (trace_instance != m_processor_trace_monitor.end()) + m_processor_trace_monitor.erase(trace_instance); + + Expected trace_monitor = + ProcessorTraceMonitor::Create(GetID(), tid, buffer_size_in_kb, + perf_config); + if (!trace_monitor) + return trace_monitor.takeError(); + + m_processor_trace_monitor.try_emplace(tid, std::move(*trace_monitor)); + return Error::success(); +} + +Expected NativeProcessLinux::TraceGetState(StringRef type) { + if (type == "intel-pt") + return GetIntelPTTraceState(); + return NativeProcessProtocol::TraceGetState(type); +} + +Expected NativeProcessLinux::GetIntelPTTraceState() { + Expected cpu_config = + ProcessorTraceMonitor::GetIntelPTCPUConfig(); + if (!cpu_config) + return cpu_config.takeError(); + + TraceIntelPTState state; + state.cpu_config = *cpu_config; + + state.threads.reserve(m_processor_trace_monitor.size()); + for (const auto &tid_monitor_pair : m_processor_trace_monitor) { + lldb::tid_t tid = tid_monitor_pair.first; + size_t size_in_bytes = tid_monitor_pair.second->GetTraceBufferSizeInBytes(); + state.threads.push_back(TraceIntelPTThreadState{tid, size_in_bytes}); + } + return json::toJSON(state); +} + +Expected> +NativeProcessLinux::TraceGetBinaryData(const TraceGetBinaryDataPacket ¶ms) { + if (params.type == "intel-pt" && params.label == "thread_trace_buffer") { + const auto &trace_instance = m_processor_trace_monitor.find(params.tid); + if (trace_instance == m_processor_trace_monitor.end()) + return createStringError(inconvertibleErrorCode(), + "thread not currently traced"); + return trace_instance->second->GetIntelPTBuffer(params.offset, params.size); + } + return NativeProcessProtocol::TraceGetBinaryData(params); +} + lldb::user_id_t NativeProcessLinux::StartTrace(const TraceOptions &config, Status &error) { if (config.getType() != TraceType::eTraceTypeProcessorTrace) diff --git a/lldb/source/Plugins/Process/Linux/ProcessorTrace.h b/lldb/source/Plugins/Process/Linux/ProcessorTrace.h --- a/lldb/source/Plugins/Process/Linux/ProcessorTrace.h +++ b/lldb/source/Plugins/Process/Linux/ProcessorTrace.h @@ -79,8 +79,25 @@ void SetTraceID(lldb::user_id_t traceid) { m_traceid = traceid; } - Status StartTrace(lldb::pid_t pid, lldb::tid_t tid, - const TraceOptions &config); + /// Start tracing a thread + /// + /// \param[in] pid + /// The pid of the process whose thread will be traced. + /// + /// \param[in] tid + /// The tid of the thread to trace. + /// + /// \param[in] buffer_size_in_kb + /// See \a NativeProcessProtocol::StartIntelPTTrace. + /// + /// \param[in] perf_config + /// See \a NativeProcessProtocol::StartIntelPTTrace. + /// + /// \return + /// \a llvm::Error::success if tracing was successful, or an + /// \a llvm::Error otherwise. + llvm::Error StartTrace(lldb::pid_t pid, lldb::tid_t tid, + size_t buffer_size_in_kb, uint32_t perf_config = 0); llvm::MutableArrayRef GetAuxBuffer(); llvm::MutableArrayRef GetDataBuffer(); @@ -99,10 +116,38 @@ static Status GetCPUType(TraceOptions &config); + static llvm::Expected GetIntelPTCPUConfig(); + static llvm::Expected Create(lldb::pid_t pid, lldb::tid_t tid, const TraceOptions &config, bool useProcessSettings); + /// Start tracing a thread. + /// + /// See \a StartTrace. + /// + /// \return + /// A \a ProcessorTraceMonitorUP instance if tracing was successful, or + /// an \a llvm::Error otherwise. + static llvm::Expected + Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size_in_kb, + uint32_t perf_config); + + /// Read the trace buffer of the currently traced thread. + /// + /// \param[in] offset + /// Offset of the data to read. + /// + /// \param[in] size + /// Number of bytes to read. + /// + /// \return + /// A vector with the requested binary data. The vector will have the + /// size of the requested \a size. Non-available positions will be + /// filled with zeroes. + llvm::Expected> GetIntelPTBuffer(size_t offset, + size_t size); + Status ReadPerfTraceAux(llvm::MutableArrayRef &buffer, size_t offset = 0); @@ -117,6 +162,10 @@ Status GetTraceConfig(TraceOptions &config) const; + /// Get the size in bytes of the aux section of the thread or process traced + /// by this object. + size_t GetTraceBufferSizeInBytes() const; + /// Read data from a cyclic buffer /// /// \param[in] [out] buf diff --git a/lldb/source/Plugins/Process/Linux/ProcessorTrace.cpp b/lldb/source/Plugins/Process/Linux/ProcessorTrace.cpp --- a/lldb/source/Plugins/Process/Linux/ProcessorTrace.cpp +++ b/lldb/source/Plugins/Process/Linux/ProcessorTrace.cpp @@ -16,6 +16,7 @@ #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" #include "ProcessorTrace.h" #include "lldb/Host/linux/Support.h" +#include "lldb/Utility/StreamString.h" #include #include @@ -46,6 +47,10 @@ #endif } +size_t ProcessorTraceMonitor::GetTraceBufferSizeInBytes() const { + return m_mmap_meta->aux_size; +} + Expected ProcessorTraceMonitor::GetOSEventType() { auto intel_pt_type_text = llvm::MemoryBuffer::getFileAsStream(kOSEventIntelPTTypeFile); @@ -67,27 +72,22 @@ bool ProcessorTraceMonitor::IsSupported() { return (bool)GetOSEventType(); } -Status ProcessorTraceMonitor::StartTrace(lldb::pid_t pid, lldb::tid_t tid, - const TraceOptions &config) { +Error ProcessorTraceMonitor::StartTrace(lldb::pid_t pid, lldb::tid_t tid, + size_t buffer_size_in_kb, + uint32_t perf_config) { #ifndef PERF_ATTR_SIZE_VER5 llvm_unreachable("perf event not supported"); #else - Status error; Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); LLDB_LOG(log, "called thread id {0}", tid); uint64_t page_size = getpagesize(); - uint64_t bufsize = config.getTraceBufferSize(); - uint64_t metabufsize = config.getMetaDataBufferSize(); + uint64_t buffer_size = buffer_size_in_kb * 1024; uint64_t numpages = static_cast( - llvm::PowerOf2Floor((bufsize + page_size - 1) / page_size)); + llvm::PowerOf2Floor((buffer_size + page_size - 1) / page_size)); numpages = std::max(1, numpages); - bufsize = page_size * numpages; - - numpages = static_cast( - llvm::PowerOf2Floor((metabufsize + page_size - 1) / page_size)); - metabufsize = page_size * numpages; + buffer_size = page_size * numpages; perf_event_attr attr; memset(&attr, 0, sizeof(attr)); @@ -98,66 +98,58 @@ attr.exclude_hv = 1; attr.exclude_idle = 1; attr.mmap = 1; + attr.config = perf_config; Expected intel_pt_type = GetOSEventType(); - if (!intel_pt_type) { - error = intel_pt_type.takeError(); - return error; - } + if (!intel_pt_type) + return intel_pt_type.takeError(); LLDB_LOG(log, "intel pt type {0}", *intel_pt_type); attr.type = *intel_pt_type; - LLDB_LOG(log, "meta buffer size {0}", metabufsize); - LLDB_LOG(log, "buffer size {0} ", bufsize); - - if (error.Fail()) { - LLDB_LOG(log, "Status in custom config"); - - return error; - } + LLDB_LOG(log, "buffer size {0} ", buffer_size); errno = 0; auto fd = syscall(SYS_perf_event_open, &attr, static_cast<::tid_t>(tid), -1, -1, 0); if (fd == -1) { LLDB_LOG(log, "syscall error {0}", errno); - error.SetErrorString("perf event syscall Failed"); - return error; + return createStringError(inconvertibleErrorCode(), + "perf event syscall failed"); } m_fd = std::unique_ptr(new int(fd), file_close()); errno = 0; auto base = - mmap(nullptr, (metabufsize + page_size), PROT_WRITE, MAP_SHARED, fd, 0); + mmap(nullptr, (buffer_size + page_size), PROT_WRITE, MAP_SHARED, fd, 0); if (base == MAP_FAILED) { LLDB_LOG(log, "mmap base error {0}", errno); - error.SetErrorString("Meta buffer allocation failed"); - return error; + return createStringError(inconvertibleErrorCode(), + "Meta buffer allocation failed"); } m_mmap_meta = std::unique_ptr( reinterpret_cast(base), - munmap_delete(metabufsize + page_size)); + munmap_delete(buffer_size + page_size)); m_mmap_meta->aux_offset = m_mmap_meta->data_offset + m_mmap_meta->data_size; - m_mmap_meta->aux_size = bufsize; + m_mmap_meta->aux_size = buffer_size; errno = 0; - auto mmap_aux = mmap(nullptr, bufsize, PROT_READ, MAP_SHARED, fd, + auto mmap_aux = mmap(nullptr, buffer_size, PROT_READ, MAP_SHARED, fd, static_cast(m_mmap_meta->aux_offset)); if (mmap_aux == MAP_FAILED) { LLDB_LOG(log, "second mmap done {0}", errno); - error.SetErrorString("Trace buffer allocation failed"); - return error; + return createStringError(inconvertibleErrorCode(), + "Trace buffer allocation failed"); } m_mmap_aux = std::unique_ptr( - reinterpret_cast(mmap_aux), munmap_delete(bufsize)); - return error; + reinterpret_cast(mmap_aux), munmap_delete(buffer_size)); + return Error::success(); #endif } @@ -180,19 +172,17 @@ #endif } -Status ProcessorTraceMonitor::GetCPUType(TraceOptions &config) { - - Status error; - uint64_t cpu_family = -1; - uint64_t model = -1; - uint64_t stepping = -1; +Expected ProcessorTraceMonitor::GetIntelPTCPUConfig() { + int64_t cpu_family = -1; + int64_t model = -1; + int64_t stepping = -1; std::string vendor_id; Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); auto BufferOrError = getProcFile("cpuinfo"); if (!BufferOrError) - return BufferOrError.getError(); + return Status(BufferOrError.getError()).ToError(); LLDB_LOG(log, "GetCPUType Function"); @@ -226,29 +216,52 @@ } LLDB_LOG(log, "{0}:{1}:{2}:{3}", cpu_family, model, stepping, vendor_id); - if ((cpu_family != static_cast(-1)) && - (model != static_cast(-1)) && - (stepping != static_cast(-1)) && (!vendor_id.empty())) { - auto params_dict = std::make_shared(); - params_dict->AddIntegerItem("cpu_family", cpu_family); - params_dict->AddIntegerItem("cpu_model", model); - params_dict->AddIntegerItem("cpu_stepping", stepping); - params_dict->AddStringItem("cpu_vendor", vendor_id); + if ((cpu_family != -1) && (model != -1) && (stepping != -1) && + (!vendor_id.empty())) { + return TraceIntelPTCPUConfig{ + static_cast(cpu_family), static_cast(model), + static_cast(stepping), + vendor_id == "GenuineIntel" ? "intel" : "unknown"}; + } + } + return createStringError(inconvertibleErrorCode(), "cpu info not found"); +} - llvm::StringRef intel_custom_params_key("intel-pt"); +Status ProcessorTraceMonitor::GetCPUType(TraceOptions &config) { + Expected cpu_info_or_err = GetIntelPTCPUConfig(); + if (!cpu_info_or_err) + return Status(cpu_info_or_err.takeError()); + TraceIntelPTCPUConfig &cpu_info = *cpu_info_or_err; + + auto params_dict = std::make_shared(); + params_dict->AddIntegerItem("cpu_family", cpu_info.family); + params_dict->AddIntegerItem("cpu_model", cpu_info.model); + params_dict->AddIntegerItem("cpu_stepping", cpu_info.stepping); + params_dict->AddStringItem("cpu_vendor", cpu_info.vendor); + + llvm::StringRef intel_custom_params_key("intel-pt"); + + auto intel_custom_params = std::make_shared(); + intel_custom_params->AddItem( + intel_custom_params_key, + StructuredData::ObjectSP(std::move(params_dict))); + + config.setTraceParams(intel_custom_params); + return Status(); +} - auto intel_custom_params = std::make_shared(); - intel_custom_params->AddItem( - intel_custom_params_key, - StructuredData::ObjectSP(std::move(params_dict))); +llvm::Expected +ProcessorTraceMonitor::Create(lldb::pid_t pid, lldb::tid_t tid, + size_t buffer_size_in_kb, uint32_t config) { + ProcessorTraceMonitorUP pt_monitor_up(new ProcessorTraceMonitor); - config.setTraceParams(intel_custom_params); - return error; // we are done - } - } + if (llvm::Error err = + pt_monitor_up->StartTrace(pid, tid, buffer_size_in_kb, config)) + return std::move(err); - error.SetErrorString("cpu info not found"); - return error; + pt_monitor_up->SetThreadID(tid); + pt_monitor_up->SetTraceID(m_trace_num++); + return std::move(pt_monitor_up); } llvm::Expected @@ -266,7 +279,7 @@ ProcessorTraceMonitorUP pt_monitor_up(new ProcessorTraceMonitor); - error = pt_monitor_up->StartTrace(pid, tid, config); + error = pt_monitor_up->StartTrace(pid, tid, config.getTraceBufferSize()); if (error.Fail()) return error.ToError(); @@ -281,6 +294,16 @@ return std::move(pt_monitor_up); } +Expected> +ProcessorTraceMonitor::GetIntelPTBuffer(size_t offset, size_t size) { + std::vector data(size, 0); + MutableArrayRef buffer_ref(data); + Status error = ReadPerfTraceAux(buffer_ref, 0); + if (error.Fail()) + return error.ToError(); + return data; +} + Status ProcessorTraceMonitor::ReadPerfTraceAux(llvm::MutableArrayRef &buffer, size_t offset) { diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -522,6 +522,15 @@ llvm::Expected SendGetSupportedTraceType(); + llvm::Error SendTraceStart(const llvm::json::Value &options); + + llvm::Error SendTraceStop(const TraceStopPacket ¶ms); + + llvm::Expected SendTraceGetState(llvm::StringRef type); + + llvm::Expected> + SendTraceGetBinaryData(const TraceGetBinaryDataPacket ¶ms); + protected: LazyBool m_supports_not_sending_acks; LazyBool m_supports_thread_suffix; diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -3480,6 +3480,142 @@ "failed to send packet: jLLDBTraceSupportedType"); } +llvm::Error +GDBRemoteCommunicationClient::SendTraceStop(const TraceStopPacket ¶ms) { + Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); + + StreamGDBRemote escaped_packet; + escaped_packet.PutCString("jLLDBTraceStop:"); + + std::string json_string; + llvm::raw_string_ostream os(json_string); + os << llvm::json::toJSON(params); + os.flush(); + + escaped_packet.PutEscapedBytes(json_string.c_str(), json_string.size()); + + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponse(escaped_packet.GetString(), response, + true) == + GDBRemoteCommunication::PacketResult::Success) { + if (response.IsErrorResponse()) + return response.GetStatus().ToError(); + if (response.IsUnsupportedResponse()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "jLLDBTraceStop is unsupported"); + if (response.IsOKResponse()) + return llvm::Error::success(); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Invalid jLLDBTraceStop response"); + } + LLDB_LOG(log, "failed to send packet: jLLDBTraceStop"); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "failed to send packet: jLLDBTraceStop '%s'", + escaped_packet.GetData()); +} + +llvm::Error +GDBRemoteCommunicationClient::SendTraceStart(const llvm::json::Value ¶ms) { + Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); + + StreamGDBRemote escaped_packet; + escaped_packet.PutCString("jLLDBTraceStart:"); + + std::string json_string; + llvm::raw_string_ostream os(json_string); + os << params; + os.flush(); + + escaped_packet.PutEscapedBytes(json_string.c_str(), json_string.size()); + + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponse(escaped_packet.GetString(), response, + true) == + GDBRemoteCommunication::PacketResult::Success) { + if (response.IsErrorResponse()) + return response.GetStatus().ToError(); + if (response.IsUnsupportedResponse()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "jLLDBTraceStart is unsupported"); + if (response.IsOKResponse()) + return llvm::Error::success(); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Invalid jLLDBTraceStart response"); + } + LLDB_LOG(log, "failed to send packet: jLLDBTraceStart"); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "failed to send packet: jLLDBTraceStart '%s'", + escaped_packet.GetData()); +} + +llvm::Expected +GDBRemoteCommunicationClient::SendTraceGetState(llvm::StringRef type) { + Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); + + StreamGDBRemote escaped_packet; + escaped_packet.PutCString("jLLDBTraceGetState:"); + + std::string json_string; + llvm::raw_string_ostream os(json_string); + os << llvm::json::toJSON(TraceGetStatePacket{type.str()}); + os.flush(); + + escaped_packet.PutEscapedBytes(json_string.c_str(), json_string.size()); + + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponse(escaped_packet.GetString(), response, + true) == + GDBRemoteCommunication::PacketResult::Success) { + if (response.IsErrorResponse()) + return response.GetStatus().ToError(); + if (response.IsUnsupportedResponse()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "jLLDBTraceGetState is unsupported"); + return std::string(response.Peek()); + } + + LLDB_LOG(log, "failed to send packet: jLLDBTraceGetState"); + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "failed to send packet: jLLDBTraceGetState '%s'", + escaped_packet.GetData()); +} + +llvm::Expected> +GDBRemoteCommunicationClient::SendTraceGetBinaryData( + const TraceGetBinaryDataPacket ¶ms) { + Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); + + StreamGDBRemote escaped_packet; + escaped_packet.PutCString("jLLDBTraceGetBinaryData:"); + + std::string json_string; + llvm::raw_string_ostream os(json_string); + os << llvm::json::toJSON(params); + os.flush(); + + escaped_packet.PutEscapedBytes(json_string.c_str(), json_string.size()); + + StringExtractorGDBRemote response; + if (SendPacketAndWaitForResponse(escaped_packet.GetString(), response, + true) == + GDBRemoteCommunication::PacketResult::Success) { + if (response.IsErrorResponse()) + return response.GetStatus().ToError(); + if (response.IsUnsupportedResponse()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "jLLDBTraceGetBinaryData is unsupported"); + std::string data; + response.GetEscapedBinaryData(data); + return std::vector(data.begin(), data.end()); + } + LLDB_LOG(log, "failed to send packet: jLLDBTraceGetBinaryData"); + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "failed to send packet: jLLDBTraceGetBinaryData '%s'", + escaped_packet.GetData()); +} + Status GDBRemoteCommunicationClient::SendGetTraceConfigPacket(lldb::user_id_t uid, TraceOptions &options) { diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.h @@ -73,6 +73,13 @@ PacketResult SendOKResponse(); + /// Serialize and send a JSON object response. + PacketResult SendJSONResponse(const llvm::json::Value &value); + + /// Serialize and send a JSON object response, or respond with an error if the + /// input object is an \a llvm::Error. + PacketResult SendJSONResponse(llvm::Expected value); + private: GDBRemoteCommunicationServer(const GDBRemoteCommunicationServer &) = delete; const GDBRemoteCommunicationServer & diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.cpp @@ -16,11 +16,13 @@ #include "lldb/Utility/StreamString.h" #include "lldb/Utility/StringExtractorGDBRemote.h" #include "lldb/Utility/UnimplementedError.h" +#include "llvm/Support/JSON.h" #include using namespace lldb; using namespace lldb_private; using namespace lldb_private::process_gdb_remote; +using namespace llvm; GDBRemoteCommunicationServer::GDBRemoteCommunicationServer( const char *comm_name, const char *listener_name) @@ -151,3 +153,21 @@ bool GDBRemoteCommunicationServer::HandshakeWithClient() { return GetAck() == PacketResult::Success; } + +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServer::SendJSONResponse(const json::Value &value) { + std::string json_string; + raw_string_ostream os(json_string); + os << value; + os.flush(); + StreamGDBRemote escaped_response; + escaped_response.PutEscapedBytes(json_string.c_str(), json_string.size()); + return SendPacketNoLock(escaped_response.GetString()); +} + +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServer::SendJSONResponse(Expected value) { + if (!value) + return SendErrorResponse(value.takeError()); + return SendJSONResponse(*value); +} diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -166,6 +166,14 @@ PacketResult Handle_jLLDBTraceSupportedType(StringExtractorGDBRemote &packet); + PacketResult Handle_jLLDBTraceStart(StringExtractorGDBRemote &packet); + + PacketResult Handle_jLLDBTraceStop(StringExtractorGDBRemote &packet); + + PacketResult Handle_jLLDBTraceGetState(StringExtractorGDBRemote &packet); + + PacketResult Handle_jLLDBTraceGetBinaryData(StringExtractorGDBRemote &packet); + PacketResult Handle_QRestoreRegisterState(StringExtractorGDBRemote &packet); PacketResult Handle_vAttach(StringExtractorGDBRemote &packet); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -194,6 +194,18 @@ RegisterMemberFunctionHandler( StringExtractorGDBRemote::eServerPacketType_jLLDBTraceSupportedType, &GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceSupportedType); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_jLLDBTraceStart, + &GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceStart); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_jLLDBTraceStop, + &GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceStop); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_jLLDBTraceGetState, + &GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceGetState); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_jLLDBTraceGetBinaryData, + &GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceGetBinaryData); RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_g, &GDBRemoteCommunicationServerLLGS::Handle_g); @@ -1256,6 +1268,94 @@ return SendPacketNoLock(escaped_response.GetString()); } +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceStop( + StringExtractorGDBRemote &packet) { + // Fail if we don't have a current process. + if (!m_debugged_process_up || + (m_debugged_process_up->GetID() == LLDB_INVALID_PROCESS_ID)) + return SendErrorResponse(Status("Process not running.")); + + packet.ConsumeFront("jLLDBTraceStop:"); + Expected stop_packet = + json::parse(packet.Peek()); + if (!stop_packet) + return SendErrorResponse(stop_packet.takeError()); + + if (Error err = m_debugged_process_up->TraceStop(*stop_packet)) + return SendErrorResponse(std::move(err)); + + return SendOKResponse(); +} + +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceStart( + StringExtractorGDBRemote &packet) { + + // Fail if we don't have a current process. + if (!m_debugged_process_up || + (m_debugged_process_up->GetID() == LLDB_INVALID_PROCESS_ID)) + return SendErrorResponse(Status("Process not running.")); + + packet.ConsumeFront("jLLDBTraceStart:"); + Expected start_packet = + json::parse(packet.Peek()); + if (!start_packet) + return SendErrorResponse(start_packet.takeError()); + + if (Error err = + m_debugged_process_up->TraceStart(packet.Peek(), start_packet->type)) + return SendErrorResponse(std::move(err)); + + return SendOKResponse(); +} + +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceGetState( + StringExtractorGDBRemote &packet) { + + // Fail if we don't have a current process. + if (!m_debugged_process_up || + (m_debugged_process_up->GetID() == LLDB_INVALID_PROCESS_ID)) + return SendErrorResponse(Status("Process not running.")); + + packet.ConsumeFront("jLLDBTraceGetState:"); + Expected get_state_packet = + json::parse(packet.Peek()); + if (!get_state_packet) + return SendErrorResponse(get_state_packet.takeError()); + + if (Expected response_or_err = + m_debugged_process_up->TraceGetState(get_state_packet->type)) + return SendJSONResponse(*response_or_err); + else + return SendErrorResponse(response_or_err.takeError()); +} + +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceGetBinaryData( + StringExtractorGDBRemote &packet) { + + // Fail if we don't have a current process. + if (!m_debugged_process_up || + (m_debugged_process_up->GetID() == LLDB_INVALID_PROCESS_ID)) + return SendErrorResponse(Status("Process not running.")); + + packet.ConsumeFront("jLLDBTraceGetBinaryData:"); + llvm::Expected get_data_packet = + llvm::json::parse(packet.Peek()); + if (!get_data_packet) + return SendErrorResponse(Status(get_data_packet.takeError())); + + if (Expected> bytes = + m_debugged_process_up->TraceGetBinaryData(*get_data_packet)) { + StreamGDBRemote response; + response.PutEscapedBytes(bytes->data(), bytes->size()); + return SendPacketNoLock(response.GetString()); + } else + return SendErrorResponse(bytes.takeError()); +} + GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::Handle_jTraceConfigRead( StringExtractorGDBRemote &packet) { diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -177,6 +177,15 @@ llvm::Expected GetSupportedTraceType() override; + llvm::Error StopTracing(const TraceStopPacket &request) override; + + llvm::Error StartTracing(const llvm::json::Value &request) override; + + llvm::Expected GetTracingState(llvm::StringRef type) override; + + llvm::Expected> + TraceGetBinaryData(const TraceGetBinaryDataPacket ¶ms) override; + Status GetTraceConfig(lldb::user_id_t uid, TraceOptions &options) override; Status GetWatchpointSupportInfo(uint32_t &num, bool &after) override; diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -1228,6 +1228,24 @@ return m_gdb_comm.SendGetSupportedTraceType(); } +llvm::Error ProcessGDBRemote::StopTracing(const TraceStopPacket &request) { + return m_gdb_comm.SendTraceStop(request); +} + +llvm::Error ProcessGDBRemote::StartTracing(const llvm::json::Value &request) { + return m_gdb_comm.SendTraceStart(request); +} + +llvm::Expected +ProcessGDBRemote::GetTracingState(llvm::StringRef type) { + return m_gdb_comm.SendTraceGetState(type); +} + +llvm::Expected> +ProcessGDBRemote::TraceGetBinaryData(const TraceGetBinaryDataPacket ¶ms) { + return m_gdb_comm.SendTraceGetBinaryData(params); +} + void ProcessGDBRemote::DidExit() { // When we exit, disconnect from the GDB server communications m_gdb_comm.Disconnect(); diff --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h --- a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h @@ -16,7 +16,7 @@ namespace lldb_private { namespace trace_intel_pt { -class CommandObjectTraceStartIntelPT : public CommandObjectIterateOverThreads { +class CommandObjectTraceStartIntelPT : public CommandObjecThreadTraceStart { public: class CommandOptions : public Options { public: @@ -31,17 +31,17 @@ llvm::ArrayRef GetDefinitions() override; - size_t m_size_in_kb; - uint32_t m_custom_config; + size_t m_buffer_size_in_kb; + uint32_t m_perf_config; }; CommandObjectTraceStartIntelPT(CommandInterpreter &interpreter) - : CommandObjectIterateOverThreads( - interpreter, "thread trace start", + : CommandObjecThreadTraceStart( + "intel-pt", 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.", + "all threads including future threads.", "thread trace start [ ...] " "[]", lldb::eCommandRequiresProcess | lldb::eCommandTryTargetAPILock | @@ -54,7 +54,9 @@ Options *GetOptions() override { return &m_options; } protected: - bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override; + llvm::Expected + CreateTraceStartParams(lldb::TraceOperationVariant variant, + const std::vector &tids) override; CommandOptions m_options; }; diff --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp --- a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp @@ -8,7 +8,9 @@ #include "CommandObjectTraceStartIntelPT.h" +#include "TraceIntelPT.h" #include "lldb/Host/OptionParser.h" +#include "lldb/Target/Process.h" #include "lldb/Target/Trace.h" using namespace lldb; @@ -33,17 +35,17 @@ error.SetErrorStringWithFormat("invalid integer value for option '%s'", option_arg.str().c_str()); else - m_size_in_kb = size_in_kb; + m_buffer_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) + int32_t perf_config; + if (option_arg.empty() || option_arg.getAsInteger(0, perf_config) || + perf_config < 0) error.SetErrorStringWithFormat("invalid integer value for option '%s'", option_arg.str().c_str()); else - m_custom_config = custom_config; + m_perf_config = perf_config; break; } default: @@ -54,8 +56,8 @@ void CommandObjectTraceStartIntelPT::CommandOptions::OptionParsingStarting( ExecutionContext *execution_context) { - m_size_in_kb = 4; - m_custom_config = 0; + m_buffer_size_in_kb = 4; + m_perf_config = 0; } llvm::ArrayRef @@ -63,11 +65,14 @@ 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(); +Expected CommandObjectTraceStartIntelPT::CreateTraceStartParams( + TraceOperationVariant variant, const std::vector &tids) { + + TraceIntelPTStartPacketParams params; + params.buffer_size_in_kb = m_options.m_buffer_size_in_kb; + params.perf_config = m_options.m_perf_config; + + TraceIntelPTStartPacket packet("intel-pt", variant, tids, params); + + return json::toJSON(packet); } diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h --- a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h @@ -111,6 +111,10 @@ : m_instructions(std::move(instructions)), m_position(GetLastPosition()) { } + /// Constructor with a single error signaling a complete failure of the + /// decoding process. + DecodedThread(llvm::Error error); + /// Get the instructions from the decoded trace. Some of them might indicate /// errors (i.e. gaps) in the trace. /// diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp --- a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp @@ -62,3 +62,8 @@ m_position = std::min(new_position, GetLastPosition()); return m_position; } + +DecodedThread::DecodedThread(Error error) { + m_instructions.emplace_back(std::move(error)); + m_position = GetLastPosition(); +} diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h --- a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h @@ -18,32 +18,75 @@ namespace lldb_private { namespace trace_intel_pt { -/// \a lldb_private::ThreadTrace decoder that stores the output from decoding, -/// avoiding recomputations, as decoding is expensive. -class ThreadTraceDecoder { +/// Base class that handles the decoding of a thread and its trace buffer / +/// file. +class ThreadDecoder { +public: + virtual ~ThreadDecoder() = default; + + ThreadDecoder(const pt_cpu &pt_cpu) : m_pt_cpu(pt_cpu) {} + + /// Decode the thread and store the result internally, to avoid + /// recomputations. + /// + /// \return + /// A \a DecodedThread instance. + const DecodedThread &Decode(); + + virtual size_t GetBufferSizeInBytes() = 0; + + ThreadDecoder(const ThreadDecoder &other) = delete; + ThreadDecoder &operator=(const ThreadDecoder &other) = delete; + +protected: + /// Decode the thread. + /// + /// \return + /// A \a DecodedThread instance. + virtual DecodedThread DoDecode() = 0; + + pt_cpu m_pt_cpu; + llvm::Optional m_decoded_thread; +}; + +/// Decoder implementation for \a lldb_private::ThreadTrace, which are non-live +/// processes that come trace session files. +class ThreadTraceDecoder : public ThreadDecoder { public: /// \param[in] trace_thread /// The thread whose trace file will be decoded. /// /// \param[in] pt_cpu - /// The libipt cpu used when recording the trace. + /// The libipt cpu used when decoding the trace. ThreadTraceDecoder(const std::shared_ptr &trace_thread, const pt_cpu &pt_cpu) - : m_trace_thread(trace_thread), m_pt_cpu(pt_cpu), m_decoded_thread() {} + : ThreadDecoder(pt_cpu), m_trace_thread(trace_thread) {} - /// Decode the thread and store the result internally. - /// - /// \return - /// A \a DecodedThread instance. - const DecodedThread &Decode(); + size_t GetBufferSizeInBytes() override; private: - ThreadTraceDecoder(const ThreadTraceDecoder &other) = delete; - ThreadTraceDecoder &operator=(const ThreadTraceDecoder &other) = delete; + DecodedThread DoDecode() override; std::shared_ptr m_trace_thread; - pt_cpu m_pt_cpu; - llvm::Optional m_decoded_thread; +}; + +class LiveThreadDecoder : public ThreadDecoder { +public: + /// \param[in] thread + /// The thread whose traces will be decoded. + /// + /// \param[in] pt_cpu + /// The libipt cpu used when decoding the trace. + LiveThreadDecoder(Thread &thread, const pt_cpu &pt_cpu, + size_t buffer_size_in_bytes); + + size_t GetBufferSizeInBytes() override; + +private: + DecodedThread DoDecode() override; + + lldb::ThreadSP m_thread_sp; + size_t m_buffer_size_in_bytes; }; } // namespace trace_intel_pt diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp --- a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp @@ -10,10 +10,12 @@ #include "llvm/Support/MemoryBuffer.h" +#include "TraceIntelPT.h" #include "lldb/Core/Module.h" #include "lldb/Core/Section.h" #include "lldb/Target/Target.h" #include "lldb/Target/ThreadTrace.h" +#include "lldb/Utility/StringExtractor.h" using namespace lldb; using namespace lldb_private; @@ -158,39 +160,22 @@ return bytes_read; } -static std::vector makeInstructionListFromError(Error err) { - std::vector instructions; - instructions.emplace_back(std::move(err)); - return instructions; -} - -static std::vector +static Expected> CreateDecoderAndDecode(Process &process, const pt_cpu &pt_cpu, - const FileSpec &trace_file) { - ErrorOr> trace_or_error = - MemoryBuffer::getFile(trace_file.GetPath()); - if (std::error_code err = trace_or_error.getError()) - return makeInstructionListFromError(errorCodeToError(err)); - - MemoryBuffer &trace = **trace_or_error; - + MutableArrayRef buffer) { pt_config config; pt_config_init(&config); config.cpu = pt_cpu; if (int errcode = pt_cpu_errata(&config.errata, &config.cpu)) - return makeInstructionListFromError(make_error(errcode)); + return make_error(errcode); - // The libipt library does not modify the trace buffer, hence the following - // cast is safe. - config.begin = - reinterpret_cast(const_cast(trace.getBufferStart())); - config.end = - reinterpret_cast(const_cast(trace.getBufferEnd())); + config.begin = buffer.data(); + config.end = buffer.data() + buffer.size(); pt_insn_decoder *decoder = pt_insn_alloc_decoder(&config); if (!decoder) - return makeInstructionListFromError(make_error(-pte_nomem)); + return make_error(-pte_nomem); pt_image *image = pt_insn_get_image(decoder); @@ -204,12 +189,74 @@ return instructions; } -const DecodedThread &ThreadTraceDecoder::Decode() { - if (!m_decoded_thread.hasValue()) { - m_decoded_thread = DecodedThread( - CreateDecoderAndDecode(*m_trace_thread->GetProcess(), m_pt_cpu, - m_trace_thread->GetTraceFile())); - } +static Expected> +CreateDecoderAndDecode(Process &process, const pt_cpu &pt_cpu, + const FileSpec &trace_file) { + ErrorOr> trace_or_error = + MemoryBuffer::getFile(trace_file.GetPath()); + if (std::error_code err = trace_or_error.getError()) + return errorCodeToError(err); + MemoryBuffer &trace = **trace_or_error; + MutableArrayRef trace_data( + // The libipt library does not modify the trace buffer, hence the + // following cast is safe. + reinterpret_cast(const_cast(trace.getBufferStart())), + trace.getBufferSize()); + return CreateDecoderAndDecode(process, pt_cpu, trace_data); +} + +static Expected> +CreateDecoderAndDecode(Thread &thread, const pt_cpu &pt_cpu, + size_t size_in_bytes) { + Expected> buffer = + thread.GetProcess()->TraceGetBinaryData(TraceGetBinaryDataPacket{ + "intel-pt", "thread_trace_buffer", thread.GetID(), 0, size_in_bytes}); + if (!buffer) + return buffer.takeError(); + return CreateDecoderAndDecode(*thread.GetProcess(), pt_cpu, + MutableArrayRef(*buffer)); +} + +DecodedThread ThreadTraceDecoder::DoDecode() { + if (Expected> instructions = + CreateDecoderAndDecode(*m_trace_thread->GetProcess(), m_pt_cpu, + m_trace_thread->GetTraceFile())) + return DecodedThread(std::move(*instructions)); + else + return DecodedThread(instructions.takeError()); +} + +LiveThreadDecoder::LiveThreadDecoder(Thread &thread, const pt_cpu &pt_cpu, + size_t buffer_size_in_bytes) + : ThreadDecoder(pt_cpu), m_thread_sp(thread.shared_from_this()), + m_buffer_size_in_bytes(buffer_size_in_bytes) {} + +DecodedThread LiveThreadDecoder::DoDecode() { + if (Expected> instructions = + CreateDecoderAndDecode(*m_thread_sp, m_pt_cpu, + GetBufferSizeInBytes())) + return DecodedThread(std::move(*instructions)); + else + return DecodedThread(instructions.takeError()); +} + +const DecodedThread &ThreadDecoder::Decode() { + if (!m_decoded_thread.hasValue()) + m_decoded_thread = DoDecode(); return *m_decoded_thread; } + +size_t LiveThreadDecoder::GetBufferSizeInBytes() { + return m_buffer_size_in_bytes; +} + +size_t ThreadTraceDecoder::GetBufferSizeInBytes() { + ErrorOr> trace_or_error = + MemoryBuffer::getFile(m_trace_thread->GetTraceFile().GetPath()); + if (std::error_code err = trace_or_error.getError()) + return 0; + + MemoryBuffer &trace = **trace_or_error; + return trace.getBufferSize(); +} diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -45,8 +45,12 @@ /// \return /// A trace instance or an error in case of failures. static llvm::Expected - CreateInstance(const llvm::json::Value &trace_session_file, - llvm::StringRef session_file_dir, Debugger &debugger); + CreateInstanceForSessionFile(const llvm::json::Value &trace_session_file, + llvm::StringRef session_file_dir, + Debugger &debugger); + + static llvm::Expected + CreateInstanceForLiveProcess(Process &process); static ConstString GetPluginNameStatic(); @@ -56,13 +60,17 @@ llvm::StringRef GetSchema() override; void TraverseInstructions( - const Thread &thread, size_t position, TraceDirection direction, + Thread &thread, size_t position, TraceDirection direction, std::function load_addr)> callback) override; - size_t GetInstructionCount(const Thread &thread) override; + size_t GetInstructionCount(Thread &thread) override; + + size_t GetCursorPosition(Thread &thread) override; - size_t GetCursorPosition(const Thread &thread) override; + void RefreshLiveProcessState(Process &process); + + bool IsTraced(const Thread &thread) override; private: friend class TraceIntelPTSessionFileParser; @@ -73,6 +81,9 @@ TraceIntelPT(const pt_cpu &pt_cpu, const std::vector> &traced_threads); + /// Constructor for live processes + TraceIntelPT() : m_pt_cpu(), m_thread_decoders(){}; + /// Decode the trace of the given thread that, i.e. recontruct the traced /// instructions. That trace must be managed by this class. /// @@ -83,11 +94,14 @@ /// \return /// A \a DecodedThread instance if decoding was successful, or a \b /// nullptr if the thread's trace is not managed by this class. - const DecodedThread *Decode(const Thread &thread); + const DecodedThread *Decode(Thread &thread); pt_cpu m_pt_cpu; - std::map, ThreadTraceDecoder> - m_trace_threads; + std::map> m_thread_decoders; + int64_t m_stop_id = -1; + /// Dummy DecodedThread used when decoding threads after there were errors + /// when refreshing the live process state. + llvm::Optional m_failed_live_threads_decoder; }; } // namespace trace_intel_pt 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 @@ -28,12 +28,13 @@ void TraceIntelPT::Initialize() { PluginManager::RegisterPlugin( - GetPluginNameStatic(), "Intel Processor Trace", CreateInstance, + GetPluginNameStatic(), "Intel Processor Trace", + CreateInstanceForSessionFile, CreateInstanceForLiveProcess, TraceIntelPTSessionFileParser::GetSchema(), GetStartCommand); } void TraceIntelPT::Terminate() { - PluginManager::UnregisterPlugin(CreateInstance); + PluginManager::UnregisterPlugin(CreateInstanceForSessionFile); } ConstString TraceIntelPT::GetPluginNameStatic() { @@ -55,34 +56,41 @@ void TraceIntelPT::Dump(Stream *s) const {} -Expected -TraceIntelPT::CreateInstance(const json::Value &trace_session_file, - StringRef session_file_dir, Debugger &debugger) { +Expected TraceIntelPT::CreateInstanceForSessionFile( + const json::Value &trace_session_file, StringRef session_file_dir, + Debugger &debugger) { return TraceIntelPTSessionFileParser(debugger, trace_session_file, session_file_dir) .Parse(); } +Expected TraceIntelPT::CreateInstanceForLiveProcess(Process &process) { + TraceSP instance(new TraceIntelPT()); + process.GetTarget().SetTrace(instance); + return instance; +} + TraceIntelPT::TraceIntelPT( const pt_cpu &pt_cpu, const std::vector> &traced_threads) : m_pt_cpu(pt_cpu) { for (const std::shared_ptr &thread : traced_threads) - m_trace_threads.emplace( - std::piecewise_construct, - std::forward_as_tuple(thread->GetProcess()->GetID(), thread->GetID()), - std::forward_as_tuple(thread, pt_cpu)); + m_thread_decoders.emplace( + thread.get(), std::make_unique(thread, pt_cpu)); } -const DecodedThread *TraceIntelPT::Decode(const Thread &thread) { - auto it = m_trace_threads.find( - std::make_pair(thread.GetProcess()->GetID(), thread.GetID())); - if (it == m_trace_threads.end()) +const DecodedThread *TraceIntelPT::Decode(Thread &thread) { + RefreshLiveProcessState(*thread.GetProcess()); + if (m_failed_live_threads_decoder.hasValue()) + return &*m_failed_live_threads_decoder; + + auto it = m_thread_decoders.find(&thread); + if (it == m_thread_decoders.end()) return nullptr; - return &it->second.Decode(); + return &it->second->Decode(); } -size_t TraceIntelPT::GetCursorPosition(const Thread &thread) { +size_t TraceIntelPT::GetCursorPosition(Thread &thread) { const DecodedThread *decoded_thread = Decode(thread); if (!decoded_thread) return 0; @@ -90,7 +98,7 @@ } void TraceIntelPT::TraverseInstructions( - const Thread &thread, size_t position, TraceDirection direction, + Thread &thread, size_t position, TraceDirection direction, std::function load_addr)> callback) { const DecodedThread *decoded_thread = Decode(thread); @@ -106,9 +114,49 @@ break; } -size_t TraceIntelPT::GetInstructionCount(const Thread &thread) { +size_t TraceIntelPT::GetInstructionCount(Thread &thread) { if (const DecodedThread *decoded_thread = Decode(thread)) return decoded_thread->GetInstructions().size(); else return 0; } + +void TraceIntelPT::RefreshLiveProcessState(Process &process) { + if (!process.IsLiveDebugSession()) + return; + + uint32_t new_stop_id = process.GetStopID(); + if (new_stop_id == m_stop_id) + return; + + m_thread_decoders.clear(); + m_stop_id = new_stop_id; + + Expected json_string = process.GetTracingState("intel-pt"); + if (!json_string) { + m_failed_live_threads_decoder = DecodedThread(json_string.takeError()); + return; + } + + Expected state = + json::parse(*json_string); + if (!state) { + std::vector instructions; + instructions.emplace_back(state.takeError()); + m_failed_live_threads_decoder = DecodedThread(state.takeError()); + return; + } + + m_pt_cpu = TraceIntelPTSessionFileParser::ParsePTCPU(state->cpu_config); + + for (const TraceIntelPTThreadState &thread_state : state->threads) { + Thread &thread = *process.GetThreadList().FindThreadByID(thread_state.tid); + m_thread_decoders.emplace( + &thread, std::make_unique( + thread, m_pt_cpu, thread_state.buffer_size_in_bytes)); + } +} + +bool TraceIntelPT::IsTraced(const Thread &thread) { + return m_thread_decoders.count(&thread); +} diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td @@ -4,13 +4,14 @@ 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">, + Desc<"The size of the trace in KB. It is rounded up to the nearest pwoer of" + " 2. Defaults to 4.">; + def thread_trace_start_intel_pt_custom_config: Option<"perf-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/*`. " + Desc<"Custom low level perf event 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/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h @@ -19,16 +19,9 @@ class TraceIntelPTSessionFileParser : public TraceSessionFileParser { public: - struct JSONPTCPU { - std::string vendor; - int64_t family; - int64_t model; - int64_t stepping; - }; - struct JSONTraceIntelPTSettings : TraceSessionFileParser::JSONTracePluginSettings { - JSONPTCPU pt_cpu; + TraceIntelPTCPUConfig pt_cpu; }; /// See \a TraceSessionFileParser::TraceSessionFileParser for the description @@ -55,9 +48,9 @@ CreateTraceIntelPTInstance(const pt_cpu &pt_cpu, std::vector &parsed_processes); -private: - pt_cpu ParsePTCPU(const JSONPTCPU &pt_cpu); + static pt_cpu ParsePTCPU(const TraceIntelPTCPUConfig &pt_cpu); +private: const llvm::json::Value &m_trace_session_file; }; @@ -67,12 +60,6 @@ namespace llvm { namespace json { -bool fromJSON( - const Value &value, - lldb_private::trace_intel_pt::TraceIntelPTSessionFileParser::JSONPTCPU - &pt_cpu, - Path path); - bool fromJSON(const Value &value, lldb_private::trace_intel_pt::TraceIntelPTSessionFileParser:: JSONTraceIntelPTSettings &plugin_settings, diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp @@ -13,6 +13,7 @@ #include "lldb/Target/Target.h" #include "lldb/Target/ThreadList.h" #include "lldb/Target/ThreadTrace.h" +#include "lldb/Utility/TraceOptions.h" using namespace lldb; using namespace lldb_private; @@ -35,7 +36,8 @@ return schema; } -pt_cpu TraceIntelPTSessionFileParser::ParsePTCPU(const JSONPTCPU &pt_cpu) { +pt_cpu +TraceIntelPTSessionFileParser::ParsePTCPU(const TraceIntelPTCPUConfig &pt_cpu) { return {pt_cpu.vendor.compare("intel") == 0 ? pcv_intel : pcv_unknown, static_cast(pt_cpu.family), static_cast(pt_cpu.model), @@ -73,14 +75,6 @@ namespace llvm { namespace json { -bool fromJSON(const Value &value, - TraceIntelPTSessionFileParser::JSONPTCPU &pt_cpu, Path path) { - ObjectMapper o(value, path); - return o && o.map("vendor", pt_cpu.vendor) && - o.map("family", pt_cpu.family) && o.map("model", pt_cpu.model) && - o.map("stepping", pt_cpu.stepping); -} - bool fromJSON( const Value &value, TraceIntelPTSessionFileParser::JSONTraceIntelPTSettings &plugin_settings, diff --git a/lldb/source/Target/Trace.cpp b/lldb/source/Target/Trace.cpp --- a/lldb/source/Target/Trace.cpp +++ b/lldb/source/Target/Trace.cpp @@ -72,6 +72,19 @@ return createInvalidPlugInError(json_session.trace.type); } +Expected Trace::FindPlugin(llvm::StringRef plugin_name, + Process &process) { + if (!process.IsLiveDebugSession()) + return createStringError(inconvertibleErrorCode(), + "Can't trace non-live processes"); + + ConstString name(plugin_name); + if (auto create_callback = + PluginManager::GetTraceCreateCallbackForLiveProcess(name)) + return create_callback(process); + return createInvalidPlugInError(plugin_name); +} + Expected Trace::FindPluginSchema(StringRef name) { ConstString plugin_name(name); StringRef schema = PluginManager::GetTraceSchema(plugin_name); @@ -204,6 +217,12 @@ void Trace::DumpTraceInstructions(Thread &thread, Stream &s, size_t count, size_t end_position, bool raw) { + if (!IsTraced(thread)) { + s.Printf("thread #%u: tid = %" PRIu64 ", not traced\n", thread.GetIndexID(), + thread.GetID()); + return; + } + size_t instructions_count = GetInstructionCount(thread); s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = %zu\n", thread.GetIndexID(), thread.GetID(), instructions_count); diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp --- a/lldb/source/Utility/StringExtractorGDBRemote.cpp +++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -310,8 +310,17 @@ return eServerPacketType_jTraceStart; if (PACKET_STARTS_WITH("jTraceStop:")) return eServerPacketType_jTraceStop; + if (PACKET_MATCHES("jLLDBTraceSupportedType")) return eServerPacketType_jLLDBTraceSupportedType; + if (PACKET_STARTS_WITH("jLLDBTraceStop:")) + return eServerPacketType_jLLDBTraceStop; + if (PACKET_STARTS_WITH("jLLDBTraceStart:")) + return eServerPacketType_jLLDBTraceStart; + if (PACKET_STARTS_WITH("jLLDBTraceGetState:")) + return eServerPacketType_jLLDBTraceGetState; + if (PACKET_STARTS_WITH("jLLDBTraceGetBinaryData:")) + return eServerPacketType_jLLDBTraceGetBinaryData; break; case 'v': diff --git a/lldb/source/Utility/TraceOptions.cpp b/lldb/source/Utility/TraceOptions.cpp --- a/lldb/source/Utility/TraceOptions.cpp +++ b/lldb/source/Utility/TraceOptions.cpp @@ -8,11 +8,76 @@ #include "lldb/Utility/TraceOptions.h" +#include + +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/StringExtractor.h" + using namespace lldb_private; namespace llvm { namespace json { +template ::value || + std::is_same::value || + std::is_same::value || + std::is_same::value>::type * = nullptr> +static bool fromJSON(const Value &value, T &out, Path path) { + if (auto number = value.getAsInteger()) { + out = *number; + return true; + } + path.report("expected integer"); + return false; +} + +template ::value>::type * = nullptr> +static Value toJSON(T number) { + return Value(static_cast(number)); +} + +template static Value toJSON(const std::vector &elements) { + Array array; + array.reserve(elements.size()); + + for (const T &element : elements) + array.push_back(toJSON(element)); + + return array; +} + +static bool fromJSON(const Value &value, lldb::TraceOperationVariant &variant, + Path path) { + std::string string_variant; + if (!fromJSON(value, string_variant, path)) + return false; + if (string_variant == "specificThreads") + variant = lldb::eTraceOperationVariantSpecificThreads; + else if (string_variant == "currentAndFutureThreads") + variant = lldb::eTraceOperationVariantCurrentAndFutureThreads; + else if (string_variant == "process") + variant = lldb::eTraceOperationVariantProcess; + else { + path.report("invalid TraceOperationVariant"); + return false; + } + return true; +} + +static std::string toJSON(lldb::TraceOperationVariant variant) { + switch (variant) { + case lldb::eTraceOperationVariantSpecificThreads: + return "specificThreads"; + case lldb::eTraceOperationVariantCurrentAndFutureThreads: + return "currentAndFutureThreads"; + case lldb::eTraceOperationVariantProcess: + return "process"; + } +}; + +// jLLDBTraceGetSupportedType bool fromJSON(const Value &value, TraceTypeInfo &info, Path path) { ObjectMapper o(value, path); if (!o) @@ -21,5 +86,112 @@ return o.map("name", info.name); } +// jLLDBTraceStop +bool fromJSON(const Value &value, TraceStopPacket &packet, Path path) { + ObjectMapper o(value, path); + return o && o.map("type", packet.type) && o.map("tids", packet.tids) && + o.map("variant", packet.variant); +} + +Value toJSON(const TraceStopPacket &packet) { + return Object{{"type", packet.type}, + {"tids", toJSON(packet.tids)}, + {"variant", toJSON(packet.variant)}}; +} + +// jLLDBTraceStart + +bool fromJSON(const Value &value, TraceStartBasePacket &packet, Path path) { + ObjectMapper o(value, path); + return o && o.map("type", packet.type) && o.map("tids", packet.tids) && + o.map("variant", packet.variant); +} + +Value toJSON(const lldb_private::TraceStartBasePacket &packet) { + return Object{{"tids", toJSON(packet.tids)}, + {"variant", toJSON(packet.variant)}, + {"type", packet.type}}; +} + +bool fromJSON(const Value &value, TraceIntelPTStartPacketParams &packet, + Path path) { + ObjectMapper o(value, path); + return o && o.map("bufferSizeInKB", packet.buffer_size_in_kb) && + o.map("perfConfig", packet.perf_config); +} + +Value toJSON(const TraceIntelPTStartPacketParams &packet) { + return Object{ + {"bufferSizeInKB", static_cast(packet.buffer_size_in_kb)}, + {"perfConfig", static_cast(packet.perf_config)}}; +} + +// jLLDBTraceGetState + +bool fromJSON(const Value &value, TraceGetStatePacket &packet, Path path) { + ObjectMapper o(value, path); + return o && o.map("type", packet.type); +} + +Value toJSON(const TraceGetStatePacket &packet) { + return Object{{"type", packet.type}}; +} + +bool fromJSON(const Value &value, TraceIntelPTThreadState &state, Path path) { + ObjectMapper o(value, path); + return o && o.map("tid", state.tid) && + o.map("bufferSizeInBytes", state.buffer_size_in_bytes); +} + +Value toJSON(const lldb_private::TraceIntelPTThreadState &state) { + return Object{ + {"tid", static_cast(state.tid)}, + {"bufferSizeInBytes", static_cast(state.buffer_size_in_bytes)}}; +} + +bool fromJSON(const Value &value, TraceIntelPTCPUConfig &cpu_config, + Path path) { + ObjectMapper o(value, path); + return o && o.map("vendor", cpu_config.vendor) && + o.map("family", cpu_config.family) && + o.map("model", cpu_config.model) && + o.map("stepping", cpu_config.stepping); +} + +Value toJSON(const TraceIntelPTCPUConfig &cpu_config) { + return Object{{"family", cpu_config.family}, + {"model", cpu_config.model}, + {"stepping", cpu_config.stepping}, + {"vendor", cpu_config.vendor}}; +} + +bool fromJSON(const Value &value, TraceIntelPTState &state, Path path) { + ObjectMapper o(value, path); + return o && o.map("threads", state.threads) && + o.map("cpu_config", state.cpu_config); +} + +Value toJSON(const TraceIntelPTState &state) { + return Object{{"threads", toJSON(state.threads)}, + {"cpu_config", toJSON(state.cpu_config)}}; +} + +// jLLDBTraceGetBinaryData + +Value toJSON(const TraceGetBinaryDataPacket &packet) { + return Object{{"type", packet.type}, + {"label", packet.label}, + {"tid", static_cast(packet.tid)}, + {"offset", static_cast(packet.offset)}, + {"size", static_cast(packet.size)}}; +} + +bool fromJSON(const Value &value, TraceGetBinaryDataPacket &packet, Path path) { + ObjectMapper o(value, path); + return o && o.map("type", packet.type) && o.map("label", packet.label) && + o.map("tid", packet.tid) && o.map("offset", packet.offset) && + o.map("size", packet.size); +} + } // namespace json } // namespace llvm diff --git a/lldb/test/API/commands/trace/TestTraceStartStop.py b/lldb/test/API/commands/trace/TestTraceStartStop.py --- a/lldb/test/API/commands/trace/TestTraceStartStop.py +++ b/lldb/test/API/commands/trace/TestTraceStartStop.py @@ -3,7 +3,9 @@ from lldbsuite.test import lldbutil from lldbsuite.test.decorators import * -class TestTraceLoad(TestBase): +ADDRESS_REGEX = '0x[0-9a-fA-F]*' + +class TestTraceStartStop(TestBase): mydir = TestBase.compute_mydir(__file__) NO_DEBUG_INFO_TESTCASE = True @@ -26,9 +28,7 @@ # 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"]) + self.expect("thread trace stop", error=True) @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) def testStartStopLiveThreads(self): @@ -49,20 +49,58 @@ self.expect("r") + # This fails because "trace start" hasn't been called yet + self.expect("thread trace stop", error=True, + substrs=["error: Process is not being traced"]) + + # 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. + # We start tracing with a small buffer size + self.expect("thread trace start 1 --size 1") + + # We fail if we try to trace again + #self.expect("thread trace start", error=True, + # substrs=["error: tracing already active on this thread"]) + + # We can reconstruct the single instruction executed in the first line + self.expect("n") + self.expect("thread trace dump instructions", + patterns=[f'''thread #1: tid = .*, total instructions = 1 + a.out`main \+ 4 at main.cpp:2 + \[0\] {ADDRESS_REGEX} movl''']) + + # We can reconstruct the instructions up to the second line + self.expect("n") + self.expect("thread trace dump instructions", + patterns=[f'''thread #1: tid = .*, total instructions = 5 + a.out`main \+ 4 at main.cpp:2 + \[0\] {ADDRESS_REGEX} movl .* + a.out`main \+ 11 at main.cpp:4 + \[1\] {ADDRESS_REGEX} movl .* + \[2\] {ADDRESS_REGEX} jmp .* ; <\+28> at main.cpp:4 + a.out`main \+ 28 at main.cpp:4 + \[3\] {ADDRESS_REGEX} cmpl .* + \[4\] {ADDRESS_REGEX} jle .* ; <\+20> at main.cpp:5''']) + + # We stop tracing + self.expect("thread trace stop") + + # We can't stop twice self.expect("thread trace stop", error=True, - substrs=["error: Process is not being traced"]) + substrs=["error: thread not traced"]) + + # We trace again from scratch, this time letting LLDB to pick the current + # thread + self.expect("thread trace start") + self.expect("n") + self.expect("thread trace dump instructions", + patterns=[f'''thread #1: tid = .*, total instructions = 1 + a.out`main \+ 20 at main.cpp:5 + \[0\] {ADDRESS_REGEX} xorl''']) self.expect("c") # Now the process has finished, so the commands should fail diff --git a/lldb/test/API/commands/trace/multiple-threads/Makefile b/lldb/test/API/commands/trace/multiple-threads/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/trace/multiple-threads/Makefile @@ -0,0 +1,4 @@ +CXX_SOURCES := main.cpp +ENABLE_THREADS = YES + +include Makefile.rules diff --git a/lldb/test/API/commands/trace/multiple-threads/TestTraceStartStopMultipleThreads.py b/lldb/test/API/commands/trace/multiple-threads/TestTraceStartStopMultipleThreads.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/trace/multiple-threads/TestTraceStartStopMultipleThreads.py @@ -0,0 +1,105 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test.decorators import * + +ADDRESS_REGEX = '0x[0-9a-fA-F]*' + +class TestTraceStartStopMultipleThreads(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") + + @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) + def testStartMultipleLiveThreads(self): + self.build() + exe = self.getBuildArtifact("a.out") + target = self.dbg.CreateTarget(exe) + + self.expect("b main") + self.expect("b 6") + self.expect("b 11") + + self.expect("r") + self.expect("thread trace start all") + + + # We'll see here the first thread + self.expect("continue") + self.expect("thread trace dump instructions", substrs=['main.cpp:9']) + + # We'll see here the second thread + self.expect("continue") + self.expect("thread trace dump instructions", substrs=['main.cpp:4']) + + @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) + def testStartMultipleLiveThreadsWithStops(self): + self.build() + exe = self.getBuildArtifact("a.out") + target = self.dbg.CreateTarget(exe) + + self.expect("b main") + self.expect("b 6") + self.expect("b 11") + + self.expect("r") + self.expect("thread trace start all") + + + # We'll see here the first thread + self.expect("continue") + + # We are in thread 2 + self.expect("thread trace dump instructions", substrs=['main.cpp:9']) + self.expect("thread trace dump instructions 2", substrs=['main.cpp:9']) + + # We stop tracing it + self.expect("thread trace stop 2") + + # The trace is still in memory + self.expect("thread trace dump instructions 2", substrs=['main.cpp:9']) + + # We'll stop at the next breakpoint, thread 2 will be still alive, but not traced. Thread 3 will be traced + self.expect("continue") + self.expect("thread trace dump instructions", substrs=['main.cpp:4']) + self.expect("thread trace dump instructions 3", substrs=['main.cpp:4']) + + self.expect("thread trace dump instructions 2", substrs=['not traced']) + + @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) + def testStartMultipleLiveThreadsWithAllStops(self): + self.build() + exe = self.getBuildArtifact("a.out") + target = self.dbg.CreateTarget(exe) + + self.expect("b main") + self.expect("b 6") + self.expect("b 11") + + self.expect("r") + self.expect("thread trace start all") + + + # We'll see here the first thread + self.expect("continue") + + # We are in thread 2 + self.expect("thread trace dump instructions", substrs=['main.cpp:9']) + self.expect("thread trace dump instructions 2", substrs=['main.cpp:9']) + + # We stop tracing all + self.expect("thread trace stop all") + + # The trace is still in memory + self.expect("thread trace dump instructions 2", substrs=['main.cpp:9']) + + # We'll stop at the next breakpoint, thread 2 and 3 will be alive, but not traced. + self.expect("continue") + self.expect("thread trace dump instructions", substrs=['not traced']) + self.expect("thread trace dump instructions 2", substrs=['not traced']) + self.expect("thread trace dump instructions 3", substrs=['not traced']) diff --git a/lldb/test/API/commands/trace/multiple-threads/main.cpp b/lldb/test/API/commands/trace/multiple-threads/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/trace/multiple-threads/main.cpp @@ -0,0 +1,20 @@ +#include +#include + +void f3() { + int m; + m = 2; // thread 3 +} + +void f2() { + int n; + n = 1; // thread 2 + std::thread t3(f3); + t3.join(); +} + +int main() { // main + std::thread t2(f2); + t2.join(); + return 0; +}