Index: source/Plugins/Process/Linux/NativeProcessLinux.h =================================================================== --- source/Plugins/Process/Linux/NativeProcessLinux.h +++ source/Plugins/Process/Linux/NativeProcessLinux.h @@ -11,6 +11,8 @@ #define liblldb_NativeProcessLinux_H_ // C++ Includes +#include +#include #include // Other libraries and framework includes @@ -105,6 +107,21 @@ return getProcFile(GetID(), "auxv"); } + lldb::user_id_t StartTrace(const TraceOptions &config, Status &error) override; + + Status StopTrace(lldb::user_id_t uid, + lldb::tid_t thread = LLDB_INVALID_THREAD_ID) override; + + Status GetData(lldb::user_id_t uid, lldb::tid_t thread, + llvm::MutableArrayRef &buffer, + size_t offset = 0) override; + + Status GetMetaData(lldb::user_id_t uid, lldb::tid_t thread, + llvm::MutableArrayRef &buffer, + size_t offset = 0) override; + + Status GetTraceConfig(lldb::user_id_t uid, TraceOptions &config) override; + // --------------------------------------------------------------------- // Interface used by NativeRegisterContext-derived classes. // --------------------------------------------------------------------- @@ -114,6 +131,58 @@ bool SupportHardwareSingleStepping() const; + // --------------------------------------------------------------------- + /// Read data from a cyclic buffer + /// + /// @param[in] buf + /// Destination buffer start pointer. + /// + /// @param[in] buf_size + /// Size of the destination buffer. + /// + /// @param[in] cyc_buf + /// Source buffer (which is the cyclic buffer) start pointer. + /// + /// @param[in] cyc_buf_size + /// The size of the cyclic buffer (also the source buffer). + /// + /// @param[in] cyc_start + /// 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 size_t ReadCyclicBuffer(void *buf, size_t buf_size, void *cyc_buf, + size_t cyc_buf_size, size_t cyc_start, + size_t offset); + enum PTErrorCode { + FileNotFound = 0x23, + ThreadNotTraced, + UIDNotTraced, + ThreadNotSpecified, + ProcessNotBeingTraced, + ThreadAlreadyBeingTraced, + ProcessAlreadyBeingTraced, + InvalidFile, + PerfEventNotSupported, + PerfEventSyscallFailed, + MetaBufferAllocFailed, + TraceBufferAllocFailed, + TraceBufferDeAllocFailed, + MetaBufferDeAllocFailed, + PerfEventCloseFailed, + EmptyTraceBuffer, + EmptyMetaBuffer, + CPUInfoNotFound, + NullPointer, + AllocFailed, + InvalidUID, + InvalidOffset, + TraceNotSupported, + InvalidThread + }; + protected: // --------------------------------------------------------------------- // NativeProcessProtocol protected interface @@ -130,6 +199,8 @@ LazyBool m_supports_mem_region; std::vector> m_mem_region_cache; + // Counter to track trace instances. + lldb::user_id_t m_trace_num; lldb::tid_t m_pending_notification_tid; // List of thread ids stepping with a breakpoint with the address of @@ -228,6 +299,114 @@ void SigchldHandler(); Status PopulateMemoryRegionCache(); + + // --------------------------------------------------------------------- + // 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 user id. The user id acts like + // a key to the tracing instance and trace manipulations could be + // performed using the user id. + // + // The user 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 { + int m_fd; + void *m_mmap_data; + void *m_mmap_aux; + void *m_mmap_base; + lldb::user_id_t m_uid; + lldb::tid_t m_thread_id; + int m_perf_dynamic_type; + + uint64_t getAuxBufferSize(Status &error) const; + + uint64_t getDataBufferSize(Status &error) const; + + public: + ProcessorTraceMonitor() + : m_fd(-1), m_mmap_data(nullptr), m_mmap_aux(nullptr), + m_mmap_base(nullptr), m_uid(LLDB_INVALID_UID), + m_thread_id(LLDB_INVALID_THREAD_ID), m_perf_dynamic_type(0) {} + + Status StartTrace(lldb::pid_t pid, lldb::tid_t tid, TraceOptions &config); + + Status ReadPerfTraceAux(llvm::MutableArrayRef &buffer, + size_t offset = 0); + + Status ReadPerfTraceData(llvm::MutableArrayRef &buffer, + size_t offset = 0); + + Status Destroy(); + + ~ProcessorTraceMonitor() { (void)Destroy(); } + + void setUserID(lldb::user_id_t uid) { m_uid = uid; } + + void setThreadID(lldb::tid_t tid) { m_thread_id = tid; } + + lldb::tid_t getThreadID() const { return m_thread_id; } + + lldb::user_id_t getUID() const { return m_uid; } + + void setPerfType(int type) { m_perf_dynamic_type = type; } + + Status GetProcessorTraceConfig(TraceOptions &config) const; + + int getPerfType() const { return m_perf_dynamic_type; } + + static Status getCPUType(uint64_t &cpu_family, uint64_t &model, + uint64_t &stepping, std::string &vendor_id); + }; + + lldb::user_id_t StartTracingAllThreads(const TraceOptions &config, + Status &error); + + typedef std::shared_ptr ProcessorTraceMonitorSP; + // This function is intended to be used to stop tracing + // on a thread that exited. + Status StopTracingForThread(lldb::tid_t thread); + + Status StartProcessorTracing(lldb::tid_t thread, TraceOptions &config, + ProcessorTraceMonitorSP &traceMonitor); + + // 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 uid 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 uid + // is sufficient to obtain the actual ProcessorTrace instance. + ProcessorTraceMonitorSP LookupProcessorTraceInstance(lldb::user_id_t uid, + 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 uid, lldb::tid_t thread); + + // Intended to stop tracing on complete process. + // Should not be used for stopping trace on + // individual threads. + Status StopProcessorTracingOnProcess(lldb::user_id_t uid); + + std::unordered_map + m_processor_trace_monitor; + + // Set for tracking threads being traced under + // same process user id. + std::set m_pt_traced_thread_group; + + // User id of trace instance corresponding to the process. + lldb::user_id_t m_pt_process_uid; + // 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. + TraceOptions m_pt_process_config; }; } // namespace process_linux Index: source/Plugins/Process/Linux/NativeProcessLinux.cpp =================================================================== --- source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -51,7 +51,9 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/Threading.h" +#include #include +#include #include #include #include @@ -289,7 +291,8 @@ NativeProcessLinux::NativeProcessLinux() : NativeProcessProtocol(LLDB_INVALID_PROCESS_ID), m_arch(), m_supports_mem_region(eLazyBoolCalculate), m_mem_region_cache(), - m_pending_notification_tid(LLDB_INVALID_THREAD_ID) {} + m_trace_num(0), m_pending_notification_tid(LLDB_INVALID_THREAD_ID), + m_pt_process_uid(LLDB_INVALID_UID) {} void NativeProcessLinux::AttachToInferior(MainLoop &mainloop, lldb::pid_t pid, Status &error) { @@ -607,6 +610,17 @@ info.si_pid); auto thread_sp = AddThread(pid); + + if (m_pt_process_uid != LLDB_INVALID_UID) { + ProcessorTraceMonitorSP traceMonitor; + StartProcessorTracing(thread_sp->GetID(), m_pt_process_config, + traceMonitor); + if (traceMonitor.get() != nullptr) { + traceMonitor->setUserID(m_pt_process_uid); + m_pt_traced_thread_group.insert(thread_sp->GetID()); + } + } + // Resume the newly created thread. ResumeThread(*thread_sp, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER); ThreadWasCreated(*thread_sp); @@ -719,6 +733,14 @@ LLDB_LOG(log, "pid = {0}: tracking new thread tid {1}", GetID(), tid); new_thread_sp = AddThread(tid); + if (m_pt_process_uid != LLDB_INVALID_UID) { + ProcessorTraceMonitorSP traceMonitor; + StartProcessorTracing(tid, m_pt_process_config, traceMonitor); + if (traceMonitor.get() != nullptr) { + traceMonitor->setUserID(m_pt_process_uid); + m_pt_traced_thread_group.insert(tid); + } + } ResumeThread(*new_thread_sp, eStateRunning, LLDB_INVALID_SIGNAL_NUMBER); ThreadWasCreated(*new_thread_sp); } @@ -1331,6 +1353,9 @@ e; // Save the error, but still attempt to detach from other threads. } + m_processor_trace_monitor.clear(); + m_pt_process_uid = LLDB_INVALID_UID; + return error; } @@ -2119,6 +2144,8 @@ } } + if (found) + StopTracingForThread(thread_id); SignalIfAllThreadsStopped(); return found; } @@ -2451,3 +2478,877 @@ return error; } + +template +static Status ReadFromFile(const char *filename, T &read, + std::ios_base::fmtflags mode = (std::ios_base::hex | + std::ios_base::oct | + std::ios_base::dec)) { + std::ifstream file(filename); + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + + Status error; + if (file.fail()) { + if (log) + log->Printf("NativeProcessLinux %s Status opening file %s", __FUNCTION__, + filename); + error.SetError(NativeProcessLinux::PTErrorCode::FileNotFound, + eErrorTypeGeneric); + return error; + } + file.setf(mode, std::ios::basefield); + file >> read; + return error; +} + +NativeProcessLinux::ProcessorTraceMonitorSP +NativeProcessLinux::LookupProcessorTraceInstance(lldb::user_id_t uid, + lldb::tid_t thread, + Status &error) { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + if (thread == LLDB_INVALID_THREAD_ID && uid == m_pt_process_uid) { + if (log) + log->Printf("NativeProcessLinux %s_thread not specified: %" PRIu64, + __FUNCTION__, uid); + error.SetError(ThreadNotSpecified, eErrorTypeGeneric); + return nullptr; + } + + auto iter = m_processor_trace_monitor.begin(); + for (; iter != m_processor_trace_monitor.end(); iter++) { + if (uid == iter->second->getUID() && + (thread == iter->first || thread == LLDB_INVALID_THREAD_ID)) + return iter->second; + } + + if (log) + log->Printf("NativeProcessLinux %s_uid not being traced: %" PRIu64, + __FUNCTION__, uid); + error.SetError(UIDNotTraced, eErrorTypeGeneric); + return nullptr; +} + +Status NativeProcessLinux::GetMetaData(lldb::user_id_t uid, lldb::tid_t thread, + llvm::MutableArrayRef &buffer, + size_t offset) { + TraceOptions trace_options; + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + Status error; + + if (log) + log->Printf("NativeProcessLinux %s_uid %" PRIu64, __FUNCTION__, uid); + + auto perf_monitor = LookupProcessorTraceInstance(uid, thread, error); + if (!perf_monitor) { + if (log) + log->Printf("NativeProcessLinux %s_uid not being traced: %" PRIu64, + __FUNCTION__, uid); + buffer = buffer.slice(buffer.size()); + return error; + } + return perf_monitor->ReadPerfTraceData(buffer, offset); +} + +Status NativeProcessLinux::GetData(lldb::user_id_t uid, lldb::tid_t thread, + llvm::MutableArrayRef &buffer, + size_t offset) { + TraceOptions trace_options; + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + Status error; + + if (log) + log->Printf("NativeProcessLinux %s_uid %" PRIu64, __FUNCTION__, uid); + + auto perf_monitor = LookupProcessorTraceInstance(uid, thread, error); + if (!perf_monitor) { + if (log) + log->Printf("NativeProcessLinux %s_uid not being traced: %" PRIu64, + __FUNCTION__, uid); + buffer = buffer.slice(buffer.size()); + return error; + } + return perf_monitor->ReadPerfTraceAux(buffer, offset); +} + +uint64_t NativeProcessLinux::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 NativeProcessLinux::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 NativeProcessLinux::GetTraceConfig(lldb::user_id_t uid, + TraceOptions &config) { + Status error; + + if (config.getThreadID() == LLDB_INVALID_THREAD_ID && + uid == m_pt_process_uid) { + config = m_pt_process_config; + } else { + auto perf_monitor = + LookupProcessorTraceInstance(uid, config.getThreadID(), error); + if (!perf_monitor) + return error; + error = perf_monitor->GetProcessorTraceConfig(config); + } + return error; +} + +lldb::user_id_t +NativeProcessLinux::StartTracingAllThreads(const TraceOptions &config, + Status &error) { + lldb::user_id_t uid = LLDB_INVALID_UID; + if (config.getType() == lldb::TraceType::eTraceTypeProcessorTrace) { + if (m_pt_process_uid == LLDB_INVALID_UID) { + m_pt_process_config = config; + + uint64_t cpu_family, model, stepping; + std::string vendor_id; + error = ProcessorTraceMonitor::getCPUType(cpu_family, model, stepping, + vendor_id); + if (error.Success()) { + StructuredData::Dictionary *intel_params = + new StructuredData::Dictionary(); + + llvm::StringRef intel_custom_params_key("intel-pt"); + llvm::StringRef cpu_family_key("cpu_family"); + llvm::StringRef cpu_model_key("cpu_model"); + llvm::StringRef cpu_stepping_key("cpu_stepping"); + llvm::StringRef cpu_vendor_intel_key("cpu_vendor_intel"); + + intel_params->AddIntegerItem(cpu_family_key, cpu_family); + intel_params->AddIntegerItem(cpu_model_key, model); + intel_params->AddIntegerItem(cpu_stepping_key, stepping); + + if (vendor_id == "GenuineIntel") + intel_params->AddIntegerItem(cpu_vendor_intel_key, 1ull); + + StructuredData::DictionarySP custom_params_sp( + new StructuredData::Dictionary()); + StructuredData::ObjectSP intel_custom_params(intel_params); + custom_params_sp->AddItem(intel_custom_params_key, intel_custom_params); + + m_pt_process_config.setTraceParams(custom_params_sp); + } + + uid = m_pt_process_uid = ++m_trace_num; + for (const auto &thread_sp : m_threads) { + ProcessorTraceMonitorSP traceMonitor; + StartProcessorTracing(thread_sp->GetID(), m_pt_process_config, + traceMonitor); + if (traceMonitor.get() != nullptr) { + traceMonitor->setUserID(m_pt_process_uid); + m_pt_traced_thread_group.insert(thread_sp->GetID()); + } + } + } else + error.SetError(ProcessAlreadyBeingTraced, eErrorTypeGeneric); + return uid; + } else { + return LLDB_INVALID_UID; + } +} + +Status NativeProcessLinux::StartProcessorTracing( + lldb::tid_t thread, TraceOptions &config, + NativeProcessLinux::ProcessorTraceMonitorSP &traceMonitor) { + Status error; + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + + if (log) + log->Printf("NativeProcessLinux %s", __FUNCTION__); + + auto iter = m_processor_trace_monitor.find(thread); + if (iter != m_processor_trace_monitor.end()) { + if (log) + log->Printf("NativeProcessLinux %s", "Thread already being traced"); + error.SetError(ThreadAlreadyBeingTraced, eErrorTypeGeneric); + return error; + } + + int intel_pt_type = 0; + error = ReadFromFile("/sys/bus/event_source/devices/intel_pt/type", + intel_pt_type, std::ios::dec); + + if (error.Fail()) + return error; + + traceMonitor.reset(new ProcessorTraceMonitor()); + if (traceMonitor.get() == nullptr) { + if (log) + log->Printf("NativeProcessLinux %s", "Alloc failed"); + error.SetError(AllocFailed, eErrorTypeGeneric); + return error; + } + traceMonitor->setThreadID(thread); + traceMonitor->setPerfType(intel_pt_type); + + error = traceMonitor->StartTrace(GetID(), thread, config); + + if (error.Fail()) + return error; + + m_processor_trace_monitor.insert( + std::pair(thread, traceMonitor)); + + return error; +} + +Status NativeProcessLinux::ProcessorTraceMonitor::GetProcessorTraceConfig( + 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); + + static const uint64_t one = 1ull; + + uint64_t cpu_family, model, stepping; + std::string vendor_id; + error = getCPUType(cpu_family, model, stepping, vendor_id); + if (error.Success()) { + StructuredData::Dictionary *intel_params = new StructuredData::Dictionary(); + + llvm::StringRef intel_custom_params_key("intel-pt"); + llvm::StringRef cpu_family_key("cpu_family"); + llvm::StringRef cpu_model_key("cpu_model"); + llvm::StringRef cpu_stepping_key("cpu_stepping"); + llvm::StringRef cpu_vendor_intel_key("cpu_vendor_intel"); + + intel_params->AddIntegerItem(cpu_family_key, cpu_family); + intel_params->AddIntegerItem(cpu_model_key, model); + intel_params->AddIntegerItem(cpu_stepping_key, stepping); + + if (vendor_id == "GenuineIntel") + intel_params->AddIntegerItem(cpu_vendor_intel_key, one); + + StructuredData::DictionarySP custom_params_sp( + new StructuredData::Dictionary()); + StructuredData::ObjectSP intel_custom_params(intel_params); + custom_params_sp->AddItem(intel_custom_params_key, intel_custom_params); + + config.setTraceParams(custom_params_sp); + } + + return error; +} + +lldb::user_id_t NativeProcessLinux::StartTrace(const TraceOptions &config, + Status &error) { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + + if (log) + log->Printf("NativeProcessLinux %s", __FUNCTION__); + + lldb::user_id_t uid = LLDB_INVALID_UID; + if (config.getThreadID() == LLDB_INVALID_THREAD_ID) + return StartTracingAllThreads(config, error); + + auto thread_sp = GetThreadByID(config.getThreadID()); + if (!thread_sp) { + // Thread not tracked by lldb so don't trace. + error.SetError(InvalidThread, eErrorTypeGeneric); + return uid; + } + + TraceOptions pt_config = config; + std::map params; + lldb::TraceType trace_type = pt_config.getType(); + ProcessorTraceMonitorSP traceMonitor; + + switch (trace_type) { + case lldb::TraceType::eTraceTypeProcessorTrace: + error = + StartProcessorTracing(pt_config.getThreadID(), pt_config, traceMonitor); + if (traceMonitor) { + uid = ++m_trace_num; + traceMonitor->setUserID(uid); + } + break; + default: + return NativeProcessProtocol::StartTrace(config, error); + } + return uid; +} + +Status NativeProcessLinux::StopTracingForThread(lldb::tid_t thread) { + Status error; + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + if (log) + log->Printf("NativeProcessLinux %s Thread %lu", __FUNCTION__, thread); + + auto iter = m_processor_trace_monitor.find(thread); + if (iter != m_processor_trace_monitor.end()) + error = StopTrace(iter->second->getUID(), thread); + + return error; +} + +Status NativeProcessLinux::StopTrace(lldb::user_id_t uid, lldb::tid_t thread) { + Status error; + + TraceOptions trace_options; + trace_options.setThreadID(thread); + error = NativeProcessLinux::GetTraceConfig(uid, trace_options); + + if (error.Fail()) + return error; + + switch (trace_options.getType()) { + case lldb::TraceType::eTraceTypeProcessorTrace: + if (uid == m_pt_process_uid && thread == LLDB_INVALID_THREAD_ID) + error = StopProcessorTracingOnProcess(uid); + else + error = StopProcessorTracingOnThread(uid, thread); + break; + default: + error.SetError(TraceNotSupported, eErrorTypeGeneric); + break; + } + + return error; +} + +Status NativeProcessLinux::StopProcessorTracingOnProcess(lldb::user_id_t uid) { + Status ret_error; + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + if (log) + log->Printf("NativeProcessLinux %s", __FUNCTION__); + + if (uid == m_pt_process_uid) { + for (auto thread_id_iter : m_pt_traced_thread_group) + m_processor_trace_monitor.erase(thread_id_iter); + m_pt_traced_thread_group.clear(); + m_pt_process_uid = LLDB_INVALID_UID; + } else { + if (log) + log->Printf("NativeProcessLinux %s Complete process not being tracked", + __FUNCTION__); + ret_error.SetError(ProcessNotBeingTraced, eErrorTypeGeneric); + } + + return ret_error; +} + +Status NativeProcessLinux::StopProcessorTracingOnThread(lldb::user_id_t uid, + lldb::tid_t thread) { + Status error; + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + + if (thread == LLDB_INVALID_THREAD_ID) { + auto iter = m_processor_trace_monitor.begin(); + bool uid_found = false; + for (; iter != m_processor_trace_monitor.end(); iter++) { + if (iter->second->getUID() == uid) { + // Stopping a trace instance for an individual thread + // hence there will only be one uid that can match. + uid_found = true; + m_processor_trace_monitor.erase(iter); + break; + } + } + if (!uid_found) { + if (log) + log->Printf("NativeProcessLinux %s Invalid UID", __FUNCTION__); + error.SetError(InvalidUID, eErrorTypeGeneric); + } + } else { + // 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()) { + if (iter->second->getUID() == uid) { + if (log) + log->Printf("NativeProcessLinux %s UID - %lu , Thread -%lu", + __FUNCTION__, uid, thread); + + if (uid == m_pt_process_uid) { + // uid maps to the whole process so we have to erase it from the + // thread group. + if (log) + log->Printf("NativeProcessLinux %s uid maps to process", + __FUNCTION__); + m_pt_traced_thread_group.erase(thread); + } else { + // uid maps to individual thread so not extra work. + if (log) + log->Printf("NativeProcessLinux %s uid maps to one thread", + __FUNCTION__); + } + m_processor_trace_monitor.erase(iter); + } else { + // uid did not match so it has to be invalid. + if (log) + log->Printf("NativeProcessLinux %s Invalid UID", __FUNCTION__); + error.SetError(InvalidUID, eErrorTypeGeneric); + } + } else { + // thread not found in our map. + if (log) + log->Printf("NativeProcessLinux %s thread not being traced", + __FUNCTION__); + error.SetError(ThreadNotTraced, eErrorTypeGeneric); + } + } + + return error; +} + +static uint64_t ComputeFloorLog2(uint64_t input) { + uint64_t prev = input; + while ((input & (input - 1)) != 0) { + input &= (input - 1); + prev = input; + } + return prev; +} + +Status NativeProcessLinux::ProcessorTraceMonitor::StartTrace( + lldb::pid_t pid, lldb::tid_t tid, TraceOptions &config) { + Status error; + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + if (log) + log->Printf("NativeProcessLinux %s %lu", __FUNCTION__, tid); + +#ifndef PERF_ATTR_SIZE_VER5 + if (log) + log->Printf("NativeProcessLinux %s Not supported", __FUNCTION__); + error.SetError(PerfEventNotSupported, eErrorTypeGeneric); +#else + + uint64_t page_size = getpagesize(); + uint64_t bufsize = config.getTraceBufferSize(); + uint64_t metabufsize = config.getMetaDataBufferSize(); + + uint64_t numpages = + std::max(1ul, ComputeFloorLog2((bufsize + page_size - 1) / page_size)); + bufsize = page_size * numpages; + + numpages = std::max( + 0ul, ComputeFloorLog2((metabufsize + page_size - 1) / page_size)); + metabufsize = page_size * numpages; + + config.setTraceBufferSize(bufsize); + config.setMetaDataBufferSize(metabufsize); + + 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; + + attr.type = getPerfType(); + + if (log) + log->Printf("NativeProcessLinux %s meta buffer size %lu ", __FUNCTION__, + metabufsize); + if (log) + log->Printf("NativeProcessLinux %s buffer size %lu ", __FUNCTION__, + bufsize); + + if (error.Fail()) { + if (log) + log->Printf("NativeProcessLinux %s Status in custom config %s", + __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) { + if (log) + log->Printf("NativeProcessLinux %s syscall error %d", __FUNCTION__, + errno); + error.SetError(PerfEventSyscallFailed, eErrorTypeGeneric); + 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; + if (log) + log->Printf("NativeProcessLinux %s mmap base error %d", __FUNCTION__, + errno); + error.SetError(MetaBufferAllocFailed, eErrorTypeGeneric); + (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; + if (log) + log->Printf("NativeProcessLinux %s second mmap done %d", __FUNCTION__, + errno); + error.SetError(TraceBufferAllocFailed, eErrorTypeGeneric); + (void)Destroy(); + return error; + } +#endif + return error; +} + +Status NativeProcessLinux::ProcessorTraceMonitor::getCPUType( + uint64_t &cpu_family, uint64_t &model, uint64_t &stepping, + std::string &vendor_id) { + Status error; + + cpu_family = -1; + model = -1; + stepping = -1; + vendor_id.erase(); + + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + + auto BufferOrError = getProcFile("cpuinfo"); + if (!BufferOrError) { + return BufferOrError.getError(); + } + 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 + + if (log) + log->Printf("NativeProcessLinux %s %s", columns[0].data(), + columns[1].data()); + + if ((columns[0].find("cpu family") != std::string::npos) && + columns[1].trim(" ").getAsInteger(10, cpu_family)) + continue; + + else if ((columns[0].find("model") != std::string::npos) && + columns[1].trim(" ").getAsInteger(10, model)) + continue; + + else if ((columns[0].find("stepping") != std::string::npos) && + columns[1].trim(" ").getAsInteger(10, stepping)) + continue; + + else if ((columns[0].find("vendor_id") != std::string::npos)) { + vendor_id = columns[1].trim(" ").str(); + if (!vendor_id.empty()) + continue; + } + + if (log) + log->Printf("NativeProcessLinux %lu:%lu:%lu:%s", 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())) + break; // we are done + } + } + + if ((cpu_family == static_cast(-1)) || + (model == static_cast(-1)) || + (stepping == static_cast(-1)) || (vendor_id.empty())) + error.SetError(CPUInfoNotFound, eErrorTypeGeneric); + return error; +} + +Status NativeProcessLinux::ProcessorTraceMonitor::Destroy() { + Status error; + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); + int ret; +#ifndef PERF_ATTR_SIZE_VER5 + if (log) + log->Printf("NativeProcessLinux %s 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) { + if (log) + log->Printf("NativeProcessLinux %s munmap aux error %d", __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) { + if (log) + log->Printf("NativeProcessLinux %s munmap data error %d", __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) { + if (log) + log->Printf("NativeProcessLinux %s closing file descriptor error %d", + __FUNCTION__, errno); + error.SetError(PerfEventCloseFailed, eErrorTypeGeneric); + } else + m_fd = -1; + } +#endif + return error; +} + +size_t NativeProcessLinux::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 (buf == nullptr || cyc_buf == nullptr || buf_size == 0 || + cyc_buf_size == 0 || cyc_start > cyc_buf_size) + return 0; + + if (offset >= cyc_buf_size) { + if (log) + log->Printf("NativeProcessLinux %s Too Big offset ", __FUNCTION__); + return 0; + } + + size_t bytes_remaining = buf_size; + uint64_t firstpartsize = cyc_buf_size - cyc_start; + size_t size_to_read = cyc_buf_size - offset; + + if (bytes_remaining > size_to_read) { + if (offset >= firstpartsize) { + memcpy(buf, + (reinterpret_cast(cyc_buf) + offset - firstpartsize), + size_to_read); + } else { + memcpy(buf, (reinterpret_cast(cyc_buf) + offset + cyc_start), + (firstpartsize - offset)); + memcpy((reinterpret_cast(buf) + firstpartsize - offset), + cyc_buf, (size_to_read - firstpartsize + offset)); + } + bytes_remaining = bytes_remaining - size_to_read; + } else { + if (offset >= firstpartsize) { + memcpy(buf, + (reinterpret_cast(cyc_buf) + offset - firstpartsize), + bytes_remaining); + bytes_remaining = 0; + } else { + if (bytes_remaining <= (firstpartsize - offset)) { + memcpy(buf, (reinterpret_cast(cyc_buf) + offset + cyc_start), + bytes_remaining); + } else { + memcpy(buf, (reinterpret_cast(cyc_buf) + offset + cyc_start), + (firstpartsize - offset)); + bytes_remaining = bytes_remaining - firstpartsize + offset; + + memcpy((reinterpret_cast(buf) + firstpartsize - offset), + cyc_buf, bytes_remaining); + } + bytes_remaining = 0; + } + } + return buf_size - bytes_remaining; +} + +Status NativeProcessLinux::ProcessorTraceMonitor::ReadPerfTraceAux( + 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 + if (log) + log->Printf("NativeProcessLinux %s Not supported ", __FUNCTION__); + error.SetError(PerfEventNotSupported, eErrorTypeGeneric); +#else + if (buf_size == 0) { + if (log) + log->Printf("NativeProcessLinux %s Empty buffer ", __FUNCTION__); + error.SetError(EmptyTraceBuffer, eErrorTypeGeneric); + return error; + } + + perf_event_mmap_page *base = + reinterpret_cast(m_mmap_base); + if (base == nullptr) { + if (log) + log->Printf("NativeProcessLinux %s 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; + + if (log) + log->Printf("NativeProcessLinux %s Aux size -%" PRIx64 ", Head - %" PRIx64, + __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 ->| + * + * */ + + size_t size_read = ReadCyclicBuffer(buffer.data(), buf_size, m_mmap_aux, + static_cast(aux_size), + static_cast(head), offset); + LLDB_LOG(log, "ReadCyclic BUffer Done"); + bytes_remaining -= size_read; +#endif + buffer = llvm::MutableArrayRef(buffer.data(), + (buf_size - bytes_remaining)); + return error; +} + +Status NativeProcessLinux::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 + if (log) + log->Printf("NativeProcessLinux %s Not supported ", __FUNCTION__); + error.SetError(PerfEventNotSupported, eErrorTypeGeneric); +#else + if (buf_size == 0) { + if (log) + log->Printf("NativeProcessLinux %s Empty buffer ", __FUNCTION__); + error.SetError(EmptyMetaBuffer, eErrorTypeGeneric); + return error; + } + perf_event_mmap_page *base = + reinterpret_cast(m_mmap_base); + if (base == nullptr) { + if (log) + log->Printf("NativeProcessLinux %s 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()); + + if (log) + log->Printf("NativeProcessLinux %s bytes_remaining - %" PRIx64, + __FUNCTION__, bytes_remaining); + + if (head > data_size) { + head = head % data_size; + if (log) + log->Printf("NativeProcessLinux %s Data size -%" PRIx64 + ", Head - %" PRIx64, + __FUNCTION__, data_size, head); + + size_t size_read = ReadCyclicBuffer(buf, buf_size, m_mmap_data, + static_cast(data_size), + static_cast(head), offset); + bytes_remaining = bytes_remaining - size_read; + } else { + if (log) + log->Printf("NativeProcessLinux %s, Head - %" PRIx64, __FUNCTION__, head); + if (offset >= head) { + if (log) + log->Printf("NativeProcessLinux %s 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; + } + } +#endif + buffer = llvm::MutableArrayRef(buffer.data(), + (buf_size - bytes_remaining)); + return error; +} 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,7 +1316,7 @@ if (error.Fail()) return SendErrorResponse(error.GetError()); - for (size_t i = 0; i < buf.size(); ++i) + for (uint64_t i = 0; i < buf.size(); ++i) response.PutHex8(buf[i]); StreamGDBRemote escaped_response; Index: unittests/CMakeLists.txt =================================================================== --- unittests/CMakeLists.txt +++ unittests/CMakeLists.txt @@ -73,4 +73,5 @@ if(LLDB_CAN_USE_DEBUGSERVER) add_subdirectory(debugserver) -endif() \ No newline at end of file +endif() +add_subdirectory(Linux) 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)); + } +}