Index: include/lldb/Host/common/NativeProcessProtocol.h =================================================================== --- include/lldb/Host/common/NativeProcessProtocol.h +++ include/lldb/Host/common/NativeProcessProtocol.h @@ -335,7 +335,7 @@ //------------------------------------------------------------------ /// StopTracing API as the name suggests stops a tracing instance. /// - /// @param[in] uid + /// @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 @@ -348,7 +348,7 @@ /// @return /// Status indicating what went wrong. //------------------------------------------------------------------ - virtual Status StopTrace(lldb::user_id_t uid, + virtual Status StopTrace(lldb::user_id_t traceid, lldb::tid_t thread = LLDB_INVALID_THREAD_ID) { return Status("Not implemented"); } @@ -357,8 +357,8 @@ /// This API provides the trace data collected in the form of raw /// data. /// - /// @param[in] uid thread - /// The uid and thread provide the context for the trace + /// @param[in] traceid thread + /// The traceid and thread provide the context for the trace /// instance. /// /// @param[in] buffer @@ -374,7 +374,7 @@ /// @return /// The size of the data actually read. //------------------------------------------------------------------ - virtual Status GetData(lldb::user_id_t uid, lldb::tid_t thread, + virtual Status GetData(lldb::user_id_t traceid, lldb::tid_t thread, llvm::MutableArrayRef &buffer, size_t offset = 0) { return Status("Not implemented"); @@ -384,7 +384,7 @@ /// 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 uid, lldb::tid_t thread, + virtual Status GetMetaData(lldb::user_id_t traceid, lldb::tid_t thread, llvm::MutableArrayRef &buffer, size_t offset = 0) { return Status("Not implemented"); @@ -393,7 +393,7 @@ //------------------------------------------------------------------ /// API to query the TraceOptions for a given user id /// - /// @param[in] uid + /// @param[in] traceid /// The user id of the tracing instance. /// /// @param[in] config @@ -407,7 +407,7 @@ /// @param[out] config /// The actual configuration being used for tracing. //------------------------------------------------------------------ - virtual Status GetTraceConfig(lldb::user_id_t uid, TraceOptions &config) { + virtual Status GetTraceConfig(lldb::user_id_t traceid, TraceOptions &config) { return Status("Not implemented"); } Index: include/lldb/Host/linux/Support.h =================================================================== --- include/lldb/Host/linux/Support.h +++ include/lldb/Host/linux/Support.h @@ -22,6 +22,9 @@ llvm::ErrorOr> getProcFile(::pid_t pid, const llvm::Twine &file); +llvm::ErrorOr> +getProcFile(const llvm::Twine &file); + } // namespace lldb_private #endif // #ifndef LLDB_HOST_LINUX_SUPPORT_H Index: source/Host/linux/Support.cpp =================================================================== --- source/Host/linux/Support.cpp +++ source/Host/linux/Support.cpp @@ -32,3 +32,13 @@ LLDB_LOG(log, "Failed to open {0}: {1}", File, Ret.getError().message()); return Ret; } + +llvm::ErrorOr> +lldb_private::getProcFile(const llvm::Twine &file) { + Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + std::string File = ("/proc/" + file).str(); + auto Ret = llvm::MemoryBuffer::getFileAsStream(File); + if (!Ret) + LLDB_LOG(log, "Failed to open {0}: {1}", File, Ret.getError().message()); + return Ret; +} Index: source/Plugins/Process/Linux/CMakeLists.txt =================================================================== --- source/Plugins/Process/Linux/CMakeLists.txt +++ source/Plugins/Process/Linux/CMakeLists.txt @@ -11,6 +11,7 @@ NativeRegisterContextLinux_mips64.cpp NativeRegisterContextLinux_s390x.cpp NativeThreadLinux.cpp + ProcessorTrace.cpp SingleStepCheck.cpp LINK_LIBS Index: source/Plugins/Process/Linux/NativeProcessLinux.h =================================================================== --- source/Plugins/Process/Linux/NativeProcessLinux.h +++ source/Plugins/Process/Linux/NativeProcessLinux.h @@ -23,6 +23,7 @@ #include "lldb/lldb-types.h" #include "NativeThreadLinux.h" +#include "ProcessorTrace.h" #include "lldb/Host/common/NativeProcessProtocol.h" namespace lldb_private { @@ -105,6 +106,22 @@ return getProcFile(GetID(), "auxv"); } + lldb::user_id_t StartTrace(const TraceOptions &config, + Status &error) override; + + Status StopTrace(lldb::user_id_t traceid, + lldb::tid_t thread) override; + + Status GetData(lldb::user_id_t traceid, lldb::tid_t thread, + llvm::MutableArrayRef &buffer, + size_t offset = 0) override; + + Status GetMetaData(lldb::user_id_t traceid, lldb::tid_t thread, + llvm::MutableArrayRef &buffer, + size_t offset = 0) override; + + Status GetTraceConfig(lldb::user_id_t traceid, TraceOptions &config) override; + // --------------------------------------------------------------------- // Interface used by NativeRegisterContext-derived classes. // --------------------------------------------------------------------- @@ -228,6 +245,43 @@ void SigchldHandler(); 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; + TraceOptions m_pt_process_trace_config; }; } // namespace process_linux Index: source/Plugins/Process/Linux/NativeProcessLinux.cpp =================================================================== --- source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -289,7 +289,8 @@ NativeProcessLinux::NativeProcessLinux() : NativeProcessProtocol(LLDB_INVALID_PROCESS_ID), m_arch(), m_supports_mem_region(eLazyBoolCalculate), m_mem_region_cache(), - m_pending_notification_tid(LLDB_INVALID_THREAD_ID) {} + m_pending_notification_tid(LLDB_INVALID_THREAD_ID), + m_pt_proces_trace_id(LLDB_INVALID_UID) {} void NativeProcessLinux::AttachToInferior(MainLoop &mainloop, lldb::pid_t pid, Status &error) { @@ -607,6 +608,7 @@ info.si_pid); auto thread_sp = AddThread(pid); + // Resume the newly created thread. ResumeThread(*thread_sp, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER); ThreadWasCreated(*thread_sp); @@ -719,6 +721,7 @@ LLDB_LOG(log, "pid = {0}: tracking new thread tid {1}", GetID(), tid); new_thread_sp = AddThread(tid); + ResumeThread(*new_thread_sp, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER); ThreadWasCreated(*new_thread_sp); } @@ -1331,6 +1334,9 @@ 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; + return error; } @@ -2119,6 +2125,8 @@ } } + if (found) + StopTracingForThread(thread_id); SignalIfAllThreadsStopped(); return found; } @@ -2136,6 +2144,21 @@ auto thread_sp = std::make_shared(this, thread_id); m_threads.push_back(thread_sp); + + 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 thread_sp; } @@ -2451,3 +2474,258 @@ 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(); +} + +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; +} + +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); + } + m_processor_trace_monitor.erase(iter); + + return error; +} + +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; +} + +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; +} + +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; +} Index: source/Plugins/Process/Linux/ProcessorTrace.h =================================================================== --- /dev/null +++ source/Plugins/Process/Linux/ProcessorTrace.h @@ -0,0 +1,141 @@ +//===-- ProcessorTrace.h -------------------------------------- -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_ProcessorTrace_H_ +#define liblldb_ProcessorTrace_H_ + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" + +#include "lldb/Core/TraceOptions.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-types.h" + +#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 traace 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 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 \ No newline at end of file Index: source/Plugins/Process/Linux/ProcessorTrace.cpp =================================================================== --- /dev/null +++ source/Plugins/Process/Linux/ProcessorTrace.cpp @@ -0,0 +1,400 @@ +//===-- ProcessorTrace.cpp ------------------------------------ -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#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 + +using namespace lldb; +using namespace lldb_private; +using namespace process_linux; +using namespace llvm; + +lldb::user_id_t ProcessorTraceMonitor::m_trace_num = 1; + +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 +} + +Status ProcessorTraceMonitor::StartTrace(lldb::pid_t pid, lldb::tid_t tid, + const TraceOptions &config) { + Status error; + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + LLDB_LOG(log, "{0}", config.getThreadID()); + +#ifndef PERF_ATTR_SIZE_VER5 + llvm_unreachable("perf event not supported"); +#else + + 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(1ul, numpages); + bufsize = page_size * numpages; + + numpages = static_cast( + llvm::PowerOf2Floor((metabufsize + page_size - 1) / page_size)); + numpages = std::max(0ul, numpages); + 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; + + int intel_pt_type = 0; + + auto ret = llvm::MemoryBuffer::getFileAsStream( + "/sys/bus/event_source/devices/intel_pt/type"); + if (!ret) { + LLDB_LOG(log, "failed to open Config file"); + return ret.getError(); + } + + StringRef rest = ret.get()->getBuffer(); + if (rest.empty() || rest.trim().getAsInteger(10, intel_pt_type)) { + LLDB_LOG(log, "failed to read Config file"); + error.SetErrorString("invalid file"); + return error; + } + + rest.trim().getAsInteger(10, intel_pt_type); + 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::move(std::unique_ptr(new int(fd), file_close())); + + errno = 0; + auto base = + mmap(NULL, (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::move(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(NULL, 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::move(std::unique_ptr( + reinterpret_cast(mmap_aux), munmap_delete(bufsize))); +#endif + return error; +} + +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 std::move(error.ToError()); + } + + ProcessorTraceMonitorUP pt_monitor_up(new ProcessorTraceMonitor); + + error = pt_monitor_up->StartTrace(pid, tid, config); + if (error.Fail()) + return std::move(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) { + + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + Status error; + +#ifndef PERF_ATTR_SIZE_VER5 + llvm_unreachable("perf event not supported"); +#else + 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"); + return error; +#endif +} + +Status +ProcessorTraceMonitor::ReadPerfTraceData(llvm::MutableArrayRef &buffer, + size_t offset) { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + uint64_t bytes_remaining = buffer.size(); + Status error; +#ifndef PERF_ATTR_SIZE_VER5 + llvm_unreachable("perf event not supported"); +#else + + 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); +} Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -1280,9 +1280,9 @@ lldb::user_id_t uid = LLDB_INVALID_UID; - size_t byte_count = std::numeric_limits::max(); + uint64_t byte_count = std::numeric_limits::max(); lldb::tid_t tid = LLDB_INVALID_THREAD_ID; - size_t offset = std::numeric_limits::max(); + uint64_t offset = std::numeric_limits::max(); auto json_object = StructuredData::ParseJSON(packet.Peek()); @@ -1316,8 +1316,8 @@ if (error.Fail()) return SendErrorResponse(error.GetError()); - for (size_t i = 0; i < buf.size(); ++i) - response.PutHex8(buf[i]); + for (auto i : buf) + response.PutHex8(i); StreamGDBRemote escaped_response; escaped_response.PutEscapedBytes(response.GetData(), response.GetSize()); Index: unittests/Process/CMakeLists.txt =================================================================== --- unittests/Process/CMakeLists.txt +++ unittests/Process/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(gdb-remote) +add_subdirectory(Linux) add_subdirectory(minidump) Index: unittests/Process/Linux/CMakeLists.txt =================================================================== --- /dev/null +++ unittests/Process/Linux/CMakeLists.txt @@ -0,0 +1,8 @@ +include_directories(${LLDB_SOURCE_DIR}/source/Plugins/Process/Linux) + +add_lldb_unittest(ProcessorTraceTest + ProcessorTraceTest.cpp + + LINK_LIBS + lldbPluginProcessLinux + ) \ No newline at end of file Index: unittests/Process/Linux/ProcessorTraceTest.cpp =================================================================== --- /dev/null +++ unittests/Process/Linux/ProcessorTraceTest.cpp @@ -0,0 +1,152 @@ +//===-- ProcessorTraceMonitorTest.cpp ------------------------- -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "gtest/gtest.h" + +#include "ProcessorTrace.h" +#include "llvm/ADT/ArrayRef.h" +// C Includes + +// C++ Includes + +using namespace lldb_private; +using namespace process_linux; + +size_t ReadCylicBufferWrapper(void *buf, size_t buf_size, void *cyc_buf, + size_t cyc_buf_size, size_t cyc_start, + size_t offset) { + llvm::MutableArrayRef dst(reinterpret_cast(buf), + buf_size); + llvm::MutableArrayRef src(reinterpret_cast(cyc_buf), + cyc_buf_size); + ProcessorTraceMonitor::ReadCyclicBuffer(dst, src, cyc_start, offset); + return dst.size(); +} + +TEST(CyclicBuffer, EdgeCases) { + size_t bytes_read = 0; + uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'}; + + // We will always leave the last bytes untouched + // so that string comparisions work. + char bigger_buffer[10] = {}; + char equal_size_buffer[7] = {}; + char smaller_buffer[4] = {}; + + // empty buffer to read into + bytes_read = ReadCylicBufferWrapper(smaller_buffer, 0, cyclic_buffer, + sizeof(cyclic_buffer), 3, 0); + ASSERT_EQ(0, bytes_read); + + // empty cyclic buffer + bytes_read = ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer), + cyclic_buffer, 0, 3, 0); + ASSERT_EQ(0, bytes_read); + + // bigger offset + bytes_read = + ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer), + cyclic_buffer, sizeof(cyclic_buffer), 3, 6); + ASSERT_EQ(0, bytes_read); + + // wrong offset + bytes_read = + ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer), + cyclic_buffer, sizeof(cyclic_buffer), 3, 7); + ASSERT_EQ(0, bytes_read); + + // wrong start + bytes_read = + ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer), + cyclic_buffer, sizeof(cyclic_buffer), 3, 7); + ASSERT_EQ(0, bytes_read); +} + +TEST(CyclicBuffer, EqualSizeBuffer) { + size_t bytes_read = 0; + uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'}; + + char cyclic[] = "cyclic"; + for (int i = 0; i < sizeof(cyclic); i++) { + // We will always leave the last bytes untouched + // so that string comparisions work. + char equal_size_buffer[7] = {}; + bytes_read = + ReadCylicBufferWrapper(equal_size_buffer, sizeof(cyclic_buffer), + cyclic_buffer, sizeof(cyclic_buffer), 3, i); + ASSERT_EQ((sizeof(cyclic) - i - 1), bytes_read); + ASSERT_STREQ(equal_size_buffer, (cyclic + i)); + } +} + +TEST(CyclicBuffer, SmallerSizeBuffer) { + size_t bytes_read = 0; + uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'}; + + // We will always leave the last bytes untouched + // so that string comparisions work. + char smaller_buffer[4] = {}; + bytes_read = + ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1), + cyclic_buffer, sizeof(cyclic_buffer), 3, 0); + ASSERT_EQ(3, bytes_read); + ASSERT_STREQ(smaller_buffer, "cyc"); + + bytes_read = + ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1), + cyclic_buffer, sizeof(cyclic_buffer), 3, 1); + ASSERT_EQ(3, bytes_read); + ASSERT_STREQ(smaller_buffer, "ycl"); + + bytes_read = + ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1), + cyclic_buffer, sizeof(cyclic_buffer), 3, 2); + ASSERT_EQ(3, bytes_read); + ASSERT_STREQ(smaller_buffer, "cli"); + + bytes_read = + ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1), + cyclic_buffer, sizeof(cyclic_buffer), 3, 3); + ASSERT_EQ(3, bytes_read); + ASSERT_STREQ(smaller_buffer, "lic"); + + { + char smaller_buffer[4] = {}; + bytes_read = + ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1), + cyclic_buffer, sizeof(cyclic_buffer), 3, 4); + ASSERT_EQ(2, bytes_read); + ASSERT_STREQ(smaller_buffer, "ic"); + } + { + char smaller_buffer[4] = {}; + bytes_read = + ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1), + cyclic_buffer, sizeof(cyclic_buffer), 3, 5); + ASSERT_EQ(1, bytes_read); + ASSERT_STREQ(smaller_buffer, "c"); + } +} + +TEST(CyclicBuffer, BiggerSizeBuffer) { + size_t bytes_read = 0; + uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'}; + + char cyclic[] = "cyclic"; + for (int i = 0; i < sizeof(cyclic); i++) { + // We will always leave the last bytes untouched + // so that string comparisions work. + char bigger_buffer[10] = {}; + bytes_read = + ReadCylicBufferWrapper(bigger_buffer, (sizeof(bigger_buffer) - 1), + cyclic_buffer, sizeof(cyclic_buffer), 3, i); + ASSERT_EQ((sizeof(cyclic) - i - 1), bytes_read); + ASSERT_STREQ(bigger_buffer, (cyclic + i)); + } +}