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 @@ -235,22 +235,23 @@ read packet: OK //---------------------------------------------------------------------- -// jLLDBTraceSupportedType +// jLLDBTraceSupported // // BRIEF // Get the processor tracing type supported by the gdb-server for the current // inferior. Responses might be different depending on the architecture and // capabilities of the underlying OS. // -// The return packet is a JSON object with the following schema +// OUTPUT SCHEMA +// { +// "name": , +// Tracing technology name, e.g. intel-pt, arm-coresight. +// "description": , +// Description for this technology. +// } // -// { -// "name": -// "description": -// } -// -// If no tracing technology is supported for the inferior, or no process is -// running, then an error should be returned. +// If no tracing technology is supported for the inferior, or no process is +// running, then an error message is returned. // // NOTE // This packet is used by Trace plug-ins (see lldb_private::Trace.h) to @@ -258,176 +259,217 @@ // of the tracing technology returned by this packet. //---------------------------------------------------------------------- -send packet: jLLDBTraceSupportedType -read packet: {"name": , "description", }/E;AAAAAAAAA +send packet: jLLDBTraceSupported +read packet: {"name":, "description":}/E;AAAAAAAAA //---------------------------------------------------------------------- -// jTraceStart: -// -// This packet is deprecated. +// jLLDBTraceStart // // BRIEF -// Packet for starting trace of type lldb::TraceType. The following -// parameters should be appended to the packet formatted as a JSON -// dictionary, where the schematic for the JSON dictionary in terms of -// the recognized Keys is given below in the table. -// Different tracing types could require different custom parameters. -// Such custom tracing parameters if needed should be collectively -// specified in a JSON dictionary and the dictionary can be appended -// to this packet (as Value corresponding to "params"). Since sending -// JSON data over gdb-remote protocol has certain limitations, binary -// escaping convention should be used. +// Start tracing a process or its threads using a provided tracing technology. +// The input and output are specified as JSON objects. In case of success, an OK +// response is returned, or an error otherwise. +// +// PROCESS TRACING +// This traces existing and future threads of the current process. An error is +// returned if the process is already being traced. // -// Following is the list of parameters - +// THREAD TRACING +// This traces specific threads. // -// Key Value (Integer) (O)Optional/ -// (except params which should be a (M)Mandatory -// JSON dictionary) -// ========== ==================================================== +// INPUT SCHEMA +// { +// "type": , +// Tracing technology name, e.g. intel-pt, arm-coresight. +// +// /* thread tracing only */ +// "tids": [], +// Individual threads to trace. +// +// ... other parameters specific to the provided tracing type +// } // -// type The type of trace to start (see M -// lldb-enumerations for TraceType) +// NOTES +// - If "tids" is not provided, then the operation is "process tracing", +// otherwise it's "thread tracing". +// - Each tracing technology can have different levels of support for "thread +// tracing" and "process tracing". // -// buffersize The size of the buffer to allocate M -// for trace gathering. +// INTEL-PT +// intel-pt supports both "thread tracing" and "process tracing". // -// threadid The id of the thread to start tracing O -// on. +// "Process tracing" is implemented by tracing each thread individually, but +// managed by the same "process trace" instance. +// Each actual thread trace, either from "process tracing" or "thread tracing", +// is stored in an in-memory circular buffer, which keeps the most recent data. // -// metabuffersize The size of buffer to hold meta data O -// used for decoding the trace data. +// Additional params in the input schema: +// { +// "threadBufferSize": , +// Trace buffer size per thread in bytes. It must be a power of 2 +// greater than or equal to 4096 (2^12) bytes. // -// params Any parameters that are specific to O -// certain trace technologies should be -// collectively specified as a JSON -// dictionary -// ========== ==================================================== +// /* process tracing only */ +// "processBufferSizeLimit": , +// Maximum total buffer size per process in bytes. +// This limit applies to the sum of the sizes of all trace buffers for +// the current process, excluding the ones started with "thread tracing". // -// Each tracing instance is identified by a trace id which is returned -// as the reply to this packet. In case the tracing failed to begin an -// error code along with a hex encoded ASCII message is returned -// instead. +// Whenever a thread is attempted to be traced due to "process tracing" +// and the limit would be reached, the process is stopped with a +// "tracing" reason along with a meaningful description, so that the +// user can retrace the process if needed. +// } +// +// Notes: +// - Modifying the parameters of an existing trace is not supported. The user +// needs to stop the trace and start a new one. +// - If "process tracing" is attempted and there are individual threads +// already being traced with "thread tracing", these traces are left +// unaffected and the threads not traced twice. +// - If "thread tracing" is attempted on a thread already being traced with +// either "thread tracing" or "process tracing", it fails. //---------------------------------------------------------------------- -send packet: jTraceStart:{"type":,"buffersize":}] -read packet: /E;AAAAAAAAA +Process tracing: +send packet: jLLDBTraceStart:{"type":,...other params}] +read packet: OK/E;AAAAAAAAA + +Thread tracing: +send packet: jLLDBTraceStart:{"type":,"tids":,...other params}] +read packet: OK/E;AAAAAAAAA //---------------------------------------------------------------------- -// jTraceStop: -// -// This packet is deprecated. +// jLLDBTraceStop // // BRIEF -// Stop tracing instance with trace id , of course trace -// needs to be started before. The following parameters should be -// formatted as a JSON dictionary to the packet. Since sending -// JSON data over gdb-remote protocol has certain limitations, binary -// escaping convention should be used. +// Stop tracing a process or its threads using a provided tracing technology. +// The input and output are specified as JSON objects. In case of success, an OK +// response is returned, or an error otherwise. +// +// PROCESS TRACE STOPPING +// Stopping a process trace doesn't stop the active traces initiated with +// "thread tracing". // -// Following is the list of parameters - +// THREAD TRACE STOPPING +// This is a best effort request, which tries to stop as many traces as +// possible. // -// Key Value (Integer) (O)Optional/ -// (M)Mandatory -// ========== ==================================================== +// INPUT SCHEMA +// The schema for the input is // -// traceid The trace id of the tracing instance M +// { +// "type": +// Tracing technology name, e.g. intel-pt, arm-coresight. +// +// /* thread trace stopping only */ +// "tids": [] +// Individual thread traces to stop. +// } // -// threadid The id of the thread to stop tracing O -// on. Since could map to -// multiple trace instances (in case it -// maps to the complete process), the -// threadid of a particular thread could -// be appended as "threadid:;" -// to stop tracing on that thread. -// ========== ==================================================== +// NOTES +// - If "tids" is not provided, then the operation is "process trace stopping". // -// An OK response is sent in case of success else an error code along -// with a hex encoded ASCII message is returned. +// INTEL PT +// Stopping a specific thread trace started with "process tracing" is allowed. //---------------------------------------------------------------------- -send packet: jTraceStop:{"traceid":}] -read packet: /E;AAAAAAAAA +Process trace stopping: +send packet: jLLDBTraceStop:{"type":}] +read packet: OK/E;AAAAAAAAA + +Thread trace stopping: +send packet: jLLDBTraceStop:{"type":,"tids":}] +read packet: OK/E;AAAAAAAAA //---------------------------------------------------------------------- -// jTraceBufferRead: -// -// This packet is deprecated. +// jLLDBTraceGetState // // BRIEF -// Packet for reading the trace for tracing instance , i.e the -// id obtained from StartTrace API. The following parameters should be -// formatted as a JSON dictionary to the packet. Since sending -// JSON data over gdb-remote protocol has certain limitations, binary -// escaping convention should be used. -// -// Following is the list of parameters - +// 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. In case of errors, an +// error message is returned. // -// Key Value (Integer) (O)Optional/ -// (M)Mandatory -// ========== ==================================================== -// traceid The trace id of the tracing instance M +// INPUT SCHEMA +// { +// "type": +// Tracing technology name, e.g. intel-pt, arm-coresight. +// } // -// offset The offset to start reading the data M -// from. +// OUTPUT SCHEMA +// { +// "tracedThreads": [{ +// "tid": , +// "binaryData": [ +// { +// "kind": , +// Identifier for some binary data related to this thread to +// fetch with the jLLDBTraceGetBinaryData packet. +// "size": , +// Size in bytes of this thread data. +// }, +// ] +// }], +// "processBinaryData": [ +// { +// "kind": , +// Identifier for some binary data related to this process to +// fetch with the jLLDBTraceGetBinaryData packet. +// "size": , +// Size in bytes of this thread data. +// }, +// }] +// } // -// buffersize The size of the data intended to read. M +// NOTES +// - "traceThreads" includes all thread traced by both "process tracing" and +// "thread tracing". // -// threadid The id of the thread to retrieve data O -// from. -// ========== ==================================================== +// INTEL PT // -// The trace data is sent as raw binary data if the read was successful -// else an error code along with a hex encoded ASCII message is sent. +// Binary data kinds: +// - threadTraceBuffer: trace buffer for a thread. +// - cpuInfo: contents of the /proc/cpuinfo file. //---------------------------------------------------------------------- -send packet: jTraceBufferRead:{"traceid":,"offset":,"buffersize":}] -read packet: /E;AAAAAAAAA +send packet: jLLDBTraceGetState:{"type":}] +read packet: {...object}/E;AAAAAAAAA //---------------------------------------------------------------------- -// jTraceMetaRead: -// -// This packet is deprecated. +// jLLDBTraceGetBinaryData // // BRIEF -// Similar Packet as above except it reads meta data. -//---------------------------------------------------------------------- - -/---------------------------------------------------------------------- -// jTraceConfigRead: +// 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. In case of failures, an error +// message is returned. // -// This packet is deprecated. +// SCHEMA +// The schema for the input is // -// BRIEF -// Request the trace configuration for the tracing instance with id -// . -// -// Following is the list of parameters - -// -// Key Value (Integer) (O)Optional/ -// (M)Mandatory -// ========== ==================================================== -// traceid The trace id of the tracing instance M -// -// threadid The id of the thread to obtain trace O -// configuration from. Since -// could map to multiple trace instances -// (in case it maps to the complete -// process), the threadid of a particular -// thread could be appended as -// "threadid:;" to obtain the -// trace configuration of that thread. -// ========== ==================================================== -// -// In the response packet the trace configuration is sent as text, -// formatted as a JSON dictionary. Since sending JSON data over -// gdb-remote protocol has certain limitations, binary escaping -// convention is used. -// In case the trace instance with the was not found, an -// error code along with a hex encoded ASCII message is returned. -//---------------------------------------------------------------------- - -send packet: jTraceConfigRead:{"traceid":} -read packet: {"conf1":,"conf2":,"params":{"paramName":paramValue}]}];/E;AAAAAAAAA +// { +// "type": , +// Tracing technology name, e.g. intel-pt, arm-coresight. +// "kind": , +// Identifier for the data. +// "tid"?: , +// Tid in decimal if the data belongs to a thread. +// "offset": , +// Offset of the data in bytes. +// "size": , +// Number of bytes in to read starting from the offset. +// } +// +// INTEL PT +// +// Binary data kinds: +// - threadTraceBuffer: trace buffer for a thread. +// - cpuInfo: contents of the /proc/cpuinfo file. +//---------------------------------------------------------------------- + +send packet: jLLDBTraceGetBinaryData:{"type":,"kind":,"tid":,"offset":,"size":}] +read packet: /E;AAAAAAAAA //---------------------------------------------------------------------- // "qRegisterInfo" 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,18 +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); - static bool UnregisterPlugin(TraceCreateInstance create_callback); + static bool + UnregisterPlugin(TraceCreateInstanceForSessionFile create_callback); - static TraceCreateInstance GetTraceCreateCallback(ConstString plugin_name); + static TraceCreateInstanceForSessionFile + GetTraceCreateCallback(ConstString plugin_name); - static lldb::CommandObjectSP - GetTraceStartCommand(llvm::StringRef plugin_name, - CommandInterpreter &interpreter); + static TraceCreateInstanceForLiveProcess + GetTraceCreateCallbackForLiveProcess(ConstString plugin_name); /// Get the JSON schema for a trace session file corresponding to the given /// plugin. 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 @@ -16,7 +16,7 @@ #include "lldb/Host/MainLoop.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/Status.h" -#include "lldb/Utility/TraceOptions.h" +#include "lldb/Utility/TraceGDBRemotePackets.h" #include "lldb/Utility/UnimplementedError.h" #include "lldb/lldb-private-forward.h" #include "lldb/lldb-types.h" @@ -306,94 +306,55 @@ MainLoop &mainloop) const = 0; }; - /// StartTracing API for starting a tracing instance with the - /// TraceOptions on a specific thread or process. + /// Start tracing a process or its threads. /// - /// \param[in] config - /// The configuration to use when starting tracing. + /// \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. /// - /// \param[out] error - /// Status indicates what went wrong. + /// This object should have a string entry called "type", which is the + /// tracing technology name. /// - /// \return - /// The API returns a user_id which can be used to get trace - /// data, trace configuration or stopping the trace instance. - /// The user_id is a key to identify and operate with a tracing - /// instance. It may refer to the complete process or a single - /// thread. - virtual lldb::user_id_t StartTrace(const TraceOptions &config, - Status &error) { - error.SetErrorString("Not implemented"); - return LLDB_INVALID_UID; - } - - /// StopTracing API as the name suggests stops a tracing instance. - /// - /// \param[in] traceid - /// The user id of the trace intended to be stopped. Now a - /// user_id may map to multiple threads in which case this API - /// could be used to stop the tracing for a specific thread by - /// supplying its thread id. - /// - /// \param[in] thread - /// Thread is needed when the complete process is being traced - /// and the user wishes to stop tracing on a particular thread. + /// \param[in] type + /// Tracing technology type, as described in the \a json_params. /// /// \return - /// Status indicating what went wrong. - virtual Status StopTrace(lldb::user_id_t traceid, - lldb::tid_t thread = LLDB_INVALID_THREAD_ID) { - return Status("Not implemented"); + /// \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()); } - /// This API provides the trace data collected in the form of raw - /// data. - /// - /// \param[in] traceid thread - /// The traceid and thread provide the context for the trace - /// instance. - /// - /// \param[in] buffer - /// The buffer provides the destination buffer where the trace - /// data would be read to. The buffer should be truncated to the - /// filled length by this function. - /// - /// \param[in] offset - /// There is possibility to read partially the trace data from - /// a specified offset where in such cases the buffer provided - /// may be smaller than the internal trace collection container. - /// - /// \return - /// The size of the data actually read. - virtual Status GetData(lldb::user_id_t traceid, lldb::tid_t thread, - llvm::MutableArrayRef &buffer, - size_t offset = 0) { - return Status("Not implemented"); + /// \copydoc Process::TraceStop(const TraceStopRequest &) + virtual llvm::Error TraceStop(const TraceStopRequest &request) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Unsupported tracing type '%s'", + request.type.data()); } - /// 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, - llvm::MutableArrayRef &buffer, - size_t offset = 0) { - return Status("Not implemented"); + /// \copydoc Process::TraceGetState(llvm::StringRef type) + virtual llvm::Expected + TraceGetState(llvm::StringRef type) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Unsupported tracing type '%s'", + type.data()); } - /// API to query the TraceOptions for a given user id - /// - /// \param[in] traceid - /// The user id of the tracing instance. - /// - /// \param[out] config - /// The configuration being used for tracing. - /// - /// \return A status indicating what went wrong. - virtual Status GetTraceConfig(lldb::user_id_t traceid, TraceOptions &config) { - return Status("Not implemented"); + /// \copydoc Process::TraceGetBinaryData(const TraceGetBinaryDataRequest &) + virtual llvm::Expected> + TraceGetBinaryData(const TraceGetBinaryDataRequest &request) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Unsupported data kind '%s' for the '%s' tracing technology", + request.kind.c_str(), request.type.c_str()); } - /// \copydoc Process::GetSupportedTraceType() - virtual llvm::Expected GetSupportedTraceType() { + /// \copydoc Process::TraceSupported() + virtual llvm::Expected TraceSupported() { return llvm::make_error(); } 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 @@ -46,7 +46,7 @@ #include "lldb/Utility/ProcessInfo.h" #include "lldb/Utility/Status.h" #include "lldb/Utility/StructuredData.h" -#include "lldb/Utility/TraceOptions.h" +#include "lldb/Utility/TraceGDBRemotePackets.h" #include "lldb/Utility/UnimplementedError.h" #include "lldb/Utility/UserIDResolver.h" #include "lldb/lldb-private.h" @@ -2455,6 +2455,8 @@ lldb::StructuredDataPluginSP GetStructuredDataPlugin(ConstString type_name) const; + /// Deprecated + /// /// Starts tracing with the configuration provided in options. To enable /// tracing on the complete process the thread_id in the options should be /// set to LLDB_INVALID_THREAD_ID. The API returns a user_id which is needed @@ -2469,6 +2471,8 @@ return LLDB_INVALID_UID; } + /// Deprecated + /// /// Stops the tracing instance leading to deletion of the trace data. The /// tracing instance is identified by the user_id which is obtained when /// tracing was started from the StartTrace. In case tracing of the complete @@ -2479,6 +2483,8 @@ return Status("Not implemented"); } + /// Deprecated + /// /// Provides the trace data as raw bytes. A buffer needs to be supplied to /// copy the trace data. The exact behavior of this API may vary across /// trace technology, as some may support partial reading of the trace data @@ -2490,6 +2496,8 @@ return Status("Not implemented"); } + /// Deprecated + /// /// Similar API as above except for obtaining meta data virtual Status GetMetaData(lldb::user_id_t uid, lldb::tid_t thread_id, llvm::MutableArrayRef &buffer, @@ -2497,17 +2505,8 @@ return Status("Not implemented"); } - /// API to obtain the trace configuration used by a trace instance. - /// Configurations that may be specific to some trace technology should be - /// stored in the custom parameters. The options are transported to the - /// server, which shall interpret accordingly. The thread_id can be - /// specified in the options to obtain the configuration used by a specific - /// thread. The thread_id specified should also match the uid otherwise an - /// error will be returned. - virtual Status GetTraceConfig(lldb::user_id_t uid, TraceOptions &options) { - return Status("Not implemented"); - } - +protected: + friend class Trace; /// Get the processor tracing type supported for this process. /// Responses might be different depending on the architecture and /// capabilities of the underlying OS. @@ -2515,14 +2514,64 @@ /// \return /// The supported trace type or an \a llvm::Error if tracing is /// not supported for the inferior. - virtual llvm::Expected GetSupportedTraceType(); + virtual llvm::Expected TraceSupported(); + + /// 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 TraceStart(const llvm::json::Value &request) { + return llvm::make_error(); + } + + /// Stop tracing a live process or its threads. + /// + /// \param[in] request + /// The information determining which threads or process to stop tracing. + /// + /// \return + /// \a llvm::Error::success if the operation was successful, or + /// \a llvm::Error otherwise. + virtual llvm::Error TraceStop(const TraceStopRequest &request) { + 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 TraceGetState(llvm::StringRef type) { + return llvm::make_error(); + } + + /// Get binary data given a trace technology and a data identifier. + /// + /// \param[in] request + /// 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 TraceGetBinaryDataRequest &request) { + return llvm::make_error(); + } // This calls a function of the form "void * (*)(void)". bool CallVoidArgVoidPtrReturn(const Address *address, lldb::addr_t &returned_func, bool trap_exceptions = false); -protected: /// Update the thread list following process plug-in's specific logic. /// /// This method should only be invoked by \a UpdateThreadList. diff --git a/lldb/include/lldb/Target/ProcessTrace.h b/lldb/include/lldb/Target/ProcessTrace.h --- a/lldb/include/lldb/Target/ProcessTrace.h +++ b/lldb/include/lldb/Target/ProcessTrace.h @@ -15,6 +15,8 @@ namespace lldb_private { +/// Class that represents a defunct process loaded on memory via the "trace +/// load" command. class ProcessTrace : public PostMortemProcess { public: static void Initialize(); @@ -54,8 +56,6 @@ return error; } - bool IsAlive() override; - bool WarnBeforeDetach() const override { return false; } size_t ReadMemory(lldb::addr_t addr, void *buf, size_t size, diff --git a/lldb/include/lldb/Target/StopInfo.h b/lldb/include/lldb/Target/StopInfo.h --- a/lldb/include/lldb/Target/StopInfo.h +++ b/lldb/include/lldb/Target/StopInfo.h @@ -129,6 +129,9 @@ static lldb::StopInfoSP CreateStopReasonWithExec(Thread &thread); + static lldb::StopInfoSP + CreateStopReasonProcessorTrace(Thread &thread, const char *description); + static lldb::ValueObjectSP GetReturnValueObject(lldb::StopInfoSP &stop_info_sp); diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -1126,7 +1126,12 @@ /// /// \return /// The trace object. It might be undefined. - const lldb::TraceSP &GetTrace(); + lldb::TraceSP &GetTrace(); + + /// Similar to \a GetTrace, but this also tries to create a \a Trace object + /// if not available using the default supported tracing technology for + /// this process. + llvm::Expected GetTraceOrCreate(); // Since expressions results can persist beyond the lifetime of a process, // and the const expression results are available after a process is gone, we diff --git a/lldb/include/lldb/Target/ThreadTrace.h b/lldb/include/lldb/Target/ThreadPostMortemTrace.h rename from lldb/include/lldb/Target/ThreadTrace.h rename to lldb/include/lldb/Target/ThreadPostMortemTrace.h --- a/lldb/include/lldb/Target/ThreadTrace.h +++ b/lldb/include/lldb/Target/ThreadPostMortemTrace.h @@ -1,4 +1,4 @@ -//===-- ThreadTrace.h -------------------------------------------*- C++ -*-===// +//===-- ThreadPostMortemTrace.h ---------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,21 +6,21 @@ // //===----------------------------------------------------------------------===// -#ifndef LLDB_TARGET_THREADTRACE_H -#define LLDB_TARGET_THREADTRACE_H +#ifndef LLDB_TARGET_THREADPOSTMORTEMTRACE_H +#define LLDB_TARGET_THREADPOSTMORTEMTRACE_H #include "lldb/Target/Thread.h" namespace lldb_private { -/// \class ThreadTrace ThreadTrace.h +/// \class ThreadPostMortemTrace ThreadPostMortemTrace.h /// /// Thread implementation used for representing threads gotten from trace /// session files, which are similar to threads from core files. /// /// See \a TraceSessionFileParser for more information regarding trace session /// files. -class ThreadTrace : public Thread { +class ThreadPostMortemTrace : public Thread { public: /// \param[in] process /// The process who owns this thread. @@ -31,7 +31,8 @@ /// \param[in] trace_file /// The file that contains the list of instructions that were traced when /// this thread was being executed. - ThreadTrace(Process &process, lldb::tid_t tid, const FileSpec &trace_file) + ThreadPostMortemTrace(Process &process, lldb::tid_t tid, + const FileSpec &trace_file) : Thread(process, tid), m_trace_file(trace_file) {} void RefreshStateAfterStop() override; @@ -54,8 +55,6 @@ FileSpec m_trace_file; }; -typedef std::shared_ptr ThreadTraceSP; - } // namespace lldb_private -#endif // LLDB_TARGET_THREADTRACE_H +#endif // LLDB_TARGET_THREADPOSTMORTEMTRACE_H diff --git a/lldb/include/lldb/Target/Trace.h b/lldb/include/lldb/Target/Trace.h --- a/lldb/include/lldb/Target/Trace.h +++ b/lldb/include/lldb/Target/Trace.h @@ -9,10 +9,14 @@ #ifndef LLDB_TARGET_TRACE_H #define LLDB_TARGET_TRACE_H +#include + #include "llvm/Support/JSON.h" #include "lldb/Core/PluginInterface.h" +#include "lldb/Target/Thread.h" #include "lldb/Utility/ArchSpec.h" +#include "lldb/Utility/TraceGDBRemotePackets.h" #include "lldb/Utility/UnimplementedError.h" #include "lldb/lldb-private.h" @@ -36,7 +40,7 @@ /// /// In order to support live tracing, the name of the plug-in should match the /// name of the tracing type returned by the gdb-remote packet -/// \a jLLDBTraceSupportedType. +/// \a jLLDBTraceSupported. class Trace : public PluginInterface, public std::enable_shared_from_this { public: @@ -94,8 +98,24 @@ /// The path to the directory that contains the session file. It's used to /// resolved relative paths in the session file. static llvm::Expected - FindPlugin(Debugger &debugger, const llvm::json::Value &trace_session_file, - llvm::StringRef session_file_dir); + FindPluginForPostMortemProcess(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 + FindPluginForLiveProcess(llvm::StringRef plugin_name, Process &process); /// Get the schema of a Trace plug-in given its name. /// @@ -104,6 +124,14 @@ static llvm::Expected FindPluginSchema(llvm::StringRef plugin_name); + /// Get the command handle for the "process trace start" command. + virtual lldb::CommandObjectSP + GetProcessTraceStartCommand(CommandInterpreter &interpreter) = 0; + + /// Get the command handle for the "thread trace start" command. + virtual lldb::CommandObjectSP + GetThreadTraceStartCommand(CommandInterpreter &interpreter) = 0; + /// \return /// The JSON schema of this Trace plug-in. virtual llvm::StringRef GetSchema() = 0; @@ -118,7 +146,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 +200,130 @@ /// 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; + + /// Stop tracing live threads. + /// + /// \param[in] tids + /// The threads to stop tracing on. + /// + /// \return + /// \a llvm::Error::success if the operation was successful, or + /// \a llvm::Error otherwise. + llvm::Error StopThreads(const std::vector &tids); + + /// Stop tracing a live process. + /// + /// \param[in] request + /// The information determining which threads or process to stop tracing. + /// + /// \return + /// \a llvm::Error::success if the operation was successful, or + /// \a llvm::Error otherwise. + llvm::Error StopProcess(); + + /// Get the trace file of the given post mortem thread. + llvm::Expected GetPostMortemTraceFile(lldb::tid_t tid); + +protected: + /// Get binary data of a live thread given a data identifier. + /// + /// \param[in] tid + /// The thread whose data is requested. + /// + /// \param[in] kind + /// The kind of data requested. + /// + /// \return + /// A vector of bytes with the requested data, or an \a llvm::Error in + /// case of failures. + llvm::Expected> + GetLiveThreadBinaryData(lldb::tid_t tid, llvm::StringRef kind); + + /// Get binary data of the current process given a data identifier. + /// + /// \param[in] kind + /// The kind of data requested. + /// + /// \return + /// A vector of bytes with the requested data, or an \a llvm::Error in + /// case of failures. + llvm::Expected> + GetLiveProcessBinaryData(llvm::StringRef kind); + + /// Get the size of the data returned by \a GetLiveThreadBinaryData + llvm::Optional GetLiveThreadBinaryDataSize(lldb::tid_t tid, + llvm::StringRef kind); + + /// Get the size of the data returned by \a GetLiveProcessBinaryData + llvm::Optional GetLiveProcessBinaryDataSize(llvm::StringRef kind); + /// Constructor for post mortem processes + Trace() = default; + + /// Constructor for a live process + Trace(Process &live_process) : m_live_process(&live_process) {} + + /// Start tracing a live 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. + llvm::Error Start(const llvm::json::Value &request); + + /// Get the current tracing state of a live process and its threads. + /// + /// \return + /// A JSON object string with custom data depending on the trace + /// technology, or an \a llvm::Error in case of errors. + llvm::Expected GetLiveProcessState(); + + /// Method to be overriden by the plug-in to refresh its own state. + /// + /// This is invoked by RefreshLiveProcessState when a new state is found. + /// + /// \param[in] state + /// The jLLDBTraceGetState response. + virtual void + DoRefreshLiveProcessState(llvm::Expected state) = 0; + + /// Method to be invoked by the plug-in to refresh the live process state. + /// + /// The result is cached through the same process stop. + void RefreshLiveProcessState(); + + /// Process traced by this object if doing live tracing. Otherwise it's null. + int64_t m_stop_id = -1; + Process *m_live_process = nullptr; + /// tid -> data kind -> size + std::map> + m_live_thread_data; + /// data kind -> size + std::unordered_map m_live_process_data; }; } // namespace lldb_private diff --git a/lldb/include/lldb/Target/TraceSessionFileParser.h b/lldb/include/lldb/Target/TraceSessionFileParser.h --- a/lldb/include/lldb/Target/TraceSessionFileParser.h +++ b/lldb/include/lldb/Target/TraceSessionFileParser.h @@ -11,7 +11,7 @@ #include "llvm/Support/JSON.h" -#include "lldb/Target/ThreadTrace.h" +#include "lldb/Target/ThreadPostMortemTrace.h" namespace lldb_private { @@ -68,7 +68,7 @@ /// Helper struct holding the objects created when parsing a process struct ParsedProcess { lldb::TargetSP target_sp; - std::vector threads; + std::vector threads; }; TraceSessionFileParser(Debugger &debugger, llvm::StringRef session_file_dir, @@ -103,8 +103,8 @@ /// modifies the given file_spec. void NormalizePath(lldb_private::FileSpec &file_spec); - ThreadTraceSP ParseThread(lldb::ProcessSP &process_sp, - const JSONThread &thread); + lldb::ThreadPostMortemTraceSP ParseThread(lldb::ProcessSP &process_sp, + const JSONThread &thread); llvm::Expected ParseProcess(const JSONProcess &process); 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 @@ -162,13 +162,11 @@ eServerPacketType__m, eServerPacketType_notify, // '%' notification - eServerPacketType_jTraceStart, // deprecated - eServerPacketType_jTraceBufferRead, // deprecated - eServerPacketType_jTraceMetaRead, // deprecated - eServerPacketType_jTraceStop, // deprecated - eServerPacketType_jTraceConfigRead, // deprecated - - eServerPacketType_jLLDBTraceSupportedType, + eServerPacketType_jLLDBTraceSupported, + eServerPacketType_jLLDBTraceStart, + eServerPacketType_jLLDBTraceStop, + eServerPacketType_jLLDBTraceGetState, + eServerPacketType_jLLDBTraceGetBinaryData, }; ServerPacketType GetServerPacketType() const; diff --git a/lldb/include/lldb/Utility/TraceGDBRemotePackets.h b/lldb/include/lldb/Utility/TraceGDBRemotePackets.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Utility/TraceGDBRemotePackets.h @@ -0,0 +1,151 @@ +//===-- TraceGDBRemotePackets.h ---------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_UTILITY_TRACEGDBREMOTEPACKETS_H +#define LLDB_UTILITY_TRACEGDBREMOTEPACKETS_H + +#include "llvm/Support/JSON.h" + +#include "lldb/lldb-defines.h" +#include "lldb/lldb-enumerations.h" + +/// See docs/lldb-gdb-remote.txt for more information. +namespace lldb_private { + +/// jLLDBTraceSupported gdb-remote packet +/// \{ +struct TraceSupportedResponse { + /// 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; + /// The description for the technology. + std::string description; +}; + +bool fromJSON(const llvm::json::Value &value, TraceSupportedResponse &info, + llvm::json::Path path); + +llvm::json::Value toJSON(const TraceSupportedResponse &packet); +/// \} + +/// jLLDBTraceStart gdb-remote packet +/// \{ +struct TraceStartRequest { + /// Tracing technology name, e.g. intel-pt, arm-coresight. + std::string type; + /// If \a llvm::None, then this starts tracing the whole process. Otherwise, + /// only tracing for the specified threads is enabled. + llvm::Optional> tids; + + bool IsProcessTracing() const; +}; + +bool fromJSON(const llvm::json::Value &value, TraceStartRequest &packet, + llvm::json::Path path); + +llvm::json::Value toJSON(const TraceStartRequest &packet); +/// \} + +/// jLLDBTraceStop gdb-remote packet +/// \{ +struct TraceStopRequest { + TraceStopRequest() {} + + TraceStopRequest(llvm::StringRef type, const std::vector &tids); + + TraceStopRequest(llvm::StringRef type) : type(type){}; + + bool IsProcessTracing() const; + + /// Tracing technology name, e.g. intel-pt, arm-coresight. + std::string type; + /// If \a llvm::None, then this stops tracing the whole process. Otherwise, + /// only tracing for the specified threads is stopped. + llvm::Optional> tids; +}; + +bool fromJSON(const llvm::json::Value &value, TraceStopRequest &packet, + llvm::json::Path path); + +llvm::json::Value toJSON(const TraceStopRequest &packet); +///} + +/// jLLDBTraceGetState gdb-remote packet +/// \{ +struct TraceGetStateRequest { + /// Tracing technology name, e.g. intel-pt, arm-coresight. + std::string type; +}; + +bool fromJSON(const llvm::json::Value &value, TraceGetStateRequest &packet, + llvm::json::Path path); + +llvm::json::Value toJSON(const TraceGetStateRequest &packet); + +struct TraceBinaryData { + /// Identifier of data to fetch with jLLDBTraceGetBinaryData. + std::string kind; + /// Size in bytes for this data. + int64_t size; +}; + +bool fromJSON(const llvm::json::Value &value, TraceBinaryData &packet, + llvm::json::Path path); + +llvm::json::Value toJSON(const TraceBinaryData &packet); + +struct TraceThreadState { + int64_t tid; + /// List of binary data objects for this thread. + std::vector binaryData; +}; + +bool fromJSON(const llvm::json::Value &value, TraceThreadState &packet, + llvm::json::Path path); + +llvm::json::Value toJSON(const TraceThreadState &packet); + +struct TraceGetStateResponse { + std::vector tracedThreads; + std::vector processBinaryData; +}; + +bool fromJSON(const llvm::json::Value &value, TraceGetStateResponse &packet, + llvm::json::Path path); + +llvm::json::Value toJSON(const TraceGetStateResponse &packet); +/// \} + +/// jLLDBTraceGetBinaryData gdb-remote packet +/// \{ +struct TraceGetBinaryDataRequest { + /// Tracing technology name, e.g. intel-pt, arm-coresight. + std::string type; + /// Identifier for the data. + std::string kind; + /// Optional tid if the data is related to a thread. + llvm::Optional tid; + /// Offset in bytes from where to start reading the data. + int64_t offset; + /// Number of bytes to read. + int64_t size; +}; + +bool fromJSON(const llvm::json::Value &value, + lldb_private::TraceGetBinaryDataRequest &packet, + llvm::json::Path path); + +llvm::json::Value toJSON(const lldb_private::TraceGetBinaryDataRequest &packet); +/// \} + +} // namespace lldb_private + +#endif // LLDB_UTILITY_TRACEGDBREMOTEPACKETS_H diff --git a/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h b/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h @@ -0,0 +1,38 @@ +//===-- TraceIntelPTGDBRemotePackets.h --------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_UTILITY_TRACEINTELPTGDBREMOTEPACKETS_H +#define LLDB_UTILITY_TRACEINTELPTGDBREMOTEPACKETS_H + +#include "lldb/Utility/TraceGDBRemotePackets.h" + +/// See docs/lldb-gdb-remote.txt for more information. +namespace lldb_private { + +/// jLLDBTraceStart gdb-remote packet +/// \{ +struct TraceIntelPTStartRequest : TraceStartRequest { + /// Size in bytes to use for each thread's trace buffer. + int64_t threadBufferSize; + /// Required when doing "process tracing". + /// + /// Limit in bytes on all the thread traces started by this "process trace" + /// instance. When a thread is about to be traced and the limit would be hit, + /// then a "tracing" stop event is triggered. + llvm::Optional processBufferSizeLimit; +}; + +bool fromJSON(const llvm::json::Value &value, TraceIntelPTStartRequest &packet, + llvm::json::Path path); + +llvm::json::Value toJSON(const TraceIntelPTStartRequest &packet); +/// \} + +} // namespace lldb_private + +#endif // LLDB_UTILITY_TRACEINTELPTGDBREMOTEPACKETS_H 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,14 +57,7 @@ /// the lldb-server. StructuredData::DictionarySP m_trace_params; }; -} - -namespace llvm { -namespace json { - -bool fromJSON(const Value &value, lldb_private::TraceTypeInfo &info, Path path); -} // namespace json -} // namespace llvm +} // namespace lldb_private #endif // LLDB_UTILITY_TRACEOPTIONS_H 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 @@ -247,7 +247,8 @@ eStopReasonExec, ///< Program was re-exec'ed eStopReasonPlanComplete, eStopReasonThreadExiting, - eStopReasonInstrumentation + eStopReasonInstrumentation, + eStopReasonProcessorTrace, }; /// Command Return Status Types. diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -227,7 +227,7 @@ class ThreadPlanStepThrough; class ThreadPlanTracer; class ThreadSpec; -class ThreadTrace; +class ThreadPostMortemTrace; class Trace; class TraceSessionFileParser; class TraceOptions; @@ -437,6 +437,8 @@ typedef std::weak_ptr ThreadWP; typedef std::shared_ptr ThreadCollectionSP; typedef std::shared_ptr ThreadPlanSP; +typedef std::shared_ptr + ThreadPostMortemTraceSP; typedef std::weak_ptr ThreadPlanWP; typedef std::shared_ptr ThreadPlanTracerSP; typedef std::shared_ptr TraceSP; 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,12 +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 lldb::CommandObjectSP (*TraceGetStartCommand)( - CommandInterpreter &interpreter); - +typedef llvm::Expected (*TraceCreateInstanceForLiveProcess)( + Process &process); } // namespace lldb_private #endif // #if defined(__cplusplus) diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp --- a/lldb/source/API/SBThread.cpp +++ b/lldb/source/API/SBThread.cpp @@ -172,6 +172,7 @@ case eStopReasonPlanComplete: case eStopReasonThreadExiting: case eStopReasonInstrumentation: + case eStopReasonProcessorTrace: // There is no data for these stop reasons. return 0; @@ -223,6 +224,7 @@ case eStopReasonPlanComplete: case eStopReasonThreadExiting: case eStopReasonInstrumentation: + case eStopReasonProcessorTrace: // There is no data for these stop reasons. return 0; diff --git a/lldb/source/API/SBTrace.cpp b/lldb/source/API/SBTrace.cpp --- a/lldb/source/API/SBTrace.cpp +++ b/lldb/source/API/SBTrace.cpp @@ -77,18 +77,7 @@ } void SBTrace::GetTraceConfig(SBTraceOptions &options, SBError &error) { - LLDB_RECORD_METHOD(void, SBTrace, GetTraceConfig, - (lldb::SBTraceOptions &, lldb::SBError &), options, error); - - ProcessSP process_sp(GetSP()); - error.Clear(); - - if (!process_sp) { - error.SetErrorString("invalid process"); - } else { - error.SetError(process_sp->GetTraceConfig(GetTraceUID(), - *(options.m_traceoptions_sp))); - } + error.SetErrorString("deprecated"); } lldb::user_id_t SBTrace::GetTraceUID() { diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp --- a/lldb/source/Commands/CommandObjectProcess.cpp +++ b/lldb/source/Commands/CommandObjectProcess.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "CommandObjectProcess.h" +#include "CommandObjectTrace.h" #include "CommandOptionsProcessLaunch.h" #include "lldb/Breakpoint/Breakpoint.h" #include "lldb/Breakpoint/BreakpointLocation.h" @@ -1577,6 +1578,71 @@ CommandOptions m_options; }; +// Next are the subcommands of CommandObjectMultiwordProcessTrace + +// CommandObjectProcessTraceStart +class CommandObjectProcessTraceStart : public CommandObjectTraceProxy { +public: + CommandObjectProcessTraceStart(CommandInterpreter &interpreter) + : CommandObjectTraceProxy( + /*live_debug_session_only*/ true, interpreter, + "process trace start", + "Start tracing this process with the corresponding trace " + "plug-in.", + "process trace start []") {} + +protected: + lldb::CommandObjectSP GetDelegateCommand(Trace &trace) override { + return trace.GetProcessTraceStartCommand(m_interpreter); + } +}; + +// CommandObjectProcessTraceStop +class CommandObjectProcessTraceStop : public CommandObjectParsed { +public: + CommandObjectProcessTraceStop(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "process trace stop", + "Stop tracing this process. This does not affect " + "traces started with the " + "\"thread trace start\" command.", + "process trace stop", + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused | + eCommandProcessMustBeTraced) {} + + ~CommandObjectProcessTraceStop() override = default; + + bool DoExecute(Args &command, CommandReturnObject &result) override { + ProcessSP process_sp = m_exe_ctx.GetProcessSP(); + + TraceSP trace_sp = process_sp->GetTarget().GetTrace(); + + if (llvm::Error err = trace_sp->StopProcess()) + result.SetError(toString(std::move(err))); + else + result.SetStatus(eReturnStatusSuccessFinishResult); + + return result.Succeeded(); + } +}; + +// CommandObjectMultiwordProcessTrace +class CommandObjectMultiwordProcessTrace : public CommandObjectMultiword { +public: + CommandObjectMultiwordProcessTrace(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "trace", "Commands for tracing the current process.", + "process trace []") { + LoadSubCommand("start", CommandObjectSP(new CommandObjectProcessTraceStart( + interpreter))); + LoadSubCommand("stop", CommandObjectSP( + new CommandObjectProcessTraceStop(interpreter))); + } + + ~CommandObjectMultiwordProcessTrace() override = default; +}; + // CommandObjectMultiwordProcess CommandObjectMultiwordProcess::CommandObjectMultiwordProcess( @@ -1613,6 +1679,9 @@ CommandObjectSP(new CommandObjectProcessPlugin(interpreter))); LoadSubCommand("save-core", CommandObjectSP(new CommandObjectProcessSaveCore( interpreter))); + LoadSubCommand( + "trace", + CommandObjectSP(new CommandObjectMultiwordProcessTrace(interpreter))); } CommandObjectMultiwordProcess::~CommandObjectMultiwordProcess() = default; 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 @@ -11,6 +11,7 @@ #include #include "CommandObjectThreadUtil.h" +#include "CommandObjectTrace.h" #include "lldb/Core/PluginManager.h" #include "lldb/Core/ValueObject.h" #include "lldb/Host/OptionParser.h" @@ -1978,75 +1979,34 @@ // CommandObjectTraceStart -/// This class works by delegating the logic to the actual trace plug-in that -/// can support the current process. -class CommandObjectTraceStart : public CommandObjectProxy { +class CommandObjectTraceStart : public CommandObjectTraceProxy { public: CommandObjectTraceStart(CommandInterpreter &interpreter) - : CommandObjectProxy(interpreter, "thread trace start", - "Start tracing threads with the corresponding trace " - "plug-in for the current process.", - "thread trace start []") {} + : CommandObjectTraceProxy( + /*live_debug_session_only*/ true, interpreter, "thread trace start", + "Start tracing threads with the corresponding trace " + "plug-in for the current process.", + "thread trace start []") {} protected: - llvm::Expected DoGetProxyCommandObject() { - ProcessSP process_sp = m_interpreter.GetExecutionContext().GetProcessSP(); - - if (!process_sp) - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "Process not available."); - if (!process_sp->IsAlive()) - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "Process must be launched."); - - llvm::Expected trace_type = - process_sp->GetSupportedTraceType(); - - if (!trace_type) - return llvm::createStringError( - llvm::inconvertibleErrorCode(), "Tracing is not supported. %s", - llvm::toString(trace_type.takeError()).c_str()); - - CommandObjectSP delegate_sp = - PluginManager::GetTraceStartCommand(trace_type->name, m_interpreter); - if (!delegate_sp) - return llvm::createStringError( - llvm::inconvertibleErrorCode(), - "No trace plug-in matches the specified type: \"%s\"", - trace_type->name.c_str()); - return delegate_sp; + lldb::CommandObjectSP GetDelegateCommand(Trace &trace) override { + return trace.GetThreadTraceStartCommand(m_interpreter); } - - CommandObject *GetProxyCommandObject() override { - if (llvm::Expected delegate = DoGetProxyCommandObject()) { - m_delegate_sp = *delegate; - m_delegate_error.clear(); - return m_delegate_sp.get(); - } else { - m_delegate_sp.reset(); - m_delegate_error = llvm::toString(delegate.takeError()); - return nullptr; - } - } - -private: - llvm::StringRef GetUnsupportedError() override { return m_delegate_error; } - - CommandObjectSP m_delegate_sp; - std::string m_delegate_error; }; // CommandObjectTraceStop -class CommandObjectTraceStop : public CommandObjectIterateOverThreads { +class CommandObjectTraceStop : public CommandObjectMultipleThreads { public: CommandObjectTraceStop(CommandInterpreter &interpreter) - : CommandObjectIterateOverThreads( + : CommandObjectMultipleThreads( interpreter, "thread trace stop", - "Stop tracing threads. " + "Stop tracing threads, including the ones traced with the " + "\"process trace start\" command." "Defaults to the current thread. Thread indices can be " - "specified as arguments.\n Use the thread-index \"all\" to trace " - "all threads.", + "specified as arguments.\n Use the thread-index \"all\" to stop " + "tracing " + "for all existing threads.", "thread trace stop [ ...]", eCommandRequiresProcess | eCommandTryTargetAPILock | eCommandProcessMustBeLaunched | eCommandProcessMustBePaused | @@ -2054,20 +2014,18 @@ ~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 DoExecuteOnThreads(Args &command, CommandReturnObject &result, + 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; + if (llvm::Error err = trace_sp->StopThreads(tids)) + 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,26 @@ bool m_add_return = true; }; +/// Class similar to \a CommandObjectIterateOverThreads, but which performs +/// an action on multiple threads at once instead of iterating over each thread. +class CommandObjectMultipleThreads : 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] tids + /// The thread ids passed as arguments. + /// + /// \return + /// A boolean result similar to the one expected from \a DoExecute. + virtual bool DoExecuteOnThreads(Args &command, CommandReturnObject &result, + const std::vector &tids) = 0; +}; + } // 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,47 @@ } return true; } + +bool CommandObjectMultipleThreads::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()); + + if (num_args > 0 && ::strcmp(command.GetArgumentAtIndex(0), "all") == 0) { + for (ThreadSP thread_sp : process.Threads()) + tids.push_back(thread_sp->GetID()); + } 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 DoExecuteOnThreads(command, result, tids); +} diff --git a/lldb/source/Commands/CommandObjectTrace.h b/lldb/source/Commands/CommandObjectTrace.h --- a/lldb/source/Commands/CommandObjectTrace.h +++ b/lldb/source/Commands/CommandObjectTrace.h @@ -9,7 +9,7 @@ #ifndef LLDB_SOURCE_COMMANDS_COMMANDOBJECTTRACE_H #define LLDB_SOURCE_COMMANDS_COMMANDOBJECTTRACE_H -#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "CommandObjectThreadUtil.h" namespace lldb_private { @@ -20,6 +20,32 @@ ~CommandObjectTrace() override; }; +/// This class works by delegating the logic to the actual trace plug-in that +/// can support the current process. +class CommandObjectTraceProxy : public CommandObjectProxy { +public: + CommandObjectTraceProxy(bool live_debug_session_only, + CommandInterpreter &interpreter, const char *name, + const char *help = nullptr, + const char *syntax = nullptr, uint32_t flags = 0) + : CommandObjectProxy(interpreter, name, help, syntax, flags), + m_live_debug_session_only(live_debug_session_only) {} + +protected: + virtual lldb::CommandObjectSP GetDelegateCommand(Trace &trace) = 0; + + llvm::Expected DoGetProxyCommandObject(); + + CommandObject *GetProxyCommandObject() override; + +private: + llvm::StringRef GetUnsupportedError() override { return m_delegate_error; } + + bool m_live_debug_session_only; + lldb::CommandObjectSP m_delegate_sp; + std::string m_delegate_error; +}; + } // namespace lldb_private #endif // LLDB_SOURCE_COMMANDS_COMMANDOBJECTTRACE_H diff --git a/lldb/source/Commands/CommandObjectTrace.cpp b/lldb/source/Commands/CommandObjectTrace.cpp --- a/lldb/source/Commands/CommandObjectTrace.cpp +++ b/lldb/source/Commands/CommandObjectTrace.cpp @@ -23,6 +23,7 @@ #include "lldb/Interpreter/OptionValueLanguage.h" #include "lldb/Interpreter/OptionValueString.h" #include "lldb/Interpreter/Options.h" +#include "lldb/Target/Process.h" #include "lldb/Target/Trace.h" using namespace lldb; @@ -113,8 +114,9 @@ return end_with_failure(session_file.takeError()); if (Expected traceOrErr = - Trace::FindPlugin(GetDebugger(), *session_file, - json_file.GetDirectory().AsCString())) { + Trace::FindPluginForPostMortemProcess( + GetDebugger(), *session_file, + json_file.GetDirectory().AsCString())) { lldb::TraceSP trace_sp = traceOrErr.get(); if (m_options.m_verbose && trace_sp) result.AppendMessageWithFormat("loading trace with plugin %s\n", @@ -303,3 +305,33 @@ } CommandObjectTrace::~CommandObjectTrace() = default; + +Expected CommandObjectTraceProxy::DoGetProxyCommandObject() { + ProcessSP process_sp = m_interpreter.GetExecutionContext().GetProcessSP(); + + if (!process_sp) + return createStringError(inconvertibleErrorCode(), + "Process not available."); + if (m_live_debug_session_only && !process_sp->IsLiveDebugSession()) + return createStringError(inconvertibleErrorCode(), + "Process must be alive."); + + if (Expected trace_sp = process_sp->GetTarget().GetTraceOrCreate()) + return GetDelegateCommand(**trace_sp); + else + return createStringError(inconvertibleErrorCode(), + "Tracing is not supported. %s", + toString(trace_sp.takeError()).c_str()); +} + +CommandObject *CommandObjectTraceProxy::GetProxyCommandObject() { + if (Expected delegate = DoGetProxyCommandObject()) { + m_delegate_sp = *delegate; + m_delegate_error.clear(); + return m_delegate_sp.get(); + } else { + m_delegate_sp.reset(); + m_delegate_error = toString(delegate.takeError()); + return nullptr; + } +} 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,20 @@ #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) + : PluginInstance( + name, std::move(description), create_callback_for_session_file), + schema(schema), + 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,37 +1031,40 @@ 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) { return GetTracePluginInstances().RegisterPlugin( - name, description, create_callback, schema, get_start_command); + name, description, create_callback_for_session_file, + create_callback_for_live_process, schema); } -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); } -llvm::StringRef PluginManager::GetTraceSchema(ConstString plugin_name) { +TraceCreateInstanceForLiveProcess +PluginManager::GetTraceCreateCallbackForLiveProcess(ConstString plugin_name) { for (const TraceInstance &instance : GetTracePluginInstances().GetInstances()) if (instance.name == plugin_name) - return instance.schema; - return llvm::StringRef(); + return instance.create_callback_for_live_process; + return nullptr; } -CommandObjectSP -PluginManager::GetTraceStartCommand(llvm::StringRef plugin_name, - CommandInterpreter &interpreter) { +llvm::StringRef PluginManager::GetTraceSchema(ConstString plugin_name) { for (const TraceInstance &instance : GetTracePluginInstances().GetInstances()) - if (instance.name.GetStringRef() == plugin_name) - return instance.get_start_command(interpreter); - return CommandObjectSP(); + if (instance.name == plugin_name) + return instance.schema; + return llvm::StringRef(); } llvm::StringRef PluginManager::GetTraceSchema(size_t index) { diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -2244,7 +2244,9 @@ return false; const StopReason reason = stop_info->GetStopReason(); - if (reason == eStopReasonException || reason == eStopReasonInstrumentation) + if (reason == eStopReasonException || + reason == eStopReasonInstrumentation || + reason == eStopReasonProcessorTrace) return true; if (reason == eStopReasonSignal) { diff --git a/lldb/source/Plugins/Process/Linux/CMakeLists.txt b/lldb/source/Plugins/Process/Linux/CMakeLists.txt --- a/lldb/source/Plugins/Process/Linux/CMakeLists.txt +++ b/lldb/source/Plugins/Process/Linux/CMakeLists.txt @@ -1,4 +1,5 @@ add_lldb_library(lldbPluginProcessLinux + IntelPTManager.cpp NativeProcessLinux.cpp NativeRegisterContextLinux.cpp NativeRegisterContextLinux_arm.cpp @@ -7,7 +8,6 @@ NativeRegisterContextLinux_s390x.cpp NativeRegisterContextLinux_x86_64.cpp NativeThreadLinux.cpp - ProcessorTrace.cpp SingleStepCheck.cpp LINK_LIBS diff --git a/lldb/source/Plugins/Process/Linux/IntelPTManager.h b/lldb/source/Plugins/Process/Linux/IntelPTManager.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/Linux/IntelPTManager.h @@ -0,0 +1,254 @@ +//===-- IntelPTManager.h -------------------------------------- -*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_IntelPTManager_H_ +#define liblldb_IntelPTManager_H_ + +#include "lldb/Utility/Status.h" +#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" +#include "lldb/lldb-types.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" + +#include +#include +#include + +namespace lldb_private { + +namespace process_linux { + +/// This class keeps track of one tracing instance of +/// Intel(R) Processor Trace on Linux OS at thread level. +/// +/// The kernel interface for us is the perf_event_open. +class IntelPTThreadTrace; +typedef std::unique_ptr IntelPTThreadTraceUP; + +class IntelPTThreadTrace { + + class munmap_delete { + size_t m_length; + + public: + munmap_delete(size_t length) : m_length(length) {} + void operator()(void *ptr) { + if (m_length) + munmap(ptr, m_length); + } + }; + + class file_close { + + public: + file_close() = default; + void operator()(int *ptr) { + if (ptr == nullptr) + return; + if (*ptr == -1) + return; + close(*ptr); + std::default_delete()(ptr); + } + }; + + std::unique_ptr m_mmap_meta; + std::unique_ptr m_mmap_aux; + std::unique_ptr m_fd; + lldb::tid_t m_tid; + + /// Start tracing a thread + /// + /// \param[in] pid + /// The pid of the process whose thread will be traced. + /// + /// \param[in] buffer_size + /// Size of the thread buffer in bytes. + /// + /// \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, + uint64_t buffer_size); + + llvm::MutableArrayRef GetAuxBuffer() const; + llvm::MutableArrayRef GetDataBuffer() const; + + IntelPTThreadTrace() + : m_mmap_meta(nullptr, munmap_delete(0)), + m_mmap_aux(nullptr, munmap_delete(0)), m_fd(nullptr, file_close()) {} + +public: + /// Get the content of /proc/cpuinfo that can be later used to decode traces. + static llvm::Expected> GetCPUInfo(); + + /// Start tracing a thread. + /// + /// See \a StartTrace. + /// + /// \return + /// A \a IntelPTThreadTrace 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); + + /// 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) const; + + Status ReadPerfTraceAux(llvm::MutableArrayRef &buffer, + size_t offset = 0) const; + + Status ReadPerfTraceData(llvm::MutableArrayRef &buffer, + size_t offset = 0) const; + + /// Get the size in bytes of the aux section of the thread or process traced + /// by this object. + size_t GetTraceBufferSize() const; + + /// Read data from a cyclic buffer + /// + /// \param[in] [out] buf + /// Destination buffer, the buffer will be truncated to written size. + /// + /// \param[in] src + /// Source buffer which must be a cyclic buffer. + /// + /// \param[in] src_cyc_index + /// The index pointer (start of the valid data in the cyclic + /// buffer). + /// + /// \param[in] offset + /// The offset to begin reading the data in the cyclic buffer. + static void ReadCyclicBuffer(llvm::MutableArrayRef &dst, + llvm::MutableArrayRef src, + size_t src_cyc_index, size_t offset); + + /// Return the thread-specific part of the jLLDBTraceGetState packet. + TraceThreadState GetState() const; +}; + +/// Manages a list of thread traces. +class IntelPTThreadTraceCollection { +public: + IntelPTThreadTraceCollection(lldb::pid_t pid) : m_pid(pid) {} + + /// Dispose of all traces + void Clear(); + + bool TracesThread(lldb::tid_t tid) const; + + size_t GetTotalBufferSize() const; + + std::vector GetThreadStates() const; + + llvm::Expected + GetTracedThread(lldb::tid_t tid) const; + + llvm::Error TraceStart(lldb::tid_t tid, + const TraceIntelPTStartRequest &request); + + llvm::Error TraceStop(lldb::tid_t tid); + +private: + lldb::pid_t m_pid; + llvm::DenseMap m_thread_traces; + /// Total actual thread buffer size in bytes + size_t m_total_buffer_size = 0; +}; + +/// Manages a "process trace" instance. +class IntelPTProcessTrace { +public: + IntelPTProcessTrace(lldb::pid_t pid, const TraceIntelPTStartRequest &request) + : m_thread_traces(pid), m_tracing_params(request) {} + + bool TracesThread(lldb::tid_t tid) const; + + const IntelPTThreadTraceCollection &GetThreadTraces() const; + + llvm::Error TraceStart(lldb::tid_t tid); + + llvm::Error TraceStop(lldb::tid_t tid); + +private: + IntelPTThreadTraceCollection m_thread_traces; + /// Params used to trace threads when the user started "process tracing". + TraceIntelPTStartRequest m_tracing_params; +}; + +/// Main class that manages intel-pt process and thread tracing. +class IntelPTManager { +public: + IntelPTManager(lldb::pid_t pid) : m_pid(pid), m_thread_traces(pid) {} + + static bool IsSupported(); + + /// If "process tracing" is enabled, then trace the given thread. + llvm::Error OnThreadCreated(lldb::tid_t tid); + + /// Stops tracing a tracing upon a destroy event. + llvm::Error OnThreadDestroyed(lldb::tid_t tid); + + /// Implementation of the jLLDBTraceStop packet + llvm::Error TraceStop(const TraceStopRequest &request); + + /// Implementation of the jLLDBTraceStart packet + /// + /// \param[in] process_threads + /// A list of all threads owned by the process. + llvm::Error TraceStart(const TraceIntelPTStartRequest &request, + const std::vector &process_threads); + + /// Implementation of the jLLDBTraceGetState packet + llvm::Expected GetState() const; + + /// Implementation of the jLLDBTraceGetBinaryData packet + llvm::Expected> + GetBinaryData(const TraceGetBinaryDataRequest &request) const; + + /// Dispose of all traces + void Clear(); + +private: + llvm::Error TraceStop(lldb::tid_t tid); + + /// Start tracing a specific thread. + llvm::Error TraceStart(lldb::tid_t tid, + const TraceIntelPTStartRequest &request); + + llvm::Expected + GetTracedThread(lldb::tid_t tid) const; + + bool IsProcessTracingEnabled() const; + + void ClearProcessTracing(); + + lldb::pid_t m_pid; + /// Threads traced due to "thread tracing" + IntelPTThreadTraceCollection m_thread_traces; + /// Threads traced due to "process tracing". Only one active "process tracing" + /// instance is assumed for a single process. + llvm::Optional m_process_trace; +}; + +} // namespace process_linux +} // namespace lldb_private + +#endif // liblldb_IntelPTManager_H_ diff --git a/lldb/source/Plugins/Process/Linux/IntelPTManager.cpp b/lldb/source/Plugins/Process/Linux/IntelPTManager.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/Linux/IntelPTManager.cpp @@ -0,0 +1,554 @@ +//===-- IntelPTManager.cpp ------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/MathExtras.h" + +#include "IntelPTManager.h" +#include "Plugins/Process/POSIX/ProcessPOSIXLog.h" +#include "lldb/Host/linux/Support.h" +#include "lldb/Utility/StreamString.h" + +#include +#include + +using namespace lldb; +using namespace lldb_private; +using namespace process_linux; +using namespace llvm; + +const char *kOSEventIntelPTTypeFile = + "/sys/bus/event_source/devices/intel_pt/type"; + +/// Return the Linux perf event type for Intel PT. +static Expected GetOSEventType() { + auto intel_pt_type_text = + llvm::MemoryBuffer::getFileAsStream(kOSEventIntelPTTypeFile); + + if (!intel_pt_type_text) + return createStringError(inconvertibleErrorCode(), + "Can't open the file '%s'", + kOSEventIntelPTTypeFile); + + uint32_t intel_pt_type = 0; + StringRef buffer = intel_pt_type_text.get()->getBuffer(); + if (buffer.trim().getAsInteger(10, intel_pt_type)) + return createStringError( + inconvertibleErrorCode(), + "The file '%s' has a invalid value. It should be an unsigned int.", + kOSEventIntelPTTypeFile); + return intel_pt_type; +} + +size_t IntelPTThreadTrace::GetTraceBufferSize() const { + return m_mmap_meta->aux_size; +} + +Error IntelPTThreadTrace::StartTrace(lldb::pid_t pid, lldb::tid_t tid, + uint64_t buffer_size) { +#ifndef PERF_ATTR_SIZE_VER5 + llvm_unreachable("Intel PT Linux perf event not supported"); +#else + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + + m_tid = tid; + LLDB_LOG(log, "called thread id {0}", tid); + uint64_t page_size = getpagesize(); + + if (__builtin_popcount(buffer_size) != 1 || buffer_size < 4096) { + return createStringError( + inconvertibleErrorCode(), + "The trace buffer size must be a power of 2 greater than or equal to " + "4096 (2^12) bytes. It was %" PRIu64 ".", + buffer_size); + } + uint64_t numpages = static_cast( + llvm::PowerOf2Floor((buffer_size + page_size - 1) / page_size)); + numpages = std::max(1, numpages); + buffer_size = page_size * numpages; + + perf_event_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.size = sizeof(attr); + attr.exclude_kernel = 1; + attr.sample_type = PERF_SAMPLE_TIME; + attr.sample_id_all = 1; + attr.exclude_hv = 1; + attr.exclude_idle = 1; + attr.mmap = 1; + attr.config = 0; + + Expected intel_pt_type = GetOSEventType(); + + 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, "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); + return createStringError(inconvertibleErrorCode(), + "perf event syscall failed"); + } + + m_fd = std::unique_ptr(new int(fd), file_close()); + + errno = 0; + auto base = + mmap(nullptr, (buffer_size + page_size), PROT_WRITE, MAP_SHARED, fd, 0); + + if (base == MAP_FAILED) { + LLDB_LOG(log, "mmap base error {0}", errno); + return createStringError(inconvertibleErrorCode(), + "Meta buffer allocation failed"); + } + + m_mmap_meta = std::unique_ptr( + reinterpret_cast(base), + 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 = buffer_size; + + errno = 0; + 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); + return createStringError(inconvertibleErrorCode(), + "Trace buffer allocation failed"); + } + m_mmap_aux = std::unique_ptr( + reinterpret_cast(mmap_aux), munmap_delete(buffer_size)); + return Error::success(); +#endif +} + +llvm::MutableArrayRef IntelPTThreadTrace::GetDataBuffer() const { +#ifndef PERF_ATTR_SIZE_VER5 + llvm_unreachable("Intel PT Linux perf event not supported"); +#else + return MutableArrayRef( + (reinterpret_cast(m_mmap_meta.get()) + + m_mmap_meta->data_offset), + m_mmap_meta->data_size); +#endif +} + +llvm::MutableArrayRef IntelPTThreadTrace::GetAuxBuffer() const { +#ifndef PERF_ATTR_SIZE_VER5 + llvm_unreachable("Intel PT Linux perf event not supported"); +#else + return MutableArrayRef(m_mmap_aux.get(), m_mmap_meta->aux_size); +#endif +} + +Expected> IntelPTThreadTrace::GetCPUInfo() { + static llvm::Optional> cpu_info; + if (!cpu_info) { + auto buffer_or_error = getProcFile("cpuinfo"); + if (!buffer_or_error) + return Status(buffer_or_error.getError()).ToError(); + MemoryBuffer &buffer = **buffer_or_error; + cpu_info = std::vector( + reinterpret_cast(buffer.getBufferStart()), + reinterpret_cast(buffer.getBufferEnd())); + } + return *cpu_info; +} + +llvm::Expected +IntelPTThreadTrace::Create(lldb::pid_t pid, lldb::tid_t tid, + size_t buffer_size) { + IntelPTThreadTraceUP thread_trace_up(new IntelPTThreadTrace()); + + if (llvm::Error err = thread_trace_up->StartTrace(pid, tid, buffer_size)) + return std::move(err); + + return std::move(thread_trace_up); +} + +Expected> +IntelPTThreadTrace::GetIntelPTBuffer(size_t offset, size_t size) const { + std::vector data(size, 0); + MutableArrayRef buffer_ref(data); + Status error = ReadPerfTraceAux(buffer_ref, 0); + if (error.Fail()) + return error.ToError(); + return data; +} + +Status +IntelPTThreadTrace::ReadPerfTraceAux(llvm::MutableArrayRef &buffer, + size_t offset) const { +#ifndef PERF_ATTR_SIZE_VER5 + llvm_unreachable("perf event not supported"); +#else + // Disable the perf event to force a flush out of the CPU's internal buffer. + // Besides, we can guarantee that the CPU won't override any data as we are + // reading the buffer. + // + // The Intel documentation says: + // + // Packets are first buffered internally and then written out asynchronously. + // To collect packet output for postprocessing, a collector needs first to + // ensure that all packet data has been flushed from internal buffers. + // Software can ensure this by stopping packet generation by clearing + // IA32_RTIT_CTL.TraceEn (see “Disabling Packet Generation” in + // Section 35.2.7.2). + // + // This is achieved by the PERF_EVENT_IOC_DISABLE ioctl request, as mentioned + // in the man page of perf_event_open. + ioctl(*m_fd, PERF_EVENT_IOC_DISABLE); + + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + Status error; + uint64_t head = m_mmap_meta->aux_head; + + LLDB_LOG(log, "Aux size -{0} , Head - {1}", m_mmap_meta->aux_size, head); + + /** + * When configured as ring buffer, the aux buffer keeps wrapping around + * the buffer and its not possible to detect how many times the buffer + * wrapped. Initially the buffer is filled with zeros,as shown below + * so in order to get complete buffer we first copy firstpartsize, followed + * by any left over part from beginning to aux_head + * + * aux_offset [d,d,d,d,d,d,d,d,0,0,0,0,0,0,0,0,0,0,0] aux_size + * aux_head->||<- firstpartsize ->| + * + * */ + + ReadCyclicBuffer(buffer, GetAuxBuffer(), static_cast(head), offset); + LLDB_LOG(log, "ReadCyclic BUffer Done"); + + // Reenable tracing now we have read the buffer + ioctl(*m_fd, PERF_EVENT_IOC_ENABLE); + return error; +#endif +} + +Status +IntelPTThreadTrace::ReadPerfTraceData(llvm::MutableArrayRef &buffer, + size_t offset) const { +#ifndef PERF_ATTR_SIZE_VER5 + llvm_unreachable("perf event not supported"); +#else + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + uint64_t bytes_remaining = buffer.size(); + Status error; + + uint64_t head = m_mmap_meta->data_head; + + /* + * The data buffer and aux buffer have different implementations + * with respect to their definition of head pointer. In the case + * of Aux data buffer the head always wraps around the aux buffer + * and we don't need to care about it, whereas the data_head keeps + * increasing and needs to be wrapped by modulus operator + */ + + LLDB_LOG(log, "bytes_remaining - {0}", bytes_remaining); + + auto data_buffer = GetDataBuffer(); + + if (head > data_buffer.size()) { + head = head % data_buffer.size(); + LLDB_LOG(log, "Data size -{0} Head - {1}", m_mmap_meta->data_size, head); + + ReadCyclicBuffer(buffer, data_buffer, static_cast(head), offset); + bytes_remaining -= buffer.size(); + } else { + LLDB_LOG(log, "Head - {0}", head); + if (offset >= head) { + LLDB_LOG(log, "Invalid Offset "); + error.SetErrorString("invalid offset"); + buffer = buffer.slice(buffer.size()); + return error; + } + + auto data = data_buffer.slice(offset, (head - offset)); + auto remaining = std::copy(data.begin(), data.end(), buffer.begin()); + bytes_remaining -= (remaining - buffer.begin()); + } + buffer = buffer.drop_back(bytes_remaining); + return error; +#endif +} + +void IntelPTThreadTrace::ReadCyclicBuffer(llvm::MutableArrayRef &dst, + llvm::MutableArrayRef src, + size_t src_cyc_index, size_t offset) { + + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + + if (dst.empty() || src.empty()) { + dst = dst.drop_back(dst.size()); + return; + } + + if (dst.data() == nullptr || src.data() == nullptr) { + dst = dst.drop_back(dst.size()); + return; + } + + if (src_cyc_index > src.size()) { + dst = dst.drop_back(dst.size()); + return; + } + + if (offset >= src.size()) { + LLDB_LOG(log, "Too Big offset "); + dst = dst.drop_back(dst.size()); + return; + } + + llvm::SmallVector, 2> parts = { + src.slice(src_cyc_index), src.take_front(src_cyc_index)}; + + if (offset > parts[0].size()) { + parts[1] = parts[1].slice(offset - parts[0].size()); + parts[0] = parts[0].drop_back(parts[0].size()); + } else if (offset == parts[0].size()) { + parts[0] = parts[0].drop_back(parts[0].size()); + } else { + parts[0] = parts[0].slice(offset); + } + auto next = dst.begin(); + auto bytes_left = dst.size(); + for (auto part : parts) { + size_t chunk_size = std::min(part.size(), bytes_left); + next = std::copy_n(part.begin(), chunk_size, next); + bytes_left -= chunk_size; + } + dst = dst.drop_back(bytes_left); +} + +TraceThreadState IntelPTThreadTrace::GetState() const { + return {static_cast(m_tid), + {TraceBinaryData{"threadTraceBuffer", + static_cast(GetTraceBufferSize())}}}; +} + +/// IntelPTThreadTraceCollection + +bool IntelPTThreadTraceCollection::TracesThread(lldb::tid_t tid) const { + return m_thread_traces.count(tid); +} + +Error IntelPTThreadTraceCollection::TraceStop(lldb::tid_t tid) { + auto it = m_thread_traces.find(tid); + if (it == m_thread_traces.end()) + return createStringError(inconvertibleErrorCode(), + "Thread %" PRIu64 " not currently traced", tid); + m_total_buffer_size -= it->second->GetTraceBufferSize(); + m_thread_traces.erase(tid); + return Error::success(); +} + +Error IntelPTThreadTraceCollection::TraceStart( + lldb::tid_t tid, const TraceIntelPTStartRequest &request) { + if (TracesThread(tid)) + return createStringError(inconvertibleErrorCode(), + "Thread %" PRIu64 " already traced", tid); + + Expected trace_up = + IntelPTThreadTrace::Create(m_pid, tid, request.threadBufferSize); + if (!trace_up) + return trace_up.takeError(); + + m_total_buffer_size += (*trace_up)->GetTraceBufferSize(); + m_thread_traces.try_emplace(tid, std::move(*trace_up)); + return Error::success(); +} + +size_t IntelPTThreadTraceCollection::GetTotalBufferSize() const { + return m_total_buffer_size; +} + +std::vector +IntelPTThreadTraceCollection::GetThreadStates() const { + std::vector states; + for (const auto &it : m_thread_traces) + states.push_back(it.second->GetState()); + return states; +} + +Expected +IntelPTThreadTraceCollection::GetTracedThread(lldb::tid_t tid) const { + auto it = m_thread_traces.find(tid); + if (it == m_thread_traces.end()) + return createStringError(inconvertibleErrorCode(), + "Thread %" PRIu64 " not currently traced", tid); + return *it->second.get(); +} + +void IntelPTThreadTraceCollection::Clear() { + m_thread_traces.clear(); + m_total_buffer_size = 0; +} + +/// IntelPTProcessTrace + +bool IntelPTProcessTrace::TracesThread(lldb::tid_t tid) const { + return m_thread_traces.TracesThread(tid); +} + +Error IntelPTProcessTrace::TraceStop(lldb::tid_t tid) { + return m_thread_traces.TraceStop(tid); +} + +Error IntelPTProcessTrace::TraceStart(lldb::tid_t tid) { + if (m_thread_traces.GetTotalBufferSize() + m_tracing_params.threadBufferSize > + static_cast(*m_tracing_params.processBufferSizeLimit)) + return createStringError( + inconvertibleErrorCode(), + "Thread %" PRIu64 " can't be traced as the process trace size limit " + "has been reached. Consider retracing with a higher " + "limit.", + tid); + + return m_thread_traces.TraceStart(tid, m_tracing_params); +} + +const IntelPTThreadTraceCollection & +IntelPTProcessTrace::GetThreadTraces() const { + return m_thread_traces; +} + +/// IntelPTManager + +Error IntelPTManager::TraceStop(lldb::tid_t tid) { + if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) + return m_process_trace->TraceStop(tid); + return m_thread_traces.TraceStop(tid); +} + +Error IntelPTManager::TraceStop(const TraceStopRequest &request) { + if (request.IsProcessTracing()) { + if (!IsProcessTracingEnabled()) { + return createStringError(inconvertibleErrorCode(), + "Process not currently traced"); + } + ClearProcessTracing(); + return Error::success(); + } else { + Error error = Error::success(); + for (int64_t tid : *request.tids) + error = joinErrors(std::move(error), + TraceStop(static_cast(tid))); + return error; + } +} + +Error IntelPTManager::TraceStart( + const TraceIntelPTStartRequest &request, + const std::vector &process_threads) { + if (request.IsProcessTracing()) { + if (IsProcessTracingEnabled()) { + return createStringError( + inconvertibleErrorCode(), + "Process currently traced. Stop process tracing first"); + } + m_process_trace = IntelPTProcessTrace(m_pid, request); + + Error error = Error::success(); + for (lldb::tid_t tid : process_threads) + error = joinErrors(std::move(error), m_process_trace->TraceStart(tid)); + return error; + } else { + Error error = Error::success(); + for (int64_t tid : *request.tids) + error = joinErrors(std::move(error), + m_thread_traces.TraceStart(tid, request)); + return error; + } +} + +Error IntelPTManager::OnThreadCreated(lldb::tid_t tid) { + if (!IsProcessTracingEnabled()) + return Error::success(); + return m_process_trace->TraceStart(tid); +} + +Error IntelPTManager::OnThreadDestroyed(lldb::tid_t tid) { + if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) + return m_process_trace->TraceStop(tid); + else if (m_thread_traces.TracesThread(tid)) + return m_thread_traces.TraceStop(tid); + return Error::success(); +} + +Expected IntelPTManager::GetState() const { + Expected> cpu_info = IntelPTThreadTrace::GetCPUInfo(); + if (!cpu_info) + return cpu_info.takeError(); + + TraceGetStateResponse state; + state.processBinaryData.push_back( + {"cpuInfo", static_cast(cpu_info->size())}); + + std::vector thread_states = + m_thread_traces.GetThreadStates(); + state.tracedThreads.insert(state.tracedThreads.end(), thread_states.begin(), + thread_states.end()); + + if (IsProcessTracingEnabled()) { + thread_states = m_process_trace->GetThreadTraces().GetThreadStates(); + state.tracedThreads.insert(state.tracedThreads.end(), thread_states.begin(), + thread_states.end()); + } + return toJSON(state); +} + +Expected +IntelPTManager::GetTracedThread(lldb::tid_t tid) const { + if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) + return m_process_trace->GetThreadTraces().GetTracedThread(tid); + return m_thread_traces.GetTracedThread(tid); +} + +Expected> +IntelPTManager::GetBinaryData(const TraceGetBinaryDataRequest &request) const { + if (request.kind == "threadTraceBuffer") { + if (Expected trace = + GetTracedThread(*request.tid)) + return trace->GetIntelPTBuffer(request.offset, request.size); + else + return trace.takeError(); + } else if (request.kind == "cpuInfo") { + return IntelPTThreadTrace::GetCPUInfo(); + } + return createStringError(inconvertibleErrorCode(), + "Unsuported trace binary data kind: %s", + request.kind.c_str()); +} + +void IntelPTManager::ClearProcessTracing() { m_process_trace = None; } + +bool IntelPTManager::IsSupported() { return (bool)GetOSEventType(); } + +bool IntelPTManager::IsProcessTracingEnabled() const { + return (bool)m_process_trace; +} + +void IntelPTManager::Clear() { + ClearProcessTracing(); + m_thread_traces.Clear(); +} 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 @@ -20,10 +20,10 @@ #include "lldb/Utility/FileSpec.h" #include "lldb/lldb-types.h" +#include "IntelPTManager.h" #include "NativeThreadLinux.h" #include "Plugins/Process/POSIX/NativeProcessELF.h" #include "Plugins/Process/Utility/NativeProcessSoftwareSingleStep.h" -#include "ProcessorTrace.h" namespace lldb_private { class Status; @@ -103,23 +103,22 @@ return getProcFile(GetID(), "auxv"); } - lldb::user_id_t StartTrace(const TraceOptions &config, - Status &error) override; + /// Tracing + /// These methods implement the jLLDBTrace packets + /// \{ + llvm::Error TraceStart(llvm::StringRef json_request, + llvm::StringRef type) override; - Status StopTrace(lldb::user_id_t traceid, - lldb::tid_t thread) override; + llvm::Error TraceStop(const TraceStopRequest &request) override; - Status GetData(lldb::user_id_t traceid, lldb::tid_t thread, - llvm::MutableArrayRef &buffer, - size_t offset = 0) override; + llvm::Expected + TraceGetState(llvm::StringRef type) override; - Status GetMetaData(lldb::user_id_t traceid, lldb::tid_t thread, - llvm::MutableArrayRef &buffer, - size_t offset = 0) override; + llvm::Expected> + TraceGetBinaryData(const TraceGetBinaryDataRequest &request) override; - Status GetTraceConfig(lldb::user_id_t traceid, TraceOptions &config) override; - - virtual llvm::Expected GetSupportedTraceType() override; + llvm::Expected TraceSupported() override; + /// } // Interface used by NativeRegisterContext-derived classes. static Status PtraceWrapper(int req, lldb::pid_t pid, void *addr = nullptr, @@ -175,7 +174,28 @@ bool StopTrackingThread(lldb::tid_t thread_id); - NativeThreadLinux &AddThread(lldb::tid_t thread_id); + /// Create a new thread. + /// + /// If process tracing is enabled and the thread can't be traced, then the + /// thread is left stopped with a \a eStopReasonProcessorTrace status, and + /// then the process is stopped. + /// + /// \param[in] resume + /// If a tracing error didn't happen, then resume the thread after + /// creation if \b true, or leave it stopped with SIGSTOP if \b false. + NativeThreadLinux &AddThread(lldb::tid_t thread_id, bool resume); + + /// Start tracing a new thread if process tracing is enabled. + /// + /// Trace mechanisms should modify this method to provide automatic tracing + /// for new threads. + Status NotifyTracersOfNewThread(lldb::tid_t tid); + + /// Stop tracing threads upon a destroy event. + /// + /// Trace mechanisms should modify this method to provide automatic trace + /// stopping for threads being destroyed. + Status NotifyTracersOfThreadDestroyed(lldb::tid_t tid); /// Writes a siginfo_t structure corresponding to the given thread ID to the /// memory region pointed to by \p siginfo. @@ -212,42 +232,8 @@ Status PopulateMemoryRegionCache(); - lldb::user_id_t StartTraceGroup(const TraceOptions &config, - Status &error); - - // This function is intended to be used to stop tracing - // on a thread that exited. - Status StopTracingForThread(lldb::tid_t thread); - - // The below function as the name suggests, looks up a ProcessorTrace - // instance from the m_processor_trace_monitor map. In the case of - // process tracing where the traceid passed would map to the complete - // process, it is mandatory to provide a threadid to obtain a trace - // instance (since ProcessorTrace is tied to a thread). In the other - // scenario that an individual thread is being traced, just the traceid - // is sufficient to obtain the actual ProcessorTrace instance. - llvm::Expected - LookupProcessorTraceInstance(lldb::user_id_t traceid, lldb::tid_t thread); - - // Stops tracing on individual threads being traced. Not intended - // to be used to stop tracing on complete process. - Status StopProcessorTracingOnThread(lldb::user_id_t traceid, - lldb::tid_t thread); - - // Intended to stop tracing on complete process. - // Should not be used for stopping trace on - // individual threads. - void StopProcessorTracingOnProcess(); - - llvm::DenseMap - m_processor_trace_monitor; - - // Set for tracking threads being traced under - // same process user id. - llvm::DenseSet m_pt_traced_thread_group; - - lldb::user_id_t m_pt_proces_trace_id = LLDB_INVALID_UID; - TraceOptions m_pt_process_trace_config; + /// Manages Intel PT process and thread traces. + IntelPTManager m_intel_pt_manager; }; } // 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 @@ -286,7 +286,8 @@ NativeDelegate &delegate, const ArchSpec &arch, MainLoop &mainloop, llvm::ArrayRef<::pid_t> tids) - : NativeProcessELF(pid, terminal_fd, delegate), m_arch(arch) { + : NativeProcessELF(pid, terminal_fd, delegate), m_arch(arch), + m_intel_pt_manager(pid) { if (m_terminal_fd != -1) { Status status = EnsureFDFlags(m_terminal_fd, O_NONBLOCK); assert(status.Success()); @@ -298,8 +299,7 @@ assert(m_sigchld_handle && status.Success()); for (const auto &tid : tids) { - NativeThreadLinux &thread = AddThread(tid); - thread.SetStoppedBySignal(SIGSTOP); + NativeThreadLinux &thread = AddThread(tid, /*resume*/ false); ThreadWasCreated(thread); } @@ -444,10 +444,7 @@ LLDB_LOG(log, "tid {0}, si_code: {1}, si_pid: {2}", pid, info.si_code, info.si_pid); - NativeThreadLinux &thread = AddThread(pid); - - // Resume the newly created thread. - ResumeThread(thread, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER); + NativeThreadLinux &thread = AddThread(pid, /*resume*/ true); ThreadWasCreated(thread); return; } @@ -550,9 +547,8 @@ } LLDB_LOG(log, "pid = {0}: tracking new thread tid {1}", GetID(), tid); - NativeThreadLinux &new_thread = AddThread(tid); + NativeThreadLinux &new_thread = AddThread(tid, /*resume*/ true); - ResumeThread(new_thread, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER); ThreadWasCreated(new_thread); } @@ -954,8 +950,7 @@ e; // Save the error, but still attempt to detach from other threads. } - m_processor_trace_monitor.clear(); - m_pt_proces_trace_id = LLDB_INVALID_UID; + m_intel_pt_manager.Clear(); return error; } @@ -1511,12 +1506,33 @@ } if (found) - StopTracingForThread(thread_id); + NotifyTracersOfThreadDestroyed(thread_id); + SignalIfAllThreadsStopped(); return found; } -NativeThreadLinux &NativeProcessLinux::AddThread(lldb::tid_t thread_id) { +Status NativeProcessLinux::NotifyTracersOfNewThread(lldb::tid_t tid) { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_THREAD)); + Status error(m_intel_pt_manager.OnThreadCreated(tid)); + if (error.Fail()) + LLDB_LOG(log, "Failed to trace a new thread with intel-pt, tid = {0}. {1}", + tid, error.AsCString()); + return error; +} + +Status NativeProcessLinux::NotifyTracersOfThreadDestroyed(lldb::tid_t tid) { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_THREAD)); + Status error(m_intel_pt_manager.OnThreadDestroyed(tid)); + if (error.Fail()) + LLDB_LOG(log, + "Failed to stop a destroyed thread with intel-pt, tid = {0}. {1}", + tid, error.AsCString()); + return error; +} + +NativeThreadLinux &NativeProcessLinux::AddThread(lldb::tid_t thread_id, + bool resume) { Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_THREAD)); LLDB_LOG(log, "pid {0} adding thread with tid {1}", GetID(), thread_id); @@ -1528,22 +1544,19 @@ SetCurrentThreadID(thread_id); m_threads.push_back(std::make_unique(*this, thread_id)); + NativeThreadLinux &thread = + static_cast(*m_threads.back()); + + Status tracing_error = NotifyTracersOfNewThread(thread.GetID()); + if (tracing_error.Fail()) { + thread.SetStoppedByProcessorTrace(tracing_error.AsCString()); + StopRunningThreads(thread.GetID()); + } else if (resume) + ResumeThread(thread, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER); + else + thread.SetStoppedBySignal(SIGSTOP); - 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); - } - } - - return static_cast(*m_threads.back()); + return thread; } Status NativeProcessLinux::GetLoadedModuleFileSpec(const char *module_path, @@ -1761,263 +1774,43 @@ return error; } -llvm::Expected -NativeProcessLinux::LookupProcessorTraceInstance(lldb::user_id_t traceid, - lldb::tid_t thread) { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); - if (thread == LLDB_INVALID_THREAD_ID && traceid == m_pt_proces_trace_id) { - LLDB_LOG(log, "thread not specified: {0}", traceid); - return Status("tracing not active thread not specified").ToError(); - } - - for (auto& iter : m_processor_trace_monitor) { - if (traceid == iter.second->GetTraceID() && - (thread == iter.first || thread == LLDB_INVALID_THREAD_ID)) - return *(iter.second); - } - - LLDB_LOG(log, "traceid not being traced: {0}", traceid); - return Status("tracing not active for this thread").ToError(); +llvm::Expected NativeProcessLinux::TraceSupported() { + if (IntelPTManager::IsSupported()) + return TraceSupportedResponse{"intel-pt", "Intel Processor Trace"}; + return NativeProcessProtocol::TraceSupported(); } -Status NativeProcessLinux::GetMetaData(lldb::user_id_t traceid, - lldb::tid_t thread, - llvm::MutableArrayRef &buffer, - size_t offset) { - TraceOptions trace_options; - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); - Status error; - - LLDB_LOG(log, "traceid {0}", traceid); - - auto perf_monitor = LookupProcessorTraceInstance(traceid, thread); - if (!perf_monitor) { - LLDB_LOG(log, "traceid not being traced: {0}", traceid); - buffer = buffer.slice(buffer.size()); - error = perf_monitor.takeError(); - return error; - } - return (*perf_monitor).ReadPerfTraceData(buffer, offset); -} - -Status NativeProcessLinux::GetData(lldb::user_id_t traceid, lldb::tid_t thread, - llvm::MutableArrayRef &buffer, - size_t offset) { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); - Status error; - - LLDB_LOG(log, "traceid {0}", traceid); - - auto perf_monitor = LookupProcessorTraceInstance(traceid, thread); - if (!perf_monitor) { - LLDB_LOG(log, "traceid not being traced: {0}", traceid); - buffer = buffer.slice(buffer.size()); - error = perf_monitor.takeError(); - return error; - } - return (*perf_monitor).ReadPerfTraceAux(buffer, offset); -} - -Status NativeProcessLinux::GetTraceConfig(lldb::user_id_t traceid, - TraceOptions &config) { - Status error; - if (config.getThreadID() == LLDB_INVALID_THREAD_ID && - m_pt_proces_trace_id == traceid) { - if (m_pt_proces_trace_id == LLDB_INVALID_UID) { - error.SetErrorString("tracing not active for this process"); - return error; - } - config = m_pt_process_trace_config; - } else { - auto perf_monitor = - LookupProcessorTraceInstance(traceid, config.getThreadID()); - if (!perf_monitor) { - error = perf_monitor.takeError(); - return error; - } - error = (*perf_monitor).GetTraceConfig(config); - } - return error; -} - -llvm::Expected NativeProcessLinux::GetSupportedTraceType() { - if (ProcessorTraceMonitor::IsSupported()) - return TraceTypeInfo{"intel-pt", "Intel Processor Trace"}; - return NativeProcessProtocol::GetSupportedTraceType(); -} - -lldb::user_id_t -NativeProcessLinux::StartTraceGroup(const TraceOptions &config, - Status &error) { - - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); - if (config.getType() != TraceType::eTraceTypeProcessorTrace) - return LLDB_INVALID_UID; - - if (m_pt_proces_trace_id != LLDB_INVALID_UID) { - error.SetErrorString("tracing already active on this process"); - return m_pt_proces_trace_id; - } - - for (const auto &thread_sp : m_threads) { - if (auto traceInstance = ProcessorTraceMonitor::Create( - GetID(), thread_sp->GetID(), config, true)) { - m_pt_traced_thread_group.insert(thread_sp->GetID()); - m_processor_trace_monitor.insert( - std::make_pair(thread_sp->GetID(), std::move(*traceInstance))); - } - } - - m_pt_process_trace_config = config; - error = ProcessorTraceMonitor::GetCPUType(m_pt_process_trace_config); - - // Trace on Complete process will have traceid of 0 - m_pt_proces_trace_id = 0; - - LLDB_LOG(log, "Process Trace ID {0}", m_pt_proces_trace_id); - return m_pt_proces_trace_id; -} - -lldb::user_id_t NativeProcessLinux::StartTrace(const TraceOptions &config, - Status &error) { - if (config.getType() != TraceType::eTraceTypeProcessorTrace) - return NativeProcessProtocol::StartTrace(config, error); - - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); - - lldb::tid_t threadid = config.getThreadID(); - - if (threadid == LLDB_INVALID_THREAD_ID) - return StartTraceGroup(config, error); - - auto thread_sp = GetThreadByID(threadid); - if (!thread_sp) { - // Thread not tracked by lldb so don't trace. - error.SetErrorString("invalid thread id"); - return LLDB_INVALID_UID; - } - - const auto &iter = m_processor_trace_monitor.find(threadid); - if (iter != m_processor_trace_monitor.end()) { - LLDB_LOG(log, "Thread already being traced"); - error.SetErrorString("tracing already active on this thread"); - return LLDB_INVALID_UID; - } - - auto traceMonitor = - ProcessorTraceMonitor::Create(GetID(), threadid, config, false); - if (!traceMonitor) { - error = traceMonitor.takeError(); - LLDB_LOG(log, "error {0}", error); - return LLDB_INVALID_UID; - } - lldb::user_id_t ret_trace_id = (*traceMonitor)->GetTraceID(); - m_processor_trace_monitor.insert( - std::make_pair(threadid, std::move(*traceMonitor))); - return ret_trace_id; -} - -Status NativeProcessLinux::StopTracingForThread(lldb::tid_t thread) { - Status error; - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); - LLDB_LOG(log, "Thread {0}", thread); - - const auto& iter = m_processor_trace_monitor.find(thread); - if (iter == m_processor_trace_monitor.end()) { - error.SetErrorString("tracing not active for this thread"); - return error; - } - - if (iter->second->GetTraceID() == m_pt_proces_trace_id) { - // traceid maps to the whole process so we have to erase it from the thread - // group. - LLDB_LOG(log, "traceid maps to process"); - m_pt_traced_thread_group.erase(thread); +Error NativeProcessLinux::TraceStart(StringRef json_request, StringRef type) { + if (type == "intel-pt") { + if (Expected request = + json::parse(json_request, + "TraceIntelPTStartRequest")) { + std::vector process_threads; + for (auto &thread : m_threads) + process_threads.push_back(thread->GetID()); + return m_intel_pt_manager.TraceStart(*request, process_threads); + } else + return request.takeError(); } - m_processor_trace_monitor.erase(iter); - return error; + return NativeProcessProtocol::TraceStart(json_request, type); } -Status NativeProcessLinux::StopTrace(lldb::user_id_t traceid, - lldb::tid_t thread) { - Status error; - - TraceOptions trace_options; - trace_options.setThreadID(thread); - error = NativeProcessLinux::GetTraceConfig(traceid, trace_options); - - if (error.Fail()) - return error; - - switch (trace_options.getType()) { - case lldb::TraceType::eTraceTypeProcessorTrace: - if (traceid == m_pt_proces_trace_id && - thread == LLDB_INVALID_THREAD_ID) - StopProcessorTracingOnProcess(); - else - error = StopProcessorTracingOnThread(traceid, thread); - break; - default: - error.SetErrorString("trace not supported"); - break; - } - - return error; +Error NativeProcessLinux::TraceStop(const TraceStopRequest &request) { + if (request.type == "intel-pt") + return m_intel_pt_manager.TraceStop(request); + return NativeProcessProtocol::TraceStop(request); } -void NativeProcessLinux::StopProcessorTracingOnProcess() { - for (auto thread_id_iter : m_pt_traced_thread_group) - m_processor_trace_monitor.erase(thread_id_iter); - m_pt_traced_thread_group.clear(); - m_pt_proces_trace_id = LLDB_INVALID_UID; +Expected NativeProcessLinux::TraceGetState(StringRef type) { + if (type == "intel-pt") + return m_intel_pt_manager.GetState(); + return NativeProcessProtocol::TraceGetState(type); } -Status NativeProcessLinux::StopProcessorTracingOnThread(lldb::user_id_t traceid, - lldb::tid_t thread) { - Status error; - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); - - if (thread == LLDB_INVALID_THREAD_ID) { - for (auto& iter : m_processor_trace_monitor) { - if (iter.second->GetTraceID() == traceid) { - // Stopping a trace instance for an individual thread hence there will - // only be one traceid that can match. - m_processor_trace_monitor.erase(iter.first); - return error; - } - LLDB_LOG(log, "Trace ID {0}", iter.second->GetTraceID()); - } - - LLDB_LOG(log, "Invalid TraceID"); - error.SetErrorString("invalid trace id"); - return error; - } - - // thread is specified so we can use find function on the map. - const auto& iter = m_processor_trace_monitor.find(thread); - if (iter == m_processor_trace_monitor.end()) { - // thread not found in our map. - LLDB_LOG(log, "thread not being traced"); - error.SetErrorString("tracing not active for this thread"); - return error; - } - if (iter->second->GetTraceID() != traceid) { - // traceid did not match so it has to be invalid. - LLDB_LOG(log, "Invalid TraceID"); - error.SetErrorString("invalid trace id"); - return error; - } - - LLDB_LOG(log, "UID - {0} , Thread -{1}", traceid, thread); - - if (traceid == m_pt_proces_trace_id) { - // traceid maps to the whole process so we have to erase it from the thread - // group. - LLDB_LOG(log, "traceid maps to process"); - m_pt_traced_thread_group.erase(thread); - } - m_processor_trace_monitor.erase(iter); - - return error; +Expected> NativeProcessLinux::TraceGetBinaryData( + const TraceGetBinaryDataRequest &request) { + if (request.type == "intel-pt") + return m_intel_pt_manager.GetBinaryData(request); + return NativeProcessProtocol::TraceGetBinaryData(request); } diff --git a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.h b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.h --- a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.h +++ b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.h @@ -14,6 +14,8 @@ #include "lldb/Host/common/NativeThreadProtocol.h" #include "lldb/lldb-private-forward.h" +#include "llvm/ADT/StringRef.h" + #include #include #include @@ -85,6 +87,8 @@ void SetStoppedWithNoReason(); + void SetStoppedByProcessorTrace(llvm::StringRef description); + void SetExited(); Status RequestStop(); diff --git a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp --- a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp @@ -77,6 +77,9 @@ case eStopReasonInstrumentation: log.Printf("%s: %s instrumentation", __FUNCTION__, header); return; + case eStopReasonProcessorTrace: + log.Printf("%s: %s processor trace", __FUNCTION__, header); + return; default: log.Printf("%s: %s invalid stop reason %" PRIu32, __FUNCTION__, header, static_cast(stop_info.reason)); @@ -398,6 +401,15 @@ m_stop_info.details.signal.signo = 0; } +void NativeThreadLinux::SetStoppedByProcessorTrace( + llvm::StringRef description) { + SetStopped(); + + m_stop_info.reason = StopReason::eStopReasonProcessorTrace; + m_stop_info.details.signal.signo = 0; + m_stop_description = description.str(); +} + void NativeThreadLinux::SetExited() { const StateType new_state = StateType::eStateExited; MaybeLogStateChange(new_state); diff --git a/lldb/source/Plugins/Process/Linux/ProcessorTrace.h b/lldb/source/Plugins/Process/Linux/ProcessorTrace.h deleted file mode 100644 --- a/lldb/source/Plugins/Process/Linux/ProcessorTrace.h +++ /dev/null @@ -1,140 +0,0 @@ -//===-- ProcessorTrace.h -------------------------------------- -*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef liblldb_ProcessorTrace_H_ -#define liblldb_ProcessorTrace_H_ - -#include "lldb/Utility/Status.h" -#include "lldb/Utility/TraceOptions.h" -#include "lldb/lldb-types.h" -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/DenseSet.h" - -#include -#include -#include - -namespace lldb_private { - -namespace process_linux { - -// This class keeps track of one tracing instance of -// Intel(R) Processor Trace on Linux OS. There is a map keeping track -// of different tracing instances on each thread, which enables trace -// gathering on a per thread level. -// -// The tracing instance is linked with a trace id. The trace id acts like -// a key to the tracing instance and trace manipulations could be -// performed using the trace id. -// -// The trace id could map to trace instances for a group of threads -// (spanning to all the threads in the process) or a single thread. -// The kernel interface for us is the perf_event_open. - -class ProcessorTraceMonitor; -typedef std::unique_ptr ProcessorTraceMonitorUP; - -class ProcessorTraceMonitor { - - class munmap_delete { - size_t m_length; - - public: - munmap_delete(size_t length) : m_length(length) {} - void operator()(void *ptr) { - if (m_length) - munmap(ptr, m_length); - } - }; - - class file_close { - - public: - file_close() = default; - void operator()(int *ptr) { - if (ptr == nullptr) - return; - if (*ptr == -1) - return; - close(*ptr); - std::default_delete()(ptr); - } - }; - - std::unique_ptr m_mmap_meta; - std::unique_ptr m_mmap_aux; - std::unique_ptr m_fd; - - // perf_event_mmap_page *m_mmap_base; - lldb::user_id_t m_traceid; - lldb::tid_t m_thread_id; - - // Counter to track trace instances. - static lldb::user_id_t m_trace_num; - - void SetTraceID(lldb::user_id_t traceid) { m_traceid = traceid; } - - Status StartTrace(lldb::pid_t pid, lldb::tid_t tid, - const TraceOptions &config); - - llvm::MutableArrayRef GetAuxBuffer(); - llvm::MutableArrayRef GetDataBuffer(); - - ProcessorTraceMonitor() - : m_mmap_meta(nullptr, munmap_delete(0)), - m_mmap_aux(nullptr, munmap_delete(0)), m_fd(nullptr, file_close()), - m_traceid(LLDB_INVALID_UID), m_thread_id(LLDB_INVALID_THREAD_ID){}; - - void SetThreadID(lldb::tid_t tid) { m_thread_id = tid; } - -public: - static llvm::Expected GetOSEventType(); - - static bool IsSupported(); - - static Status GetCPUType(TraceOptions &config); - - static llvm::Expected - Create(lldb::pid_t pid, lldb::tid_t tid, const TraceOptions &config, - bool useProcessSettings); - - Status ReadPerfTraceAux(llvm::MutableArrayRef &buffer, - size_t offset = 0); - - Status ReadPerfTraceData(llvm::MutableArrayRef &buffer, - size_t offset = 0); - - ~ProcessorTraceMonitor() = default; - - lldb::tid_t GetThreadID() const { return m_thread_id; } - - lldb::user_id_t GetTraceID() const { return m_traceid; } - - Status GetTraceConfig(TraceOptions &config) const; - - /// Read data from a cyclic buffer - /// - /// \param[in] [out] buf - /// Destination buffer, the buffer will be truncated to written size. - /// - /// \param[in] src - /// Source buffer which must be a cyclic buffer. - /// - /// \param[in] src_cyc_index - /// The index pointer (start of the valid data in the cyclic - /// buffer). - /// - /// \param[in] offset - /// The offset to begin reading the data in the cyclic buffer. - static void ReadCyclicBuffer(llvm::MutableArrayRef &dst, - llvm::MutableArrayRef src, - size_t src_cyc_index, size_t offset); -}; -} // namespace process_linux -} // namespace lldb_private -#endif diff --git a/lldb/source/Plugins/Process/Linux/ProcessorTrace.cpp b/lldb/source/Plugins/Process/Linux/ProcessorTrace.cpp deleted file mode 100644 --- a/lldb/source/Plugins/Process/Linux/ProcessorTrace.cpp +++ /dev/null @@ -1,428 +0,0 @@ -//===-- ProcessorTrace.cpp ------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include -#include - -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/MathExtras.h" - -#include "Plugins/Process/POSIX/ProcessPOSIXLog.h" -#include "ProcessorTrace.h" -#include "lldb/Host/linux/Support.h" - -#include -#include - -using namespace lldb; -using namespace lldb_private; -using namespace process_linux; -using namespace llvm; - -lldb::user_id_t ProcessorTraceMonitor::m_trace_num = 1; -const char *kOSEventIntelPTTypeFile = - "/sys/bus/event_source/devices/intel_pt/type"; - -Status ProcessorTraceMonitor::GetTraceConfig(TraceOptions &config) const { -#ifndef PERF_ATTR_SIZE_VER5 - llvm_unreachable("perf event not supported"); -#else - Status error; - - config.setType(lldb::TraceType::eTraceTypeProcessorTrace); - config.setMetaDataBufferSize(m_mmap_meta->data_size); - - config.setTraceBufferSize(m_mmap_meta->aux_size); - - error = GetCPUType(config); - - return error; -#endif -} - -Expected ProcessorTraceMonitor::GetOSEventType() { - auto intel_pt_type_text = - llvm::MemoryBuffer::getFileAsStream(kOSEventIntelPTTypeFile); - - if (!intel_pt_type_text) - return createStringError(inconvertibleErrorCode(), - "Can't open the file '%s'", - kOSEventIntelPTTypeFile); - - uint32_t intel_pt_type = 0; - StringRef buffer = intel_pt_type_text.get()->getBuffer(); - if (buffer.trim().getAsInteger(10, intel_pt_type)) - return createStringError( - inconvertibleErrorCode(), - "The file '%s' has a invalid value. It should be an unsigned int.", - kOSEventIntelPTTypeFile); - return intel_pt_type; -} - -bool ProcessorTraceMonitor::IsSupported() { return (bool)GetOSEventType(); } - -Status ProcessorTraceMonitor::StartTrace(lldb::pid_t pid, lldb::tid_t tid, - const TraceOptions &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 numpages = static_cast( - llvm::PowerOf2Floor((bufsize + 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; - - perf_event_attr attr; - memset(&attr, 0, sizeof(attr)); - attr.size = sizeof(attr); - attr.exclude_kernel = 1; - attr.sample_type = PERF_SAMPLE_TIME; - attr.sample_id_all = 1; - attr.exclude_hv = 1; - attr.exclude_idle = 1; - attr.mmap = 1; - - Expected intel_pt_type = GetOSEventType(); - - if (!intel_pt_type) { - error = intel_pt_type.takeError(); - return error; - } - - 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; - } - - 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; - } - - 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); - - if (base == MAP_FAILED) { - LLDB_LOG(log, "mmap base error {0}", errno); - error.SetErrorString("Meta buffer allocation failed"); - return error; - } - - m_mmap_meta = std::unique_ptr( - reinterpret_cast(base), - munmap_delete(metabufsize + page_size)); - - m_mmap_meta->aux_offset = m_mmap_meta->data_offset + m_mmap_meta->data_size; - m_mmap_meta->aux_size = bufsize; - - errno = 0; - auto mmap_aux = mmap(nullptr, bufsize, 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; - } - m_mmap_aux = std::unique_ptr( - reinterpret_cast(mmap_aux), munmap_delete(bufsize)); - return error; -#endif -} - -llvm::MutableArrayRef ProcessorTraceMonitor::GetDataBuffer() { -#ifndef PERF_ATTR_SIZE_VER5 - llvm_unreachable("perf event not supported"); -#else - return MutableArrayRef( - (reinterpret_cast(m_mmap_meta.get()) + - m_mmap_meta->data_offset), - m_mmap_meta->data_size); -#endif -} - -llvm::MutableArrayRef ProcessorTraceMonitor::GetAuxBuffer() { -#ifndef PERF_ATTR_SIZE_VER5 - llvm_unreachable("perf event not supported"); -#else - return MutableArrayRef(m_mmap_aux.get(), m_mmap_meta->aux_size); -#endif -} - -Status ProcessorTraceMonitor::GetCPUType(TraceOptions &config) { - - Status error; - uint64_t cpu_family = -1; - uint64_t model = -1; - uint64_t stepping = -1; - std::string vendor_id; - - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); - - auto BufferOrError = getProcFile("cpuinfo"); - if (!BufferOrError) - return BufferOrError.getError(); - - LLDB_LOG(log, "GetCPUType Function"); - - StringRef Rest = BufferOrError.get()->getBuffer(); - while (!Rest.empty()) { - StringRef Line; - std::tie(Line, Rest) = Rest.split('\n'); - - SmallVector columns; - Line.split(columns, StringRef(":"), -1, false); - - if (columns.size() < 2) - continue; // continue searching - - columns[1] = columns[1].trim(" "); - if (columns[0].contains("cpu family") && - columns[1].getAsInteger(10, cpu_family)) - continue; - - else if (columns[0].contains("model") && columns[1].getAsInteger(10, model)) - continue; - - else if (columns[0].contains("stepping") && - columns[1].getAsInteger(10, stepping)) - continue; - - else if (columns[0].contains("vendor_id")) { - vendor_id = columns[1].str(); - if (!vendor_id.empty()) - continue; - } - 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); - - 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 error; // we are done - } - } - - error.SetErrorString("cpu info not found"); - return error; -} - -llvm::Expected -ProcessorTraceMonitor::Create(lldb::pid_t pid, lldb::tid_t tid, - const TraceOptions &config, - bool useProcessSettings) { - - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); - - Status error; - if (tid == LLDB_INVALID_THREAD_ID) { - error.SetErrorString("thread not specified"); - return error.ToError(); - } - - ProcessorTraceMonitorUP pt_monitor_up(new ProcessorTraceMonitor); - - error = pt_monitor_up->StartTrace(pid, tid, config); - if (error.Fail()) - return error.ToError(); - - pt_monitor_up->SetThreadID(tid); - - if (useProcessSettings) { - pt_monitor_up->SetTraceID(0); - } else { - pt_monitor_up->SetTraceID(m_trace_num++); - LLDB_LOG(log, "Trace ID {0}", m_trace_num); - } - return std::move(pt_monitor_up); -} - -Status -ProcessorTraceMonitor::ReadPerfTraceAux(llvm::MutableArrayRef &buffer, - size_t offset) { -#ifndef PERF_ATTR_SIZE_VER5 - llvm_unreachable("perf event not supported"); -#else - // Disable the perf event to force a flush out of the CPU's internal buffer. - // Besides, we can guarantee that the CPU won't override any data as we are - // reading the buffer. - // - // The Intel documentation says: - // - // Packets are first buffered internally and then written out asynchronously. - // To collect packet output for postprocessing, a collector needs first to - // ensure that all packet data has been flushed from internal buffers. - // Software can ensure this by stopping packet generation by clearing - // IA32_RTIT_CTL.TraceEn (see “Disabling Packet Generation” in - // Section 35.2.7.2). - // - // This is achieved by the PERF_EVENT_IOC_DISABLE ioctl request, as mentioned - // in the man page of perf_event_open. - ioctl(*m_fd, PERF_EVENT_IOC_DISABLE); - - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); - Status error; - uint64_t head = m_mmap_meta->aux_head; - - LLDB_LOG(log, "Aux size -{0} , Head - {1}", m_mmap_meta->aux_size, head); - - /** - * When configured as ring buffer, the aux buffer keeps wrapping around - * the buffer and its not possible to detect how many times the buffer - * wrapped. Initially the buffer is filled with zeros,as shown below - * so in order to get complete buffer we first copy firstpartsize, followed - * by any left over part from beginning to aux_head - * - * aux_offset [d,d,d,d,d,d,d,d,0,0,0,0,0,0,0,0,0,0,0] aux_size - * aux_head->||<- firstpartsize ->| - * - * */ - - ReadCyclicBuffer(buffer, GetAuxBuffer(), static_cast(head), offset); - LLDB_LOG(log, "ReadCyclic BUffer Done"); - - // Reenable tracing now we have read the buffer - ioctl(*m_fd, PERF_EVENT_IOC_ENABLE); - return error; -#endif -} - -Status -ProcessorTraceMonitor::ReadPerfTraceData(llvm::MutableArrayRef &buffer, - size_t offset) { -#ifndef PERF_ATTR_SIZE_VER5 - llvm_unreachable("perf event not supported"); -#else - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); - uint64_t bytes_remaining = buffer.size(); - Status error; - - uint64_t head = m_mmap_meta->data_head; - - /* - * The data buffer and aux buffer have different implementations - * with respect to their definition of head pointer. In the case - * of Aux data buffer the head always wraps around the aux buffer - * and we don't need to care about it, whereas the data_head keeps - * increasing and needs to be wrapped by modulus operator - */ - - LLDB_LOG(log, "bytes_remaining - {0}", bytes_remaining); - - auto data_buffer = GetDataBuffer(); - - if (head > data_buffer.size()) { - head = head % data_buffer.size(); - LLDB_LOG(log, "Data size -{0} Head - {1}", m_mmap_meta->data_size, head); - - ReadCyclicBuffer(buffer, data_buffer, static_cast(head), offset); - bytes_remaining -= buffer.size(); - } else { - LLDB_LOG(log, "Head - {0}", head); - if (offset >= head) { - LLDB_LOG(log, "Invalid Offset "); - error.SetErrorString("invalid offset"); - buffer = buffer.slice(buffer.size()); - return error; - } - - auto data = data_buffer.slice(offset, (head - offset)); - auto remaining = std::copy(data.begin(), data.end(), buffer.begin()); - bytes_remaining -= (remaining - buffer.begin()); - } - buffer = buffer.drop_back(bytes_remaining); - return error; -#endif -} - -void ProcessorTraceMonitor::ReadCyclicBuffer( - llvm::MutableArrayRef &dst, llvm::MutableArrayRef src, - size_t src_cyc_index, size_t offset) { - - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); - - if (dst.empty() || src.empty()) { - dst = dst.drop_back(dst.size()); - return; - } - - if (dst.data() == nullptr || src.data() == nullptr) { - dst = dst.drop_back(dst.size()); - return; - } - - if (src_cyc_index > src.size()) { - dst = dst.drop_back(dst.size()); - return; - } - - if (offset >= src.size()) { - LLDB_LOG(log, "Too Big offset "); - dst = dst.drop_back(dst.size()); - return; - } - - llvm::SmallVector, 2> parts = { - src.slice(src_cyc_index), src.take_front(src_cyc_index)}; - - if (offset > parts[0].size()) { - parts[1] = parts[1].slice(offset - parts[0].size()); - parts[0] = parts[0].drop_back(parts[0].size()); - } else if (offset == parts[0].size()) { - parts[0] = parts[0].drop_back(parts[0].size()); - } else { - parts[0] = parts[0].slice(offset); - } - auto next = dst.begin(); - auto bytes_left = dst.size(); - for (auto part : parts) { - size_t chunk_size = std::min(part.size(), bytes_left); - next = std::copy_n(part.begin(), chunk_size, next); - bytes_left -= chunk_size; - } - dst = dst.drop_back(bytes_left); -} 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 @@ -22,7 +22,7 @@ #include "lldb/Utility/GDBRemote.h" #include "lldb/Utility/ProcessInfo.h" #include "lldb/Utility/StructuredData.h" -#include "lldb/Utility/TraceOptions.h" +#include "lldb/Utility/TraceGDBRemotePackets.h" #if defined(_WIN32) #include "lldb/Host/windows/PosixApi.h" #endif @@ -505,22 +505,16 @@ ConfigureRemoteStructuredData(ConstString type_name, const StructuredData::ObjectSP &config_sp); - lldb::user_id_t SendStartTracePacket(const TraceOptions &options, - Status &error); + llvm::Expected SendTraceSupported(); - Status SendStopTracePacket(lldb::user_id_t uid, lldb::tid_t thread_id); + llvm::Error SendTraceStart(const llvm::json::Value &request); - Status SendGetDataPacket(lldb::user_id_t uid, lldb::tid_t thread_id, - llvm::MutableArrayRef &buffer, - size_t offset = 0); + llvm::Error SendTraceStop(const TraceStopRequest &request); - Status SendGetMetaDataPacket(lldb::user_id_t uid, lldb::tid_t thread_id, - llvm::MutableArrayRef &buffer, - size_t offset = 0); + llvm::Expected SendTraceGetState(llvm::StringRef type); - Status SendGetTraceConfigPacket(lldb::user_id_t uid, TraceOptions &options); - - llvm::Expected SendGetSupportedTraceType(); + llvm::Expected> + SendTraceGetBinaryData(const TraceGetBinaryDataRequest &request); protected: LazyBool m_supports_not_sending_acks; 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 @@ -3372,110 +3372,78 @@ response.IsOKResponse(); } -lldb::user_id_t -GDBRemoteCommunicationClient::SendStartTracePacket(const TraceOptions &options, - Status &error) { +llvm::Expected +GDBRemoteCommunicationClient::SendTraceSupported() { Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); - lldb::user_id_t ret_uid = LLDB_INVALID_UID; StreamGDBRemote escaped_packet; - escaped_packet.PutCString("jTraceStart:"); - - StructuredData::Dictionary json_packet; - json_packet.AddIntegerItem("type", options.getType()); - json_packet.AddIntegerItem("buffersize", options.getTraceBufferSize()); - json_packet.AddIntegerItem("metabuffersize", options.getMetaDataBufferSize()); - - if (options.getThreadID() != LLDB_INVALID_THREAD_ID) - json_packet.AddIntegerItem("threadid", options.getThreadID()); - - StructuredData::DictionarySP custom_params = options.getTraceParams(); - if (custom_params) - json_packet.AddItem("params", custom_params); - - StreamString json_string; - json_packet.Dump(json_string, false); - escaped_packet.PutEscapedBytes(json_string.GetData(), json_string.GetSize()); + escaped_packet.PutCString("jLLDBTraceSupported"); StringExtractorGDBRemote response; if (SendPacketAndWaitForResponse(escaped_packet.GetString(), response, true) == GDBRemoteCommunication::PacketResult::Success) { - if (!response.IsNormalResponse()) { - error = response.GetStatus(); - LLDB_LOG(log, "Target does not support Tracing , error {0}", error); - } else { - ret_uid = response.GetHexMaxU64(false, LLDB_INVALID_UID); - } - } else { - LLDB_LOG(log, "failed to send packet"); - error.SetErrorStringWithFormat("failed to send packet: '%s'", - escaped_packet.GetData()); + if (response.IsErrorResponse()) + return response.GetStatus().ToError(); + if (response.IsUnsupportedResponse()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "jLLDBTraceSupported is unsupported"); + + return llvm::json::parse(response.Peek(), + "TraceSupportedResponse"); } - return ret_uid; + LLDB_LOG(log, "failed to send packet: jLLDBTraceSupported"); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "failed to send packet: jLLDBTraceSupported"); } -Status -GDBRemoteCommunicationClient::SendStopTracePacket(lldb::user_id_t uid, - lldb::tid_t thread_id) { +llvm::Error +GDBRemoteCommunicationClient::SendTraceStop(const TraceStopRequest &request) { Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); - StringExtractorGDBRemote response; - Status error; - StructuredData::Dictionary json_packet; StreamGDBRemote escaped_packet; - StreamString json_string; - escaped_packet.PutCString("jTraceStop:"); - - json_packet.AddIntegerItem("traceid", uid); + escaped_packet.PutCString("jLLDBTraceStop:"); - if (thread_id != LLDB_INVALID_THREAD_ID) - json_packet.AddIntegerItem("threadid", thread_id); + std::string json_string; + llvm::raw_string_ostream os(json_string); + os << toJSON(request); + os.flush(); - json_packet.Dump(json_string, false); - - escaped_packet.PutEscapedBytes(json_string.GetData(), json_string.GetSize()); + escaped_packet.PutEscapedBytes(json_string.c_str(), json_string.size()); + StringExtractorGDBRemote response; if (SendPacketAndWaitForResponse(escaped_packet.GetString(), response, true) == GDBRemoteCommunication::PacketResult::Success) { - if (!response.IsOKResponse()) { - error = response.GetStatus(); - LLDB_LOG(log, "stop tracing failed"); - } - } else { - LLDB_LOG(log, "failed to send packet"); - error.SetErrorStringWithFormat( - "failed to send packet: '%s' with error '%d'", escaped_packet.GetData(), - response.GetError()); + 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 jLLDBTraceStart response"); } - return error; + LLDB_LOG(log, "failed to send packet: jLLDBTraceStop"); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "failed to send packet: jLLDBTraceStop '%s'", + escaped_packet.GetData()); } -Status GDBRemoteCommunicationClient::SendGetDataPacket( - lldb::user_id_t uid, lldb::tid_t thread_id, - llvm::MutableArrayRef &buffer, size_t offset) { +llvm::Error +GDBRemoteCommunicationClient::SendTraceStart(const llvm::json::Value ¶ms) { + Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); StreamGDBRemote escaped_packet; - escaped_packet.PutCString("jTraceBufferRead:"); - return SendGetTraceDataPacket(escaped_packet, uid, thread_id, buffer, offset); -} + escaped_packet.PutCString("jLLDBTraceStart:"); -Status GDBRemoteCommunicationClient::SendGetMetaDataPacket( - lldb::user_id_t uid, lldb::tid_t thread_id, - llvm::MutableArrayRef &buffer, size_t offset) { + std::string json_string; + llvm::raw_string_ostream os(json_string); + os << params; + os.flush(); - StreamGDBRemote escaped_packet; - escaped_packet.PutCString("jTraceMetaRead:"); - return SendGetTraceDataPacket(escaped_packet, uid, thread_id, buffer, offset); -} - -llvm::Expected -GDBRemoteCommunicationClient::SendGetSupportedTraceType() { - Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); - - StreamGDBRemote escaped_packet; - escaped_packet.PutCString("jLLDBTraceSupportedType"); + escaped_packet.PutEscapedBytes(json_string.c_str(), json_string.size()); StringExtractorGDBRemote response; if (SendPacketAndWaitForResponse(escaped_packet.GetString(), response, @@ -3485,127 +3453,84 @@ return response.GetStatus().ToError(); if (response.IsUnsupportedResponse()) return llvm::createStringError(llvm::inconvertibleErrorCode(), - "jLLDBTraceSupportedType is unsupported"); - - if (llvm::Expected type = - llvm::json::parse(response.Peek())) - return *type; - else - return type.takeError(); + "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: jLLDBTraceSupportedType"); - return llvm::createStringError( - llvm::inconvertibleErrorCode(), - "failed to send packet: jLLDBTraceSupportedType"); + LLDB_LOG(log, "failed to send packet: jLLDBTraceStart"); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "failed to send packet: jLLDBTraceStart '%s'", + escaped_packet.GetData()); } -Status -GDBRemoteCommunicationClient::SendGetTraceConfigPacket(lldb::user_id_t uid, - TraceOptions &options) { +llvm::Expected +GDBRemoteCommunicationClient::SendTraceGetState(llvm::StringRef type) { Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); - StringExtractorGDBRemote response; - Status error; - StreamString json_string; StreamGDBRemote escaped_packet; - escaped_packet.PutCString("jTraceConfigRead:"); + escaped_packet.PutCString("jLLDBTraceGetState:"); - StructuredData::Dictionary json_packet; - json_packet.AddIntegerItem("traceid", uid); + std::string json_string; + llvm::raw_string_ostream os(json_string); + os << toJSON(TraceGetStateRequest{type.str()}); + os.flush(); - if (options.getThreadID() != LLDB_INVALID_THREAD_ID) - json_packet.AddIntegerItem("threadid", options.getThreadID()); - - json_packet.Dump(json_string, false); - escaped_packet.PutEscapedBytes(json_string.GetData(), json_string.GetSize()); + escaped_packet.PutEscapedBytes(json_string.c_str(), json_string.size()); + StringExtractorGDBRemote response; if (SendPacketAndWaitForResponse(escaped_packet.GetString(), response, true) == GDBRemoteCommunication::PacketResult::Success) { - if (response.IsNormalResponse()) { - uint64_t type = std::numeric_limits::max(); - uint64_t buffersize = std::numeric_limits::max(); - uint64_t metabuffersize = std::numeric_limits::max(); - - auto json_object = StructuredData::ParseJSON(response.Peek()); - - if (!json_object || - json_object->GetType() != lldb::eStructuredDataTypeDictionary) { - error.SetErrorString("Invalid Configuration obtained"); - return error; - } - - auto json_dict = json_object->GetAsDictionary(); - - json_dict->GetValueForKeyAsInteger("metabuffersize", - metabuffersize); - options.setMetaDataBufferSize(metabuffersize); - - json_dict->GetValueForKeyAsInteger("buffersize", buffersize); - options.setTraceBufferSize(buffersize); - - json_dict->GetValueForKeyAsInteger("type", type); - options.setType(static_cast(type)); - - StructuredData::ObjectSP custom_params_sp = - json_dict->GetValueForKey("params"); - if (custom_params_sp) { - if (custom_params_sp->GetType() != - lldb::eStructuredDataTypeDictionary) { - error.SetErrorString("Invalid Configuration obtained"); - return error; - } else - options.setTraceParams( - std::static_pointer_cast( - custom_params_sp)); - } - } else { - error = response.GetStatus(); - } - } else { - LLDB_LOG(log, "failed to send packet"); - error.SetErrorStringWithFormat("failed to send packet: '%s'", - escaped_packet.GetData()); + if (response.IsErrorResponse()) + return response.GetStatus().ToError(); + if (response.IsUnsupportedResponse()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "jLLDBTraceGetState is unsupported"); + return std::string(response.Peek()); } - return error; + + LLDB_LOG(log, "failed to send packet: jLLDBTraceGetState"); + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "failed to send packet: jLLDBTraceGetState '%s'", + escaped_packet.GetData()); } -Status GDBRemoteCommunicationClient::SendGetTraceDataPacket( - StreamGDBRemote &packet, lldb::user_id_t uid, lldb::tid_t thread_id, - llvm::MutableArrayRef &buffer, size_t offset) { +llvm::Expected> +GDBRemoteCommunicationClient::SendTraceGetBinaryData( + const TraceGetBinaryDataRequest &request) { Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); - Status error; - StructuredData::Dictionary json_packet; - - json_packet.AddIntegerItem("traceid", uid); - json_packet.AddIntegerItem("offset", offset); - json_packet.AddIntegerItem("buffersize", buffer.size()); + StreamGDBRemote escaped_packet; + escaped_packet.PutCString("jLLDBTraceGetBinaryData:"); - if (thread_id != LLDB_INVALID_THREAD_ID) - json_packet.AddIntegerItem("threadid", thread_id); + std::string json_string; + llvm::raw_string_ostream os(json_string); + os << toJSON(request); + os.flush(); - StreamString json_string; - json_packet.Dump(json_string, false); + escaped_packet.PutEscapedBytes(json_string.c_str(), json_string.size()); - packet.PutEscapedBytes(json_string.GetData(), json_string.GetSize()); StringExtractorGDBRemote response; - if (SendPacketAndWaitForResponse(packet.GetString(), response, true) == + if (SendPacketAndWaitForResponse(escaped_packet.GetString(), response, + true) == GDBRemoteCommunication::PacketResult::Success) { - if (response.IsNormalResponse()) { - size_t filled_size = response.GetHexBytesAvail(buffer); - buffer = llvm::MutableArrayRef(buffer.data(), filled_size); - } else { - error = response.GetStatus(); - buffer = buffer.slice(buffer.size()); - } - } else { - LLDB_LOG(log, "failed to send packet"); - error.SetErrorStringWithFormat("failed to send packet: '%s'", - packet.GetData()); - buffer = buffer.slice(buffer.size()); + 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()); } - return error; + LLDB_LOG(log, "failed to send packet: jLLDBTraceGetBinaryData"); + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "failed to send packet: jLLDBTraceGetBinaryData '%s'", + escaped_packet.GetData()); } llvm::Optional GDBRemoteCommunicationClient::GetQOffsets() { 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 @@ -72,6 +72,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 @@ -167,15 +167,15 @@ PacketResult Handle_QSaveRegisterState(StringExtractorGDBRemote &packet); - PacketResult Handle_jTraceStart(StringExtractorGDBRemote &packet); + PacketResult Handle_jLLDBTraceSupported(StringExtractorGDBRemote &packet); - PacketResult Handle_jTraceRead(StringExtractorGDBRemote &packet); + PacketResult Handle_jLLDBTraceStart(StringExtractorGDBRemote &packet); - PacketResult Handle_jTraceStop(StringExtractorGDBRemote &packet); + PacketResult Handle_jLLDBTraceStop(StringExtractorGDBRemote &packet); - PacketResult Handle_jTraceConfigRead(StringExtractorGDBRemote &packet); + PacketResult Handle_jLLDBTraceGetState(StringExtractorGDBRemote &packet); - PacketResult Handle_jLLDBTraceSupportedType(StringExtractorGDBRemote &packet); + PacketResult Handle_jLLDBTraceGetBinaryData(StringExtractorGDBRemote &packet); PacketResult Handle_QRestoreRegisterState(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 @@ -186,23 +186,20 @@ &GDBRemoteCommunicationServerLLGS::Handle_QPassSignals); RegisterMemberFunctionHandler( - StringExtractorGDBRemote::eServerPacketType_jTraceStart, - &GDBRemoteCommunicationServerLLGS::Handle_jTraceStart); + StringExtractorGDBRemote::eServerPacketType_jLLDBTraceSupported, + &GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceSupported); RegisterMemberFunctionHandler( - StringExtractorGDBRemote::eServerPacketType_jTraceBufferRead, - &GDBRemoteCommunicationServerLLGS::Handle_jTraceRead); + StringExtractorGDBRemote::eServerPacketType_jLLDBTraceStart, + &GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceStart); RegisterMemberFunctionHandler( - StringExtractorGDBRemote::eServerPacketType_jTraceMetaRead, - &GDBRemoteCommunicationServerLLGS::Handle_jTraceRead); + StringExtractorGDBRemote::eServerPacketType_jLLDBTraceStop, + &GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceStop); RegisterMemberFunctionHandler( - StringExtractorGDBRemote::eServerPacketType_jTraceStop, - &GDBRemoteCommunicationServerLLGS::Handle_jTraceStop); + StringExtractorGDBRemote::eServerPacketType_jLLDBTraceGetState, + &GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceGetState); RegisterMemberFunctionHandler( - StringExtractorGDBRemote::eServerPacketType_jTraceConfigRead, - &GDBRemoteCommunicationServerLLGS::Handle_jTraceConfigRead); - RegisterMemberFunctionHandler( - StringExtractorGDBRemote::eServerPacketType_jLLDBTraceSupportedType, - &GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceSupportedType); + StringExtractorGDBRemote::eServerPacketType_jLLDBTraceGetBinaryData, + &GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceGetBinaryData); RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_g, &GDBRemoteCommunicationServerLLGS::Handle_g); @@ -648,6 +645,8 @@ return "exception"; case eStopReasonExec: return "exec"; + case eStopReasonProcessorTrace: + return "processor trace"; case eStopReasonInstrumentation: case eStopReasonInvalid: case eStopReasonPlanComplete: @@ -1170,108 +1169,39 @@ } GDBRemoteCommunication::PacketResult -GDBRemoteCommunicationServerLLGS::Handle_jTraceStart( +GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceSupported( StringExtractorGDBRemote &packet) { - Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS)); + // 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(68); - - if (!packet.ConsumeFront("jTraceStart:")) - return SendIllFormedResponse(packet, "jTraceStart: Ill formed packet "); - - TraceOptions options; - uint64_t type = std::numeric_limits::max(); - uint64_t buffersize = std::numeric_limits::max(); - lldb::tid_t tid = LLDB_INVALID_THREAD_ID; - uint64_t metabuffersize = std::numeric_limits::max(); - - auto json_object = StructuredData::ParseJSON(packet.Peek()); - - if (!json_object || - json_object->GetType() != lldb::eStructuredDataTypeDictionary) - return SendIllFormedResponse(packet, "jTraceStart: Ill formed packet "); - - auto json_dict = json_object->GetAsDictionary(); - - json_dict->GetValueForKeyAsInteger("metabuffersize", metabuffersize); - options.setMetaDataBufferSize(metabuffersize); - - json_dict->GetValueForKeyAsInteger("buffersize", buffersize); - options.setTraceBufferSize(buffersize); - - json_dict->GetValueForKeyAsInteger("type", type); - options.setType(static_cast(type)); - - json_dict->GetValueForKeyAsInteger("threadid", tid); - options.setThreadID(tid); - - StructuredData::ObjectSP custom_params_sp = - json_dict->GetValueForKey("params"); - if (custom_params_sp && - custom_params_sp->GetType() != lldb::eStructuredDataTypeDictionary) - return SendIllFormedResponse(packet, "jTraceStart: Ill formed packet "); - - options.setTraceParams( - std::static_pointer_cast(custom_params_sp)); - - if (buffersize == std::numeric_limits::max() || - type != lldb::TraceType::eTraceTypeProcessorTrace) { - LLDB_LOG(log, "Ill formed packet buffersize = {0} type = {1}", buffersize, - type); - return SendIllFormedResponse(packet, "JTrace:start: Ill formed packet "); - } - - Status error; - lldb::user_id_t uid = LLDB_INVALID_UID; - uid = m_debugged_process_up->StartTrace(options, error); - LLDB_LOG(log, "uid is {0} , error is {1}", uid, error.GetError()); - if (error.Fail()) - return SendErrorResponse(error); + return SendErrorResponse(Status("Process not running.")); - StreamGDBRemote response; - response.Printf("%" PRIx64, uid); - return SendPacketNoLock(response.GetString()); + return SendJSONResponse(m_debugged_process_up->TraceSupported()); } GDBRemoteCommunication::PacketResult -GDBRemoteCommunicationServerLLGS::Handle_jTraceStop( +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(68); - - if (!packet.ConsumeFront("jTraceStop:")) - return SendIllFormedResponse(packet, "jTraceStop: Ill formed packet "); - - lldb::user_id_t uid = LLDB_INVALID_UID; - lldb::tid_t tid = LLDB_INVALID_THREAD_ID; - - auto json_object = StructuredData::ParseJSON(packet.Peek()); - - if (!json_object || - json_object->GetType() != lldb::eStructuredDataTypeDictionary) - return SendIllFormedResponse(packet, "jTraceStop: Ill formed packet "); - - auto json_dict = json_object->GetAsDictionary(); - - if (!json_dict->GetValueForKeyAsInteger("traceid", uid)) - return SendIllFormedResponse(packet, "jTraceStop: Ill formed packet "); - - json_dict->GetValueForKeyAsInteger("threadid", tid); + return SendErrorResponse(Status("Process not running.")); - Status error = m_debugged_process_up->StopTrace(uid, tid); + packet.ConsumeFront("jLLDBTraceStop:"); + Expected stop_request = + json::parse(packet.Peek(), "TraceStopRequest"); + if (!stop_request) + return SendErrorResponse(stop_request.takeError()); - if (error.Fail()) - return SendErrorResponse(error); + if (Error err = m_debugged_process_up->TraceStop(*stop_request)) + return SendErrorResponse(std::move(err)); return SendOKResponse(); } GDBRemoteCommunication::PacketResult -GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceSupportedType( +GDBRemoteCommunicationServerLLGS::Handle_jLLDBTraceStart( StringExtractorGDBRemote &packet) { // Fail if we don't have a current process. @@ -1279,146 +1209,60 @@ (m_debugged_process_up->GetID() == LLDB_INVALID_PROCESS_ID)) return SendErrorResponse(Status("Process not running.")); - llvm::Expected supported_trace_type = - m_debugged_process_up->GetSupportedTraceType(); - if (!supported_trace_type) - return SendErrorResponse(supported_trace_type.takeError()); - - StreamGDBRemote escaped_response; - StructuredData::Dictionary json_packet; + packet.ConsumeFront("jLLDBTraceStart:"); + Expected request = + json::parse(packet.Peek(), "TraceStartRequest"); + if (!request) + return SendErrorResponse(request.takeError()); - json_packet.AddStringItem("name", supported_trace_type->name); - json_packet.AddStringItem("description", supported_trace_type->description); + if (Error err = + m_debugged_process_up->TraceStart(packet.Peek(), request->type)) + return SendErrorResponse(std::move(err)); - StreamString json_string; - json_packet.Dump(json_string, false); - escaped_response.PutEscapedBytes(json_string.GetData(), - json_string.GetSize()); - return SendPacketNoLock(escaped_response.GetString()); + return SendOKResponse(); } GDBRemoteCommunication::PacketResult -GDBRemoteCommunicationServerLLGS::Handle_jTraceConfigRead( +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(68); - - if (!packet.ConsumeFront("jTraceConfigRead:")) - return SendIllFormedResponse(packet, - "jTraceConfigRead: Ill formed packet "); - - lldb::user_id_t uid = LLDB_INVALID_UID; - lldb::tid_t threadid = LLDB_INVALID_THREAD_ID; - - auto json_object = StructuredData::ParseJSON(packet.Peek()); - - if (!json_object || - json_object->GetType() != lldb::eStructuredDataTypeDictionary) - return SendIllFormedResponse(packet, - "jTraceConfigRead: Ill formed packet "); - - auto json_dict = json_object->GetAsDictionary(); - - if (!json_dict->GetValueForKeyAsInteger("traceid", uid)) - return SendIllFormedResponse(packet, - "jTraceConfigRead: Ill formed packet "); - - json_dict->GetValueForKeyAsInteger("threadid", threadid); - - TraceOptions options; - StreamGDBRemote response; - - options.setThreadID(threadid); - Status error = m_debugged_process_up->GetTraceConfig(uid, options); - - if (error.Fail()) - return SendErrorResponse(error); - - StreamGDBRemote escaped_response; - StructuredData::Dictionary json_packet; - - json_packet.AddIntegerItem("type", options.getType()); - json_packet.AddIntegerItem("buffersize", options.getTraceBufferSize()); - json_packet.AddIntegerItem("metabuffersize", options.getMetaDataBufferSize()); + return SendErrorResponse(Status("Process not running.")); - StructuredData::DictionarySP custom_params = options.getTraceParams(); - if (custom_params) - json_packet.AddItem("params", custom_params); + packet.ConsumeFront("jLLDBTraceGetState:"); + Expected request = + json::parse(packet.Peek(), "TraceGetStateRequest"); + if (!request) + return SendErrorResponse(request.takeError()); - StreamString json_string; - json_packet.Dump(json_string, false); - escaped_response.PutEscapedBytes(json_string.GetData(), - json_string.GetSize()); - return SendPacketNoLock(escaped_response.GetString()); + return SendJSONResponse(m_debugged_process_up->TraceGetState(request->type)); } GDBRemoteCommunication::PacketResult -GDBRemoteCommunicationServerLLGS::Handle_jTraceRead( +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(68); - - enum PacketType { MetaData, BufferData }; - PacketType tracetype = MetaData; - - if (packet.ConsumeFront("jTraceBufferRead:")) - tracetype = BufferData; - else if (packet.ConsumeFront("jTraceMetaRead:")) - tracetype = MetaData; - else { - return SendIllFormedResponse(packet, "jTrace: Ill formed packet "); - } - - lldb::user_id_t uid = LLDB_INVALID_UID; - - uint64_t byte_count = std::numeric_limits::max(); - lldb::tid_t tid = LLDB_INVALID_THREAD_ID; - uint64_t offset = std::numeric_limits::max(); - - auto json_object = StructuredData::ParseJSON(packet.Peek()); - - if (!json_object || - json_object->GetType() != lldb::eStructuredDataTypeDictionary) - return SendIllFormedResponse(packet, "jTrace: Ill formed packet "); - - auto json_dict = json_object->GetAsDictionary(); - - if (!json_dict->GetValueForKeyAsInteger("traceid", uid) || - !json_dict->GetValueForKeyAsInteger("offset", offset) || - !json_dict->GetValueForKeyAsInteger("buffersize", byte_count)) - return SendIllFormedResponse(packet, "jTrace: Ill formed packet "); - - json_dict->GetValueForKeyAsInteger("threadid", tid); - - // Allocate the response buffer. - std::unique_ptr buffer (new (std::nothrow) uint8_t[byte_count]); - if (!buffer) - return SendErrorResponse(0x78); - - StreamGDBRemote response; - Status error; - llvm::MutableArrayRef buf(buffer.get(), byte_count); - - if (tracetype == BufferData) - error = m_debugged_process_up->GetData(uid, tid, buf, offset); - else if (tracetype == MetaData) - error = m_debugged_process_up->GetMetaData(uid, tid, buf, offset); - - if (error.Fail()) - return SendErrorResponse(error); + return SendErrorResponse(Status("Process not running.")); - for (auto i : buf) - response.PutHex8(i); + packet.ConsumeFront("jLLDBTraceGetBinaryData:"); + llvm::Expected request = + llvm::json::parse(packet.Peek(), + "TraceGetBinaryDataRequest"); + if (!request) + return SendErrorResponse(Status(request.takeError())); - StreamGDBRemote escaped_response; - escaped_response.PutEscapedBytes(response.GetData(), response.GetSize()); - return SendPacketNoLock(escaped_response.GetString()); + if (Expected> bytes = + m_debugged_process_up->TraceGetBinaryData(*request)) { + StreamGDBRemote response; + response.PutEscapedBytes(bytes->data(), bytes->size()); + return SendPacketNoLock(response.GetString()); + } else + return SendErrorResponse(bytes.takeError()); } GDBRemoteCommunication::PacketResult 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 @@ -165,22 +165,16 @@ Status GetWatchpointSupportInfo(uint32_t &num) override; - lldb::user_id_t StartTrace(const TraceOptions &options, - Status &error) override; + llvm::Expected TraceSupported() override; - Status StopTrace(lldb::user_id_t uid, lldb::tid_t thread_id) override; + llvm::Error TraceStop(const TraceStopRequest &request) override; - Status GetData(lldb::user_id_t uid, lldb::tid_t thread_id, - llvm::MutableArrayRef &buffer, - size_t offset = 0) override; + llvm::Error TraceStart(const llvm::json::Value &request) override; - Status GetMetaData(lldb::user_id_t uid, lldb::tid_t thread_id, - llvm::MutableArrayRef &buffer, - size_t offset = 0) override; + llvm::Expected TraceGetState(llvm::StringRef type) override; - llvm::Expected GetSupportedTraceType() override; - - Status GetTraceConfig(lldb::user_id_t uid, TraceOptions &options) override; + llvm::Expected> + TraceGetBinaryData(const TraceGetBinaryDataRequest &request) 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 @@ -1203,34 +1203,26 @@ return error; } -lldb::user_id_t ProcessGDBRemote::StartTrace(const TraceOptions &options, - Status &error) { - return m_gdb_comm.SendStartTracePacket(options, error); +llvm::Expected ProcessGDBRemote::TraceSupported() { + return m_gdb_comm.SendTraceSupported(); } -Status ProcessGDBRemote::StopTrace(lldb::user_id_t uid, lldb::tid_t thread_id) { - return m_gdb_comm.SendStopTracePacket(uid, thread_id); +llvm::Error ProcessGDBRemote::TraceStop(const TraceStopRequest &request) { + return m_gdb_comm.SendTraceStop(request); } -Status ProcessGDBRemote::GetData(lldb::user_id_t uid, lldb::tid_t thread_id, - llvm::MutableArrayRef &buffer, - size_t offset) { - return m_gdb_comm.SendGetDataPacket(uid, thread_id, buffer, offset); +llvm::Error ProcessGDBRemote::TraceStart(const llvm::json::Value &request) { + return m_gdb_comm.SendTraceStart(request); } -Status ProcessGDBRemote::GetMetaData(lldb::user_id_t uid, lldb::tid_t thread_id, - llvm::MutableArrayRef &buffer, - size_t offset) { - return m_gdb_comm.SendGetMetaDataPacket(uid, thread_id, buffer, offset); +llvm::Expected +ProcessGDBRemote::TraceGetState(llvm::StringRef type) { + return m_gdb_comm.SendTraceGetState(type); } -Status ProcessGDBRemote::GetTraceConfig(lldb::user_id_t uid, - TraceOptions &options) { - return m_gdb_comm.SendGetTraceConfigPacket(uid, options); -} - -llvm::Expected ProcessGDBRemote::GetSupportedTraceType() { - return m_gdb_comm.SendGetSupportedTraceType(); +llvm::Expected> +ProcessGDBRemote::TraceGetBinaryData(const TraceGetBinaryDataRequest &request) { + return m_gdb_comm.SendTraceGetBinaryData(request); } void ProcessGDBRemote::DidExit() { @@ -1906,6 +1898,9 @@ thread_sp->SetStopInfo( StopInfo::CreateStopReasonWithExec(*thread_sp)); handled = true; + } else if (reason == "processor trace") { + thread_sp->SetStopInfo(StopInfo::CreateStopReasonProcessorTrace( + *thread_sp, description.c_str())); } } else if (!signo) { addr_t pc = thread_sp->GetRegisterContext()->GetPC(); 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 @@ -9,21 +9,21 @@ #ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_COMMANDOBJECTTRACESTARTINTELPT_H #define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_COMMANDOBJECTTRACESTARTINTELPT_H -#include "../../../../source/Commands/CommandObjectThreadUtil.h" +#include "../../../../source/Commands/CommandObjectTrace.h" +#include "TraceIntelPT.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandReturnObject.h" namespace lldb_private { namespace trace_intel_pt { -class CommandObjectTraceStartIntelPT : public CommandObjectIterateOverThreads { +class CommandObjectThreadTraceStartIntelPT + : public CommandObjectMultipleThreads { public: class CommandOptions : public Options { public: CommandOptions() : Options() { OptionParsingStarting(nullptr); } - ~CommandOptions() override = default; - Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, ExecutionContext *execution_context) override; @@ -31,31 +31,72 @@ llvm::ArrayRef GetDefinitions() override; - size_t m_size_in_kb; - uint32_t m_custom_config; + size_t m_thread_buffer_size; }; - CommandObjectTraceStartIntelPT(CommandInterpreter &interpreter) - : CommandObjectIterateOverThreads( + CommandObjectThreadTraceStartIntelPT(TraceIntelPT &trace, + CommandInterpreter &interpreter) + : CommandObjectMultipleThreads( 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 | lldb::eCommandProcessMustBeLaunched | lldb::eCommandProcessMustBePaused), - m_options() {} + m_trace(trace), m_options() {} + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecuteOnThreads(Args &command, CommandReturnObject &result, + const std::vector &tids) override; + + TraceIntelPT &m_trace; + CommandOptions m_options; +}; + +class CommandObjectProcessTraceStartIntelPT : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } - ~CommandObjectTraceStartIntelPT() override = default; + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override; + + void OptionParsingStarting(ExecutionContext *execution_context) override; + + llvm::ArrayRef GetDefinitions() override; + + size_t m_thread_buffer_size; + size_t m_process_buffer_size_limit; + }; + + CommandObjectProcessTraceStartIntelPT(TraceIntelPT &trace, + CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "process trace start", + "Start tracing this process with intel-pt, including future " + "threads. " + "This is implemented by tracing each thread independently. " + "Threads traced with the \"thread trace start\" command are left " + "unaffected ant not retraced.", + "process trace start []", + lldb::eCommandRequiresProcess | lldb::eCommandTryTargetAPILock | + lldb::eCommandProcessMustBeLaunched | + lldb::eCommandProcessMustBePaused), + m_trace(trace), m_options() {} Options *GetOptions() override { return &m_options; } protected: - bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override; + bool DoExecute(Args &command, CommandReturnObject &result) override; + TraceIntelPT &m_trace; 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; @@ -16,10 +18,12 @@ using namespace lldb_private::trace_intel_pt; using namespace llvm; +// CommandObjectThreadTraceStartIntelPT + #define LLDB_OPTIONS_thread_trace_start_intel_pt #include "TraceIntelPTCommandOptions.inc" -Status CommandObjectTraceStartIntelPT::CommandOptions::SetOptionValue( +Status CommandObjectThreadTraceStartIntelPT::CommandOptions::SetOptionValue( uint32_t option_idx, llvm::StringRef option_arg, ExecutionContext *execution_context) { Status error; @@ -27,23 +31,73 @@ switch (short_option) { case 's': { - int32_t size_in_kb; - if (option_arg.empty() || option_arg.getAsInteger(0, size_in_kb) || - size_in_kb < 0) + int64_t thread_buffer_size; + if (option_arg.empty() || option_arg.getAsInteger(0, thread_buffer_size) || + thread_buffer_size < 0) error.SetErrorStringWithFormat("invalid integer value for option '%s'", option_arg.str().c_str()); else - m_size_in_kb = size_in_kb; + m_thread_buffer_size = thread_buffer_size; break; } - case 'c': { - int32_t custom_config; - if (option_arg.empty() || option_arg.getAsInteger(0, custom_config) || - custom_config < 0) + default: + llvm_unreachable("Unimplemented option"); + } + return error; +} + +void CommandObjectThreadTraceStartIntelPT::CommandOptions:: + OptionParsingStarting(ExecutionContext *execution_context) { + m_thread_buffer_size = 4 * 1024; // 4KB +} + +llvm::ArrayRef +CommandObjectThreadTraceStartIntelPT::CommandOptions::GetDefinitions() { + return llvm::makeArrayRef(g_thread_trace_start_intel_pt_options); +} + +bool CommandObjectThreadTraceStartIntelPT::DoExecuteOnThreads( + Args &command, CommandReturnObject &result, + const std::vector &tids) { + if (Error err = m_trace.Start(tids, m_options.m_thread_buffer_size)) + result.SetError(toString(std::move(err))); + else + result.SetStatus(eReturnStatusSuccessFinishResult); + + return result.Succeeded(); +} + +/// CommandObjectProcessTraceStartIntelPT + +#define LLDB_OPTIONS_process_trace_start_intel_pt +#include "TraceIntelPTCommandOptions.inc" + +Status CommandObjectProcessTraceStartIntelPT::CommandOptions::SetOptionValue( + uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 's': { + int64_t thread_buffer_size; + if (option_arg.empty() || option_arg.getAsInteger(0, thread_buffer_size) || + thread_buffer_size < 0) error.SetErrorStringWithFormat("invalid integer value for option '%s'", option_arg.str().c_str()); else - m_custom_config = custom_config; + m_thread_buffer_size = thread_buffer_size; + break; + } + case 'l': { + int64_t process_buffer_size_limit; + if (option_arg.empty() || + option_arg.getAsInteger(0, process_buffer_size_limit) || + process_buffer_size_limit < 0) + error.SetErrorStringWithFormat("invalid integer value for option '%s'", + option_arg.str().c_str()); + else + m_process_buffer_size_limit = process_buffer_size_limit; break; } default: @@ -52,22 +106,24 @@ return error; } -void CommandObjectTraceStartIntelPT::CommandOptions::OptionParsingStarting( - ExecutionContext *execution_context) { - m_size_in_kb = 4; - m_custom_config = 0; +void CommandObjectProcessTraceStartIntelPT::CommandOptions:: + OptionParsingStarting(ExecutionContext *execution_context) { + m_thread_buffer_size = 4 * 1024; // 4KB + m_process_buffer_size_limit = 5 * 1024 * 1024; // 500MB } llvm::ArrayRef -CommandObjectTraceStartIntelPT::CommandOptions::GetDefinitions() { - return llvm::makeArrayRef(g_thread_trace_start_intel_pt_options); +CommandObjectProcessTraceStartIntelPT::CommandOptions::GetDefinitions() { + return llvm::makeArrayRef(g_process_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); +bool CommandObjectProcessTraceStartIntelPT::DoExecute( + Args &command, CommandReturnObject &result) { + if (Error err = m_trace.Start(m_options.m_thread_buffer_size, + m_options.m_process_buffer_size_limit)) + result.SetError(toString(std::move(err))); + else + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); } 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 @@ -15,6 +15,7 @@ #include "llvm/Support/Error.h" #include "lldb/Target/Trace.h" +#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" #include "intel-pt.h" @@ -111,6 +112,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 @@ -12,40 +12,75 @@ #include "intel-pt.h" #include "DecodedThread.h" +#include "forward-declarations.h" #include "lldb/Target/Process.h" #include "lldb/Utility/FileSpec.h" 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 caches the result. +class 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. - 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() {} + virtual ~ThreadDecoder() = default; + + ThreadDecoder() = default; - /// Decode the thread and store the result internally. + /// Decode the thread and store the result internally, to avoid + /// recomputations. /// /// \return /// A \a DecodedThread instance. const DecodedThread &Decode(); -private: - ThreadTraceDecoder(const ThreadTraceDecoder &other) = delete; - ThreadTraceDecoder &operator=(const ThreadTraceDecoder &other) = delete; + ThreadDecoder(const ThreadDecoder &other) = delete; + ThreadDecoder &operator=(const ThreadDecoder &other) = delete; + +protected: + /// Decode the thread. + /// + /// \return + /// A \a DecodedThread instance. + virtual DecodedThread DoDecode() = 0; - std::shared_ptr m_trace_thread; - pt_cpu m_pt_cpu; llvm::Optional m_decoded_thread; }; +/// Decoder implementation for \a lldb_private::ThreadPostMortemTrace, which are +/// non-live processes that come trace session files. +class PostMortemThreadDecoder : public ThreadDecoder { +public: + /// \param[in] trace_thread + /// The thread whose trace file will be decoded. + /// + /// \param[in] trace + /// The main Trace object who owns this decoder and its data. + PostMortemThreadDecoder(const lldb::ThreadPostMortemTraceSP &trace_thread, + TraceIntelPT &trace); + +private: + DecodedThread DoDecode() override; + + lldb::ThreadPostMortemTraceSP m_trace_thread; + TraceIntelPT &m_trace; +}; + +class LiveThreadDecoder : public ThreadDecoder { +public: + /// \param[in] thread + /// The thread whose traces will be decoded. + /// + /// \param[in] trace + /// The main Trace object who owns this decoder and its data. + LiveThreadDecoder(Thread &thread, TraceIntelPT &trace); + +private: + DecodedThread DoDecode() override; + + lldb::ThreadSP m_thread_sp; + TraceIntelPT &m_trace; +}; + } // namespace trace_intel_pt } // namespace lldb_private 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 @@ -1,5 +1,4 @@ -//===-- IntelPTDecoder.cpp --------------------------------------*- C++ -*-===// -// +//===-- IntelPTDecoder.cpp --======----------------------------------------===// // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception @@ -10,10 +9,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/Target/ThreadPostMortemTrace.h" +#include "lldb/Utility/StringExtractor.h" using namespace lldb; using namespace lldb_private; @@ -85,7 +86,7 @@ return errcode; } return 0; -}; +} /// Decode all the instructions from a configured decoder. /// The decoding flow is based on @@ -158,39 +159,26 @@ return bytes_read; } -static std::vector makeInstructionListFromError(Error err) { - std::vector instructions; - instructions.emplace_back(std::move(err)); - return instructions; -} - -static std::vector -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; +static Expected> +DecodeInMemoryTrace(Process &process, TraceIntelPT &trace_intel_pt, + MutableArrayRef buffer) { + Expected cpu_info = trace_intel_pt.GetCPUInfo(); + if (!cpu_info) + return cpu_info.takeError(); pt_config config; pt_config_init(&config); - config.cpu = pt_cpu; + config.cpu = *cpu_info; 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 +192,62 @@ 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> +DecodeTraceFile(Process &process, TraceIntelPT &trace_intel_pt, + 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 DecodeInMemoryTrace(process, trace_intel_pt, trace_data); +} + +static Expected> +DecodeLiveThread(Thread &thread, TraceIntelPT &trace) { + Expected> buffer = + trace.GetLiveThreadBuffer(thread.GetID()); + if (!buffer) + return buffer.takeError(); + if (Expected cpu_info = trace.GetCPUInfo()) + return DecodeInMemoryTrace(*thread.GetProcess(), trace, + MutableArrayRef(*buffer)); + else + return cpu_info.takeError(); +} + +const DecodedThread &ThreadDecoder::Decode() { + if (!m_decoded_thread.hasValue()) + m_decoded_thread = DoDecode(); return *m_decoded_thread; } + +PostMortemThreadDecoder::PostMortemThreadDecoder( + const lldb::ThreadPostMortemTraceSP &trace_thread, TraceIntelPT &trace) + : m_trace_thread(trace_thread), m_trace(trace) {} + +DecodedThread PostMortemThreadDecoder::DoDecode() { + if (Expected> instructions = + DecodeTraceFile(*m_trace_thread->GetProcess(), m_trace, + m_trace_thread->GetTraceFile())) + return DecodedThread(std::move(*instructions)); + else + return DecodedThread(instructions.takeError()); +} + +LiveThreadDecoder::LiveThreadDecoder(Thread &thread, TraceIntelPT &trace) + : m_thread_sp(thread.shared_from_this()), m_trace(trace) {} + +DecodedThread LiveThreadDecoder::DoDecode() { + if (Expected> instructions = + DecodeLiveThread(*m_thread_sp, m_trace)) + return DecodedThread(std::move(*instructions)); + else + return DecodedThread(instructions.takeError()); +} 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,33 +45,93 @@ /// \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(); uint32_t GetPluginVersion() override; /// \} + lldb::CommandObjectSP + GetProcessTraceStartCommand(CommandInterpreter &interpreter) override; + + lldb::CommandObjectSP + GetThreadTraceStartCommand(CommandInterpreter &interpreter) override; + 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; + + void DoRefreshLiveProcessState( + llvm::Expected state) override; + + bool IsTraced(const Thread &thread) override; + + /// Start tracing a live process. + /// + /// \param[in] thread_buffer_size + /// Trace size per thread in bytes. + /// + /// \param[in] total_buffer_size_limit + /// Maximum total trace size per process in bytes. This limit applies to + /// the sum of the sizes of all thread traces of this process, excluding + /// the threads traced explicitly. + /// + /// Whenever a thread is attempted to be traced due to this operation and + /// the limit would be reached, the process is stopped with a "tracing" + /// reason, so that the user can retrace the process if needed. + /// + /// \return + /// \a llvm::Error::success if the operation was successful, or + /// \a llvm::Error otherwise. + llvm::Error Start(size_t thread_buffer_size, size_t total_buffer_size_limit); + + /// Start tracing a live threads. + /// + /// \param[in] tids + /// Threads to trace. + /// + /// \param[in] thread_buffer_size + /// Trace size per thread in bytes. + /// + /// \return + /// \a llvm::Error::success if the operation was successful, or + /// \a llvm::Error otherwise. + llvm::Error Start(const std::vector &tids, + size_t thread_buffer_size); + + /// Get the thread buffer content for a live thread + llvm::Expected> GetLiveThreadBuffer(lldb::tid_t tid); - size_t GetCursorPosition(const Thread &thread) override; + llvm::Expected GetCPUInfo(); private: friend class TraceIntelPTSessionFileParser; + llvm::Expected GetCPUInfoForLiveProcess(); + /// \param[in] trace_threads /// ThreadTrace instances, which are not live-processes and whose trace /// files are fixed. - TraceIntelPT(const pt_cpu &pt_cpu, - const std::vector> &traced_threads); + TraceIntelPT( + const pt_cpu &cpu_info, + const std::vector &traced_threads); + + /// Constructor for live processes + TraceIntelPT(Process &live_process) + : Trace(live_process), 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 +143,15 @@ /// \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); - - pt_cpu m_pt_cpu; - std::map, ThreadTraceDecoder> - m_trace_threads; + const DecodedThread *Decode(Thread &thread); + + /// It is provided by either a session file or a live process' "cpuInfo" + /// binary data. + llvm::Optional m_cpu_info; + std::map> m_thread_decoders; + /// 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 @@ -13,7 +13,7 @@ #include "lldb/Core/PluginManager.h" #include "lldb/Target/Process.h" #include "lldb/Target/Target.h" -#include "lldb/Target/ThreadTrace.h" +#include "lldb/Target/ThreadPostMortemTrace.h" using namespace lldb; using namespace lldb_private; @@ -22,18 +22,27 @@ LLDB_PLUGIN_DEFINE(TraceIntelPT) -CommandObjectSP GetStartCommand(CommandInterpreter &interpreter) { - return CommandObjectSP(new CommandObjectTraceStartIntelPT(interpreter)); +lldb::CommandObjectSP +TraceIntelPT::GetProcessTraceStartCommand(CommandInterpreter &interpreter) { + return CommandObjectSP( + new CommandObjectProcessTraceStartIntelPT(*this, interpreter)); +} + +lldb::CommandObjectSP +TraceIntelPT::GetThreadTraceStartCommand(CommandInterpreter &interpreter) { + return CommandObjectSP( + new CommandObjectThreadTraceStartIntelPT(*this, interpreter)); } void TraceIntelPT::Initialize() { - PluginManager::RegisterPlugin( - GetPluginNameStatic(), "Intel Processor Trace", CreateInstance, - TraceIntelPTSessionFileParser::GetSchema(), GetStartCommand); + PluginManager::RegisterPlugin(GetPluginNameStatic(), "Intel Processor Trace", + CreateInstanceForSessionFile, + CreateInstanceForLiveProcess, + TraceIntelPTSessionFileParser::GetSchema()); } void TraceIntelPT::Terminate() { - PluginManager::UnregisterPlugin(CreateInstance); + PluginManager::UnregisterPlugin(CreateInstanceForSessionFile); } ConstString TraceIntelPT::GetPluginNameStatic() { @@ -55,34 +64,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)); + 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)); -} - -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 pt_cpu &cpu_info, + const std::vector &traced_threads) + : m_cpu_info(cpu_info) { + for (const ThreadPostMortemTraceSP &thread : traced_threads) + m_thread_decoders.emplace( + thread.get(), std::make_unique(thread, *this)); +} + +const DecodedThread *TraceIntelPT::Decode(Thread &thread) { + RefreshLiveProcessState(); + 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 +106,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 +122,117 @@ 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; } + +Expected TraceIntelPT::GetCPUInfoForLiveProcess() { + Expected> cpu_info = GetLiveProcessBinaryData("cpuInfo"); + if (!cpu_info) + return cpu_info.takeError(); + + int64_t cpu_family = -1; + int64_t model = -1; + int64_t stepping = -1; + std::string vendor_id; + + StringRef rest(reinterpret_cast(cpu_info->data()), + cpu_info->size()); + while (!rest.empty()) { + StringRef line; + std::tie(line, rest) = rest.split('\n'); + + SmallVector columns; + line.split(columns, StringRef(":"), -1, false); + + if (columns.size() < 2) + continue; // continue searching + + columns[1] = columns[1].trim(" "); + if (columns[0].contains("cpu family") && + columns[1].getAsInteger(10, cpu_family)) + continue; + + else if (columns[0].contains("model") && columns[1].getAsInteger(10, model)) + continue; + + else if (columns[0].contains("stepping") && + columns[1].getAsInteger(10, stepping)) + continue; + + else if (columns[0].contains("vendor_id")) { + vendor_id = columns[1].str(); + if (!vendor_id.empty()) + continue; + } + + if ((cpu_family != -1) && (model != -1) && (stepping != -1) && + (!vendor_id.empty())) { + return pt_cpu{vendor_id == "GenuineIntel" ? pcv_intel : pcv_unknown, + static_cast(cpu_family), + static_cast(model), + static_cast(stepping)}; + } + } + return createStringError(inconvertibleErrorCode(), + "Failed parsing the target's /proc/cpuinfo file"); +} + +Expected TraceIntelPT::GetCPUInfo() { + if (!m_cpu_info) { + if (llvm::Expected cpu_info = GetCPUInfoForLiveProcess()) + m_cpu_info = *cpu_info; + else + return cpu_info.takeError(); + } + return *m_cpu_info; +} + +void TraceIntelPT::DoRefreshLiveProcessState( + Expected state) { + m_thread_decoders.clear(); + + if (!state) { + m_failed_live_threads_decoder = DecodedThread(state.takeError()); + return; + } + + for (const TraceThreadState &thread_state : state->tracedThreads) { + Thread &thread = + *m_live_process->GetThreadList().FindThreadByID(thread_state.tid); + m_thread_decoders.emplace( + &thread, std::make_unique(thread, *this)); + } +} + +bool TraceIntelPT::IsTraced(const Thread &thread) { + return m_thread_decoders.count(&thread); +} + +Error TraceIntelPT::Start(size_t thread_buffer_size, + size_t total_buffer_size_limit) { + TraceIntelPTStartRequest request; + request.threadBufferSize = thread_buffer_size; + request.processBufferSizeLimit = total_buffer_size_limit; + request.type = GetPluginName().AsCString(); + return Trace::Start(toJSON(request)); +} + +llvm::Error TraceIntelPT::Start(const std::vector &tids, + size_t thread_buffer_size) { + TraceIntelPTStartRequest request; + request.threadBufferSize = thread_buffer_size; + request.type = GetPluginName().AsCString(); + request.tids.emplace(); + for (lldb::tid_t tid : tids) + request.tids->push_back(tid); + return Trace::Start(toJSON(request)); +} + +Expected> +TraceIntelPT::GetLiveThreadBuffer(lldb::tid_t tid) { + return Trace::GetLiveThreadBinaryData(tid, "threadTraceBuffer"); +} 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,25 @@ 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<"Trace size in bytes per thread. It must be a power of 2 greater " + "than or equal to 4096 (2^12). The trace is circular keeping " + "the most recent data. Defaults to 4096 bytes.">; +} + +let Command = "process trace start intel pt" in { + def process_trace_start_intel_pt_thread_size: Option<"thread-size", "s">, + Group<1>, + Arg<"Value">, + Desc<"Trace size in bytes per thread. It must be a power of 2 greater " + "than or equal to 4096 (2^12). The trace is circular keeping " + "the most recent data. Defaults to 4096 bytes.">; + def process_trace_start_intel_pt_process_size_limit: Option<"total-size-limit", "l">, Group<1>, Arg<"Value">, - Desc<"Low level bitmask configuration for the kernel based on the values " - "in `grep -H /sys/bus/event_source/devices/intel_pt/format/*`. " - "See https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/perf-intel-pt.txt" - " for more information. Defaults to 0.">; + Desc<"Maximum total trace size per process in bytes. This limit applies to " + "the sum of the sizes of all thread traces of this process, excluding " + "the ones created with the \"thread trace start\" command. " + "Whenever a thread is attempted to be traced due to this command and " + "the limit would be reached, the process is stopped with a \"tracing\" " + "reason, so that the user can retrace the process if needed.">; } 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,16 @@ class TraceIntelPTSessionFileParser : public TraceSessionFileParser { public: - struct JSONPTCPU { - std::string vendor; + struct JSONTraceIntelPTCPUInfo { int64_t family; int64_t model; int64_t stepping; + std::string vendor; }; struct JSONTraceIntelPTSettings : TraceSessionFileParser::JSONTracePluginSettings { - JSONPTCPU pt_cpu; + JSONTraceIntelPTCPUInfo cpuInfo; }; /// See \a TraceSessionFileParser::TraceSessionFileParser for the description @@ -52,11 +52,11 @@ llvm::Expected Parse(); lldb::TraceSP - CreateTraceIntelPTInstance(const pt_cpu &pt_cpu, + CreateTraceIntelPTInstance(const pt_cpu &cpu_info, std::vector &parsed_processes); private: - pt_cpu ParsePTCPU(const JSONPTCPU &pt_cpu); + static pt_cpu ParsePTCPU(const JSONTraceIntelPTCPUInfo &cpu_info); const llvm::json::Value &m_trace_session_file; }; @@ -67,17 +67,20 @@ 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, Path path); +bool fromJSON(const llvm::json::Value &value, + lldb_private::trace_intel_pt::TraceIntelPTSessionFileParser:: + JSONTraceIntelPTCPUInfo &packet, + llvm::json::Path path); + +llvm::json::Value +toJSON(const lldb_private::trace_intel_pt::TraceIntelPTSessionFileParser:: + JSONTraceIntelPTCPUInfo &packet); + } // namespace json } // namespace llvm 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 @@ -12,7 +12,8 @@ #include "lldb/Target/Process.h" #include "lldb/Target/Target.h" #include "lldb/Target/ThreadList.h" -#include "lldb/Target/ThreadTrace.h" +#include "lldb/Target/ThreadPostMortemTrace.h" +#include "lldb/Utility/TraceOptions.h" using namespace lldb; using namespace lldb_private; @@ -24,7 +25,7 @@ if (schema.empty()) { schema = TraceSessionFileParser::BuildSchema(R"({ "type": "intel-pt", - "pt_cpu": { + "cpuInfo": { "vendor": "intel" | "unknown", "family": integer, "model": integer, @@ -35,21 +36,22 @@ return schema; } -pt_cpu TraceIntelPTSessionFileParser::ParsePTCPU(const JSONPTCPU &pt_cpu) { - return {pt_cpu.vendor.compare("intel") == 0 ? pcv_intel : pcv_unknown, - static_cast(pt_cpu.family), - static_cast(pt_cpu.model), - static_cast(pt_cpu.stepping)}; +pt_cpu TraceIntelPTSessionFileParser::ParsePTCPU( + const JSONTraceIntelPTCPUInfo &cpu_info) { + return {cpu_info.vendor.compare("intel") == 0 ? pcv_intel : pcv_unknown, + static_cast(cpu_info.family), + static_cast(cpu_info.model), + static_cast(cpu_info.stepping)}; } TraceSP TraceIntelPTSessionFileParser::CreateTraceIntelPTInstance( - const pt_cpu &pt_cpu, std::vector &parsed_processes) { - std::vector threads; + const pt_cpu &cpu_info, std::vector &parsed_processes) { + std::vector threads; for (const ParsedProcess &parsed_process : parsed_processes) threads.insert(threads.end(), parsed_process.threads.begin(), parsed_process.threads.end()); - TraceSP trace_instance(new TraceIntelPT(pt_cpu, threads)); + TraceSP trace_instance(new TraceIntelPT(cpu_info, threads)); for (const ParsedProcess &parsed_process : parsed_processes) parsed_process.target_sp->SetTrace(trace_instance); @@ -64,7 +66,7 @@ if (Expected> parsed_processes = ParseCommonSessionFile(session)) - return CreateTraceIntelPTInstance(ParsePTCPU(session.trace.pt_cpu), + return CreateTraceIntelPTInstance(ParsePTCPU(session.trace.cpuInfo), *parsed_processes); else return parsed_processes.takeError(); @@ -73,25 +75,34 @@ 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, Path path) { ObjectMapper o(value, path); - return o && o.map("pt_cpu", plugin_settings.pt_cpu) && + return o && o.map("cpuInfo", plugin_settings.cpuInfo) && fromJSON( value, (TraceSessionFileParser::JSONTracePluginSettings &)plugin_settings, path); } +bool fromJSON(const json::Value &value, + TraceIntelPTSessionFileParser::JSONTraceIntelPTCPUInfo &cpu_info, + Path path) { + ObjectMapper o(value, path); + return o && o.map("vendor", cpu_info.vendor) && + o.map("family", cpu_info.family) && o.map("model", cpu_info.model) && + o.map("stepping", cpu_info.stepping); +} + +Value toJSON( + const TraceIntelPTSessionFileParser::JSONTraceIntelPTCPUInfo &cpu_info) { + return Value(Object{{"family", cpu_info.family}, + {"model", cpu_info.model}, + {"stepping", cpu_info.stepping}, + {"vendor", cpu_info.vendor}}); +} + } // namespace json } // namespace llvm diff --git a/lldb/source/Plugins/Trace/intel-pt/forward-declarations.h b/lldb/source/Plugins/Trace/intel-pt/forward-declarations.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/forward-declarations.h @@ -0,0 +1,20 @@ +//===-- forward-declarations.h ----------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_FORWARD_DECLARATIONS_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_FORWARD_DECLARATIONS_H + +namespace lldb_private { +namespace trace_intel_pt { + +class TraceIntelPT; +class ThreadDecoder; + +} // namespace trace_intel_pt +} // namespace lldb_private +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_FORWARD_DECLARATIONS_H diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -66,7 +66,7 @@ ThreadPlanTracer.cpp ThreadPlanStack.cpp ThreadSpec.cpp - ThreadTrace.cpp + ThreadPostMortemTrace.cpp Trace.cpp TraceSessionFileParser.cpp UnixSignals.cpp diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -802,6 +802,7 @@ case eStopReasonExec: case eStopReasonThreadExiting: case eStopReasonInstrumentation: + case eStopReasonProcessorTrace: if (!other_thread) other_thread = thread; break; @@ -5958,7 +5959,7 @@ return m_dlopen_utility_func_up.get(); } -llvm::Expected Process::GetSupportedTraceType() { +llvm::Expected Process::TraceSupported() { if (!IsLiveDebugSession()) return llvm::createStringError(llvm::inconvertibleErrorCode(), "Can't trace a non-live process."); diff --git a/lldb/source/Target/ProcessTrace.cpp b/lldb/source/Target/ProcessTrace.cpp --- a/lldb/source/Target/ProcessTrace.cpp +++ b/lldb/source/Target/ProcessTrace.cpp @@ -87,8 +87,6 @@ Status ProcessTrace::DoDestroy() { return Status(); } -bool ProcessTrace::IsAlive() { return true; } - size_t ProcessTrace::ReadMemory(addr_t addr, void *buf, size_t size, Status &error) { // Don't allow the caching that lldb_private::Process::ReadMemory does since diff --git a/lldb/source/Target/StopInfo.cpp b/lldb/source/Target/StopInfo.cpp --- a/lldb/source/Target/StopInfo.cpp +++ b/lldb/source/Target/StopInfo.cpp @@ -1044,6 +1044,30 @@ } }; +// StopInfoProcessorTrace + +class StopInfoProcessorTrace : public StopInfo { +public: + StopInfoProcessorTrace(Thread &thread, const char *description) + : StopInfo(thread, LLDB_INVALID_UID) { + if (description) + SetDescription(description); + } + + ~StopInfoProcessorTrace() override = default; + + StopReason GetStopReason() const override { + return eStopReasonProcessorTrace; + } + + const char *GetDescription() override { + if (m_description.empty()) + return "processor trace event"; + else + return m_description.c_str(); + } +}; + // StopInfoThreadPlan class StopInfoThreadPlan : public StopInfo { @@ -1161,6 +1185,11 @@ return StopInfoSP(new StopInfoException(thread, description)); } +StopInfoSP StopInfo::CreateStopReasonProcessorTrace(Thread &thread, + const char *description) { + return StopInfoSP(new StopInfoProcessorTrace(thread, description)); +} + StopInfoSP StopInfo::CreateStopReasonWithExec(Thread &thread) { return StopInfoSP(new StopInfoExec(thread)); } diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -3075,7 +3075,27 @@ void Target::SetTrace(const TraceSP &trace_sp) { m_trace_sp = trace_sp; } -const TraceSP &Target::GetTrace() { return m_trace_sp; } +TraceSP &Target::GetTrace() { return m_trace_sp; } + +llvm::Expected Target::GetTraceOrCreate() { + if (!m_trace_sp && m_process_sp) { + llvm::Expected trace_type = + m_process_sp->TraceSupported(); + if (!trace_type) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), "Tracing is not supported. %s", + llvm::toString(trace_type.takeError()).c_str()); + if (llvm::Expected trace_sp = + Trace::FindPluginForLiveProcess(trace_type->name, *m_process_sp)) + m_trace_sp = *trace_sp; + else + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Couldn't start tracing the process. %s", + llvm::toString(trace_type.takeError()).c_str()); + } + return m_trace_sp; +} Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) { auto state = eStateInvalid; diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -1685,6 +1685,8 @@ return "thread exiting"; case eStopReasonInstrumentation: return "instrumentation break"; + case eStopReasonProcessorTrace: + return "processor trace"; } return "StopReason = " + std::to_string(reason); diff --git a/lldb/source/Target/ThreadTrace.cpp b/lldb/source/Target/ThreadPostMortemTrace.cpp rename from lldb/source/Target/ThreadTrace.cpp rename to lldb/source/Target/ThreadPostMortemTrace.cpp --- a/lldb/source/Target/ThreadTrace.cpp +++ b/lldb/source/Target/ThreadPostMortemTrace.cpp @@ -1,4 +1,4 @@ -//===-- ThreadTrace.cpp ---------------------------------------------------===// +//===-- ThreadPostMortemTrace.cpp -----------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "lldb/Target/ThreadTrace.h" +#include "lldb/Target/ThreadPostMortemTrace.h" #include @@ -17,9 +17,9 @@ using namespace lldb; using namespace lldb_private; -void ThreadTrace::RefreshStateAfterStop() {} +void ThreadPostMortemTrace::RefreshStateAfterStop() {} -RegisterContextSP ThreadTrace::GetRegisterContext() { +RegisterContextSP ThreadPostMortemTrace::GetRegisterContext() { if (!m_reg_context_sp) m_reg_context_sp = CreateRegisterContextForFrame(nullptr); @@ -27,13 +27,15 @@ } RegisterContextSP -ThreadTrace::CreateRegisterContextForFrame(StackFrame *frame) { +ThreadPostMortemTrace::CreateRegisterContextForFrame(StackFrame *frame) { // Eventually this will calculate the register context based on the current // trace position. return std::make_shared( *this, 0, GetProcess()->GetAddressByteSize(), LLDB_INVALID_ADDRESS); } -bool ThreadTrace::CalculateStopInfo() { return false; } +bool ThreadPostMortemTrace::CalculateStopInfo() { return false; } -const FileSpec &ThreadTrace::GetTraceFile() const { return m_trace_file; } +const FileSpec &ThreadPostMortemTrace::GetTraceFile() const { + return m_trace_file; +} 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 @@ -16,6 +16,7 @@ #include "lldb/Target/Process.h" #include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPostMortemTrace.h" #include "lldb/Utility/Stream.h" using namespace lldb; @@ -57,9 +58,10 @@ plugin_name.data()); } -Expected Trace::FindPlugin(Debugger &debugger, - const json::Value &trace_session_file, - StringRef session_file_dir) { +Expected +Trace::FindPluginForPostMortemProcess(Debugger &debugger, + const json::Value &trace_session_file, + StringRef session_file_dir) { JSONSimpleTraceSession json_session; json::Path::Root root("traceSession"); if (!json::fromJSON(trace_session_file, json_session, root)) @@ -72,6 +74,20 @@ return createInvalidPlugInError(json_session.trace.type); } +Expected +Trace::FindPluginForLiveProcess(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 +220,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); @@ -266,3 +288,121 @@ return index < end_position; }); } + +Error Trace::Start(const llvm::json::Value &request) { + if (!m_live_process) + return createStringError(inconvertibleErrorCode(), + "Tracing requires a live process."); + return m_live_process->TraceStart(request); +} + +Error Trace::StopProcess() { + if (!m_live_process) + return createStringError(inconvertibleErrorCode(), + "Tracing requires a live process."); + return m_live_process->TraceStop( + TraceStopRequest(GetPluginName().AsCString())); +} + +Error Trace::StopThreads(const std::vector &tids) { + if (!m_live_process) + return createStringError(inconvertibleErrorCode(), + "Tracing requires a live process."); + return m_live_process->TraceStop( + TraceStopRequest(GetPluginName().AsCString(), tids)); +} + +Expected Trace::GetLiveProcessState() { + if (!m_live_process) + return createStringError(inconvertibleErrorCode(), + "Tracing requires a live process."); + return m_live_process->TraceGetState(GetPluginName().AsCString()); +} + +Optional Trace::GetLiveThreadBinaryDataSize(lldb::tid_t tid, + llvm::StringRef kind) { + auto it = m_live_thread_data.find(tid); + if (it == m_live_thread_data.end()) + return None; + std::unordered_map &single_thread_data = it->second; + auto single_thread_data_it = single_thread_data.find(kind.str()); + if (single_thread_data_it == single_thread_data.end()) + return None; + return single_thread_data_it->second; +} + +Optional Trace::GetLiveProcessBinaryDataSize(llvm::StringRef kind) { + auto data_it = m_live_process_data.find(kind.str()); + if (data_it == m_live_process_data.end()) + return None; + return data_it->second; +} + +Expected> +Trace::GetLiveThreadBinaryData(lldb::tid_t tid, llvm::StringRef kind) { + if (!m_live_process) + return createStringError(inconvertibleErrorCode(), + "Tracing requires a live process."); + llvm::Optional size = GetLiveThreadBinaryDataSize(tid, kind); + if (!size) + return createStringError( + inconvertibleErrorCode(), + "Tracing data \"%s\" is not available for thread %" PRIu64 ".", + kind.data(), tid); + + TraceGetBinaryDataRequest request{GetPluginName().AsCString(), kind.str(), + static_cast(tid), 0, + static_cast(*size)}; + return m_live_process->TraceGetBinaryData(request); +} + +Expected> +Trace::GetLiveProcessBinaryData(llvm::StringRef kind) { + if (!m_live_process) + return createStringError(inconvertibleErrorCode(), + "Tracing requires a live process."); + llvm::Optional size = GetLiveProcessBinaryDataSize(kind); + if (!size) + return createStringError( + inconvertibleErrorCode(), + "Tracing data \"%s\" is not available for the process.", kind.data()); + + TraceGetBinaryDataRequest request{GetPluginName().AsCString(), kind.str(), + None, 0, static_cast(*size)}; + return m_live_process->TraceGetBinaryData(request); +} + +void Trace::RefreshLiveProcessState() { + if (!m_live_process) + return; + + uint32_t new_stop_id = m_live_process->GetStopID(); + if (new_stop_id == m_stop_id) + return; + + m_stop_id = new_stop_id; + m_live_thread_data.clear(); + + Expected json_string = GetLiveProcessState(); + if (!json_string) { + DoRefreshLiveProcessState(json_string.takeError()); + return; + } + Expected live_process_state = + json::parse(*json_string, "TraceGetStateResponse"); + if (!live_process_state) { + DoRefreshLiveProcessState(live_process_state.takeError()); + return; + } + + for (const TraceThreadState &thread_state : + live_process_state->tracedThreads) { + for (const TraceBinaryData &item : thread_state.binaryData) + m_live_thread_data[thread_state.tid][item.kind] = item.size; + } + + for (const TraceBinaryData &item : live_process_state->processBinaryData) + m_live_process_data[item.kind] = item.size; + + DoRefreshLiveProcessState(std::move(live_process_state)); +} diff --git a/lldb/source/Target/TraceSessionFileParser.cpp b/lldb/source/Target/TraceSessionFileParser.cpp --- a/lldb/source/Target/TraceSessionFileParser.cpp +++ b/lldb/source/Target/TraceSessionFileParser.cpp @@ -14,7 +14,7 @@ #include "lldb/Core/Module.h" #include "lldb/Target/Process.h" #include "lldb/Target/Target.h" -#include "lldb/Target/ThreadTrace.h" +#include "lldb/Target/ThreadPostMortemTrace.h" using namespace lldb; using namespace lldb_private; @@ -96,15 +96,16 @@ return schema_builder.str(); } -ThreadTraceSP TraceSessionFileParser::ParseThread(ProcessSP &process_sp, - const JSONThread &thread) { +ThreadPostMortemTraceSP +TraceSessionFileParser::ParseThread(ProcessSP &process_sp, + const JSONThread &thread) { lldb::tid_t tid = static_cast(thread.tid); FileSpec trace_file(thread.trace_file); NormalizePath(trace_file); - ThreadTraceSP thread_sp = - std::make_shared(*process_sp, tid, trace_file); + ThreadPostMortemTraceSP thread_sp = + std::make_shared(*process_sp, tid, trace_file); process_sp->GetThreadList().AddThread(thread_sp); return thread_sp; } diff --git a/lldb/source/Utility/CMakeLists.txt b/lldb/source/Utility/CMakeLists.txt --- a/lldb/source/Utility/CMakeLists.txt +++ b/lldb/source/Utility/CMakeLists.txt @@ -65,7 +65,8 @@ StructuredData.cpp TildeExpressionResolver.cpp Timer.cpp - TraceOptions.cpp + TraceGDBRemotePackets.cpp + TraceIntelPTGDBRemotePackets.cpp UnimplementedError.cpp UUID.cpp UriParser.cpp 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 @@ -303,18 +303,17 @@ return eServerPacketType_jSignalsInfo; if (PACKET_MATCHES("jThreadsInfo")) return eServerPacketType_jThreadsInfo; - if (PACKET_STARTS_WITH("jTraceBufferRead:")) - return eServerPacketType_jTraceBufferRead; - if (PACKET_STARTS_WITH("jTraceConfigRead:")) - return eServerPacketType_jTraceConfigRead; - if (PACKET_STARTS_WITH("jTraceMetaRead:")) - return eServerPacketType_jTraceMetaRead; - if (PACKET_STARTS_WITH("jTraceStart:")) - return eServerPacketType_jTraceStart; - if (PACKET_STARTS_WITH("jTraceStop:")) - return eServerPacketType_jTraceStop; - if (PACKET_MATCHES("jLLDBTraceSupportedType")) - return eServerPacketType_jLLDBTraceSupportedType; + + if (PACKET_MATCHES("jLLDBTraceSupported")) + return eServerPacketType_jLLDBTraceSupported; + 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/TraceGDBRemotePackets.cpp b/lldb/source/Utility/TraceGDBRemotePackets.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Utility/TraceGDBRemotePackets.cpp @@ -0,0 +1,130 @@ +//===-- TraceGDBRemotePackets.cpp -------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Utility/TraceGDBRemotePackets.h" + +using namespace llvm; +using namespace llvm::json; + +namespace lldb_private { +/// jLLDBTraceSupported +/// \{ +bool fromJSON(const json::Value &value, TraceSupportedResponse &packet, + Path path) { + ObjectMapper o(value, path); + return o && o.map("description", packet.description) && + o.map("name", packet.name); +} + +json::Value toJSON(const TraceSupportedResponse &packet) { + return json::Value( + Object{{"description", packet.description}, {"name", packet.name}}); +} +/// \} + +/// jLLDBTraceStart +/// \{ +bool TraceStartRequest::IsProcessTracing() const { return !(bool)tids; } + +bool fromJSON(const json::Value &value, TraceStartRequest &packet, Path path) { + ObjectMapper o(value, path); + return o && o.map("type", packet.type) && o.map("tids", packet.tids); +} + +json::Value toJSON(const TraceStartRequest &packet) { + return json::Value(Object{{"tids", packet.tids}, {"type", packet.type}}); +} +/// \} + +/// jLLDBTraceStop +/// \{ +TraceStopRequest::TraceStopRequest(llvm::StringRef type, + const std::vector &tids_) + : type(type) { + tids.emplace(); + for (lldb::tid_t tid : tids_) + tids->push_back(static_cast(tid)); +} + +bool TraceStopRequest::IsProcessTracing() const { return !(bool)tids; } + +bool fromJSON(const json::Value &value, TraceStopRequest &packet, Path path) { + ObjectMapper o(value, path); + return o && o.map("type", packet.type) && o.map("tids", packet.tids); +} + +json::Value toJSON(const TraceStopRequest &packet) { + return json::Value(Object{{"type", packet.type}, {"tids", packet.tids}}); +} +/// \} + +/// jLLDBTraceGetState +/// \{ +bool fromJSON(const json::Value &value, TraceGetStateRequest &packet, + Path path) { + ObjectMapper o(value, path); + return o && o.map("type", packet.type); +} + +json::Value toJSON(const TraceGetStateRequest &packet) { + return json::Value(Object{{"type", packet.type}}); +} + +bool fromJSON(const json::Value &value, TraceBinaryData &packet, Path path) { + ObjectMapper o(value, path); + return o && o.map("kind", packet.kind) && o.map("size", packet.size); +} + +json::Value toJSON(const TraceBinaryData &packet) { + return json::Value(Object{{"kind", packet.kind}, {"size", packet.size}}); +} + +bool fromJSON(const json::Value &value, TraceThreadState &packet, Path path) { + ObjectMapper o(value, path); + return o && o.map("tid", packet.tid) && + o.map("binaryData", packet.binaryData); +} + +json::Value toJSON(const TraceThreadState &packet) { + return json::Value( + Object{{"tid", packet.tid}, {"binaryData", packet.binaryData}}); +} + +bool fromJSON(const json::Value &value, TraceGetStateResponse &packet, + Path path) { + ObjectMapper o(value, path); + return o && o.map("tracedThreads", packet.tracedThreads) && + o.map("processBinaryData", packet.processBinaryData); +} + +json::Value toJSON(const TraceGetStateResponse &packet) { + return json::Value(Object{{"tracedThreads", packet.tracedThreads}, + {"processBinaryData", packet.processBinaryData}}); +} +/// \} + +/// jLLDBTraceGetBinaryData +/// \{ +json::Value toJSON(const TraceGetBinaryDataRequest &packet) { + return json::Value(Object{{"type", packet.type}, + {"kind", packet.kind}, + {"offset", packet.offset}, + {"tid", packet.tid}, + {"size", packet.size}}); +} + +bool fromJSON(const json::Value &value, TraceGetBinaryDataRequest &packet, + Path path) { + ObjectMapper o(value, path); + return o && o.map("type", packet.type) && o.map("kind", packet.kind) && + o.map("tid", packet.tid) && o.map("offset", packet.offset) && + o.map("size", packet.size); +} +/// \} + +} // namespace lldb_private diff --git a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp @@ -0,0 +1,42 @@ +//===-- TraceIntelPTGDBRemotePackets.cpp ------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" + +using namespace llvm; +using namespace llvm::json; + +namespace lldb_private { + +bool fromJSON(const json::Value &value, TraceIntelPTStartRequest &packet, + Path path) { + ObjectMapper o(value, path); + if (!o || !fromJSON(value, (TraceStartRequest &)packet, path) || + !o.map("threadBufferSize", packet.threadBufferSize) || + !o.map("processBufferSizeLimit", packet.processBufferSizeLimit)) + return false; + if (packet.tids && packet.processBufferSizeLimit) { + path.report("processBufferSizeLimit must be provided"); + return false; + } + if (!packet.tids && !packet.processBufferSizeLimit) { + path.report("processBufferSizeLimit must not be provided"); + return false; + } + return true; +} + +json::Value toJSON(const TraceIntelPTStartRequest &packet) { + json::Value base = toJSON((const TraceStartRequest &)packet); + base.getAsObject()->try_emplace("threadBufferSize", packet.threadBufferSize); + base.getAsObject()->try_emplace("processBufferSizeLimit", + packet.processBufferSizeLimit); + return base; +} + +} // namespace lldb_private diff --git a/lldb/source/Utility/TraceOptions.cpp b/lldb/source/Utility/TraceOptions.cpp deleted file mode 100644 --- a/lldb/source/Utility/TraceOptions.cpp +++ /dev/null @@ -1,25 +0,0 @@ -//===-- TraceOptions.cpp ----------------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "lldb/Utility/TraceOptions.h" - -using namespace lldb_private; - -namespace llvm { -namespace json { - -bool fromJSON(const Value &value, TraceTypeInfo &info, Path path) { - ObjectMapper o(value, path); - if (!o) - return false; - o.map("description", info.description); - return o.map("name", info.name); -} - -} // namespace json -} // namespace llvm diff --git a/lldb/test/API/commands/trace/TestTraceLoad.py b/lldb/test/API/commands/trace/TestTraceLoad.py --- a/lldb/test/API/commands/trace/TestTraceLoad.py +++ b/lldb/test/API/commands/trace/TestTraceLoad.py @@ -59,7 +59,7 @@ { "trace": { "type": "intel-pt", - "pt_cpu": { + "cpuInfo": { "vendor": "intel" | "unknown", "family": integer, "model": integer, @@ -73,13 +73,13 @@ # Now we test a missing field in the intel-pt settings self.expect("trace load -v " + os.path.join(src_dir, "intelpt-trace", "trace_bad4.json"), error=True, - substrs=['''error: missing value at traceSession.trace.pt_cpu.family + substrs=['''error: missing value at traceSession.trace.cpuInfo.family Context: { "processes": [], "trace": { - "pt_cpu": /* error: missing value */ { + "cpuInfo": /* error: missing value */ { "model": 79, "stepping": 1, "vendor": "intel" diff --git a/lldb/test/API/commands/trace/TestTraceSchema.py b/lldb/test/API/commands/trace/TestTraceSchema.py --- a/lldb/test/API/commands/trace/TestTraceSchema.py +++ b/lldb/test/API/commands/trace/TestTraceSchema.py @@ -25,7 +25,7 @@ self.expect("trace schema all", substrs=['''{ "trace": { "type": "intel-pt", - "pt_cpu": { + "cpuInfo": { "vendor": "intel" | "unknown", "family": integer, "model": integer, 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 @@ -21,14 +23,30 @@ # it should fail for processes from json session files self.expect("trace load -v " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json")) self.expect("thread trace start", error=True, - substrs=["error: Tracing is not supported. Can't trace a non-live process"]) + substrs=["error: Process must be alive"]) # 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) + + def testStartWithNoProcess(self): + self.expect("thread trace start", error=True, + substrs=["error: Process not available."]) + + + def testStartSessionWithWrongSize(self): + self.expect("file " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")) + self.expect("b main") + self.expect("r") + self.expect("thread trace start -s 2000", error=True, + substrs=["The trace buffer size must be a power of 2", "It was 2000"]) + self.expect("thread trace start -s 5000", error=True, + substrs=["The trace buffer size must be a power of 2", "It was 5000"]) + self.expect("thread trace start -s 0", error=True, + substrs=["The trace buffer size must be a power of 2", "It was 0"]) + self.expect("thread trace start -s 1048576") + @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) def testStartStopLiveThreads(self): @@ -49,20 +67,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 4096") + + # We fail if we try to trace again + self.expect("thread trace start", error=True, + substrs=["error: Thread ", "already traced"]) + + # 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 currently 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/intelpt-trace-multi-file/multi-file-no-ld.json b/lldb/test/API/commands/trace/intelpt-trace-multi-file/multi-file-no-ld.json --- a/lldb/test/API/commands/trace/intelpt-trace-multi-file/multi-file-no-ld.json +++ b/lldb/test/API/commands/trace/intelpt-trace-multi-file/multi-file-no-ld.json @@ -1,7 +1,7 @@ { "trace": { "type": "intel-pt", - "pt_cpu": { + "cpuInfo": { "vendor": "intel", "family": 6, "model": 79, diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace.json b/lldb/test/API/commands/trace/intelpt-trace/trace.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace.json @@ -1,7 +1,7 @@ { "trace": { "type": "intel-pt", - "pt_cpu": { + "cpuInfo": { "vendor": "intel", "family": 6, "model": 79, diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_2threads.json b/lldb/test/API/commands/trace/intelpt-trace/trace_2threads.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_2threads.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_2threads.json @@ -1,7 +1,7 @@ { "trace": { "type": "intel-pt", - "pt_cpu": { + "cpuInfo": { "vendor": "intel", "family": 6, "model": 79, diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_bad.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad.json @@ -1,7 +1,7 @@ { "trace": { "type": "intel-pt", - "pt_cpu": { + "cpuInfo": { "vendor": "intel", "family": 6, "model": 79, diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json @@ -1,7 +1,7 @@ { "trace": { "type": "intel-pt", - "pt_cpu": { + "cpuInfo": { "vendor": "intel", "family": 6, "model": 79, diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad3.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad3.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_bad3.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad3.json @@ -1,7 +1,7 @@ { "trace": { "type": "intel-pt", - "pt_cpu": { + "cpuInfo": { "vendor": "intel", "family": 6, "model": 79, diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad4.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad4.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_bad4.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad4.json @@ -1,7 +1,7 @@ { "trace": { "type": "intel-pt", - "pt_cpu": { + "cpuInfo": { "vendor": "intel", "model": 79, "stepping": 1 diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad5.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad5.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_bad5.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad5.json @@ -1,7 +1,7 @@ { "trace": { "type": "intel-pt", - "pt_cpu": { + "cpuInfo": { "vendor": "intel", "family": 6, "model": 79, diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json @@ -1,7 +1,7 @@ { "trace": { "type": "intel-pt", - "pt_cpu": { + "cpuInfo": { "vendor": "intel", "family": 6, "model": 79, diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json b/lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json @@ -1,7 +1,7 @@ { "trace": { "type": "intel-pt", - "pt_cpu": { + "cpuInfo": { "vendor": "intel", "family": 2123123, "model": 12123123, 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,152 @@ +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("proce trace start") + + + # 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("process trace start") + + + # 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 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("process trace start") + + # 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 in thread 3, thread 2 and 3 will be alive, but only 3 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 1", substrs=['not traced']) + self.expect("thread trace dump instructions 2", substrs=['not traced']) + + @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) + def testStartMultipleLiveThreadsWithThreadStartAll(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("continue") + # We are in thread 2 + self.expect("thread trace start all") + # Now we have instructions in thread's 2 trace + self.expect("n") + + self.expect("thread trace dump instructions 2", substrs=['main.cpp:11']) + + # We stop tracing all + self.runCmd("thread trace stop all") + + # The trace is still in memory + self.expect("thread trace dump instructions 2", substrs=['main.cpp:11']) + + # We'll stop at the next breakpoint in thread 3, and nothing should be traced + self.expect("continue") + self.expect("thread trace dump instructions 3", substrs=['not traced']) + self.expect("thread trace dump instructions 1", substrs=['not traced']) + self.expect("thread trace dump instructions 2", substrs=['not traced']) + + @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) + def testStartMultipleLiveThreadsWithSmallTotalLimit(self): + self.build() + exe = self.getBuildArtifact("a.out") + target = self.dbg.CreateTarget(exe) + + self.expect("b main") + self.expect("r") + # trace the entire process with enough total size for 1 thread trace + self.expect("process trace start -l 5000") + # we get the stop event when trace 2 appears and can't be traced + self.expect("c", substrs=['Thread', "can't be traced"]) + # we get the stop event when trace 3 appears and can't be traced + self.expect("c", substrs=['Thread', "can't be 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; +} diff --git a/lldb/tools/lldb-vscode/JSONUtils.cpp b/lldb/tools/lldb-vscode/JSONUtils.cpp --- a/lldb/tools/lldb-vscode/JSONUtils.cpp +++ b/lldb/tools/lldb-vscode/JSONUtils.cpp @@ -867,9 +867,10 @@ case lldb::eStopReasonInstrumentation: body.try_emplace("reason", "breakpoint"); break; - case lldb::eStopReasonSignal: - body.try_emplace("reason", "exception"); + case lldb::eStopReasonProcessorTrace: + body.try_emplace("reason", "processor trace"); break; + case lldb::eStopReasonSignal: case lldb::eStopReasonException: body.try_emplace("reason", "exception"); break; diff --git a/lldb/tools/lldb-vscode/LLDBUtils.cpp b/lldb/tools/lldb-vscode/LLDBUtils.cpp --- a/lldb/tools/lldb-vscode/LLDBUtils.cpp +++ b/lldb/tools/lldb-vscode/LLDBUtils.cpp @@ -55,6 +55,7 @@ case lldb::eStopReasonSignal: case lldb::eStopReasonException: case lldb::eStopReasonExec: + case lldb::eStopReasonProcessorTrace: return true; case lldb::eStopReasonThreadExiting: case lldb::eStopReasonInvalid: diff --git a/lldb/unittests/Process/Linux/CMakeLists.txt b/lldb/unittests/Process/Linux/CMakeLists.txt --- a/lldb/unittests/Process/Linux/CMakeLists.txt +++ b/lldb/unittests/Process/Linux/CMakeLists.txt @@ -1,9 +1,9 @@ -add_lldb_unittest(ProcessorTraceTests - ProcessorTraceTest.cpp +add_lldb_unittest(TraceIntelPTTests + IntelPTManagerTests.cpp LINK_LIBS lldbPluginProcessLinux ) -target_include_directories(ProcessorTraceTests PRIVATE +target_include_directories(TraceIntelPTTests PRIVATE ${LLDB_SOURCE_DIR}/source/Plugins/Process/Linux) diff --git a/lldb/unittests/Process/Linux/ProcessorTraceTest.cpp b/lldb/unittests/Process/Linux/IntelPTManagerTests.cpp rename from lldb/unittests/Process/Linux/ProcessorTraceTest.cpp rename to lldb/unittests/Process/Linux/IntelPTManagerTests.cpp --- a/lldb/unittests/Process/Linux/ProcessorTraceTest.cpp +++ b/lldb/unittests/Process/Linux/IntelPTManagerTests.cpp @@ -1,4 +1,4 @@ -//===-- ProcessorTraceMonitorTest.cpp -------------------------------------===// +//===-- IntelPTManagerTests.cpp -------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -8,7 +8,7 @@ #include "gtest/gtest.h" -#include "ProcessorTrace.h" +#include "IntelPTManager.h" #include "llvm/ADT/ArrayRef.h" @@ -22,7 +22,7 @@ buf_size); llvm::MutableArrayRef src(reinterpret_cast(cyc_buf), cyc_buf_size); - ProcessorTraceMonitor::ReadCyclicBuffer(dst, src, cyc_start, offset); + IntelPTThreadTrace::ReadCyclicBuffer(dst, src, cyc_start, offset); return dst.size(); } diff --git a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp --- a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp +++ b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp @@ -381,12 +381,12 @@ EXPECT_FALSE(result.get().Success()); } -TEST_F(GDBRemoteCommunicationClientTest, SendTraceSupportedTypePacket) { - TraceTypeInfo trace_type; +TEST_F(GDBRemoteCommunicationClientTest, SendTraceSupportedPacket) { + TraceSupportedResponse trace_type; std::string error_message; auto callback = [&] { - if (llvm::Expected trace_type_or_err = - client.SendGetSupportedTraceType()) { + if (llvm::Expected trace_type_or_err = + client.SendTraceSupported()) { trace_type = *trace_type_or_err; error_message = ""; return true; @@ -402,7 +402,7 @@ std::future result = std::async(std::launch::async, callback); HandlePacket( - server, "jLLDBTraceSupportedType", + server, "jLLDBTraceSupported", R"({"name":"intel-pt","description":"Intel Processor Trace"}])"); EXPECT_TRUE(result.get()); @@ -414,17 +414,17 @@ { std::future result = std::async(std::launch::async, callback); - HandlePacket(server, "jLLDBTraceSupportedType", R"({"type":"intel-pt"}])"); + HandlePacket(server, "jLLDBTraceSupported", R"({"type":"intel-pt"}])"); EXPECT_FALSE(result.get()); - ASSERT_STREQ(error_message.c_str(), "missing value at (root).name"); + ASSERT_STREQ(error_message.c_str(), "missing value at (root).description"); } // Error response { std::future result = std::async(std::launch::async, callback); - HandlePacket(server, "jLLDBTraceSupportedType", "E23"); + HandlePacket(server, "jLLDBTraceSupported", "E23"); EXPECT_FALSE(result.get()); } @@ -433,7 +433,7 @@ { std::future result = std::async(std::launch::async, callback); - HandlePacket(server, "jLLDBTraceSupportedType", + HandlePacket(server, "jLLDBTraceSupported", "E23;50726F63657373206E6F742072756E6E696E672E"); EXPECT_FALSE(result.get()); @@ -441,197 +441,6 @@ } } -TEST_F(GDBRemoteCommunicationClientTest, SendStartTracePacket) { - TraceOptions options; - Status error; - - options.setType(lldb::TraceType::eTraceTypeProcessorTrace); - options.setMetaDataBufferSize(8192); - options.setTraceBufferSize(8192); - options.setThreadID(0x23); - - StructuredData::DictionarySP custom_params = - std::make_shared(); - custom_params->AddStringItem("tracetech", "intel-pt"); - custom_params->AddIntegerItem("psb", 0x01); - - options.setTraceParams(custom_params); - - std::future result = std::async(std::launch::async, [&] { - return client.SendStartTracePacket(options, error); - }); - - // Since the line is exceeding 80 characters. - std::string expected_packet1 = - R"(jTraceStart:{"buffersize":8192,"metabuffersize":8192,"params":)"; - std::string expected_packet2 = - R"({"psb":1,"tracetech":"intel-pt"},"threadid":35,"type":1})"; - HandlePacket(server, (expected_packet1 + expected_packet2), "1"); - ASSERT_TRUE(error.Success()); - ASSERT_EQ(result.get(), 1u); - - error.Clear(); - result = std::async(std::launch::async, [&] { - return client.SendStartTracePacket(options, error); - }); - - HandlePacket(server, (expected_packet1 + expected_packet2), "E23"); - ASSERT_EQ(result.get(), LLDB_INVALID_UID); - ASSERT_FALSE(error.Success()); -} - -TEST_F(GDBRemoteCommunicationClientTest, SendStopTracePacket) { - lldb::tid_t thread_id = 0x23; - lldb::user_id_t trace_id = 3; - - std::future result = std::async(std::launch::async, [&] { - return client.SendStopTracePacket(trace_id, thread_id); - }); - - const char *expected_packet = R"(jTraceStop:{"threadid":35,"traceid":3})"; - HandlePacket(server, expected_packet, "OK"); - ASSERT_TRUE(result.get().Success()); - - result = std::async(std::launch::async, [&] { - return client.SendStopTracePacket(trace_id, thread_id); - }); - - HandlePacket(server, expected_packet, "E23"); - ASSERT_FALSE(result.get().Success()); -} - -TEST_F(GDBRemoteCommunicationClientTest, SendGetDataPacket) { - lldb::tid_t thread_id = 0x23; - lldb::user_id_t trace_id = 3; - - uint8_t buf[32] = {}; - llvm::MutableArrayRef buffer(buf, 32); - size_t offset = 0; - - std::future result = std::async(std::launch::async, [&] { - return client.SendGetDataPacket(trace_id, thread_id, buffer, offset); - }); - - std::string expected_packet1 = - R"(jTraceBufferRead:{"buffersize":32,"offset":0,"threadid":35,)"; - std::string expected_packet2 = R"("traceid":3})"; - HandlePacket(server, expected_packet1+expected_packet2, "123456"); - ASSERT_TRUE(result.get().Success()); - ASSERT_EQ(buffer.size(), 3u); - ASSERT_EQ(buf[0], 0x12); - ASSERT_EQ(buf[1], 0x34); - ASSERT_EQ(buf[2], 0x56); - - llvm::MutableArrayRef buffer2(buf, 32); - result = std::async(std::launch::async, [&] { - return client.SendGetDataPacket(trace_id, thread_id, buffer2, offset); - }); - - HandlePacket(server, expected_packet1+expected_packet2, "E23"); - ASSERT_FALSE(result.get().Success()); - ASSERT_EQ(buffer2.size(), 0u); -} - -TEST_F(GDBRemoteCommunicationClientTest, SendGetMetaDataPacket) { - lldb::tid_t thread_id = 0x23; - lldb::user_id_t trace_id = 3; - - uint8_t buf[32] = {}; - llvm::MutableArrayRef buffer(buf, 32); - size_t offset = 0; - - std::future result = std::async(std::launch::async, [&] { - return client.SendGetMetaDataPacket(trace_id, thread_id, buffer, offset); - }); - - std::string expected_packet1 = - R"(jTraceMetaRead:{"buffersize":32,"offset":0,"threadid":35,)"; - std::string expected_packet2 = R"("traceid":3})"; - HandlePacket(server, expected_packet1+expected_packet2, "123456"); - ASSERT_TRUE(result.get().Success()); - ASSERT_EQ(buffer.size(), 3u); - ASSERT_EQ(buf[0], 0x12); - ASSERT_EQ(buf[1], 0x34); - ASSERT_EQ(buf[2], 0x56); - - llvm::MutableArrayRef buffer2(buf, 32); - result = std::async(std::launch::async, [&] { - return client.SendGetMetaDataPacket(trace_id, thread_id, buffer2, offset); - }); - - HandlePacket(server, expected_packet1+expected_packet2, "E23"); - ASSERT_FALSE(result.get().Success()); - ASSERT_EQ(buffer2.size(), 0u); -} - -TEST_F(GDBRemoteCommunicationClientTest, SendGetTraceConfigPacket) { - lldb::tid_t thread_id = 0x23; - lldb::user_id_t trace_id = 3; - TraceOptions options; - options.setThreadID(thread_id); - - std::future result = std::async(std::launch::async, [&] { - return client.SendGetTraceConfigPacket(trace_id, options); - }); - - const char *expected_packet = - R"(jTraceConfigRead:{"threadid":35,"traceid":3})"; - std::string response1 = - R"({"buffersize":8192,"params":{"psb":1,"tracetech":"intel-pt"})"; - std::string response2 = R"(],"metabuffersize":8192,"threadid":35,"type":1}])"; - HandlePacket(server, expected_packet, response1+response2); - ASSERT_TRUE(result.get().Success()); - ASSERT_EQ(options.getTraceBufferSize(), 8192u); - ASSERT_EQ(options.getMetaDataBufferSize(), 8192u); - ASSERT_EQ(options.getType(), 1); - - auto custom_params = options.getTraceParams(); - - uint64_t psb_value; - llvm::StringRef trace_tech_value; - - ASSERT_TRUE(custom_params); - ASSERT_EQ(custom_params->GetType(), eStructuredDataTypeDictionary); - ASSERT_TRUE(custom_params->GetValueForKeyAsInteger("psb", psb_value)); - ASSERT_EQ(psb_value, 1u); - ASSERT_TRUE( - custom_params->GetValueForKeyAsString("tracetech", trace_tech_value)); - ASSERT_STREQ(trace_tech_value.data(), "intel-pt"); - - // Checking error response. - std::future result2 = std::async(std::launch::async, [&] { - return client.SendGetTraceConfigPacket(trace_id, options); - }); - - HandlePacket(server, expected_packet, "E23"); - ASSERT_FALSE(result2.get().Success()); - - // Wrong JSON as response. - std::future result3 = std::async(std::launch::async, [&] { - return client.SendGetTraceConfigPacket(trace_id, options); - }); - - std::string incorrect_json1 = - R"("buffersize" : 8192,"params" : {"psb" : 1,"tracetech" : "intel-pt"})"; - std::string incorrect_json2 = - R"(],"metabuffersize" : 8192,"threadid" : 35,"type" : 1}])"; - HandlePacket(server, expected_packet, incorrect_json1+incorrect_json2); - ASSERT_FALSE(result3.get().Success()); - - // Wrong JSON as custom_params. - std::future result4 = std::async(std::launch::async, [&] { - return client.SendGetTraceConfigPacket(trace_id, options); - }); - - std::string incorrect_custom_params1 = - R"({"buffersize" : 8192,"params" : "psb" : 1,"tracetech" : "intel-pt"})"; - std::string incorrect_custom_params2 = - R"(],"metabuffersize" : 8192,"threadid" : 35,"type" : 1}])"; - HandlePacket(server, expected_packet, incorrect_custom_params1+ - incorrect_custom_params2); - ASSERT_FALSE(result4.get().Success()); -} - TEST_F(GDBRemoteCommunicationClientTest, GetQOffsets) { const auto &GetQOffsets = [&](llvm::StringRef response) { std::future> result = std::async(