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 @@ -310,6 +310,12 @@ virtual Extension GetSupportedExtensions() const { return {}; } }; + /// Notify tracers that the target process will resume + virtual void NotifyTracersProcessWillResume() {} + + /// Notify tracers that the target process just stopped + virtual void NotifyTracersProcessDidStop() {} + /// Start tracing a process or its threads. /// /// \param[in] json_params @@ -461,9 +467,6 @@ NativeThreadProtocol *GetThreadByIDUnlocked(lldb::tid_t tid); - /// Notify tracers that the state of the target process has changed. - virtual void NotifyTracersProcessStateChanged(lldb::StateType state) {} - private: void SynchronouslyNotifyProcessStateChanged(lldb::StateType state); llvm::Expected 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 @@ -25,6 +25,7 @@ struct IntelPTDataKinds { static const char *kProcFsCpuInfo; static const char *kTraceBuffer; + static const char *kPerfContextSwitchTrace; }; /// jLLDBTraceStart gdb-remote packet diff --git a/lldb/source/Host/common/NativeProcessProtocol.cpp b/lldb/source/Host/common/NativeProcessProtocol.cpp --- a/lldb/source/Host/common/NativeProcessProtocol.cpp +++ b/lldb/source/Host/common/NativeProcessProtocol.cpp @@ -312,7 +312,16 @@ Log *log = GetLog(LLDBLog::Process); m_delegate.ProcessStateChanged(this, state); - NotifyTracersProcessStateChanged(state); + + switch (state) { + case eStateStopped: + case eStateExited: + case eStateCrashed: + NotifyTracersProcessDidStop(); + break; + default: + break; + } LLDB_LOG(log, "sent state notification [{0}] from process {1}", state, GetID()); 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 @@ -37,8 +37,12 @@ static bool IsSupported(); - /// To be invoked whenever the state of the target process has changed. - void OnProcessStateChanged(lldb::StateType state); + /// To be invoked as soon as we know the process stopped. + void ProcessDidStop(); + + /// To be invoked before the process will resume, so that we can capture all + /// the instructions. + void ProcessWillResume(); /// If "process tracing" is enabled, then trace the given thread. llvm::Error OnThreadCreated(lldb::tid_t tid); 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 @@ -116,9 +116,14 @@ } } -void IntelPTCollector::OnProcessStateChanged(lldb::StateType state) { +void IntelPTCollector::ProcessWillResume() { if (m_process_trace_up) - m_process_trace_up->OnProcessStateChanged(state); + m_process_trace_up->ProcessWillResume(); +} + +void IntelPTCollector::ProcessDidStop() { + if (m_process_trace_up) + m_process_trace_up->ProcessDidStop(); } Error IntelPTCollector::OnThreadCreated(lldb::tid_t tid) { diff --git a/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h --- a/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h +++ b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h @@ -51,7 +51,24 @@ IntelPTSingleBufferTrace &core_trace)> callback); - void OnProcessStateChanged(lldb::StateType state) override; + /// 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.intelpt_trace + /// The single-buffer intel pt trace instance for the given core. + /// + /// \param[in] callback.context_switch_trace + /// The perf event collecting context switches for the given core. + void ForEachCore(std::function + callback); + + void ProcessDidStop() override; + + void ProcessWillResume() override; TraceGetStateResponse GetState() override; @@ -65,17 +82,22 @@ GetBinaryData(const TraceGetBinaryDataRequest &request) override; private: + /// This assumes that all underlying perf_events for each core are part of the + /// same perf event group. IntelPTMultiCoreTrace( llvm::DenseMap - &&traces_per_core, + &&intelpt_traces_per_core, + llvm::DenseMap + &&context_switch_traces_per_core, NativeProcessProtocol &process) - : m_traces_per_core(std::move(traces_per_core)), m_process(process) {} - - llvm::DenseMap m_traces_per_core; - - /// The initial state is stopped because tracing can only start when the - /// process is paused. - lldb::StateType m_process_state = lldb::StateType::eStateStopped; + : m_intelpt_traces_per_core(std::move(intelpt_traces_per_core)), + m_context_switch_traces_per_core( + std::move(context_switch_traces_per_core)), + m_process(process) {} + + llvm::DenseMap + m_intelpt_traces_per_core; + llvm::DenseMap m_context_switch_traces_per_core; /// The target process. NativeProcessProtocol &m_process; diff --git a/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp --- a/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp @@ -33,6 +33,54 @@ toString(std::move(error)).c_str()); } +static Expected CreateContextSwitchTracePerfEvent( + bool disabled, lldb::core_id_t core_id, + IntelPTSingleBufferTrace &intelpt_core_trace) { + Log *log = GetLog(POSIXLog::Trace); +#ifndef PERF_ATTR_SIZE_VER5 + return createStringError(inconvertibleErrorCode(), + "Intel PT Linux perf event not supported"); +#else + perf_event_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.size = sizeof(attr); + attr.sample_period = 0; + attr.sample_type = PERF_SAMPLE_TID | PERF_SAMPLE_TIME; + attr.type = PERF_TYPE_SOFTWARE; + attr.context_switch = 1; + attr.exclude_kernel = 1; + attr.sample_id_all = 1; + attr.exclude_hv = 1; + attr.disabled = disabled; + + // The given perf configuration will product context switch records of 32 + // bytes each. Assuming that every context switch will be emitted twice (one + // for context switch ins and another one for context switch outs), and that a + // context switch will happen at least every half a millisecond per core, we + // need 500 * 32 bytes (~16 KB) for a trace of one second, which is much more + // than what a regular intel pt trace can get. Pessimistically we pick as + // 32KiB for the size of our context switch trace. + + uint64_t data_buffer_size = 32768; + uint64_t data_buffer_numpages = data_buffer_size / getpagesize(); + + LLDB_LOG(log, "Will create context switch trace buffer of size {0}", + data_buffer_size); + + if (Expected perf_event = PerfEvent::Init( + attr, /*pid=*/None, core_id, + intelpt_core_trace.GetPerfEvent().GetFd(), /*flags=*/0)) { + if (Error mmap_err = + perf_event->MmapMetadataAndBuffers(data_buffer_numpages, 0)) { + return std::move(mmap_err); + } + return perf_event; + } else { + return perf_event.takeError(); + } +#endif +} + Expected IntelPTMultiCoreTrace::StartOnAllCores(const TraceIntelPTStartRequest &request, NativeProcessProtocol &process) { @@ -46,55 +94,64 @@ "The process can't be traced because the process trace size limit " "has been reached. Consider retracing with a higher limit."); - llvm::DenseMap buffers; + DenseMap intelpt_traces; + DenseMap context_switch_traces; + for (core_id_t core_id : *core_ids) { - if (Expected core_trace = - IntelPTSingleBufferTrace::Start(request, /*tid=*/None, core_id, - TraceCollectionState::Paused)) - buffers.try_emplace(core_id, std::move(*core_trace)); - else + Expected core_trace = + IntelPTSingleBufferTrace::Start(request, /*tid=*/None, core_id, + /*disabled=*/true); + if (!core_trace) return IncludePerfEventParanoidMessageInError(core_trace.takeError()); + + if (Expected context_switch_trace = + CreateContextSwitchTracePerfEvent(/*disabled=*/true, core_id, + *core_trace.get())) { + intelpt_traces.try_emplace(core_id, std::move(*core_trace)); + context_switch_traces.try_emplace(core_id, + std::move(*context_switch_trace)); + } else { + return context_switch_trace.takeError(); + } } - return IntelPTProcessTraceUP( - new IntelPTMultiCoreTrace(std::move(buffers), process)); + return IntelPTProcessTraceUP(new IntelPTMultiCoreTrace( + std::move(intelpt_traces), std::move(context_switch_traces), process)); } void IntelPTMultiCoreTrace::ForEachCore( std::function callback) { - for (auto &it : m_traces_per_core) + for (auto &it : m_intelpt_traces_per_core) callback(it.first, *it.second); } -void IntelPTMultiCoreTrace::OnProcessStateChanged(lldb::StateType state) { - if (m_process_state == state) - return; - switch (state) { - case eStateStopped: - case eStateExited: { - ForEachCore([](core_id_t core_id, IntelPTSingleBufferTrace &core_trace) { - if (Error err = - core_trace.ChangeCollectionState(TraceCollectionState::Paused)) { - LLDB_LOG_ERROR(GetLog(POSIXLog::Trace), std::move(err), - "Unable to pause the core trace for core {0}", core_id); - } - }); - break; - } - case eStateRunning: { - ForEachCore([](core_id_t core_id, IntelPTSingleBufferTrace &core_trace) { - if (Error err = - core_trace.ChangeCollectionState(TraceCollectionState::Running)) { - LLDB_LOG_ERROR(GetLog(POSIXLog::Trace), std::move(err), - "Unable to resume the core trace for core {0}", core_id); - } - }); - break; - } - default: - break; - } +void IntelPTMultiCoreTrace::ForEachCore( + std::function + callback) { + for (auto &it : m_intelpt_traces_per_core) + callback(it.first, *it.second, + m_context_switch_traces_per_core.find(it.first)->second); +} + +void IntelPTMultiCoreTrace::ProcessDidStop() { + ForEachCore([](core_id_t core_id, IntelPTSingleBufferTrace &core_trace) { + if (Error err = core_trace.Pause()) { + LLDB_LOG_ERROR(GetLog(POSIXLog::Trace), std::move(err), + "Unable to pause the core trace for core {0}", core_id); + } + }); +} + +void IntelPTMultiCoreTrace::ProcessWillResume() { + ForEachCore([](core_id_t core_id, IntelPTSingleBufferTrace &core_trace) { + if (Error err = core_trace.Resume()) { + LLDB_LOG_ERROR(GetLog(POSIXLog::Trace), std::move(err), + "Unable to resume the core trace for core {0}", core_id); + } + }); } TraceGetStateResponse IntelPTMultiCoreTrace::GetState() { @@ -106,10 +163,13 @@ state.cores.emplace(); ForEachCore([&](lldb::core_id_t core_id, - const IntelPTSingleBufferTrace &core_trace) { + const IntelPTSingleBufferTrace &core_trace, + const PerfEvent &context_switch_trace) { state.cores->push_back( {core_id, - {{IntelPTDataKinds::kTraceBuffer, core_trace.GetTraceBufferSize()}}}); + {{IntelPTDataKinds::kTraceBuffer, core_trace.GetTraceBufferSize()}, + {IntelPTDataKinds::kPerfContextSwitchTrace, + context_switch_trace.GetEffectiveDataBufferSize()}}}); }); return state; diff --git a/lldb/source/Plugins/Process/Linux/IntelPTProcessTrace.h b/lldb/source/Plugins/Process/Linux/IntelPTProcessTrace.h --- a/lldb/source/Plugins/Process/Linux/IntelPTProcessTrace.h +++ b/lldb/source/Plugins/Process/Linux/IntelPTProcessTrace.h @@ -21,19 +21,9 @@ public: virtual ~IntelPTProcessTrace() = default; - /// This method should be invoked as early as possible whenever the process - /// resumes or stops so that intel-pt collection is not enabled when - /// the process is not running. A case in which this is useful in when - /// tracing is done per-core. In this case we want to prevent polluting the - /// core traces with executions of unrelated processes, which increases the - /// data loss of the target process, given that core traces don't filter by - /// process. - /// A possible way to avoid this is to use CR3 filtering, which is equivalent - /// to process filtering, but the perf_event API doesn't support it. - /// - /// \param[in] state - /// The new state of the target process. - virtual void OnProcessStateChanged(lldb::StateType state){}; + virtual void ProcessDidStop() {} + + virtual void ProcessWillResume() {} /// Construct a minimal jLLDBTraceGetState response for this process trace. virtual TraceGetStateResponse GetState() = 0; 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 @@ -27,11 +27,6 @@ using IntelPTSingleBufferTraceUP = std::unique_ptr; -enum class TraceCollectionState { - Running, - Paused, -}; - /// This class wraps a single perf event collecting intel pt data in a single /// buffer. class IntelPTSingleBufferTrace { @@ -48,8 +43,8 @@ /// \param[in] core_id /// The CPU core id where to trace. If \b None, then this traces all CPUs. /// - /// \param[in] initial_state - /// The initial trace collection state. + /// \param[in] disabled + /// Whether to start the tracing paused. /// /// \return /// A \a IntelPTSingleBufferTrace instance if tracing was successful, or @@ -57,8 +52,8 @@ static llvm::Expected Start(const TraceIntelPTStartRequest &request, llvm::Optional tid, - llvm::Optional core_id, - TraceCollectionState initial_state); + llvm::Optional core_id = llvm::None, + bool disabled = false); /// \return /// The bytes requested by a jLLDBTraceGetBinaryData packet that was routed @@ -89,16 +84,23 @@ /// trace instance. size_t GetTraceBufferSize() const; - /// Change the collection state for this trace. + /// Resume the collection of this trace. /// - /// This is a no-op if \p state is the same as the current state. - /// - /// \param[in] state - /// The new state. + /// \return + /// An error if the trace couldn't be resumed. If the trace is already + /// running, this returns \a Error::success(). + llvm::Error Resume(); + + /// Pause the collection of this trace. /// /// \return - /// An error if the state couldn't be changed. - llvm::Error ChangeCollectionState(TraceCollectionState state); + /// An error if the trace couldn't be paused. If the trace is already + /// paused, this returns \a Error::success(). + llvm::Error Pause(); + + /// \return + /// The underlying PerfEvent for this trace. + const PerfEvent &GetPerfEvent() const; private: /// Construct new \a IntelPTSingleBufferThreadTrace. Users are supposed to @@ -110,17 +112,11 @@ /// /// \param[in] collection_state /// The initial collection state for the provided perf_event. - IntelPTSingleBufferTrace(PerfEvent &&perf_event, - TraceCollectionState collection_state) - : m_perf_event(std::move(perf_event)), - m_collection_state(collection_state) {} + IntelPTSingleBufferTrace(PerfEvent &&perf_event) + : m_perf_event(std::move(perf_event)) {} /// perf event configured for IntelPT. PerfEvent m_perf_event; - - /// The initial state is stopped because tracing can only start when the - /// process is paused. - TraceCollectionState m_collection_state; }; } // namespace process_linux 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 @@ -205,23 +205,12 @@ return m_perf_event.GetAuxBuffer().size(); } -Error IntelPTSingleBufferTrace::ChangeCollectionState( - TraceCollectionState new_state) { - if (new_state == m_collection_state) - return Error::success(); +Error IntelPTSingleBufferTrace::Pause() { + return m_perf_event.DisableWithIoctl(); +} - switch (new_state) { - case TraceCollectionState::Paused: - if (Error err = m_perf_event.DisableWithIoctl()) - return err; - break; - case TraceCollectionState::Running: - if (Error err = m_perf_event.EnableWithIoctl()) - return err; - break; - } - m_collection_state = new_state; - return Error::success(); +Error IntelPTSingleBufferTrace::Resume() { + return m_perf_event.EnableWithIoctl(); } Expected> @@ -240,42 +229,13 @@ // // This is achieved by the PERF_EVENT_IOC_DISABLE ioctl request, as // mentioned in the man page of perf_event_open. - TraceCollectionState previous_state = m_collection_state; - if (Error err = ChangeCollectionState(TraceCollectionState::Paused)) - return std::move(err); - - std::vector data(size, 0); - perf_event_mmap_page &mmap_metadata = m_perf_event.GetMetadataPage(); - Log *log = GetLog(POSIXLog::Trace); - uint64_t head = mmap_metadata.aux_head; - - LLDB_LOG(log, "Aux size -{0} , Head - {1}", mmap_metadata.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 ->| - * - * */ - - MutableArrayRef buffer(data); - ReadCyclicBuffer(buffer, m_perf_event.GetAuxBuffer(), - static_cast(head), offset); - - if (Error err = ChangeCollectionState(previous_state)) - return std::move(err); - - return data; + return m_perf_event.ReadFlushedOutAuxCyclicBuffer(offset, size); } -Expected IntelPTSingleBufferTrace::Start( - const TraceIntelPTStartRequest &request, Optional tid, - Optional core_id, TraceCollectionState initial_state) { +Expected +IntelPTSingleBufferTrace::Start(const TraceIntelPTStartRequest &request, + Optional tid, + Optional core_id, bool disabled) { #ifndef PERF_ATTR_SIZE_VER5 return createStringError(inconvertibleErrorCode(), "Intel PT Linux perf event not supported"); @@ -303,7 +263,7 @@ })); if (!attr) return attr.takeError(); - attr->disabled = initial_state == TraceCollectionState::Paused; + attr->disabled = disabled; LLDB_LOG(log, "Will create trace buffer of size {0}", request.trace_buffer_size); @@ -314,10 +274,14 @@ return std::move(mmap_err); } IntelPTSingleBufferTraceUP trace_up( - new IntelPTSingleBufferTrace(std::move(*perf_event), initial_state)); + new IntelPTSingleBufferTrace(std::move(*perf_event))); return trace_up; } else { return perf_event.takeError(); } #endif } + +const PerfEvent &IntelPTSingleBufferTrace::GetPerfEvent() const { + return m_perf_event; +} diff --git a/lldb/source/Plugins/Process/Linux/IntelPTThreadTraceCollection.cpp b/lldb/source/Plugins/Process/Linux/IntelPTThreadTraceCollection.cpp --- a/lldb/source/Plugins/Process/Linux/IntelPTThreadTraceCollection.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTThreadTraceCollection.cpp @@ -34,8 +34,7 @@ "Thread %" PRIu64 " already traced", tid); Expected trace_up = - IntelPTSingleBufferTrace::Start(request, tid, /*core_id=*/None, - TraceCollectionState::Running); + IntelPTSingleBufferTrace::Start(request, tid); if (!trace_up) return trace_up.takeError(); 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 @@ -209,7 +209,9 @@ /// stopping for threads being destroyed. Status NotifyTracersOfThreadDestroyed(lldb::tid_t tid); - void NotifyTracersProcessStateChanged(lldb::StateType state) override; + void NotifyTracersProcessWillResume() override; + + void NotifyTracersProcessDidStop() override; /// Writes the raw event message code (vis-a-vis PTRACE_GETEVENTMSG) /// corresponding to the given thread ID to the memory pointed to by @p 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 @@ -898,6 +898,8 @@ Log *log = GetLog(POSIXLog::Process); LLDB_LOG(log, "pid {0}", GetID()); + NotifyTracersProcessWillResume(); + bool software_single_step = !SupportHardwareSingleStepping(); if (software_single_step) { @@ -1665,9 +1667,12 @@ SignalIfAllThreadsStopped(); } -void NativeProcessLinux::NotifyTracersProcessStateChanged( - lldb::StateType state) { - m_intel_pt_collector.OnProcessStateChanged(state); +void NativeProcessLinux::NotifyTracersProcessDidStop() { + m_intel_pt_collector.ProcessDidStop(); +} + +void NativeProcessLinux::NotifyTracersProcessWillResume() { + m_intel_pt_collector.ProcessWillResume(); } Status NativeProcessLinux::NotifyTracersOfNewThread(lldb::tid_t tid) { 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 @@ -98,6 +98,11 @@ /// Handles the management of the event's file descriptor and mmap'ed /// regions. class PerfEvent { + enum class CollectionState { + Enabled, + Disabled, + }; + public: /// Create a new performance monitoring event via the perf_event_open syscall. /// @@ -213,27 +218,59 @@ /// \a ArrayRef extending \a aux_size bytes from \a aux_offset. llvm::ArrayRef GetAuxBuffer() const; - /// Use the ioctl API to disable the perf event. This doesn't terminate the - /// perf event. + /// Read the aux buffer managed by this perf event. To ensure that the + /// data is up-to-date and is not corrupted by read-write race conditions, the + /// underlying perf_event is paused during read, and later it's returned to + /// its initial state. The returned data will be linear, i.e. it will fix the + /// circular wrapping the might exist int he buffer. + /// + /// \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> + ReadFlushedOutAuxCyclicBuffer(size_t offset, size_t size); + + /// Use the ioctl API to disable the perf event and all the events in its + /// group. This doesn't terminate the perf event. + /// + /// This is no-op if the perf event is already disabled. /// /// \return /// An Error if the perf event couldn't be disabled. - llvm::Error DisableWithIoctl() const; + llvm::Error DisableWithIoctl(); - /// Use the ioctl API to enable the perf event. + /// Use the ioctl API to enable the perf event and all the events in its + /// group. + /// + /// This is no-op if the perf event is already enabled. /// /// \return /// An Error if the perf event couldn't be enabled. - llvm::Error EnableWithIoctl() const; + llvm::Error EnableWithIoctl(); + + /// \return + /// The size in bytes of the section of the data buffer that has effective + /// data. + size_t GetEffectiveDataBufferSize() const; private: /// Create new \a PerfEvent. /// /// \param[in] fd /// File descriptor of the perf event. - PerfEvent(long fd) + /// + /// \param[in] initial_state + /// Initial collection state configured for this perf_event. + PerfEvent(long fd, CollectionState initial_state) : m_fd(new long(fd), resource_handle::FileDescriptorDeleter()), - m_metadata_data_base(), m_aux_base() {} + m_collection_state(initial_state) {} /// Wrapper for \a mmap to provide custom error messages. /// @@ -270,6 +307,8 @@ /// AUX buffer is a separate region for high-bandwidth data streams /// such as IntelPT. resource_handle::MmapUP m_aux_base; + /// The state of the underlying perf_event. + CollectionState m_collection_state; }; /// Load \a PerfTscConversionParameters from \a perf_event_mmap_page, if 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 @@ -130,7 +130,8 @@ llvm::formatv("perf event syscall failed: {0}", std::strerror(errno)); return llvm::createStringError(llvm::inconvertibleErrorCode(), err_msg); } - return PerfEvent{fd}; + return PerfEvent(fd, attr.disabled ? CollectionState::Disabled + : CollectionState::Enabled); } llvm::Expected PerfEvent::Init(perf_event_attr &attr, @@ -143,7 +144,7 @@ PerfEvent::DoMmap(void *addr, size_t length, int prot, int flags, long int offset, llvm::StringRef buffer_name) { errno = 0; - auto mmap_result = ::mmap(nullptr, length, prot, flags, GetFd(), offset); + auto mmap_result = ::mmap(addr, length, prot, flags, GetFd(), offset); if (mmap_result == MAP_FAILED) { std::string err_msg = @@ -157,7 +158,7 @@ llvm::Error PerfEvent::MmapMetadataAndDataBuffer(size_t num_data_pages) { size_t mmap_size = (num_data_pages + 1) * getpagesize(); if (Expected mmap_metadata_data = - DoMmap(nullptr, mmap_size, PROT_WRITE, MAP_SHARED, 0, + DoMmap(nullptr, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, 0, "metadata and data buffer")) { m_metadata_data_base = std::move(mmap_metadata_data.get()); return Error::success(); @@ -221,18 +222,72 @@ static_cast(mmap_metadata.aux_size)}; } -Error PerfEvent::DisableWithIoctl() const { - if (ioctl(*m_fd, PERF_EVENT_IOC_DISABLE) < 0) +Expected> +PerfEvent::ReadFlushedOutAuxCyclicBuffer(size_t offset, size_t size) { + CollectionState previous_state = m_collection_state; + if (Error err = DisableWithIoctl()) + return std::move(err); + + std::vector data(size, 0); + perf_event_mmap_page &mmap_metadata = GetMetadataPage(); + Log *log = GetLog(POSIXLog::Trace); + uint64_t head = mmap_metadata.aux_head; + + LLDB_LOG(log, "Aux size -{0} , Head - {1}", mmap_metadata.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 ->| + * + * */ + + MutableArrayRef buffer(data); + ReadCyclicBuffer(buffer, GetAuxBuffer(), static_cast(head), offset); + + if (previous_state == CollectionState::Enabled) { + if (Error err = EnableWithIoctl()) + return std::move(err); + } + + return data; +} + +Error PerfEvent::DisableWithIoctl() { + if (m_collection_state == CollectionState::Disabled) + return Error::success(); + + if (ioctl(*m_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) < 0) return createStringError(inconvertibleErrorCode(), "Can't disable perf event. %s", std::strerror(errno)); + + m_collection_state = CollectionState::Disabled; return Error::success(); } -Error PerfEvent::EnableWithIoctl() const { - if (ioctl(*m_fd, PERF_EVENT_IOC_ENABLE) < 0) +Error PerfEvent::EnableWithIoctl() { + if (m_collection_state == CollectionState::Enabled) + return Error::success(); + + if (ioctl(*m_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) < 0) return createStringError(inconvertibleErrorCode(), - "Can't disable perf event. %s", + "Can't enable perf event. %s", std::strerror(errno)); + + m_collection_state = CollectionState::Enabled; return Error::success(); } + +size_t PerfEvent::GetEffectiveDataBufferSize() const { + perf_event_mmap_page &mmap_metadata = GetMetadataPage(); + if (mmap_metadata.data_head < mmap_metadata.data_size) + return mmap_metadata.data_head; + else + return mmap_metadata.data_size; // The buffer has wrapped. +} 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 @@ -15,6 +15,8 @@ const char *IntelPTDataKinds::kProcFsCpuInfo = "procfsCpuInfo"; const char *IntelPTDataKinds::kTraceBuffer = "traceBuffer"; +const char *IntelPTDataKinds::kPerfContextSwitchTrace = + "perfContextSwitchTrace"; bool TraceIntelPTStartRequest::IsPerCoreTracing() const { return per_core_tracing.getValueOr(false); 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 @@ -1,4 +1,5 @@ import lldb +import json from intelpt_testcase import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil @@ -195,8 +196,40 @@ 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":']) + # We move forward a little bit to collect some data + self.expect("b 19") + self.expect("c") + + # We will assert that the trace state will contain valid context switch and trace buffer entries + + # We first parse the json response from the custom packet + self.runCmd("""process plugin packet send 'jLLDBTraceGetState:{"type":"intel-pt"}]'""") + response_header = 'response: ' + output = None + for line in self.res.GetOutput().splitlines(): + if line.find(response_header) != -1: + response = line[line.find(response_header) + len(response_header):].strip() + output = json.loads(response) + + self.assertTrue(output is not None) + self.assertIn("cores", output) + found_non_empty_context_switch = False + + for core in output["cores"]: + context_switch_size = None + trace_buffer_size = None + for binary_data in core["binaryData"]: + if binary_data["kind"] == "traceBuffer": + trace_buffer_size = binary_data["size"] + elif binary_data["kind"] == "perfContextSwitchTrace": + context_switch_size = binary_data["size"] + self.assertTrue(context_switch_size is not None) + self.assertTrue(trace_buffer_size is not None) + if context_switch_size > 0: + found_non_empty_context_switch = True + + # We must have captured the context switch of when the target resumed + self.assertTrue(found_non_empty_context_switch) + self.traceStopProcess()