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 @@ -245,7 +245,7 @@ // OUTPUT SCHEMA // { // "name": , -// Tracing technology name, e.g. intel-pt, arm-coresight. +// Tracing technology name, e.g. intel-pt, arm-etm. // "description": , // Description for this technology. // } @@ -280,7 +280,7 @@ // INPUT SCHEMA // { // "type": , -// Tracing technology name, e.g. intel-pt, arm-coresight. +// Tracing technology name, e.g. intel-pt, arm-etm. // // /* thread tracing only */ // "tids"?: [], @@ -369,8 +369,9 @@ // /* 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". +// This limit applies to the sum of the sizes of all thread or core +// buffers for the current process, excluding the ones started with +// "thread tracing". // // If "perCoreTracing" is false, whenever a thread is attempted to be // traced due to "process tracing" and the limit would be reached, the @@ -421,7 +422,7 @@ // // { // "type": -// Tracing technology name, e.g. intel-pt, arm-coresight. +// Tracing technology name, e.g. intel-pt, arm-etm. // // /* thread trace stopping only */ // "tids": [] @@ -455,7 +456,7 @@ // INPUT SCHEMA // { // "type": -// Tracing technology name, e.g. intel-pt, arm-coresight. +// Tracing technology name, e.g. intel-pt, arm-etm. // } // // OUTPUT SCHEMA @@ -481,10 +482,23 @@ // Size in bytes of this thread data. // }, // ], +// "cores"?: [ +// "id": , +// Identifier for this CPU logical core. +// "binaryData": [ +// { +// "kind": , +// Identifier for some binary data related to this thread to +// fetch with the jLLDBTraceGetBinaryData packet. +// "size": , +// Size in bytes of this cpu core data. +// }, +// ] +// ], // "counters"?: { // "info_kind": {...parameters specific to the provided counter info kind}, -// Each entry includes information related to counters associated with the trace. -// They are described below. +// Each entry includes information related to counters associated with +// the trace. They are described below. // } // } // @@ -494,6 +508,12 @@ // // INTEL PT // +// If per-core process tracing is enabled, "tracedThreads" will contain all +// the threads of the process without any trace buffers. Besides that, the +// "cores" field will also be returned with per core trace buffers. +// A side effect of per-core tracing is that all the threads of unrelated +// processes will also be traced, thus polluting the tracing data. +// // Binary data kinds: // - traceBuffer: trace buffer for a thread or a core. // - procfsCpuInfo: contents of the /proc/cpuinfo file. @@ -536,9 +556,11 @@ // // { // "type": , -// Tracing technology name, e.g. intel-pt, arm-coresight. +// Tracing technology name, e.g. intel-pt, arm-etm. // "kind": , // Identifier for the data. +// "coreId": , +// Core id in decimal if the data belongs to a CPU core. // "tid"?: , // Tid in decimal if the data belongs to a thread. // "offset": , diff --git a/lldb/include/lldb/Utility/TraceGDBRemotePackets.h b/lldb/include/lldb/Utility/TraceGDBRemotePackets.h --- a/lldb/include/lldb/Utility/TraceGDBRemotePackets.h +++ b/lldb/include/lldb/Utility/TraceGDBRemotePackets.h @@ -109,7 +109,7 @@ struct TraceThreadState { lldb::tid_t tid; /// List of binary data objects for this thread. - std::vector binaryData; + std::vector binary_data; }; bool fromJSON(const llvm::json::Value &value, TraceThreadState &packet, @@ -117,6 +117,17 @@ llvm::json::Value toJSON(const TraceThreadState &packet); +struct TraceCoreState { + lldb::core_id_t core_id; + /// List of binary data objects for this core. + std::vector binary_data; +}; + +bool fromJSON(const llvm::json::Value &value, TraceCoreState &packet, + llvm::json::Path path); + +llvm::json::Value toJSON(const TraceCoreState &packet); + /// Interface for different algorithms used to convert trace /// counters into different units. template class TraceCounterConversion { @@ -143,8 +154,9 @@ std::unique_ptr>; struct TraceGetStateResponse { - std::vector tracedThreads; - std::vector processBinaryData; + std::vector traced_threads; + std::vector process_binary_data; + llvm::Optional> cores; }; bool fromJSON(const llvm::json::Value &value, TraceGetStateResponse &packet, diff --git a/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h b/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h --- a/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h +++ b/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h @@ -48,6 +48,8 @@ /// Whether to have a trace buffer per thread or per cpu core. llvm::Optional per_core_tracing; + + bool IsPerCoreTracing() const; }; bool fromJSON(const llvm::json::Value &value, TraceIntelPTStartRequest &packet, diff --git a/lldb/include/lldb/lldb-types.h b/lldb/include/lldb/lldb-types.h --- a/lldb/include/lldb/lldb-types.h +++ b/lldb/include/lldb/lldb-types.h @@ -89,6 +89,7 @@ typedef int32_t watch_id_t; typedef void *opaque_compiler_type_t; typedef uint64_t queue_id_t; +typedef uint32_t core_id_t; // CPU core id } // namespace lldb #endif // LLDB_LLDB_TYPES_H diff --git a/lldb/packages/Python/lldbsuite/test/tools/intelpt/intelpt_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/intelpt/intelpt_testcase.py --- a/lldb/packages/Python/lldbsuite/test/tools/intelpt/intelpt_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/intelpt/intelpt_testcase.py @@ -33,6 +33,19 @@ if 'intel-pt' not in configuration.enabled_plugins: self.skipTest("The intel-pt test plugin is not enabled") + def skipIfPerCoreTracingIsNotSupported(self): + def is_supported(): + try: + with open("/proc/sys/kernel/perf_event_paranoid", "r") as permissions: + value = int(permissions.readlines()[0]) + if value <= 0: + return True + except: + return False + if not is_supported(): + self.skipTest("Per core tracing is not supported. You need " + "/proc/sys/kernel/perf_event_paranoid to be 0 or -1.") + def getTraceOrCreate(self): if not self.target().GetTrace().IsValid(): error = lldb.SBError() @@ -110,7 +123,7 @@ else: self.expect("process trace stop") - def traceStopThread(self, thread=None, error=False): + def traceStopThread(self, thread=None, error=False, substrs=None): if self.USE_SB_API: thread = thread if thread is not None else self.thread() self.assertSBError(self.target().GetTrace().Stop(thread), error) @@ -119,4 +132,4 @@ command = "thread trace stop" if thread is not None: command += " " + str(thread.GetIndexID()) - self.expect(command, error=error) + self.expect(command, error=error, substrs=substrs) 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,6 +1,7 @@ add_lldb_library(lldbPluginProcessLinux IntelPTCollector.cpp IntelPTSingleBufferTrace.cpp + IntelPTMultiCoreTrace.cpp NativeProcessLinux.cpp NativeRegisterContextLinux.cpp NativeRegisterContextLinux_arm.cpp diff --git a/lldb/source/Plugins/Process/Linux/IntelPTCollector.h b/lldb/source/Plugins/Process/Linux/IntelPTCollector.h --- a/lldb/source/Plugins/Process/Linux/IntelPTCollector.h +++ b/lldb/source/Plugins/Process/Linux/IntelPTCollector.h @@ -1,4 +1,4 @@ -//===-- IntelPTCollector.h -------------------------------------- -*- C++ -*-===// +//===-- IntelPTCollector.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. @@ -11,8 +11,10 @@ #include "Perf.h" +#include "IntelPTMultiCoreTrace.h" #include "IntelPTSingleBufferTrace.h" +#include "lldb/Host/common/NativeProcessProtocol.h" #include "lldb/Utility/Status.h" #include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" #include "lldb/lldb-types.h" @@ -47,17 +49,37 @@ llvm::Error TraceStop(lldb::tid_t tid); + size_t GetTracedThreadsCount() const; + private: 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 { +class IntelPTPerThreadProcessTrace; +using IntelPTPerThreadProcessTraceUP = + std::unique_ptr; + +/// Manages a "process trace" instance by tracing each thread individually. +class IntelPTPerThreadProcessTrace { public: - IntelPTProcessTrace(const TraceIntelPTStartRequest &request) - : m_tracing_params(request) {} + /// Start tracing the current process by tracing each of its tids + /// individually. + /// + /// \param[in] request + /// Intel PT configuration parameters. + /// + /// \param[in] current_tids + /// List of tids currently alive. In the future, whenever a new thread is + /// spawned, they should be traced by calling the \a TraceStart(tid) method. + /// + /// \return + /// An \a IntelPTMultiCoreTrace instance if tracing was successful, or + /// an \a llvm::Error otherwise. + static llvm::Expected + Start(const TraceIntelPTStartRequest &request, + llvm::ArrayRef current_tids); bool TracesThread(lldb::tid_t tid) const; @@ -68,6 +90,9 @@ llvm::Error TraceStop(lldb::tid_t tid); private: + IntelPTPerThreadProcessTrace(const TraceIntelPTStartRequest &request) + : m_tracing_params(request) {} + IntelPTThreadTraceCollection m_thread_traces; /// Params used to trace threads when the user started "process tracing". TraceIntelPTStartRequest m_tracing_params; @@ -76,7 +101,9 @@ /// Main class that manages intel-pt process and thread tracing. class IntelPTCollector { public: - IntelPTCollector(); + /// \param[in] process + /// Process to be traced. + IntelPTCollector(NativeProcessProtocol &process); static bool IsSupported(); @@ -90,11 +117,7 @@ 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); + llvm::Error TraceStart(const TraceIntelPTStartRequest &request); /// Implementation of the jLLDBTraceGetState packet llvm::Expected GetState() const; @@ -120,11 +143,21 @@ void ClearProcessTracing(); + NativeProcessProtocol &m_process; /// 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; + + /// Only one of the following "process tracing" handlers can be active at a + /// given time. + /// + /// \{ + /// Threads traced due to per-thread "process tracing". This might be \b + /// nullptr. + IntelPTPerThreadProcessTraceUP m_per_thread_process_trace_up; + /// Cores traced due to per-core "process tracing". This might be \b nullptr. + IntelPTMultiCoreTraceUP m_per_core_process_trace_up; + /// \} + /// TSC to wall time conversion. TraceTscConversionUP m_tsc_conversion; }; diff --git a/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp b/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp --- a/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp @@ -92,17 +92,21 @@ m_total_buffer_size = 0; } -/// IntelPTProcessTrace +size_t IntelPTThreadTraceCollection::GetTracedThreadsCount() const { + return m_thread_traces.size(); +} + +/// IntelPTPerThreadProcessTrace -bool IntelPTProcessTrace::TracesThread(lldb::tid_t tid) const { +bool IntelPTPerThreadProcessTrace::TracesThread(lldb::tid_t tid) const { return m_thread_traces.TracesThread(tid); } -Error IntelPTProcessTrace::TraceStop(lldb::tid_t tid) { +Error IntelPTPerThreadProcessTrace::TraceStop(lldb::tid_t tid) { return m_thread_traces.TraceStop(tid); } -Error IntelPTProcessTrace::TraceStart(lldb::tid_t tid) { +Error IntelPTPerThreadProcessTrace::TraceStart(lldb::tid_t tid) { if (m_thread_traces.GetTotalBufferSize() + m_tracing_params.trace_buffer_size > static_cast(*m_tracing_params.process_buffer_size_limit)) @@ -117,13 +121,14 @@ } const IntelPTThreadTraceCollection & -IntelPTProcessTrace::GetThreadTraces() const { +IntelPTPerThreadProcessTrace::GetThreadTraces() const { return m_thread_traces; } /// IntelPTCollector -IntelPTCollector::IntelPTCollector() { +IntelPTCollector::IntelPTCollector(NativeProcessProtocol &process) + : m_process(process) { if (Expected tsc_conversion = LoadPerfTscConversionParameters()) m_tsc_conversion = @@ -134,8 +139,13 @@ } Error IntelPTCollector::TraceStop(lldb::tid_t tid) { - if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) - return m_process_trace->TraceStop(tid); + if (m_per_thread_process_trace_up && + m_per_thread_process_trace_up->TracesThread(tid)) + return m_per_thread_process_trace_up->TraceStop(tid); + else if (m_per_core_process_trace_up) + return createStringError(inconvertibleErrorCode(), + "Can't stop tracing an individual thread when " + "per-core process tracing is enabled."); return m_thread_traces.TraceStop(tid); } @@ -152,26 +162,60 @@ } } -Error IntelPTCollector::TraceStart( - const TraceIntelPTStartRequest &request, - const std::vector &process_threads) { +Expected +IntelPTPerThreadProcessTrace::Start(const TraceIntelPTStartRequest &request, + ArrayRef current_tids) { + IntelPTPerThreadProcessTraceUP trace( + new IntelPTPerThreadProcessTrace(request)); + + Error error = Error::success(); + for (lldb::tid_t tid : current_tids) + error = joinErrors(std::move(error), trace->TraceStart(tid)); + if (error) + return std::move(error); + return trace; +} + +Error IntelPTCollector::TraceStart(const TraceIntelPTStartRequest &request) { if (request.IsProcessTracing()) { if (IsProcessTracingEnabled()) { return createStringError( inconvertibleErrorCode(), "Process currently traced. Stop process tracing first"); } - if (request.per_core_tracing.getValueOr(false)) { - return createStringError(inconvertibleErrorCode(), - "Per-core tracing is not supported."); + if (request.IsPerCoreTracing()) { + if (m_thread_traces.GetTracedThreadsCount() > 0) + return createStringError( + inconvertibleErrorCode(), + "Threads currently traced. Stop tracing them first."); + if (Expected trace = + IntelPTMultiCoreTrace::StartOnAllCores(request)) { + m_per_core_process_trace_up = std::move(*trace); + return Error::success(); + } else { + return trace.takeError(); + } + } else { + std::vector process_threads; + for (size_t i = 0; m_process.GetThreadAtIndex(i); i++) + process_threads.push_back(m_process.GetThreadAtIndex(i)->GetID()); + + // per-thread process tracing + if (Expected trace = + IntelPTPerThreadProcessTrace::Start(request, process_threads)) { + m_per_thread_process_trace_up = std::move(trace.get()); + return Error::success(); + } else { + return trace.takeError(); + } } - m_process_trace = IntelPTProcessTrace(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 { + // individual thread tracing + if (m_per_core_process_trace_up) + return createStringError(inconvertibleErrorCode(), + "Process currently traced with per-core " + "tracing. Stop process tracing first"); + Error error = Error::success(); for (int64_t tid : *request.tids) error = joinErrors(std::move(error), @@ -181,14 +225,16 @@ } Error IntelPTCollector::OnThreadCreated(lldb::tid_t tid) { - if (!IsProcessTracingEnabled()) - return Error::success(); - return m_process_trace->TraceStart(tid); + if (m_per_thread_process_trace_up) + return m_per_thread_process_trace_up->TraceStart(tid); + + return Error::success(); } Error IntelPTCollector::OnThreadDestroyed(lldb::tid_t tid) { - if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) - return m_process_trace->TraceStop(tid); + if (m_per_thread_process_trace_up && + m_per_thread_process_trace_up->TracesThread(tid)) + return m_per_thread_process_trace_up->TraceStop(tid); else if (m_thread_traces.TracesThread(tid)) return m_thread_traces.TraceStop(tid); return Error::success(); @@ -200,26 +246,42 @@ return cpu_info.takeError(); TraceGetStateResponse state; - state.processBinaryData.push_back( + state.process_binary_data.push_back( {IntelPTDataKinds::kProcFsCpuInfo, 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()); + state.traced_threads.insert(state.traced_threads.end(), thread_states.begin(), + thread_states.end()); + + if (m_per_thread_process_trace_up) { + thread_states = + m_per_thread_process_trace_up->GetThreadTraces().GetThreadStates(); + state.traced_threads.insert(state.traced_threads.end(), + thread_states.begin(), thread_states.end()); + } else if (m_per_core_process_trace_up) { + for (size_t i = 0; m_process.GetThreadAtIndex(i); i++) + state.traced_threads.push_back( + TraceThreadState{m_process.GetThreadAtIndex(i)->GetID(), {}}); + + state.cores.emplace(); + m_per_core_process_trace_up->ForEachCore( + [&](lldb::core_id_t core_id, + const IntelPTSingleBufferTrace &core_trace) { + state.cores->push_back({core_id, + {{IntelPTDataKinds::kTraceBuffer, + core_trace.GetTraceBufferSize()}}}); + }); } return toJSON(state); } Expected IntelPTCollector::GetTracedThread(lldb::tid_t tid) const { - if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) - return m_process_trace->GetThreadTraces().GetTracedThread(tid); + if (m_per_thread_process_trace_up && + m_per_thread_process_trace_up->TracesThread(tid)) + return m_per_thread_process_trace_up->GetThreadTraces().GetTracedThread( + tid); return m_thread_traces.GetTracedThread(tid); } @@ -239,7 +301,10 @@ request.kind.c_str()); } -void IntelPTCollector::ClearProcessTracing() { m_process_trace = None; } +void IntelPTCollector::ClearProcessTracing() { + m_per_thread_process_trace_up.reset(); + m_per_core_process_trace_up.reset(); +} bool IntelPTCollector::IsSupported() { if (Expected intel_pt_type = GetIntelPTOSEventType()) { @@ -251,7 +316,8 @@ } bool IntelPTCollector::IsProcessTracingEnabled() const { - return (bool)m_process_trace; + return (bool)m_per_thread_process_trace_up || + (bool)m_per_core_process_trace_up; } void IntelPTCollector::Clear() { diff --git a/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h @@ -0,0 +1,64 @@ +//===-- IntelPTMultiCoreTrace.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_IntelPTMultiCoreTrace_H_ +#define liblldb_IntelPTMultiCoreTrace_H_ + +#include "IntelPTSingleBufferTrace.h" + +#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" +#include "lldb/lldb-types.h" + +#include "llvm/Support/Error.h" + +#include + +namespace lldb_private { +namespace process_linux { + +class IntelPTMultiCoreTrace; +using IntelPTMultiCoreTraceUP = std::unique_ptr; + +class IntelPTMultiCoreTrace { +public: + /// Start tracing all CPU cores. + /// + /// \param[in] request + /// Intel PT configuration parameters. + /// + /// \return + /// An \a IntelPTMultiCoreTrace instance if tracing was successful, or + /// an \a llvm::Error otherwise. + static llvm::Expected + StartOnAllCores(const TraceIntelPTStartRequest &request); + + /// Execute the provided callback on each core that is being traced. + /// + /// \param[in] callback.core_id + /// The core id that is being traced. + /// + /// \param[in] callback.core_trace + /// The single-buffer trace instance for the given core. + void + ForEachCore(std::function + callback); + +private: + IntelPTMultiCoreTrace( + llvm::DenseMap + &&traces_per_core) + : m_traces_per_core(std::move(traces_per_core)) {} + + llvm::DenseMap m_traces_per_core; +}; + +} // namespace process_linux +} // namespace lldb_private + +#endif // liblldb_IntelPTMultiCoreTrace_H_ diff --git a/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp @@ -0,0 +1,64 @@ +//===-- IntelPTMultiCoreTrace.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 "IntelPTMultiCoreTrace.h" + +#include "Procfs.h" + +using namespace lldb; +using namespace lldb_private; +using namespace process_linux; +using namespace llvm; + +static bool IsTotalBufferLimitReached(ArrayRef cores, + const TraceIntelPTStartRequest &request) { + uint64_t required = cores.size() * request.trace_buffer_size; + uint64_t limit = request.process_buffer_size_limit.getValueOr( + std::numeric_limits::max()); + return required > limit; +} + +static Error IncludePerfEventParanoidMessageInError(Error &&error) { + return createStringError( + inconvertibleErrorCode(), + "%s\nYou might need to rerun as sudo or to set " + "/proc/sys/kernel/perf_event_paranoid to a value of 0 or -1.", + toString(std::move(error)).c_str()); +} + +Expected IntelPTMultiCoreTrace::StartOnAllCores( + const TraceIntelPTStartRequest &request) { + Expected> core_ids = GetAvailableLogicalCoreIDs(); + if (!core_ids) + return core_ids.takeError(); + + if (IsTotalBufferLimitReached(*core_ids, request)) + return createStringError( + inconvertibleErrorCode(), + "The process can't be traced because the process trace size limit " + "has been reached. Consider retracing with a higher limit."); + + llvm::DenseMap buffers; + for (core_id_t core_id : *core_ids) { + if (Expected core_trace = + IntelPTSingleBufferTrace::Start(request, /*tid=*/None, core_id)) + buffers.try_emplace(core_id, std::move(*core_trace)); + else + return IncludePerfEventParanoidMessageInError(core_trace.takeError()); + } + + return IntelPTMultiCoreTraceUP(new IntelPTMultiCoreTrace(std::move(buffers))); +} + +void IntelPTMultiCoreTrace::ForEachCore( + std::function + callback) { + for (auto &it : m_traces_per_core) + callback(it.first, *it.second); +} diff --git a/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.h b/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.h --- a/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.h +++ b/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.h @@ -23,10 +23,8 @@ llvm::Expected GetIntelPTOSEventType(); -class IntelPTTrace; class IntelPTSingleBufferTrace; -using IntelPTThreadTraceUP = std::unique_ptr; using IntelPTSingleBufferTraceUP = std::unique_ptr; /// This class wraps a single perf event collecting intel pt data in a single @@ -39,13 +37,19 @@ /// Intel PT configuration parameters. /// /// \param[in] tid - /// The tid of the thread to be traced. + /// The tid of the thread to be traced. If \b None, then this traces all + /// threads of all processes. + /// + /// \param[in] core_id + /// The CPU core id where to trace. If \b None, then this traces all CPUs. /// /// \return /// A \a IntelPTSingleBufferTrace instance if tracing was successful, or /// an \a llvm::Error otherwise. static llvm::Expected - Start(const TraceIntelPTStartRequest &request, lldb::tid_t tid); + Start(const TraceIntelPTStartRequest &request, + llvm::Optional tid, + llvm::Optional core_id = llvm::None); /// \return /// The bytes requested by a jLLDBTraceGetBinaryData packet that was routed @@ -80,10 +84,7 @@ /// /// \param[in] perf_event /// perf event configured for IntelPT. - /// - /// \param[in] tid - /// The thread being traced. - IntelPTSingleBufferTrace(PerfEvent &&perf_event, lldb::tid_t tid) + IntelPTSingleBufferTrace(PerfEvent &&perf_event) : m_perf_event(std::move(perf_event)) {} /// perf event configured for IntelPT. diff --git a/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp b/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp --- a/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp @@ -260,14 +260,16 @@ Expected IntelPTSingleBufferTrace::Start(const TraceIntelPTStartRequest &request, - lldb::tid_t tid) { + Optional tid, + Optional core_id) { #ifndef PERF_ATTR_SIZE_VER5 return createStringError(inconvertibleErrorCode(), "Intel PT Linux perf event not supported"); #else Log *log = GetLog(POSIXLog::Trace); - LLDB_LOG(log, "Will start tracing thread id {0}", tid); + LLDB_LOG(log, "Will start tracing thread id {0} and cpu id {1}", tid, + core_id); if (__builtin_popcount(request.trace_buffer_size) != 1 || request.trace_buffer_size < 4096) { @@ -291,13 +293,13 @@ LLDB_LOG(log, "Will create trace buffer of size {0}", request.trace_buffer_size); - if (Expected perf_event = PerfEvent::Init(*attr, tid)) { + if (Expected perf_event = PerfEvent::Init(*attr, tid, core_id)) { if (Error mmap_err = perf_event->MmapMetadataAndBuffers(buffer_numpages, buffer_numpages)) { return std::move(mmap_err); } return IntelPTSingleBufferTraceUP( - new IntelPTSingleBufferTrace(std::move(*perf_event), tid)); + new IntelPTSingleBufferTrace(std::move(*perf_event))); } else { return perf_event.takeError(); } 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 @@ -312,7 +312,7 @@ const ArchSpec &arch, MainLoop &mainloop, llvm::ArrayRef<::pid_t> tids) : NativeProcessELF(pid, terminal_fd, delegate), m_arch(arch), - m_main_loop(mainloop) { + m_main_loop(mainloop), m_intel_pt_collector(*this) { if (m_terminal_fd != -1) { Status status = EnsureFDFlags(m_terminal_fd, O_NONBLOCK); assert(status.Success()); @@ -1967,10 +1967,7 @@ 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_collector.TraceStart(*request, process_threads); + return m_intel_pt_collector.TraceStart(*request); } else return request.takeError(); } diff --git a/lldb/source/Plugins/Process/Linux/Perf.h b/lldb/source/Plugins/Process/Linux/Perf.h --- a/lldb/source/Plugins/Process/Linux/Perf.h +++ b/lldb/source/Plugins/Process/Linux/Perf.h @@ -109,13 +109,16 @@ /// Configuration information for the event. /// /// \param[in] pid - /// The process to be monitored by the event. + /// The process or thread to be monitored by the event. If \b None, then + /// all processes and threads are monitored. /// /// \param[in] cpu - /// The cpu to be monitored by the event. + /// The cpu to be monitored by the event. If \b None, then all cpus are + /// monitored. /// /// \param[in] group_fd - /// File descriptor of the group leader. + /// File descriptor of the group leader. If \b None, then this perf_event + /// doesn't belong to a preexisting group. /// /// \param[in] flags /// Bitmask of additional configuration flags. @@ -123,8 +126,10 @@ /// \return /// If the perf_event_open syscall was successful, a minimal \a PerfEvent /// instance, or an \a llvm::Error otherwise. - static llvm::Expected Init(perf_event_attr &attr, lldb::pid_t pid, - int cpu, int group_fd, + static llvm::Expected Init(perf_event_attr &attr, + llvm::Optional pid, + llvm::Optional cpu, + llvm::Optional group_fd, unsigned long flags); /// Create a new performance monitoring event via the perf_event_open syscall @@ -137,8 +142,11 @@ /// Configuration information for the event. /// /// \param[in] pid - /// The process to be monitored by the event. - static llvm::Expected Init(perf_event_attr &attr, lldb::pid_t pid); + /// The process or thread to be monitored by the event. If \b None, then + /// all threads and processes are monitored. + static llvm::Expected + Init(perf_event_attr &attr, llvm::Optional pid, + llvm::Optional core = llvm::None); /// Mmap the metadata page and the data and aux buffers of the perf event and /// expose them through \a PerfEvent::GetMetadataPage() , \a diff --git a/lldb/source/Plugins/Process/Linux/Perf.cpp b/lldb/source/Plugins/Process/Linux/Perf.cpp --- a/lldb/source/Plugins/Process/Linux/Perf.cpp +++ b/lldb/source/Plugins/Process/Linux/Perf.cpp @@ -117,10 +117,13 @@ } llvm::Expected PerfEvent::Init(perf_event_attr &attr, - lldb::pid_t pid, int cpu, - int group_fd, unsigned long flags) { + Optional pid, + Optional cpu, + Optional group_fd, + unsigned long flags) { errno = 0; - long fd = syscall(SYS_perf_event_open, &attr, pid, cpu, group_fd, flags); + long fd = syscall(SYS_perf_event_open, &attr, pid.getValueOr(-1), + cpu.getValueOr(-1), group_fd.getValueOr(-1), flags); if (fd == -1) { std::string err_msg = llvm::formatv("perf event syscall failed: {0}", std::strerror(errno)); @@ -130,8 +133,9 @@ } llvm::Expected PerfEvent::Init(perf_event_attr &attr, - lldb::pid_t pid) { - return Init(attr, pid, -1, -1, 0); + Optional pid, + Optional cpu) { + return Init(attr, pid, cpu, -1, 0); } llvm::Expected diff --git a/lldb/source/Plugins/Process/Linux/Procfs.h b/lldb/source/Plugins/Process/Linux/Procfs.h --- a/lldb/source/Plugins/Process/Linux/Procfs.h +++ b/lldb/source/Plugins/Process/Linux/Procfs.h @@ -11,6 +11,8 @@ #include +#include "lldb/lldb-types.h" + #include "llvm/Support/Error.h" #include @@ -43,13 +45,13 @@ /// \return /// A list of available logical core ids given the contents of /// /proc/cpuinfo. -llvm::Expected> +llvm::Expected> GetAvailableLogicalCoreIDs(llvm::StringRef cpuinfo); /// \return /// A list with all the logical cores available in the system and cache it /// if errors didn't happen. -llvm::Expected> GetAvailableLogicalCoreIDs(); +llvm::Expected> GetAvailableLogicalCoreIDs(); } // namespace process_linux } // namespace lldb_private diff --git a/lldb/source/Plugins/Process/Linux/Procfs.cpp b/lldb/source/Plugins/Process/Linux/Procfs.cpp --- a/lldb/source/Plugins/Process/Linux/Procfs.cpp +++ b/lldb/source/Plugins/Process/Linux/Procfs.cpp @@ -11,6 +11,7 @@ #include "lldb/Host/linux/Support.h" #include "llvm/Support/MemoryBuffer.h" +using namespace lldb; using namespace lldb_private; using namespace process_linux; using namespace llvm; @@ -29,18 +30,18 @@ return *cpu_info; } -Expected> +Expected> lldb_private::process_linux::GetAvailableLogicalCoreIDs(StringRef cpuinfo) { SmallVector lines; cpuinfo.split(lines, "\n", /*MaxSplit=*/-1, /*KeepEmpty=*/false); - std::vector logical_cores; + std::vector logical_cores; for (StringRef line : lines) { std::pair key_value = line.split(':'); auto key = key_value.first.trim(); auto val = key_value.second.trim(); if (key == "processor") { - int processor; + core_id_t processor; if (val.getAsInteger(10, processor)) return createStringError( inconvertibleErrorCode(), @@ -51,17 +52,17 @@ return logical_cores; } -llvm::Expected> +llvm::Expected> lldb_private::process_linux::GetAvailableLogicalCoreIDs() { - static Optional> logical_cores_ids; + static Optional> logical_cores_ids; if (!logical_cores_ids) { // We find the actual list of core ids by parsing /proc/cpuinfo Expected> cpuinfo = GetProcfsCpuInfo(); if (!cpuinfo) return cpuinfo.takeError(); - Expected> core_ids = GetAvailableLogicalCoreIDs(StringRef( - reinterpret_cast(cpuinfo->data()), cpuinfo->size())); + Expected> core_ids = GetAvailableLogicalCoreIDs( + StringRef(reinterpret_cast(cpuinfo->data()))); if (!core_ids) return core_ids.takeError(); 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 @@ -252,7 +252,7 @@ return; } - for (const TraceThreadState &thread_state : state->tracedThreads) { + for (const TraceThreadState &thread_state : state->traced_threads) { ThreadSP thread_sp = m_live_process->GetThreadList().FindThreadByID(thread_state.tid); m_thread_decoders.emplace( @@ -354,9 +354,9 @@ Error TraceIntelPT::Start(llvm::ArrayRef tids, StructuredData::ObjectSP configuration) { - size_t trace_buffer_size = kDefaultTraceBufferSize; + uint64_t trace_buffer_size = kDefaultTraceBufferSize; bool enable_tsc = kDefaultEnableTscValue; - Optional psb_period = kDefaultPsbPeriod; + Optional psb_period = kDefaultPsbPeriod; if (configuration) { if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) { 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 @@ -57,8 +57,8 @@ Group<1>, Arg<"Value">, 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. " + "the sum of the sizes of all thread and core 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 " "\"processor trace\" reason, so that the user can retrace the process " 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 @@ -199,12 +199,12 @@ } for (const TraceThreadState &thread_state : - live_process_state->tracedThreads) { - for (const TraceBinaryData &item : thread_state.binaryData) + live_process_state->traced_threads) { + for (const TraceBinaryData &item : thread_state.binary_data) m_live_thread_data[thread_state.tid][item.kind] = item.size; } - for (const TraceBinaryData &item : live_process_state->processBinaryData) + for (const TraceBinaryData &item : live_process_state->process_binary_data) m_live_process_data[item.kind] = item.size; DoRefreshLiveProcessState(std::move(live_process_state)); diff --git a/lldb/source/Utility/TraceGDBRemotePackets.cpp b/lldb/source/Utility/TraceGDBRemotePackets.cpp --- a/lldb/source/Utility/TraceGDBRemotePackets.cpp +++ b/lldb/source/Utility/TraceGDBRemotePackets.cpp @@ -87,24 +87,42 @@ 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); + o.map("binaryData", packet.binary_data); } json::Value toJSON(const TraceThreadState &packet) { return json::Value( - Object{{"tid", packet.tid}, {"binaryData", packet.binaryData}}); + Object{{"tid", packet.tid}, {"binaryData", packet.binary_data}}); } 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); + return o && o.map("tracedThreads", packet.traced_threads) && + o.map("processBinaryData", packet.process_binary_data) && + o.map("cores", packet.cores); } json::Value toJSON(const TraceGetStateResponse &packet) { - return json::Value(Object{{"tracedThreads", packet.tracedThreads}, - {"processBinaryData", packet.processBinaryData}}); + return json::Value(Object{{"tracedThreads", packet.traced_threads}, + {"processBinaryData", packet.process_binary_data}, + {"cores", packet.cores}}); +} + +bool fromJSON(const json::Value &value, TraceCoreState &packet, + json::Path path) { + ObjectMapper o(value, path); + int64_t core_id; + if (!o || !o.map("coreId", core_id) || + !o.map("binaryData", packet.binary_data)) + return false; + packet.core_id = static_cast(core_id); + return true; +} + +json::Value toJSON(const TraceCoreState &packet) { + return json::Value( + Object{{"coreId", packet.core_id}, {"binaryData", packet.binary_data}}); } /// \} diff --git a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp --- a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp +++ b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp @@ -16,6 +16,10 @@ const char *IntelPTDataKinds::kProcFsCpuInfo = "procfsCpuInfo"; const char *IntelPTDataKinds::kTraceBuffer = "traceBuffer"; +bool TraceIntelPTStartRequest::IsPerCoreTracing() const { + return per_core_tracing.getValueOr(false); +} + bool fromJSON(const json::Value &value, TraceIntelPTStartRequest &packet, Path path) { ObjectMapper o(value, path); diff --git a/lldb/test/API/commands/trace/multiple-threads/TestTraceStartStopMultipleThreads.py b/lldb/test/API/commands/trace/multiple-threads/TestTraceStartStopMultipleThreads.py --- a/lldb/test/API/commands/trace/multiple-threads/TestTraceStartStopMultipleThreads.py +++ b/lldb/test/API/commands/trace/multiple-threads/TestTraceStartStopMultipleThreads.py @@ -30,6 +30,8 @@ self.expect("continue") self.expect("thread trace dump instructions", substrs=['main.cpp:4']) + self.traceStopProcess() + @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) @testSBAPIAndCommands def testStartMultipleLiveThreadsWithStops(self): @@ -65,6 +67,8 @@ self.expect("thread trace dump instructions 2", substrs=['not traced']) + self.traceStopProcess() + @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) @testSBAPIAndCommands def testStartMultipleLiveThreadsWithStops(self): @@ -100,6 +104,8 @@ self.expect("thread trace dump instructions 1", substrs=['not traced']) self.expect("thread trace dump instructions 2", substrs=['not traced']) + self.traceStopProcess() + @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) def testStartMultipleLiveThreadsWithThreadStartAll(self): self.build() @@ -156,6 +162,8 @@ @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) @testSBAPIAndCommands def testStartPerCoreSession(self): + self.skipIfPerCoreTracingIsNotSupported() + self.build() exe = self.getBuildArtifact("a.out") self.dbg.CreateTarget(exe) @@ -163,6 +171,32 @@ self.expect("b main") self.expect("r") - self.traceStartProcess( - error=True, perCoreTracing=True, - substrs=["Per-core tracing is not supported"]) + # We should fail if we hit the total buffer limit. Useful if the number + # of cores is huge. + self.traceStartProcess(error="True", processBufferSizeLimit=100, + perCoreTracing=True, + substrs=["The process can't be traced because the process trace size " + "limit has been reached. Consider retracing with a higher limit."]) + + self.traceStartProcess(perCoreTracing=True) + self.traceStopProcess() + + self.traceStartProcess(perCoreTracing=True) + # We can't support multiple per-core tracing sessions. + self.traceStartProcess(error=True, perCoreTracing=True, + substrs=["Process currently traced. Stop process tracing first"]) + + # We can't support tracing per thread is per core is enabled. + self.traceStartThread( + error="True", + substrs=["Process currently traced with per-core tracing. Stop process tracing first"]) + + # We can't stop individual thread when per core is enabled. + self.traceStopThread(error="True", + substrs=["Can't stop tracing an individual thread when per-core process tracing is enabled"]) + + # The GetState packet should return trace buffers per core and at least one traced thread + self.expect("""process plugin packet send 'jLLDBTraceGetState:{"type":"intel-pt"}]'""", + substrs=['''[{"kind":"traceBuffer","size":4096}],"coreId":''', '"tid":']) + + self.traceStopProcess() diff --git a/lldb/unittests/Process/Linux/ProcfsTests.cpp b/lldb/unittests/Process/Linux/ProcfsTests.cpp --- a/lldb/unittests/Process/Linux/ProcfsTests.cpp +++ b/lldb/unittests/Process/Linux/ProcfsTests.cpp @@ -18,7 +18,7 @@ using namespace llvm; TEST(Perf, HardcodedLogicalCoreIDs) { - Expected> core_ids = + Expected> core_ids = GetAvailableLogicalCoreIDs(R"(processor : 13 vendor_id : GenuineIntel cpu family : 6 @@ -98,7 +98,7 @@ GTEST_SKIP() << toString(buffer_or_error.takeError()); // At this point we shouldn't fail parsing the core ids - Expected> core_ids = GetAvailableLogicalCoreIDs(); + Expected> core_ids = GetAvailableLogicalCoreIDs(); ASSERT_TRUE((bool)core_ids); ASSERT_GT((int)core_ids->size(), 0) << "We must see at least one core"; }