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 @@ -13,6 +13,7 @@ // C++ Includes #include +#include "llvm/Support/ErrorOr.h" // Other libraries and framework includes #include "lldb/Core/ArchSpec.h" #include "lldb/Host/Debug.h" @@ -23,6 +24,7 @@ #include "lldb/lldb-types.h" #include "NativeThreadLinux.h" +#include "ProcessorTrace.h" #include "lldb/Host/common/NativeProcessProtocol.h" namespace lldb_private { @@ -105,6 +107,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 = LLDB_INVALID_THREAD_ID) 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 +246,40 @@ void SigchldHandler(); Status PopulateMemoryRegionCache(); + + lldb::user_id_t StartTracingAllThreads(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; }; } // namespace process_linux Index: source/Plugins/Process/Linux/NativeProcessLinux.cpp =================================================================== --- source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -607,6 +607,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 +720,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 +1333,9 @@ e; // Save the error, but still attempt to detach from other threads. } + m_processor_trace_monitor.clear(); + ProcessorTraceMonitor::SetProcessTraceID(GetID(), LLDB_INVALID_UID); + return error; } @@ -2119,6 +2124,8 @@ } } + if (found) + StopTracingForThread(thread_id); SignalIfAllThreadsStopped(); return found; } @@ -2136,6 +2143,25 @@ auto thread_sp = std::make_shared(this, thread_id); m_threads.push_back(thread_sp); + + if (ProcessorTraceMonitor::GetProcessTraceID(GetID()) != LLDB_INVALID_UID) { + auto process_config = ProcessorTraceMonitor::GetProcessTraceConfig(GetID()); + if (!process_config) { + LLDB_LOG(log, "process_config not found"); + return thread_sp; + } + auto traceMonitor = ProcessorTraceMonitor::Create( + GetID(), thread_id, *process_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 +2477,261 @@ 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 == ProcessorTraceMonitor::GetProcessTraceID(GetID())) { + 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 && + traceid == ProcessorTraceMonitor::GetProcessTraceID(GetID())) { + auto process_config = ProcessorTraceMonitor::GetProcessTraceConfig(GetID()); + if (!process_config) { + error = process_config.takeError(); + return error; + } + config = *process_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::StartTracingAllThreads(const TraceOptions &config, + Status &error) { + + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + if (config.getType() != TraceType::eTraceTypeProcessorTrace) + return LLDB_INVALID_UID; + + if (ProcessorTraceMonitor::GetProcessTraceID(GetID()) != LLDB_INVALID_UID) { + error.SetErrorString("tracing already active on this process"); + return ProcessorTraceMonitor::GetProcessTraceID(GetID()); + } + + ProcessorTraceMonitor::SetProcessTraceConfig(GetID(), config); + + 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))); + } + } + + LLDB_LOG(log,"Process Trace ID {0}", + ProcessorTraceMonitor::GetProcessTraceID(GetID())); + return ProcessorTraceMonitor::GetProcessTraceID(GetID()); +} + +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 StartTracingAllThreads(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() == ProcessorTraceMonitor::GetProcessTraceID(GetID())) { + // 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 == ProcessorTraceMonitor::GetProcessTraceID(GetID()) && + 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(); + ProcessorTraceMonitor::SetProcessTraceID(GetID(), 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 == ProcessorTraceMonitor::GetProcessTraceID(GetID())) { + // 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,143 @@ +//===-- 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 + +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 { + int m_fd; + perf_event_mmap_page *m_mmap_base; + lldb::user_id_t m_traceid; + lldb::tid_t m_thread_id; + + llvm::MutableArrayRef m_data; + llvm::MutableArrayRef m_aux; + + // Counter to track trace instances. + static lldb::user_id_t m_trace_num; + + // Trace id of trace instance corresponding to the process. + static llvm::DenseMap m_pt_process_traceid; + + // TraceOptions to be used for the complete process + // in case whole process is being traced.This config + // will also be applied to newly spawned threads. + static llvm::DenseMap m_pt_process_config; + + uint64_t GetAuxBufferSize() const; + + uint64_t GetDataBufferSize() const; + + void SetTraceID(lldb::user_id_t traceid) { m_traceid = traceid; } + + static Status GetCPUType(StructuredData::DictionarySP params_dict); + + Status StartTrace(lldb::pid_t pid, lldb::tid_t tid, + const TraceOptions &config); + + ProcessorTraceMonitor() + : m_fd(-1), + m_mmap_base(nullptr), m_traceid(LLDB_INVALID_UID), + m_thread_id(LLDB_INVALID_THREAD_ID){}; + +public: + 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); + + Status Destroy(); + + ~ProcessorTraceMonitor() { (void)Destroy(); } + + void SetThreadID(lldb::tid_t tid) { m_thread_id = tid; } + + lldb::tid_t GetThreadID() const { return m_thread_id; } + + lldb::user_id_t GetTraceID() const { return m_traceid; } + + Status GetTraceConfig(TraceOptions &config) const; + + static llvm::Expected GetProcessTraceConfig(lldb::pid_t pid) { + if (m_pt_process_config.count(pid) == 0) + return Status("tracing not active for this process").ToError(); + return m_pt_process_config[pid]; + } + + static lldb::user_id_t GetProcessTraceID(lldb::pid_t pid) { + if (m_pt_process_traceid.count(pid) == 0) + return LLDB_INVALID_UID; + return m_pt_process_traceid[pid]; + } + + static Status SetProcessTraceConfig(lldb::pid_t pid, const TraceOptions &config); + + static void SetProcessTraceID(lldb::pid_t pid, lldb::user_id_t traceid) { + m_pt_process_traceid[pid] = traceid; + } + + // --------------------------------------------------------------------- + /// 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,508 @@ +//===-- 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 +#include + +using namespace lldb; +using namespace lldb_private; +using namespace process_linux; +using namespace llvm; + +lldb::user_id_t ProcessorTraceMonitor::m_trace_num = 0; +llvm::DenseMap ProcessorTraceMonitor::m_pt_process_traceid; +llvm::DenseMap ProcessorTraceMonitor::m_pt_process_config; + +uint64_t ProcessorTraceMonitor::GetAuxBufferSize() const { + return m_aux.size(); +} + +uint64_t ProcessorTraceMonitor::GetDataBufferSize() const { + return m_data.size(); +} + +Status ProcessorTraceMonitor::GetTraceConfig(TraceOptions &config) const { + Status error; + + config.setType(lldb::TraceType::eTraceTypeProcessorTrace); + uint64_t data_size = GetDataBufferSize(); + config.setMetaDataBufferSize(data_size); + + uint64_t aux_size = GetAuxBufferSize(); + config.setTraceBufferSize(aux_size); + + auto custom_params_sp = std::make_shared(); + error = GetCPUType(custom_params_sp); + if (error.Fail()) + return error; + + 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(custom_params_sp))); + + config.setTraceParams(intel_custom_params); + + return error; +} + +Status +ProcessorTraceMonitor::SetProcessTraceConfig(lldb::pid_t pid, const TraceOptions &config) { + m_pt_process_config[pid] = config; + + auto custom_params_sp = std::make_shared(); + Status error = ProcessorTraceMonitor::GetCPUType(custom_params_sp); + if (error.Fail()) + return error; + + 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(custom_params_sp))); + + m_pt_process_config[pid].setTraceParams(intel_custom_params); + + return error; +} + +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"); + error.SetErrorString("file not found"); + return error; + } + + 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 {0}", error.AsCString()); + + return error; + } + + errno = 0; + m_fd = + syscall(SYS_perf_event_open, &attr, static_cast<::tid_t>(tid), -1, -1, 0); + if (m_fd == -1) { + LLDB_LOG(log, "syscall error {0}", errno); + error.SetErrorString("perf event syscall Failed"); + return error; + } + + errno = 0; + auto base = + mmap(NULL, (metabufsize + page_size), PROT_WRITE, MAP_SHARED, m_fd, 0); + if (base == MAP_FAILED) { + base = nullptr; + LLDB_LOG(log, "mmap base error {0}", errno); + error.SetErrorString("Meta buffer allocation failed"); + (void)Destroy(); + return error; + } + m_mmap_base = reinterpret_cast (base); + m_data = llvm::MutableArrayRef ((reinterpret_cast(m_mmap_base) + m_mmap_base->data_offset), m_mmap_base->data_size); + + m_mmap_base->aux_offset = m_mmap_base->data_offset + m_mmap_base->data_size; + m_mmap_base->aux_size = bufsize; + + errno = 0; + void *mmap_aux = mmap(NULL, bufsize, PROT_READ, MAP_SHARED, m_fd, + static_cast(m_mmap_base->aux_offset)); + if (mmap_aux == MAP_FAILED) { + LLDB_LOG(log, "second mmap done {0}", errno); + error.SetErrorString("Trace buffer allocation failed"); + (void)Destroy(); + return error; + } + m_aux = llvm::MutableArrayRef (reinterpret_cast (mmap_aux),bufsize); +#endif + return error; +} + +Status +ProcessorTraceMonitor::GetCPUType(StructuredData::DictionarySP params_dict) { + + Status error; + uint64_t cpu_family = -1; + uint64_t model = -1; + uint64_t stepping = -1; + std::string vendor_id; + + if (!params_dict) { + error.SetErrorString("null pointer"); + return error; + } + + 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.c_str()); + + if ((cpu_family != static_cast(-1)) && + (model != static_cast(-1)) && + (stepping != static_cast(-1)) && (!vendor_id.empty())) { + 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); + 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()); + } + + if (useProcessSettings) { + if (GetProcessTraceID(pid) == LLDB_INVALID_UID) + SetProcessTraceID(pid, ++m_trace_num); + LLDB_LOG(log, "m_trace_num {0}", m_trace_num); + LLDB_LOG(log, "m_pt_process_traceid {0}", + m_pt_process_traceid[pid]); + } + + 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(m_pt_process_traceid[pid]); + + // The buffer sizes also need to be updated + // as the buffer sizes might have been rounded + // to page sizes. + uint64_t data_size = pt_monitor_up->GetDataBufferSize(); + uint64_t aux_size = pt_monitor_up->GetAuxBufferSize(); + + m_pt_process_config[pid].setTraceBufferSize(aux_size); + m_pt_process_config[pid].setMetaDataBufferSize(data_size); + } 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::Destroy() { + Status error; + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + int ret; +#ifndef PERF_ATTR_SIZE_VER5 + llvm_unreachable("perf event not supported"); +#else + if (!m_aux.empty() && (m_mmap_base != nullptr)) { + ret = munmap(m_aux.data(), m_aux.size()); + if (ret != 0) { + LLDB_LOG(log, "munmap aux error {0}", errno); + error.SetErrorString("Trace buffer deallocation failed"); + } else + m_aux.drop_back(m_aux.size()); + } + // Since we allocate one page extra during + // initialization for the perf_event_mmap_page header + if (m_mmap_base != nullptr) { + int page_size = getpagesize(); + uint64_t data_size = m_data.size() + page_size; + ret = munmap(m_mmap_base, data_size); + if (ret != 0) { + LLDB_LOG(log, "munmap data error {0}", errno); + error.SetErrorString("Meta buffer deallocation failed"); + } else { + m_mmap_base = nullptr; + m_data.drop_back(m_data.size()); + } + } + if (m_fd != -1) { + ret = close(m_fd); + if (ret != 0) { + LLDB_LOG(log, "closing file descriptor error {0}", errno); + error.SetErrorString("perf event close failed"); + } else + m_fd = -1; + } +#endif + return error; +} + +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 + if (buffer.empty()) { + LLDB_LOG(log, "Empty buffer "); + error.SetErrorString("empty Trace buffer"); + return error; + } + + uint64_t head = m_mmap_base->aux_head; + + LLDB_LOG(log, "Aux size -{0} , Head - {1}", m_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, + m_aux, + 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 + if (buffer.empty()) { + LLDB_LOG(log, "Empty buffer "); + error.SetErrorString("empty Meta buffer"); + return error; + } + + uint64_t head = m_mmap_base->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); + + if (head > m_data.size()) { + head = head % m_data.size(); + LLDB_LOG(log, "Data size -{0} Head - {1}", m_data.size(), head); + + ReadCyclicBuffer( + buffer, + m_data, + 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 = m_data.slice(offset, (head-offset)); + auto remaining = std::copy(data.begin(), data.end(), buffer.begin()); + bytes_remaining -= (remaining - buffer.begin()); + } + buffer = llvm::MutableArrayRef(buffer.data(), + (buffer.size() - 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; + } + + auto firstpart = src.slice(src_cyc_index); + auto secondpart = src.drop_back(src_cyc_index); + size_t size_to_read = src.size() - offset; + + if (dst.size() >= size_to_read) { + if (offset >= firstpart.size()) { + secondpart = secondpart.slice(offset - firstpart.size()); + std::copy(secondpart.begin(), secondpart.end(), dst.begin()); + dst = dst.drop_back(dst.size() - secondpart.size()); + } else { + firstpart = firstpart.slice(offset); + auto read_end = + std::copy(firstpart.begin(), firstpart.end(), dst.begin()); + size_to_read -= firstpart.size(); + secondpart = secondpart.drop_back(secondpart.size() - size_to_read); + std::copy(secondpart.begin(), secondpart.end(), read_end); + dst = dst.drop_back(dst.size() - firstpart.size() - secondpart.size()); + } + } else { + if (offset >= firstpart.size()) { + secondpart = secondpart.slice(offset - firstpart.size()); + std::copy(secondpart.begin(), secondpart.begin() + dst.size(), + dst.begin()); + } else { + firstpart = firstpart.slice(offset); + if (dst.size() <= firstpart.size()) { + std::copy(firstpart.begin(), firstpart.begin() + dst.size(), + dst.begin()); + } else { + auto read_end = + std::copy(firstpart.begin(), firstpart.end(), dst.begin()); + secondpart = secondpart.drop_back(secondpart.size() - dst.size() + + firstpart.size()); + std::copy(secondpart.begin(), secondpart.end(), read_end); + } + } + } +} 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/CMakeLists.txt =================================================================== --- unittests/CMakeLists.txt +++ unittests/CMakeLists.txt @@ -73,4 +73,4 @@ if(LLDB_CAN_USE_DEBUGSERVER) add_subdirectory(debugserver) -endif() \ No newline at end of file +endif() 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,149 @@ +//===-- 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 "llvm/ADT/ArrayRef.h" +#include "ProcessorTrace.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)); + } +}