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 = 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 +245,41 @@ 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. + ProcessorTraceMonitorSP LookupProcessorTraceInstance(lldb::user_id_t traceid, + lldb::tid_t thread, + Status &error); + + // 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. + Status 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,21 @@ info.si_pid); auto thread_sp = AddThread(pid); + + if (ProcessorTraceMonitor::GetProcessTraceID() != LLDB_INVALID_UID) { + auto traceMonitor = ProcessorTraceMonitor::Create( + GetID(), pid, ProcessorTraceMonitor::GetProcessTraceConfig(), true); + if (traceMonitor) { + m_pt_traced_thread_group.insert(pid); + m_processor_trace_monitor.insert(std::make_pair(pid, *traceMonitor)); + } else { + LLDB_LOG(log, "failed to start trace on thread {0}", pid); + Status error2(traceMonitor.takeError()); + LLDB_LOG(log, "{0} error in create m_code {1} m_string {2}", + __FUNCTION__, error2.GetError(), error2.AsCString()); + } + } + // Resume the newly created thread. ResumeThread(*thread_sp, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER); ThreadWasCreated(*thread_sp); @@ -719,6 +734,19 @@ LLDB_LOG(log, "pid = {0}: tracking new thread tid {1}", GetID(), tid); new_thread_sp = AddThread(tid); + if (ProcessorTraceMonitor::GetProcessTraceID() != LLDB_INVALID_UID) { + auto traceMonitor = ProcessorTraceMonitor::Create( + GetID(), tid, ProcessorTraceMonitor::GetProcessTraceConfig(), true); + if (traceMonitor) { + m_pt_traced_thread_group.insert(tid); + m_processor_trace_monitor.insert(std::make_pair(tid, *traceMonitor)); + } else { + LLDB_LOG(log, "failed to start trace on thread {0}", tid); + Status error2(traceMonitor.takeError()); + LLDB_LOG(log, "{0} error in create m_code {1} m_string {2}", __FUNCTION__, + error2.GetError(), error2.AsCString()); + } + } ResumeThread(*new_thread_sp, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER); ThreadWasCreated(*new_thread_sp); } @@ -1331,6 +1359,9 @@ e; // Save the error, but still attempt to detach from other threads. } + m_processor_trace_monitor.clear(); + ProcessorTraceMonitor::SetProcessTraceID(LLDB_INVALID_UID); + return error; } @@ -2119,6 +2150,8 @@ } } + if (found) + StopTracingForThread(thread_id); SignalIfAllThreadsStopped(); return found; } @@ -2451,3 +2484,258 @@ return error; } + +ProcessorTraceMonitorSP NativeProcessLinux::LookupProcessorTraceInstance( + lldb::user_id_t traceid, lldb::tid_t thread, Status &error) { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + if (thread == LLDB_INVALID_THREAD_ID && + traceid == ProcessorTraceMonitor::GetProcessTraceID()) { + LLDB_LOG(log, "NativeProcessLinux {0}_thread not specified: {1}", + __FUNCTION__, traceid); + error.SetError(ThreadNotSpecified, eErrorTypeGeneric); + return nullptr; + } + + 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, "NativeProcessLinux {0}_traceid not being traced: {1}", + __FUNCTION__, traceid); + error.SetError(TraceIDNotTraced, eErrorTypeGeneric); + return nullptr; +} + +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, "NativeProcessLinux {0}_traceid {1}", __FUNCTION__, traceid); + + auto perf_monitor = LookupProcessorTraceInstance(traceid, thread, error); + if (!perf_monitor) { + LLDB_LOG(log, "NativeProcessLinux {0}_traceid not being traced: {1}", + __FUNCTION__, traceid); + buffer = buffer.slice(buffer.size()); + 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) { + TraceOptions trace_options; + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + Status error; + + LLDB_LOG(log, "NativeProcessLinux {0}_traceid {1}", __FUNCTION__, traceid); + + auto perf_monitor = LookupProcessorTraceInstance(traceid, thread, error); + if (!perf_monitor) { + LLDB_LOG(log, "NativeProcessLinux {0}_traceid not being traced: {1}", + __FUNCTION__, traceid); + buffer = buffer.slice(buffer.size()); + 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()) { + config = ProcessorTraceMonitor::GetProcessTraceConfig(); + } else { + auto perf_monitor = + LookupProcessorTraceInstance(traceid, config.getThreadID(), error); + if (!perf_monitor) + 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() != LLDB_INVALID_UID) { + error.SetError(ProcessAlreadyBeingTraced, eErrorTypeGeneric); + return ProcessorTraceMonitor::GetProcessTraceID(); + } + LLDB_LOG(log, "NativeProcessLinux {0}", __FUNCTION__); + ProcessorTraceMonitor::SetProcessTraceConfig(config); + + for (const auto &thread_sp : m_threads) { + if (auto traceInstance = ProcessorTraceMonitor::Create( + GetID(), thread_sp->GetID(), + ProcessorTraceMonitor::GetProcessTraceConfig(), true)) { + m_pt_traced_thread_group.insert(thread_sp->GetID()); + m_processor_trace_monitor.insert( + std::pair(thread_sp->GetID(), + *traceInstance)); + } + } + + LLDB_LOG(log, "NativeProcessLinux {0} {1}", + ProcessorTraceMonitor::GetProcessTraceID()); + return ProcessorTraceMonitor::GetProcessTraceID(); +} + +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_LOG(log, "NativeProcessLinux {0}", __FUNCTION__); + 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.SetError(InvalidThread, eErrorTypeGeneric); + return LLDB_INVALID_UID; + } + + auto iter = m_processor_trace_monitor.find(threadid); + if (iter != m_processor_trace_monitor.end()) { + LLDB_LOG(log, "NativeProcessLinux {0}", "Thread already being traced"); + error.SetError(ThreadAlreadyBeingTraced, eErrorTypeGeneric); + return LLDB_INVALID_UID; + } + + auto traceMonitor = + ProcessorTraceMonitor::Create(GetID(), threadid, config, false); + if (!traceMonitor) { + Status error2(traceMonitor.takeError()); + LLDB_LOG(log, "{0} error in create m_code {1} m_string {2}", __FUNCTION__, + error2.GetError(), error2.AsCString()); + error = error2; + return LLDB_INVALID_UID; + } + m_processor_trace_monitor.insert(std::make_pair(threadid, *traceMonitor)); + return (*traceMonitor)->GetTraceID(); +} + +Status NativeProcessLinux::StopTracingForThread(lldb::tid_t thread) { + Status error; + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + LLDB_LOG(log, "NativeProcessLinux {0} Thread {1}", __FUNCTION__, thread); + + auto iter = m_processor_trace_monitor.find(thread); + if (iter != m_processor_trace_monitor.end()) + error = StopTrace(iter->second->GetTraceID(), thread); + + 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() && + thread == LLDB_INVALID_THREAD_ID) + error = StopProcessorTracingOnProcess(); + else + error = StopProcessorTracingOnThread(traceid, thread); + break; + default: + error.SetError(TraceNotSupported, eErrorTypeGeneric); + break; + } + + return error; +} + +Status NativeProcessLinux::StopProcessorTracingOnProcess() { + Status error; + if (ProcessorTraceMonitor::GetProcessTraceID() == LLDB_INVALID_UID) { + error.SetError(ProcessNotBeingTraced, eErrorTypeGeneric); + return error; + } + + 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(LLDB_INVALID_UID); + return error; +} + +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, "NativeProcessLinux Trace ID {0}", + iter.second->GetTraceID()); + } + + LLDB_LOG(log, "NativeProcessLinux {0} Invalid TraceID", __FUNCTION__); + error.SetError(InvalidTraceID, eErrorTypeGeneric); + return error; + } + + // thread is specified so we can use find function on the map. + auto iter = m_processor_trace_monitor.find(thread); + if (iter == m_processor_trace_monitor.end()) { + // thread not found in our map. + LLDB_LOG(log, "NativeProcessLinux {0} thread not being traced", + __FUNCTION__); + error.SetError(ThreadNotTraced, eErrorTypeGeneric); + return error; + } + if (iter->second->GetTraceID() != traceid) { + // traceid did not match so it has to be invalid. + LLDB_LOG(log, "NativeProcessLinux {0} Invalid TraceID", __FUNCTION__); + error.SetError(InvalidTraceID, eErrorTypeGeneric); + return error; + } + + LLDB_LOG(log, "NativeProcessLinux {0} UID - {1} , Thread -{2}", __FUNCTION__, + traceid, thread); + + if (traceid == ProcessorTraceMonitor::GetProcessTraceID()) { + // traceid maps to the whole process so we have to erase it from the + // thread group. + LLDB_LOG(log, "NativeProcessLinux {0} traceid maps to process", + __FUNCTION__); + 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,156 @@ +//===-- 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" + +namespace lldb_private { + +enum PTErrorCode { + FileNotFound, + ThreadNotTraced, + TraceIDNotTraced, + ThreadNotSpecified, + ProcessNotBeingTraced, + ThreadAlreadyBeingTraced, + ProcessAlreadyBeingTraced, + InvalidFile, + PerfEventNotSupported, + PerfEventSyscallFailed, + MetaBufferAllocFailed, + TraceBufferAllocFailed, + TraceBufferDeAllocFailed, + MetaBufferDeAllocFailed, + PerfEventCloseFailed, + EmptyTraceBuffer, + EmptyMetaBuffer, + CPUInfoNotFound, + NullPointer, + AllocFailed, + InvalidTraceID, + InvalidOffset, + TraceNotSupported, + InvalidThread +}; + +// --------------------------------------------------------------------- +// 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::shared_ptr ProcessorTraceMonitorSP; + +class ProcessorTraceMonitor { + int m_fd; + void *m_mmap_data; + void *m_mmap_aux; + void *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; + + // Trace id of trace instance corresponding to the process. + static lldb::user_id_t 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 TraceOptions m_pt_process_config; + + uint64_t GetAuxBufferSize(Status &error) const; + + uint64_t GetDataBufferSize(Status &error) 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_data(nullptr), m_mmap_aux(nullptr), + 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 TraceOptions GetProcessTraceConfig() { return m_pt_process_config; } + + static lldb::user_id_t GetProcessTraceID() { return m_pt_process_traceid; } + + static Status SetProcessTraceConfig(const TraceOptions &config); + + static void SetProcessTraceID(lldb::user_id_t traceid) { + m_pt_process_traceid = 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 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,606 @@ +//===-- 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 +#include + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +lldb::user_id_t ProcessorTraceMonitor::m_trace_num = 0; +lldb::user_id_t ProcessorTraceMonitor::m_pt_process_traceid = LLDB_INVALID_UID; +TraceOptions ProcessorTraceMonitor::m_pt_process_config; + +uint64_t ProcessorTraceMonitor::GetAuxBufferSize(Status &error) const { +#ifndef PERF_ATTR_SIZE_VER5 + error.SetError(PerfEventNotSupported, eErrorTypeGeneric); + return 0; +#else + perf_event_mmap_page *base = + reinterpret_cast(m_mmap_base); + if (base == nullptr) { + error.SetError(NullPointer, eErrorTypeGeneric); + return 0; + } + return base->aux_size; +#endif +} + +uint64_t ProcessorTraceMonitor::GetDataBufferSize(Status &error) const { +#ifndef PERF_ATTR_SIZE_VER5 + error.SetError(PerfEventNotSupported, eErrorTypeGeneric); + return 0; +#else + perf_event_mmap_page *base = + reinterpret_cast(m_mmap_base); + if (base == nullptr) { + error.SetError(NullPointer, eErrorTypeGeneric); + return 0; + } + return (base->data_size); +#endif +} + +Status ProcessorTraceMonitor::GetTraceConfig(TraceOptions &config) const { + Status error; + + config.setType(lldb::TraceType::eTraceTypeProcessorTrace); + uint64_t data_size = GetDataBufferSize(error); + if (error.Fail()) + return error; + + config.setMetaDataBufferSize(data_size); + + uint64_t aux_size = GetAuxBufferSize(error); + if (error.Fail()) + return error; + + 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(const TraceOptions &config) { + m_pt_process_config = 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.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, "ProcessorTrace {0} {1}", __FUNCTION__, config.getThreadID()); + +#ifndef PERF_ATTR_SIZE_VER5 + LLDB_LOG(log, "ProcessorTrace {0} Not supported", __FUNCTION__); + error.SetError(PerfEventNotSupported, eErrorTypePOSIX); +#else + + LLDB_LOG(log, "{0} called thread id {1}", __FUNCTION__, 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, "ProcessorTrace failed to open Config file"); + error.SetError(FileNotFound, eErrorTypePOSIX); + return error; + } + + StringRef rest = ret.get()->getBuffer(); + if (rest.empty() || rest.trim().getAsInteger(10, intel_pt_type)) { + LLDB_LOG(log, "ProcessorTrace failed to read Config file"); + error.SetError(InvalidFile, eErrorTypePOSIX); + return error; + } + + rest.trim().getAsInteger(10, intel_pt_type); + LLDB_LOG(log, "ProcessorTrace intel pt type {0}", intel_pt_type); + attr.type = intel_pt_type; + + LLDB_LOG(log, "ProcessorTrace {0} meta buffer size {1}", __FUNCTION__, + metabufsize); + LLDB_LOG(log, "ProcessorTrace {0} buffer size {1} ", __FUNCTION__, bufsize); + + if (error.Fail()) { + LLDB_LOG(log, "ProcessorTrace {0} Status in custom config {1}", + __FUNCTION__, 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, "ProcessorTrace {0} syscall error {1}", __FUNCTION__, errno); + error.SetError(PerfEventSyscallFailed, eErrorTypePOSIX); + return error; + } + + perf_event_mmap_page *header; + + errno = 0; + m_mmap_base = + mmap(NULL, (metabufsize + page_size), PROT_WRITE, MAP_SHARED, m_fd, 0); + if (m_mmap_base == MAP_FAILED) { + m_mmap_base = nullptr; + LLDB_LOG(log, "ProcessorTrace {0} mmap base error {1}", __FUNCTION__, + errno); + error.SetError(MetaBufferAllocFailed, eErrorTypePOSIX); + (void)Destroy(); + return error; + } + + header = reinterpret_cast(m_mmap_base); + + m_mmap_data = reinterpret_cast(m_mmap_base) + header->data_offset; + + header->aux_offset = header->data_offset + header->data_size; + header->aux_size = bufsize; + + errno = 0; + m_mmap_aux = mmap(NULL, bufsize, PROT_READ, MAP_SHARED, m_fd, + static_cast(header->aux_offset)); + if (m_mmap_aux == MAP_FAILED) { + m_mmap_aux = nullptr; + LLDB_LOG(log, "ProcessorTrace {0} second mmap done {1}", __FUNCTION__, + errno); + error.SetError(TraceBufferAllocFailed, eErrorTypePOSIX); + (void)Destroy(); + return error; + } +#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.SetError(NullPointer, eErrorTypeGeneric); + 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, "ProcessorTrace {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.SetError(CPUInfoNotFound, eErrorTypeGeneric); + 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.SetError(ThreadNotSpecified, eErrorTypeGeneric); + return std::move(error.ToError()); + } + + if (useProcessSettings) { + if (m_pt_process_traceid == LLDB_INVALID_UID) + m_pt_process_traceid = ++m_trace_num; + LLDB_LOG(log, "NativeProcessLinux m_trace_num {0}", m_trace_num); + LLDB_LOG(log, "NativeProcessLinux m_pt_process_traceid {0}", + m_pt_process_traceid); + } + + ProcessorTraceMonitorSP pt_monitor_sp(new ProcessorTraceMonitor); + + if (!pt_monitor_sp) { + LLDB_LOG(log, "NativeProcessLinux {0}", "Alloc failed"); + error.SetError(AllocFailed, eErrorTypeGeneric); + return std::move(error.ToError()); + } + + error = pt_monitor_sp->StartTrace(pid, tid, config); + if (error.Fail()) + return std::move(error.ToError()); + + pt_monitor_sp->SetThreadID(tid); + + if (useProcessSettings) { + pt_monitor_sp->SetTraceID(m_pt_process_traceid); + + // 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_sp->GetDataBufferSize(error); + if (error.Fail()) + return std::move(error.ToError()); + + uint64_t aux_size = pt_monitor_sp->GetAuxBufferSize(error); + if (error.Fail()) + return std::move(error.ToError()); + + m_pt_process_config.setTraceBufferSize(aux_size); + m_pt_process_config.setMetaDataBufferSize(data_size); + } else { + pt_monitor_sp->SetTraceID(++m_trace_num); + LLDB_LOG(log, "NativeProcessLinux Trace ID {0}", m_trace_num); + } + return pt_monitor_sp; +} + +Status ProcessorTraceMonitor::Destroy() { + Status error; + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + int ret; +#ifndef PERF_ATTR_SIZE_VER5 + LLDB_LOG(log, "ProcessorTrace {0} Not supported", __FUNCTION__); + error.SetError(PerfEventNotSupported, eErrorTypeGeneric); +#else + if ((m_mmap_aux != nullptr) && (m_mmap_base != nullptr)) { + uint64_t aux_size = + reinterpret_cast(m_mmap_base)->aux_size; + ret = munmap(m_mmap_aux, aux_size); + if (ret != 0) { + LLDB_LOG(log, "ProcessorTrace {0} munmap aux error {1}", __FUNCTION__, + errno); + error.SetError(TraceBufferDeAllocFailed, eErrorTypeGeneric); + } else + m_mmap_aux = nullptr; + } + // Since we allocate one page extra during + // initialization for the perf_event_mmap_page header + if (m_mmap_base != nullptr) { + uint64_t data_size = + reinterpret_cast(m_mmap_base)->data_size; + int page_size = getpagesize(); + data_size = data_size + page_size; + ret = munmap(m_mmap_base, data_size); + if (ret != 0) { + LLDB_LOG(log, "ProcessorTrace {0} munmap data error {1}", __FUNCTION__, + errno); + error.SetError(MetaBufferDeAllocFailed, eErrorTypeGeneric); + } else { + m_mmap_base = nullptr; + m_mmap_data = nullptr; + } + } + if (m_fd != -1) { + ret = close(m_fd); + if (ret != 0) { + LLDB_LOG(log, "ProcessorTrace {0} closing file descriptor error {1}", + __FUNCTION__, errno); + error.SetError(PerfEventCloseFailed, eErrorTypeGeneric); + } 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 + LLDB_LOG(log, "ProcessorTrace {0} Not supported ", __FUNCTION__); + error.SetError(PerfEventNotSupported, eErrorTypeGeneric); + buffer = buffer.slice(buffer.size()); + return error; +#else + if (buffer.empty()) { + LLDB_LOG(log, "ProcessorTrace {0} Empty buffer ", __FUNCTION__); + error.SetError(EmptyTraceBuffer, eErrorTypeGeneric); + return error; + } + + perf_event_mmap_page *base = + reinterpret_cast(m_mmap_base); + if (base == nullptr) { + LLDB_LOG(log, "ProcessorTrace {0} Null pointer ", __FUNCTION__); + error.SetError(NullPointer, eErrorTypeGeneric); + buffer = buffer.slice(buffer.size()); + return error; + } + uint64_t aux_size = base->aux_size; + uint64_t head = base->aux_head; + + LLDB_LOG(log, "ProcessorTrace {0} Aux size -{1} , Head - {2}", __FUNCTION__, + 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, + llvm::MutableArrayRef(reinterpret_cast(m_mmap_aux), + static_cast(aux_size)), + 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 buf_size = static_cast(buffer.size()); + uint64_t bytes_remaining = buf_size; + Status error; +#ifndef PERF_ATTR_SIZE_VER5 + LLDB_LOG(log, "ProcessorTrace {0} Not supported ", __FUNCTION__); + error.SetError(PerfEventNotSupported, eErrorTypeGeneric); + buffer = buffer.slice(buffer.size()); + return error; +#else + if (buf_size == 0) { + LLDB_LOG(log, "ProcessorTrace {0} Empty buffer ", __FUNCTION__); + error.SetError(EmptyMetaBuffer, eErrorTypeGeneric); + return error; + } + perf_event_mmap_page *base = + reinterpret_cast(m_mmap_base); + if (base == nullptr) { + LLDB_LOG(log, "ProcessorTrace {0} Null pointer ", __FUNCTION__); + error.SetError(NullPointer, eErrorTypeGeneric); + buffer = buffer.slice(buffer.size()); + return error; + } + uint64_t data_size = base->data_size; + uint64_t head = 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 + */ + + uint8_t *buf = reinterpret_cast(buffer.data()); + + LLDB_LOG(log, "ProcessorTrace {0} bytes_remaining - {1}", __FUNCTION__, + bytes_remaining); + + if (head > data_size) { + head = head % data_size; + LLDB_LOG(log, "ProcessorTrace {0} Data size -{1} Head - {2}", __FUNCTION__, + data_size, head); + + ReadCyclicBuffer( + buffer, + llvm::MutableArrayRef(reinterpret_cast(m_mmap_data), + static_cast(data_size)), + static_cast(head), offset); + size_t size_read = buffer.size(); + bytes_remaining = bytes_remaining - size_read; + } else { + LLDB_LOG(log, "ProcessorTrace {0}, Head - {1}", __FUNCTION__, head); + if (offset >= head) { + LLDB_LOG(log, "ProcessorTrace {0} Invalid Offset ", __FUNCTION__); + error.SetError(InvalidOffset, eErrorTypeGeneric); + buffer = buffer.slice(buffer.size()); + return error; + } + uint64_t bytes_to_read = head - offset; + if (bytes_to_read < bytes_remaining) { + memcpy(buf, (reinterpret_cast(m_mmap_data) + offset), + bytes_to_read); + bytes_remaining = bytes_remaining - bytes_to_read; + } else { + memcpy(buf, (reinterpret_cast(m_mmap_data) + offset), + bytes_remaining); + bytes_remaining = 0; + } + } + buffer = llvm::MutableArrayRef(buffer.data(), + (buf_size - bytes_remaining)); + return error; +#endif +} + +void ProcessorTraceMonitor::ReadCyclicBuffer( + llvm::MutableArrayRef &dst, llvm::MutableArrayRef src, + size_t src_cyc_index, size_t offset) { + + // size_t ProcessorTraceMonitor::ReadCyclicBuffer(void *buf, size_t buf_size, + // void *cyc_buf, size_t + // cyc_buf_size, size_t cyc_start, + // 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, "NativeProcessLinux {0} Too Big offset ", __FUNCTION__); + 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); + } + } + } +} \ No newline at end of file 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/Linux/CMakeLists.txt =================================================================== --- /dev/null +++ unittests/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/Linux/ProcessorTraceTest.cpp =================================================================== --- /dev/null +++ unittests/Linux/ProcessorTraceTest.cpp @@ -0,0 +1,151 @@ +//===-- ProcessorTraceTest.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 "NativeProcessLinux.h" +// C Includes + +// C++ Includes + +using namespace lldb_private; +using namespace process_linux; + +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] = {}; + + // nullptr in buffer + bytes_read = NativeProcessLinux::ReadCyclicBuffer( + nullptr, sizeof(smaller_buffer), cyclic_buffer, sizeof(cyclic_buffer), 3, + 0); + ASSERT_EQ(0, bytes_read); + + // nullptr in cyclic buffer + bytes_read = NativeProcessLinux::ReadCyclicBuffer( + smaller_buffer, sizeof(smaller_buffer), nullptr, sizeof(cyclic_buffer), 3, + 0); + ASSERT_EQ(0, bytes_read); + + // empty buffer to read into + bytes_read = NativeProcessLinux::ReadCyclicBuffer( + smaller_buffer, 0, cyclic_buffer, sizeof(cyclic_buffer), 3, 0); + ASSERT_EQ(0, bytes_read); + + // empty cyclic buffer + bytes_read = NativeProcessLinux::ReadCyclicBuffer( + smaller_buffer, sizeof(smaller_buffer), cyclic_buffer, 0, 3, 0); + ASSERT_EQ(0, bytes_read); + + // bigger offset + bytes_read = NativeProcessLinux::ReadCyclicBuffer( + smaller_buffer, sizeof(smaller_buffer), cyclic_buffer, + sizeof(cyclic_buffer), 3, 6); + ASSERT_EQ(0, bytes_read); + + // wrong offset + bytes_read = NativeProcessLinux::ReadCyclicBuffer( + smaller_buffer, sizeof(smaller_buffer), cyclic_buffer, + sizeof(cyclic_buffer), 3, 7); + ASSERT_EQ(0, bytes_read); + + // wrong start + bytes_read = NativeProcessLinux::ReadCyclicBuffer( + 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 = NativeProcessLinux::ReadCyclicBuffer( + 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 = NativeProcessLinux::ReadCyclicBuffer( + 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 = NativeProcessLinux::ReadCyclicBuffer( + 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 = NativeProcessLinux::ReadCyclicBuffer( + 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 = NativeProcessLinux::ReadCyclicBuffer( + 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 = NativeProcessLinux::ReadCyclicBuffer( + 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 = NativeProcessLinux::ReadCyclicBuffer( + 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 = NativeProcessLinux::ReadCyclicBuffer( + 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)); + } +} 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,160 @@ +//===-- 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; + +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] = {}; + + // nullptr in buffer + bytes_read = ReadCylicBufferWrapper( + nullptr, sizeof(smaller_buffer), cyclic_buffer, sizeof(cyclic_buffer), 3, + 0); + ASSERT_EQ(0, bytes_read); + + // nullptr in cyclic buffer + bytes_read = ReadCylicBufferWrapper( + smaller_buffer, sizeof(smaller_buffer), nullptr, sizeof(cyclic_buffer), 3, + 0); + ASSERT_EQ(0, bytes_read); + + // 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)); + } +}