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<SoftwareBreakpoint>
Log *log = GetLog(LLDBLog::Process);
m_delegate.ProcessStateChanged(this, state);
+ NotifyTracersProcessStateChanged(state);
LLDB_LOG(log, "sent state notification [{0}] from process {1}", state,
GetID());
"Thread %" PRIu64 " already traced", tid);
Expected<IntelPTSingleBufferTraceUP> trace_up =
- IntelPTSingleBufferTrace::Start(request, tid);
+ IntelPTSingleBufferTrace::Start(request, tid, /*core_id=*/None,
+ TraceCollectionState::Running);
if (!trace_up)
return trace_up.takeError();
return states;
}
-Expected<const IntelPTSingleBufferTrace &>
-IntelPTThreadTraceCollection::GetTracedThread(lldb::tid_t tid) const {
+Expected<IntelPTSingleBufferTrace &>
+IntelPTThreadTraceCollection::GetTracedThread(lldb::tid_t tid) {
auto it = m_thread_traces.find(tid);
if (it == m_thread_traces.end())
return createStringError(inconvertibleErrorCode(),
return m_thread_traces.TraceStart(tid, m_tracing_params);
}
-const IntelPTThreadTraceCollection &
-IntelPTPerThreadProcessTrace::GetThreadTraces() const {
+IntelPTThreadTraceCollection &IntelPTPerThreadProcessTrace::GetThreadTraces() {
return m_thread_traces;
}
}
}
+void IntelPTCollector::OnProcessStateChanged(lldb::StateType state) {
+ if (m_per_core_process_trace_up)
+ m_per_core_process_trace_up->OnProcessStateChanged(state);
+}
+
Error IntelPTCollector::OnThreadCreated(lldb::tid_t tid) {
if (m_per_thread_process_trace_up)
return m_per_thread_process_trace_up->TraceStart(tid);
return toJSON(state);
}
-Expected<const IntelPTSingleBufferTrace &>
-IntelPTCollector::GetTracedThread(lldb::tid_t tid) const {
+Expected<IntelPTSingleBufferTrace &>
+IntelPTCollector::GetTracedThread(lldb::tid_t 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(
}
Expected<std::vector<uint8_t>>
-IntelPTCollector::GetBinaryData(const TraceGetBinaryDataRequest &request) const {
+IntelPTCollector::GetBinaryData(const TraceGetBinaryDataRequest &request) {
if (request.kind == IntelPTDataKinds::kTraceBuffer) {
- if (Expected<const IntelPTSingleBufferTrace &> trace =
+ if (Expected<IntelPTSingleBufferTrace &> trace =
GetTracedThread(*request.tid))
return trace->GetTraceBuffer(request.offset, request.size);
else
std::vector<TraceThreadState> GetThreadStates() const;
- llvm::Expected<const IntelPTSingleBufferTrace &>
- GetTracedThread(lldb::tid_t tid) const;
+ llvm::Expected<IntelPTSingleBufferTrace &> GetTracedThread(lldb::tid_t tid);
llvm::Error TraceStart(lldb::tid_t tid,
const TraceIntelPTStartRequest &request);
bool TracesThread(lldb::tid_t tid) const;
- const IntelPTThreadTraceCollection &GetThreadTraces() const;
+ IntelPTThreadTraceCollection &GetThreadTraces();
llvm::Error TraceStart(lldb::tid_t tid);
static bool IsSupported();
+ /// To be invoked whenever the state of the target process has changed.
+ void OnProcessStateChanged(lldb::StateType state);
+
/// If "process tracing" is enabled, then trace the given thread.
llvm::Error OnThreadCreated(lldb::tid_t tid);
/// Implementation of the jLLDBTraceGetBinaryData packet
llvm::Expected<std::vector<uint8_t>>
- GetBinaryData(const TraceGetBinaryDataRequest &request) const;
+ GetBinaryData(const TraceGetBinaryDataRequest &request);
/// Dispose of all traces
void Clear();
llvm::Error TraceStart(lldb::tid_t tid,
const TraceIntelPTStartRequest &request);
- llvm::Expected<const IntelPTSingleBufferTrace &>
- GetTracedThread(lldb::tid_t tid) const;
+ llvm::Expected<IntelPTSingleBufferTrace &> GetTracedThread(lldb::tid_t tid);
bool IsProcessTracingEnabled() const;
#include "Procfs.h"
+#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
+
using namespace lldb;
using namespace lldb_private;
using namespace process_linux;
llvm::DenseMap<core_id_t, IntelPTSingleBufferTraceUP> buffers;
for (core_id_t core_id : *core_ids) {
if (Expected<IntelPTSingleBufferTraceUP> core_trace =
- IntelPTSingleBufferTrace::Start(request, /*tid=*/None, core_id))
+ IntelPTSingleBufferTrace::Start(request, /*tid=*/None, core_id,
+ TraceCollectionState::Paused))
buffers.try_emplace(core_id, std::move(*core_trace));
else
return IncludePerfEventParanoidMessageInError(core_trace.takeError());
}
void IntelPTMultiCoreTrace::ForEachCore(
- std::function<void(core_id_t core_id,
- const IntelPTSingleBufferTrace &core_trace)>
+ std::function<void(core_id_t core_id, IntelPTSingleBufferTrace &core_trace)>
callback) {
for (auto &it : m_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;
+ }
+}
///
/// \param[in] callback.core_trace
/// The single-buffer trace instance for the given core.
- void
- ForEachCore(std::function<void(lldb::core_id_t core_id,
- const IntelPTSingleBufferTrace &core_trace)>
- callback);
+ void ForEachCore(std::function<void(lldb::core_id_t core_id,
+ IntelPTSingleBufferTrace &core_trace)>
+ callback);
+
+ /// 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. This is done 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.
+ void OnProcessStateChanged(lldb::StateType state);
private:
IntelPTMultiCoreTrace(
: m_traces_per_core(std::move(traces_per_core)) {}
llvm::DenseMap<lldb::core_id_t, IntelPTSingleBufferTraceUP> 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;
};
} // namespace process_linux
#include <sstream>
#include <linux/perf_event.h>
-#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <unistd.h>
return m_perf_event.GetAuxBuffer().size();
}
+Error IntelPTSingleBufferTrace::ChangeCollectionState(
+ TraceCollectionState new_state) {
+ if (new_state == m_collection_state)
+ return Error::success();
+
+ 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();
+}
+
Expected<std::vector<uint8_t>>
-IntelPTSingleBufferTrace::GetTraceBuffer(size_t offset, size_t size) const {
- auto fd = m_perf_event.GetFd();
- perf_event_mmap_page &mmap_metadata = m_perf_event.GetMetadataPage();
+IntelPTSingleBufferTrace::GetTraceBuffer(size_t offset, size_t size) {
// 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
+ // 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(fd, PERF_EVENT_IOC_DISABLE);
+ // 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<uint8_t> data(size, 0);
+ perf_event_mmap_page &mmap_metadata = m_perf_event.GetMetadataPage();
Log *log = GetLog(POSIXLog::Trace);
- Status error;
uint64_t head = mmap_metadata.aux_head;
LLDB_LOG(log, "Aux size -{0} , Head - {1}", mmap_metadata.aux_size, head);
*
* */
- std::vector<uint8_t> data(size, 0);
MutableArrayRef<uint8_t> buffer(data);
ReadCyclicBuffer(buffer, m_perf_event.GetAuxBuffer(),
static_cast<size_t>(head), offset);
- // Reenable tracing now we have read the buffer
- ioctl(fd, PERF_EVENT_IOC_ENABLE);
+ if (Error err = ChangeCollectionState(previous_state))
+ return std::move(err);
+
return data;
}
-Expected<IntelPTSingleBufferTraceUP>
-IntelPTSingleBufferTrace::Start(const TraceIntelPTStartRequest &request,
- Optional<lldb::tid_t> tid,
- Optional<core_id_t> core_id) {
+Expected<IntelPTSingleBufferTraceUP> IntelPTSingleBufferTrace::Start(
+ const TraceIntelPTStartRequest &request, Optional<lldb::tid_t> tid,
+ Optional<core_id_t> core_id, TraceCollectionState initial_state) {
#ifndef PERF_ATTR_SIZE_VER5
return createStringError(inconvertibleErrorCode(),
"Intel PT Linux perf event not supported");
}));
if (!attr)
return attr.takeError();
+ attr->disabled = initial_state == TraceCollectionState::Paused;
LLDB_LOG(log, "Will create trace buffer of size {0}",
request.trace_buffer_size);
buffer_numpages)) {
return std::move(mmap_err);
}
- return IntelPTSingleBufferTraceUP(
- new IntelPTSingleBufferTrace(std::move(*perf_event)));
+ IntelPTSingleBufferTraceUP trace_up(
+ new IntelPTSingleBufferTrace(std::move(*perf_event), initial_state));
+ return trace_up;
} else {
return perf_event.takeError();
}
using IntelPTSingleBufferTraceUP = std::unique_ptr<IntelPTSingleBufferTrace>;
+enum class TraceCollectionState {
+ Running,
+ Paused,
+};
+
/// This class wraps a single perf event collecting intel pt data in a single
/// buffer.
class IntelPTSingleBufferTrace {
/// \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.
+ ///
/// \return
/// A \a IntelPTSingleBufferTrace instance if tracing was successful, or
/// an \a llvm::Error otherwise.
static llvm::Expected<IntelPTSingleBufferTraceUP>
Start(const TraceIntelPTStartRequest &request,
llvm::Optional<lldb::tid_t> tid,
- llvm::Optional<lldb::core_id_t> core_id = llvm::None);
+ llvm::Optional<lldb::core_id_t> core_id,
+ TraceCollectionState initial_state);
/// \return
/// The bytes requested by a jLLDBTraceGetBinaryData packet that was routed
llvm::Expected<std::vector<uint8_t>>
GetBinaryData(const TraceGetBinaryDataRequest &request) const;
- /// Read the trace buffer managed by this trace instance.
+ /// Read the trace buffer managed by this trace instance. 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.
///
/// \param[in] offset
/// Offset of the data to read.
/// size of the requested \a size. Non-available positions will be
/// filled with zeroes.
llvm::Expected<std::vector<uint8_t>> GetTraceBuffer(size_t offset,
- size_t size) const;
+ size_t size);
/// \return
- /// The total the size in bytes used by the trace buffer managed by this
- /// trace instance.
+ /// The total the size in bytes used by the trace buffer managed by this
+ /// trace instance.
size_t GetTraceBufferSize() const;
+ /// Change the collection state for 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 state couldn't be changed.
+ llvm::Error ChangeCollectionState(TraceCollectionState state);
+
private:
/// Construct new \a IntelPTSingleBufferThreadTrace. Users are supposed to
/// create instances of this class via the \a Start() method and not invoke
///
/// \param[in] perf_event
/// perf event configured for IntelPT.
- IntelPTSingleBufferTrace(PerfEvent &&perf_event)
- : m_perf_event(std::move(perf_event)) {}
+ ///
+ /// \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) {}
/// 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
SignalIfAllThreadsStopped();
}
+void NativeProcessLinux::NotifyTracersProcessStateChanged(
+ lldb::StateType state) {
+ m_intel_pt_collector.OnProcessStateChanged(state);
+}
+
Status NativeProcessLinux::NotifyTracersOfNewThread(lldb::tid_t tid) {
Log *log = GetLog(POSIXLog::Thread);
Status error(m_intel_pt_collector.OnThreadCreated(tid));
/// stopping for threads being destroyed.
Status NotifyTracersOfThreadDestroyed(lldb::tid_t tid);
+ void NotifyTracersProcessStateChanged(lldb::StateType state) 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
/// message.
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/MemoryBuffer.h"
+#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
return {reinterpret_cast<uint8_t *>(m_aux_base.get()),
static_cast<size_t>(mmap_metadata.aux_size)};
}
+
+Error PerfEvent::DisableWithIoctl() const {
+ if (ioctl(*m_fd, PERF_EVENT_IOC_DISABLE) < 0)
+ return createStringError(inconvertibleErrorCode(),
+ "Can't disable perf event. %s",
+ std::strerror(errno));
+ return Error::success();
+}
+
+Error PerfEvent::EnableWithIoctl() const {
+ if (ioctl(*m_fd, PERF_EVENT_IOC_ENABLE) < 0)
+ return createStringError(inconvertibleErrorCode(),
+ "Can't disable perf event. %s",
+ std::strerror(errno));
+ return Error::success();
+}
/// \a ArrayRef<uint8_t> extending \a aux_size bytes from \a aux_offset.
llvm::ArrayRef<uint8_t> GetAuxBuffer() const;
+ /// Use the ioctl API to disable the perf event. This doesn't terminate the
+ /// perf event.
+ ///
+ /// \return
+ /// An Error if the perf event couldn't be disabled.
+ llvm::Error DisableWithIoctl() const;
+
+ /// Use the ioctl API to enable the perf event.
+ ///
+ /// \return
+ /// An Error if the perf event couldn't be enabled.
+ llvm::Error EnableWithIoctl() const;
+
private:
/// Create new \a PerfEvent.
///