diff --git a/lldb/docs/lldb-gdb-remote.txt b/lldb/docs/lldb-gdb-remote.txt --- a/lldb/docs/lldb-gdb-remote.txt +++ b/lldb/docs/lldb-gdb-remote.txt @@ -534,7 +534,7 @@ // "tscPerfZeroConversion": { // "timeMult": , // "timeShift": , -// "timeSero": , +// "timeZero": , // } //---------------------------------------------------------------------- diff --git a/lldb/docs/use/intel_pt.rst b/lldb/docs/use/intel_pt.rst --- a/lldb/docs/use/intel_pt.rst +++ b/lldb/docs/use/intel_pt.rst @@ -182,14 +182,12 @@ :: { - "trace": { - "type": "intel-pt", - "pt_cpu": { - "vendor": "intel", - "family": 6, - "model": 79, - "stepping": 1 - } + "type": "intel-pt", + "cpuInfo": { + "vendor": "GenuineIntel", + "family": 6, + "model": 79, + "stepping": 1 }, "processes": [ { @@ -198,14 +196,14 @@ "threads": [ { "tid": 815455, - "traceFile": "trace.file" # raw thread-specific trace from the AUX buffer + "traceBuffer": "trace.file" # raw thread-specific trace from the AUX buffer } ], "modules": [ # this are all the shared libraries + the main executable { "file": "a.out", # optional if it's the same as systemPath "systemPath": "a.out", - "loadAddress": "0x0000000000400000", + "loadAddress": 4194304, }, { "file": "libfoo.so", @@ -251,4 +249,4 @@ - Some details about how Meta is using Intel Processor Trace can be found in this blog_ post. .. _document: https://docs.google.com/document/d/1cOVTGp1sL_HBXjP9eB7qjVtDNr5xnuZvUUtv43G5eVI -.. _blog: https://engineering.fb.com/2021/04/27/developer-tools/reverse-debugging/ \ No newline at end of file +.. _blog: https://engineering.fb.com/2021/04/27/developer-tools/reverse-debugging/ diff --git a/lldb/include/lldb/Target/Trace.h b/lldb/include/lldb/Target/Trace.h --- a/lldb/include/lldb/Target/Trace.h +++ b/lldb/include/lldb/Target/Trace.h @@ -240,6 +240,7 @@ using OnBinaryDataReadCallback = std::function data)>; + /// Fetch binary data associated with a thread, either live or postmortem, and /// pass it to the given callback. The reason of having a callback is to free /// the caller from having to manage the life cycle of the data and to hide @@ -265,23 +266,77 @@ llvm::Error OnThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind, OnBinaryDataReadCallback callback); - /// Get the current traced live process. + /// Fetch binary data associated with a core, either live or postmortem, and + /// pass it to the given callback. The reason of having a callback is to free + /// the caller from having to manage the life cycle of the data and to hide + /// the different data fetching procedures that exist for live and post mortem + /// cores. + /// + /// The fetched data is not persisted after the callback is invoked. + /// + /// \param[in] core_id + /// The core who owns the data. + /// + /// \param[in] kind + /// The kind of data to read. + /// + /// \param[in] callback + /// The callback to be invoked once the data was successfully read. Its + /// return value, which is an \a llvm::Error, is returned by this + /// function. /// /// \return - /// The current traced live process. If it's not a live process, - /// return \a nullptr. - Process *GetLiveProcess(); + /// An \a llvm::Error if the data couldn't be fetched, or the return value + /// of the callback, otherwise. + llvm::Error OnCoreBinaryDataRead(lldb::core_id_t core_id, + llvm::StringRef kind, + OnBinaryDataReadCallback callback); + + /// \return + /// All the currently traced processes. + std::vector GetAllProcesses(); + + /// \return + /// The list of cores being traced. Might be empty depending on the + /// plugin. + llvm::ArrayRef GetTracedCores(); protected: + /// Get the currently traced live process. + /// + /// \return + /// If it's not a live process, return \a nullptr. + Process *GetLiveProcess(); + + /// Get the currently traced postmortem processes. + /// + /// \return + /// If it's not a live process session, return an empty list. + llvm::ArrayRef GetPostMortemProcesses(); + /// Implementation of \a OnThreadBinaryDataRead() for live threads. llvm::Error OnLiveThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind, OnBinaryDataReadCallback callback); + /// Implementation of \a OnLiveBinaryDataRead() for live cores. + llvm::Error OnLiveCoreBinaryDataRead(lldb::core_id_t core, + llvm::StringRef kind, + OnBinaryDataReadCallback callback); + /// Implementation of \a OnThreadBinaryDataRead() for post mortem threads. llvm::Error OnPostMortemThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind, OnBinaryDataReadCallback callback); + /// Implementation of \a OnCoreBinaryDataRead() for post mortem cores. + llvm::Error OnPostMortemCoreBinaryDataRead(lldb::core_id_t core_id, + llvm::StringRef kind, + OnBinaryDataReadCallback callback); + + /// Helper method for reading a data file and passing its data to the given + /// callback. + llvm::Error OnDataFileRead(FileSpec file, OnBinaryDataReadCallback callback); + /// Get the file path containing data of a postmortem thread given a data /// identifier. /// @@ -297,6 +352,21 @@ llvm::Expected GetPostMortemThreadDataFile(lldb::tid_t tid, llvm::StringRef kind); + /// Get the file path containing data of a postmortem core given a data + /// identifier. + /// + /// \param[in] core_id + /// The core whose data is requested. + /// + /// \param[in] kind + /// The kind of data requested. + /// + /// \return + /// The file spec containing the requested data, or an \a llvm::Error in + /// case of failures. + llvm::Expected GetPostMortemCoreDataFile(lldb::core_id_t core_id, + llvm::StringRef kind); + /// Associate a given thread with a data file using a data identifier. /// /// \param[in] tid @@ -310,6 +380,19 @@ void SetPostMortemThreadDataFile(lldb::tid_t tid, llvm::StringRef kind, FileSpec file_spec); + /// Associate a given core with a data file using a data identifier. + /// + /// \param[in] core_id + /// The core associated with the data file. + /// + /// \param[in] kind + /// The kind of data being registered. + /// + /// \param[in] file_spec + /// The path of the data file. + void SetPostMortemCoreDataFile(lldb::core_id_t core_id, llvm::StringRef kind, + FileSpec file_spec); + /// Get binary data of a live thread given a data identifier. /// /// \param[in] tid @@ -324,6 +407,20 @@ llvm::Expected> GetLiveThreadBinaryData(lldb::tid_t tid, llvm::StringRef kind); + /// Get binary data of a live core given a data identifier. + /// + /// \param[in] core_id + /// The core whose data is requested. + /// + /// \param[in] kind + /// The kind of data requested. + /// + /// \return + /// A vector of bytes with the requested data, or an \a llvm::Error in + /// case of failures. + llvm::Expected> + GetLiveCoreBinaryData(lldb::core_id_t core_id, llvm::StringRef kind); + /// Get binary data of the current process given a data identifier. /// /// \param[in] kind @@ -339,10 +436,16 @@ llvm::Optional GetLiveThreadBinaryDataSize(lldb::tid_t tid, llvm::StringRef kind); + /// Get the size of the data returned by \a GetLiveCoreBinaryData + llvm::Optional GetLiveCoreBinaryDataSize(lldb::core_id_t core_id, + llvm::StringRef kind); + /// Get the size of the data returned by \a GetLiveProcessBinaryData llvm::Optional GetLiveProcessBinaryDataSize(llvm::StringRef kind); + /// Constructor for post mortem processes - Trace() = default; + Trace(llvm::ArrayRef postmortem_processes, + llvm::Optional> postmortem_cores); /// Constructor for a live process Trace(Process &live_process) : m_live_process(&live_process) {} @@ -400,6 +503,10 @@ /// Process traced by this object if doing live tracing. Otherwise it's null. Process *m_live_process = nullptr; + /// Portmortem processes traced by this object if doing non-live tracing. + /// Otherwise it's empty. + std::vector m_postmortem_processes; + /// These data kinds are returned by lldb-server when fetching the state of /// the tracing session. The size in bytes can be used later for fetching the /// data in batches. @@ -409,16 +516,30 @@ llvm::DenseMap> m_live_thread_data; + /// core id -> data kind -> size + llvm::DenseMap> + m_live_core_data; /// data kind -> size std::unordered_map m_live_process_data; /// \} + /// The list of cores being traced. Might be \b None depending on the plug-in. + llvm::Optional> m_cores; + /// Postmortem traces can specific additional data files, which are /// represented in this variable using a data kind identifier for each file. + /// \{ + /// tid -> data kind -> file llvm::DenseMap> m_postmortem_thread_data; + /// core id -> data kind -> file + llvm::DenseMap> + m_postmortem_core_data; + + /// \} + llvm::Optional m_live_refresh_error; }; diff --git a/lldb/include/lldb/Utility/TraceGDBRemotePackets.h b/lldb/include/lldb/Utility/TraceGDBRemotePackets.h --- a/lldb/include/lldb/Utility/TraceGDBRemotePackets.h +++ b/lldb/include/lldb/Utility/TraceGDBRemotePackets.h @@ -133,7 +133,9 @@ std::vector traced_threads; std::vector process_binary_data; llvm::Optional> cores; - std::vector warnings; + llvm::Optional> warnings; + + void AddWarning(llvm::StringRef warning); }; bool fromJSON(const llvm::json::Value &value, TraceGetStateResponse &packet, @@ -151,6 +153,8 @@ std::string kind; /// Optional tid if the data is related to a thread. llvm::Optional tid; + /// Optional core id if the data is related to a cpu core. + llvm::Optional core_id; /// Offset in bytes from where to start reading the data. uint64_t offset; /// Number of bytes to read. diff --git a/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp b/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp --- a/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp @@ -182,32 +182,39 @@ FetchPerfTscConversionParameters()) state.tsc_perf_zero_conversion = *tsc_conversion; else - state.warnings.push_back(toString(tsc_conversion.takeError())); + state.AddWarning(toString(tsc_conversion.takeError())); return toJSON(state); } Expected> IntelPTCollector::GetBinaryData(const TraceGetBinaryDataRequest &request) { - if (request.kind == IntelPTDataKinds::kTraceBuffer) { - if (!request.tid) - return createStringError( - inconvertibleErrorCode(), - "Getting a trace buffer without a tid is currently unsupported"); + if (request.kind == IntelPTDataKinds::kProcFsCpuInfo) + return GetProcfsCpuInfo(); - if (m_process_trace_up && m_process_trace_up->TracesThread(*request.tid)) - return m_process_trace_up->GetBinaryData(request); + if (m_process_trace_up) { + Expected>> data = + m_process_trace_up->TryGetBinaryData(request); + if (!data) + return data.takeError(); + if (*data) + return **data; + } - if (Expected trace = - m_thread_traces.GetTracedThread(*request.tid)) - return trace->GetTraceBuffer(request.offset, request.size); - else - return trace.takeError(); - } else if (request.kind == IntelPTDataKinds::kProcFsCpuInfo) { - return GetProcfsCpuInfo(); + { + Expected>> data = + m_thread_traces.TryGetBinaryData(request); + if (!data) + return data.takeError(); + if (*data) + return **data; } - return createStringError(inconvertibleErrorCode(), - "Unsuported trace binary data kind: %s", - request.kind.c_str()); + + return createStringError( + inconvertibleErrorCode(), + formatv("Can't fetch data kind {0} for core_id {1}, tid {2} and " + "\"process tracing\" mode {3}", + request.kind, request.core_id, request.tid, + m_process_trace_up ? "enabled" : "not enabled")); } bool IntelPTCollector::IsSupported() { diff --git a/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h --- a/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h +++ b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.h @@ -80,8 +80,8 @@ llvm::Error TraceStop(lldb::tid_t tid) override; - llvm::Expected> - GetBinaryData(const TraceGetBinaryDataRequest &request) override; + llvm::Expected>> + TryGetBinaryData(const TraceGetBinaryDataRequest &request) override; private: /// This assumes that all underlying perf_events for each core are part of the diff --git a/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp --- a/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTMultiCoreTrace.cpp @@ -70,8 +70,8 @@ if (Expected perf_event = PerfEvent::Init( attr, /*pid=*/None, core_id, intelpt_core_trace.GetPerfEvent().GetFd(), /*flags=*/0)) { - if (Error mmap_err = - perf_event->MmapMetadataAndBuffers(data_buffer_numpages, 0)) { + if (Error mmap_err = perf_event->MmapMetadataAndBuffers( + data_buffer_numpages, 0, /*data_buffer_write=*/false)) { return std::move(mmap_err); } return perf_event; @@ -190,7 +190,21 @@ "per-core process tracing is enabled."); } -Expected> -IntelPTMultiCoreTrace::GetBinaryData(const TraceGetBinaryDataRequest &request) { - return createStringError(inconvertibleErrorCode(), "Unimplemented"); +Expected>> +IntelPTMultiCoreTrace::TryGetBinaryData( + const TraceGetBinaryDataRequest &request) { + if (!request.core_id) + return None; + auto it = m_traces_per_core.find(*request.core_id); + if (it == m_traces_per_core.end()) + return createStringError( + inconvertibleErrorCode(), + formatv("Core {0} is not being traced", *request.core_id)); + + if (request.kind == IntelPTDataKinds::kTraceBuffer) + return it->second.first.GetTraceBuffer(request.offset, request.size); + if (request.kind == IntelPTDataKinds::kPerfContextSwitchTrace) + return it->second.second.ReadFlushedOutDataCyclicBuffer(request.offset, + request.size); + return None; } diff --git a/lldb/source/Plugins/Process/Linux/IntelPTPerThreadProcessTrace.h b/lldb/source/Plugins/Process/Linux/IntelPTPerThreadProcessTrace.h --- a/lldb/source/Plugins/Process/Linux/IntelPTPerThreadProcessTrace.h +++ b/lldb/source/Plugins/Process/Linux/IntelPTPerThreadProcessTrace.h @@ -44,8 +44,8 @@ TraceIntelPTGetStateResponse GetState() override; - llvm::Expected> - GetBinaryData(const TraceGetBinaryDataRequest &request) override; + llvm::Expected>> + TryGetBinaryData(const TraceGetBinaryDataRequest &request) override; private: IntelPTPerThreadProcessTrace(const TraceIntelPTStartRequest &request) diff --git a/lldb/source/Plugins/Process/Linux/IntelPTPerThreadProcessTrace.cpp b/lldb/source/Plugins/Process/Linux/IntelPTPerThreadProcessTrace.cpp --- a/lldb/source/Plugins/Process/Linux/IntelPTPerThreadProcessTrace.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTPerThreadProcessTrace.cpp @@ -46,13 +46,10 @@ return state; } -Expected> IntelPTPerThreadProcessTrace::GetBinaryData( +Expected>> +IntelPTPerThreadProcessTrace::TryGetBinaryData( const TraceGetBinaryDataRequest &request) { - if (Expected trace = - m_thread_traces.GetTracedThread(*request.tid)) - return trace->GetTraceBuffer(request.offset, request.size); - else - return trace.takeError(); + return m_thread_traces.TryGetBinaryData(request); } Expected diff --git a/lldb/source/Plugins/Process/Linux/IntelPTProcessTrace.h b/lldb/source/Plugins/Process/Linux/IntelPTProcessTrace.h --- a/lldb/source/Plugins/Process/Linux/IntelPTProcessTrace.h +++ b/lldb/source/Plugins/Process/Linux/IntelPTProcessTrace.h @@ -36,9 +36,12 @@ /// \copydoc IntelPTThreadTraceCollection::TraceStop() virtual llvm::Error TraceStop(lldb::tid_t tid) = 0; - /// Get binary data owned by this instance. - virtual llvm::Expected> - GetBinaryData(const TraceGetBinaryDataRequest &request) = 0; + /// \return + /// \b None if this instance doesn't support the requested data, an \a + /// llvm::Error if this isntance supports it but fails at fetching it, and + /// \b Error::success() otherwise. + virtual llvm::Expected>> + TryGetBinaryData(const TraceGetBinaryDataRequest &request) = 0; }; using IntelPTProcessTraceUP = std::unique_ptr; diff --git a/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp b/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp --- a/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp @@ -270,7 +270,8 @@ if (Expected perf_event = PerfEvent::Init(*attr, tid, core_id)) { if (Error mmap_err = perf_event->MmapMetadataAndBuffers( - /*num_data_pages=*/0, aux_buffer_numpages)) { + /*num_data_pages=*/0, aux_buffer_numpages, + /*data_buffer_write=*/true)) { return std::move(mmap_err); } return IntelPTSingleBufferTrace(std::move(*perf_event)); diff --git a/lldb/source/Plugins/Process/Linux/IntelPTThreadTraceCollection.h b/lldb/source/Plugins/Process/Linux/IntelPTThreadTraceCollection.h --- a/lldb/source/Plugins/Process/Linux/IntelPTThreadTraceCollection.h +++ b/lldb/source/Plugins/Process/Linux/IntelPTThreadTraceCollection.h @@ -59,6 +59,10 @@ size_t GetTracedThreadsCount() const; + /// \copydoc IntelPTProcessTrace::TryGetBinaryData() + llvm::Expected>> + TryGetBinaryData(const TraceGetBinaryDataRequest &request); + private: llvm::DenseMap m_thread_traces; /// Total actual thread buffer size in bytes diff --git a/lldb/source/Plugins/Process/Linux/IntelPTThreadTraceCollection.cpp b/lldb/source/Plugins/Process/Linux/IntelPTThreadTraceCollection.cpp --- a/lldb/source/Plugins/Process/Linux/IntelPTThreadTraceCollection.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTThreadTraceCollection.cpp @@ -71,3 +71,21 @@ size_t IntelPTThreadTraceCollection::GetTracedThreadsCount() const { return m_thread_traces.size(); } + +llvm::Expected>> +IntelPTThreadTraceCollection::TryGetBinaryData( + const TraceGetBinaryDataRequest &request) { + if (!request.tid) + return None; + if (request.kind != IntelPTDataKinds::kTraceBuffer) + return None; + + if (!TracesThread(*request.tid)) + return None; + + if (Expected trace = + GetTracedThread(*request.tid)) + return trace->GetTraceBuffer(request.offset, request.size); + else + return trace.takeError(); +} diff --git a/lldb/source/Plugins/Process/Linux/Perf.h b/lldb/source/Plugins/Process/Linux/Perf.h --- a/lldb/source/Plugins/Process/Linux/Perf.h +++ b/lldb/source/Plugins/Process/Linux/Perf.h @@ -74,24 +74,6 @@ } // namespace resource_handle -/// 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. -void ReadCyclicBuffer(llvm::MutableArrayRef &dst, - llvm::ArrayRef src, size_t src_cyc_index, - size_t offset); - /// Thin wrapper of the perf_event_open API. /// /// Exposes the metadata page and data and aux buffers of a perf event. @@ -174,11 +156,16 @@ /// A value of 0 effectively is a no-op and no data is mmap'ed for this /// buffer. /// + /// \param[in] data_buffer_write + /// Whether to mmap the data buffer with WRITE permissions. This changes + /// the behavior of how the kernel writes to the data buffer. + /// /// \return /// \a llvm::Error::success if the mmap operations succeeded, /// or an \a llvm::Error otherwise. llvm::Error MmapMetadataAndBuffers(size_t num_data_pages, - size_t num_aux_pages); + size_t num_aux_pages, + bool data_buffer_write); /// Get the file descriptor associated with the perf event. long GetFd() const; @@ -237,6 +224,25 @@ llvm::Expected> ReadFlushedOutAuxCyclicBuffer(size_t offset, size_t size); + /// Read the data buffer managed by this perf event. To ensure that the + /// data is up-to-date and is not corrupted by read-write race conditions, the + /// underlying perf_event is paused during read, and later it's returned to + /// its initial state. The returned data will be linear, i.e. it will fix the + /// circular wrapping the might exist int he buffer. + /// + /// \param[in] offset + /// Offset of the data to read. + /// + /// \param[in] size + /// Number of bytes to read. + /// + /// \return + /// A vector with the requested binary data. The vector will have the + /// size of the requested \a size. Non-available positions will be + /// filled with zeroes. + llvm::Expected> + ReadFlushedOutDataCyclicBuffer(size_t offset, size_t size); + /// Use the ioctl API to disable the perf event and all the events in its /// group. This doesn't terminate the perf event. /// @@ -290,7 +296,12 @@ /// Number of pages in the data buffer to mmap, must be a power of 2. /// A value of 0 is useful for "dummy" events that only want to access /// the metadata, \a perf_event_mmap_page, or the aux buffer. - llvm::Error MmapMetadataAndDataBuffer(size_t num_data_pages); + /// + /// \param[in] data_buffer_write + /// Whether to mmap the data buffer with WRITE permissions. This changes + /// the behavior of how the kernel writes to the data buffer. + llvm::Error MmapMetadataAndDataBuffer(size_t num_data_pages, + bool data_buffer_write); /// Mmap the aux buffer of the perf event. /// diff --git a/lldb/source/Plugins/Process/Linux/Perf.cpp b/lldb/source/Plugins/Process/Linux/Perf.cpp --- a/lldb/source/Plugins/Process/Linux/Perf.cpp +++ b/lldb/source/Plugins/Process/Linux/Perf.cpp @@ -24,54 +24,6 @@ using namespace process_linux; using namespace llvm; -void lldb_private::process_linux::ReadCyclicBuffer( - llvm::MutableArrayRef &dst, llvm::ArrayRef src, - size_t src_cyc_index, size_t offset) { - - Log *log = GetLog(POSIXLog::Trace); - - if (dst.empty() || src.empty()) { - dst = dst.drop_back(dst.size()); - return; - } - - if (dst.data() == nullptr || src.data() == nullptr) { - dst = dst.drop_back(dst.size()); - return; - } - - if (src_cyc_index > src.size()) { - dst = dst.drop_back(dst.size()); - return; - } - - if (offset >= src.size()) { - LLDB_LOG(log, "Too Big offset "); - dst = dst.drop_back(dst.size()); - return; - } - - llvm::SmallVector, 2> parts = { - src.slice(src_cyc_index), src.take_front(src_cyc_index)}; - - if (offset > parts[0].size()) { - parts[1] = parts[1].slice(offset - parts[0].size()); - parts[0] = parts[0].drop_back(parts[0].size()); - } else if (offset == parts[0].size()) { - parts[0] = parts[0].drop_back(parts[0].size()); - } else { - parts[0] = parts[0].slice(offset); - } - auto next = dst.begin(); - auto bytes_left = dst.size(); - for (auto part : parts) { - size_t chunk_size = std::min(part.size(), bytes_left); - next = std::copy_n(part.begin(), chunk_size, next); - bytes_left -= chunk_size; - } - dst = dst.drop_back(bytes_left); -} - Expected lldb_private::process_linux::LoadPerfTscConversionParameters() { lldb::pid_t pid = getpid(); @@ -84,8 +36,10 @@ Expected perf_event = PerfEvent::Init(attr, pid); if (!perf_event) return perf_event.takeError(); - if (Error mmap_err = perf_event->MmapMetadataAndBuffers(/*num_data_pages*/ 0, - /*num_aux_pages*/ 0)) + if (Error mmap_err = + perf_event->MmapMetadataAndBuffers(/*num_data_pages=*/0, + /*num_aux_pages=*/0, + /*data_buffer_write=*/false)) return std::move(mmap_err); perf_event_mmap_page &mmap_metada = perf_event->GetMetadataPage(); @@ -155,11 +109,12 @@ return resource_handle::MmapUP(mmap_result, length); } -llvm::Error PerfEvent::MmapMetadataAndDataBuffer(size_t num_data_pages) { +llvm::Error PerfEvent::MmapMetadataAndDataBuffer(size_t num_data_pages, + bool data_buffer_write) { size_t mmap_size = (num_data_pages + 1) * getpagesize(); - if (Expected mmap_metadata_data = - DoMmap(nullptr, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, 0, - "metadata and data buffer")) { + if (Expected mmap_metadata_data = DoMmap( + nullptr, mmap_size, PROT_READ | (data_buffer_write ? PROT_WRITE : 0), + MAP_SHARED, 0, "metadata and data buffer")) { m_metadata_data_base = std::move(mmap_metadata_data.get()); return Error::success(); } else @@ -171,6 +126,7 @@ return Error::success(); perf_event_mmap_page &metadata_page = GetMetadataPage(); + metadata_page.aux_offset = metadata_page.data_offset + metadata_page.data_size; metadata_page.aux_size = num_aux_pages * getpagesize(); @@ -185,7 +141,8 @@ } llvm::Error PerfEvent::MmapMetadataAndBuffers(size_t num_data_pages, - size_t num_aux_pages) { + size_t num_aux_pages, + bool data_buffer_write) { if (num_data_pages != 0 && !isPowerOf2_64(num_data_pages)) return llvm::createStringError( llvm::inconvertibleErrorCode(), @@ -196,7 +153,7 @@ llvm::inconvertibleErrorCode(), llvm::formatv("Number of aux pages must be a power of 2, got: {0}", num_aux_pages)); - if (Error err = MmapMetadataAndDataBuffer(num_data_pages)) + if (Error err = MmapMetadataAndDataBuffer(num_data_pages, data_buffer_write)) return err; if (Error err = MmapAuxBuffer(num_aux_pages)) return err; @@ -222,18 +179,74 @@ static_cast(mmap_metadata.aux_size)}; } +Expected> +PerfEvent::ReadFlushedOutDataCyclicBuffer(size_t offset, size_t size) { + CollectionState previous_state = m_collection_state; + if (Error err = DisableWithIoctl()) + return std::move(err); + + /** + * 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 + */ + perf_event_mmap_page &mmap_metadata = GetMetadataPage(); + + ArrayRef data = GetDataBuffer(); + uint64_t data_head = mmap_metadata.data_head; + uint64_t data_size = mmap_metadata.data_size; + std::vector output; + output.reserve(size); + + if (data_head > data_size) { + uint64_t actual_data_head = data_head % data_size; + // The buffer has wrapped + for (uint64_t i = actual_data_head + offset; + i < data_size && output.size() < size; i++) + output.push_back(data[i]); + + // We need to find the starting position for the left part if the offset was + // too big + uint64_t left_part_start = actual_data_head + offset >= data_size + ? actual_data_head + offset - data_size + : 0; + for (uint64_t i = left_part_start; + i < actual_data_head && output.size() < size; i++) + output.push_back(data[i]); + } else { + for (uint64_t i = offset; i < data_head && output.size() < size; i++) + output.push_back(data[i]); + } + + if (previous_state == CollectionState::Enabled) { + if (Error err = EnableWithIoctl()) + return std::move(err); + } + + if (output.size() != size) + return createStringError(inconvertibleErrorCode(), + formatv("Requested {0} bytes of perf_event data " + "buffer but only {1} are available", + size, output.size())); + + return data; +} + Expected> PerfEvent::ReadFlushedOutAuxCyclicBuffer(size_t offset, size_t size) { CollectionState previous_state = m_collection_state; if (Error err = DisableWithIoctl()) return std::move(err); - std::vector data(size, 0); perf_event_mmap_page &mmap_metadata = GetMetadataPage(); - Log *log = GetLog(POSIXLog::Trace); - uint64_t head = mmap_metadata.aux_head; - LLDB_LOG(log, "Aux size -{0} , Head - {1}", mmap_metadata.aux_size, head); + ArrayRef data = GetAuxBuffer(); + uint64_t aux_head = mmap_metadata.aux_head; + uint64_t aux_size = mmap_metadata.aux_size; + std::vector output; + output.reserve(size); /** * When configured as ring buffer, the aux buffer keeps wrapping around @@ -247,14 +260,28 @@ * * */ - MutableArrayRef buffer(data); - ReadCyclicBuffer(buffer, GetAuxBuffer(), static_cast(head), offset); + for (uint64_t i = aux_head + offset; i < aux_size && output.size() < size; + i++) + output.push_back(data[i]); + + // We need to find the starting position for the left part if the offset was + // too big + uint64_t left_part_start = + aux_head + offset >= aux_size ? aux_head + offset - aux_size : 0; + for (uint64_t i = left_part_start; i < aux_head && output.size() < size; i++) + output.push_back(data[i]); if (previous_state == CollectionState::Enabled) { if (Error err = EnableWithIoctl()) return std::move(err); } + if (output.size() != size) + return createStringError(inconvertibleErrorCode(), + formatv("Requested {0} bytes of perf_event aux " + "buffer but only {1} are available", + size, output.size())); + return data; } @@ -286,7 +313,7 @@ size_t PerfEvent::GetEffectiveDataBufferSize() const { perf_event_mmap_page &mmap_metadata = GetMetadataPage(); - if (mmap_metadata.data_head < mmap_metadata.data_size) + if (mmap_metadata.data_head <= mmap_metadata.data_size) return mmap_metadata.data_head; else return mmap_metadata.data_size; // The buffer has wrapped. diff --git a/lldb/source/Plugins/Trace/common/CMakeLists.txt b/lldb/source/Plugins/Trace/common/CMakeLists.txt --- a/lldb/source/Plugins/Trace/common/CMakeLists.txt +++ b/lldb/source/Plugins/Trace/common/CMakeLists.txt @@ -1,8 +1,5 @@ add_lldb_library(lldbPluginTraceCommon ThreadPostMortemTrace.cpp - TraceJSONStructs.cpp - TraceSessionFileParser.cpp - TraceSessionSaver.cpp LINK_LIBS lldbCore diff --git a/lldb/source/Plugins/Trace/common/ThreadPostMortemTrace.h b/lldb/source/Plugins/Trace/common/ThreadPostMortemTrace.h --- a/lldb/source/Plugins/Trace/common/ThreadPostMortemTrace.h +++ b/lldb/source/Plugins/Trace/common/ThreadPostMortemTrace.h @@ -32,7 +32,7 @@ /// The file that contains the list of instructions that were traced when /// this thread was being executed. ThreadPostMortemTrace(Process &process, lldb::tid_t tid, - const FileSpec &trace_file) + const llvm::Optional &trace_file) : Thread(process, tid), m_trace_file(trace_file) {} void RefreshStateAfterStop() override; @@ -44,7 +44,7 @@ /// \return /// The trace file of this thread. - const FileSpec &GetTraceFile() const; + const llvm::Optional &GetTraceFile() const; protected: bool CalculateStopInfo() override; @@ -52,7 +52,7 @@ lldb::RegisterContextSP m_thread_reg_ctx_sp; private: - FileSpec m_trace_file; + llvm::Optional m_trace_file; }; } // namespace lldb_private diff --git a/lldb/source/Plugins/Trace/common/ThreadPostMortemTrace.cpp b/lldb/source/Plugins/Trace/common/ThreadPostMortemTrace.cpp --- a/lldb/source/Plugins/Trace/common/ThreadPostMortemTrace.cpp +++ b/lldb/source/Plugins/Trace/common/ThreadPostMortemTrace.cpp @@ -16,6 +16,7 @@ using namespace lldb; using namespace lldb_private; +using namespace llvm; void ThreadPostMortemTrace::RefreshStateAfterStop() {} @@ -36,6 +37,6 @@ bool ThreadPostMortemTrace::CalculateStopInfo() { return false; } -const FileSpec &ThreadPostMortemTrace::GetTraceFile() const { +const Optional &ThreadPostMortemTrace::GetTraceFile() const { return m_trace_file; } diff --git a/lldb/source/Plugins/Trace/common/TraceJSONStructs.h b/lldb/source/Plugins/Trace/common/TraceJSONStructs.h deleted file mode 100644 --- a/lldb/source/Plugins/Trace/common/TraceJSONStructs.h +++ /dev/null @@ -1,98 +0,0 @@ -//===-- TraceJSONStruct.h ---------------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLDB_TARGET_TRACEJSONSTRUCTS_H -#define LLDB_TARGET_TRACEJSONSTRUCTS_H - -#include "lldb/lldb-types.h" -#include "llvm/Support/JSON.h" - -namespace lldb_private { - -struct JSONAddress { - lldb::addr_t value; -}; - -struct JSONModule { - std::string system_path; - llvm::Optional file; - JSONAddress load_address; - llvm::Optional uuid; -}; - -struct JSONThread { - int64_t tid; - std::string trace_file; -}; - -struct JSONProcess { - int64_t pid; - std::string triple; - std::vector threads; - std::vector modules; -}; - -struct JSONTracePluginSettings { - std::string type; -}; - -struct JSONTraceSessionBase { - std::vector processes; -}; - -/// The trace plug-in implementation should provide its own TPluginSettings, -/// which corresponds to the "trace" section of the schema. -template -struct JSONTraceSession : JSONTraceSessionBase { - TPluginSettings trace; -}; - -} // namespace lldb_private - -namespace llvm { -namespace json { - -llvm::json::Value toJSON(const lldb_private::JSONModule &module); - -llvm::json::Value toJSON(const lldb_private::JSONThread &thread); - -llvm::json::Value toJSON(const lldb_private::JSONProcess &process); - -llvm::json::Value -toJSON(const lldb_private::JSONTraceSessionBase &session_base); - -bool fromJSON(const Value &value, lldb_private::JSONAddress &address, - Path path); - -bool fromJSON(const Value &value, lldb_private::JSONModule &module, Path path); - -bool fromJSON(const Value &value, lldb_private::JSONThread &thread, Path path); - -bool fromJSON(const Value &value, lldb_private::JSONProcess &process, - Path path); - -bool fromJSON(const Value &value, - lldb_private::JSONTracePluginSettings &plugin_settings, - Path path); - -bool fromJSON(const Value &value, lldb_private::JSONTraceSessionBase &session, - Path path); - -template -bool fromJSON(const Value &value, - lldb_private::JSONTraceSession &session, - Path path) { - ObjectMapper o(value, path); - return o && o.map("trace", session.trace) && - fromJSON(value, (lldb_private::JSONTraceSessionBase &)session, path); -} - -} // namespace json -} // namespace llvm - -#endif // LLDB_TARGET_TRACEJSONSTRUCTS_H diff --git a/lldb/source/Plugins/Trace/common/TraceJSONStructs.cpp b/lldb/source/Plugins/Trace/common/TraceJSONStructs.cpp deleted file mode 100644 --- a/lldb/source/Plugins/Trace/common/TraceJSONStructs.cpp +++ /dev/null @@ -1,106 +0,0 @@ -//===-- TraceSessionFileStructs.cpp ---------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===/ - -#include "TraceJSONStructs.h" -#include "ThreadPostMortemTrace.h" -#include "lldb/Core/Debugger.h" -#include "lldb/Core/Module.h" -#include "lldb/Target/Process.h" -#include "lldb/Target/Target.h" -#include - -using namespace lldb_private; -namespace llvm { -namespace json { - -llvm::json::Value toJSON(const JSONModule &module) { - llvm::json::Object json_module; - json_module["systemPath"] = module.system_path; - if (module.file) - json_module["file"] = *module.file; - std::ostringstream oss; - oss << "0x" << std::hex << module.load_address.value; - json_module["loadAddress"] = oss.str(); - if (module.uuid) - json_module["uuid"] = *module.uuid; - return std::move(json_module); -} - -llvm::json::Value toJSON(const JSONThread &thread) { - return Value(Object{{"tid", thread.tid}, {"traceFile", thread.trace_file}}); -} - -llvm::json::Value toJSON(const JSONProcess &process) { - llvm::json::Object json_process; - json_process["pid"] = process.pid; - json_process["triple"] = process.triple; - - llvm::json::Array threads_arr; - for (JSONThread e : process.threads) - threads_arr.push_back(toJSON(e)); - - json_process["threads"] = llvm::json::Value(std::move(threads_arr)); - - llvm::json::Array modules_arr; - for (JSONModule e : process.modules) - modules_arr.push_back(toJSON(e)); - - json_process["modules"] = llvm::json::Value(std::move(modules_arr)); - - return std::move(json_process); -} - -llvm::json::Value toJSON(const JSONTraceSessionBase &session) { - llvm::json::Array arr; - for (JSONProcess e : session.processes) - arr.push_back(toJSON(e)); - - return std::move(arr); -} - -bool fromJSON(const Value &value, JSONAddress &address, Path path) { - Optional s = value.getAsString(); - if (s.hasValue() && !s->getAsInteger(0, address.value)) - return true; - - path.report("expected numeric string"); - return false; -} - -bool fromJSON(const Value &value, JSONModule &module, Path path) { - ObjectMapper o(value, path); - return o && o.map("systemPath", module.system_path) && - o.map("file", module.file) && - o.map("loadAddress", module.load_address) && - o.map("uuid", module.uuid); -} - -bool fromJSON(const Value &value, JSONThread &thread, Path path) { - ObjectMapper o(value, path); - return o && o.map("tid", thread.tid) && o.map("traceFile", thread.trace_file); -} - -bool fromJSON(const Value &value, JSONProcess &process, Path path) { - ObjectMapper o(value, path); - return o && o.map("pid", process.pid) && o.map("triple", process.triple) && - o.map("threads", process.threads) && o.map("modules", process.modules); -} - -bool fromJSON(const Value &value, JSONTracePluginSettings &plugin_settings, - Path path) { - ObjectMapper o(value, path); - return o && o.map("type", plugin_settings.type); -} - -bool fromJSON(const Value &value, JSONTraceSessionBase &session, Path path) { - ObjectMapper o(value, path); - return o && o.map("processes", session.processes); -} - -} // namespace json -} // namespace llvm diff --git a/lldb/source/Plugins/Trace/common/TraceSessionFileParser.h b/lldb/source/Plugins/Trace/common/TraceSessionFileParser.h deleted file mode 100644 --- a/lldb/source/Plugins/Trace/common/TraceSessionFileParser.h +++ /dev/null @@ -1,93 +0,0 @@ -//===-- TraceSessionFileParser.h --------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLDB_TARGET_TRACESESSIONPARSER_H -#define LLDB_TARGET_TRACESESSIONPARSER_H - -#include "ThreadPostMortemTrace.h" -#include "TraceJSONStructs.h" - -namespace lldb_private { - -/// \class TraceSessionFileParser TraceSessionFileParser.h -/// -/// Base class for parsing the common information of JSON trace session files. -/// Contains the basic C++ structs that represent the JSON data, which include -/// \a JSONTraceSession as the root object. -/// -/// See \a Trace::FindPlugin for more information regarding these JSON files. -class TraceSessionFileParser { -public: - - /// Helper struct holding the objects created when parsing a process - struct ParsedProcess { - lldb::TargetSP target_sp; - std::vector threads; - }; - - TraceSessionFileParser(Debugger &debugger, llvm::StringRef session_file_dir, - llvm::StringRef schema) - : m_debugger(debugger), m_session_file_dir(session_file_dir), - m_schema(schema) {} - - /// Build the full schema for a Trace plug-in. - /// - /// \param[in] plugin_schema - /// The subschema that corresponds to the "trace" section of the schema. - /// - /// \return - /// The full schema containing the common attributes and the plug-in - /// specific attributes. - static std::string BuildSchema(llvm::StringRef plugin_schema); - - /// Parse the fields common to all trace session schemas. - /// - /// \param[in] session - /// The session json objects already deserialized. - /// - /// \return - /// A list of \a ParsedProcess containing all threads and targets created - /// during the parsing, or an error in case of failures. In case of - /// errors, no side effects are produced. - llvm::Expected> - ParseCommonSessionFile(const JSONTraceSessionBase &session); - -protected: - /// Resolve non-absolute paths relative to the session file folder. It - /// modifies the given file_spec. - void NormalizePath(lldb_private::FileSpec &file_spec); - - lldb::ThreadPostMortemTraceSP ParseThread(lldb::ProcessSP &process_sp, - const JSONThread &thread); - - llvm::Expected ParseProcess(const JSONProcess &process); - - llvm::Error ParseModule(lldb::TargetSP &target_sp, const JSONModule &module); - - /// Create a user-friendly error message upon a JSON-parsing failure using the - /// \a json::ObjectMapper functionality. - /// - /// \param[in] root - /// The \a llvm::json::Path::Root used to parse the JSON \a value. - /// - /// \param[in] value - /// The json value that failed to parse. - /// - /// \return - /// An \a llvm::Error containing the user-friendly error message. - llvm::Error CreateJSONError(llvm::json::Path::Root &root, - const llvm::json::Value &value); - - Debugger &m_debugger; - std::string m_session_file_dir; - llvm::StringRef m_schema; -}; -} // namespace lldb_private - - -#endif // LLDB_TARGET_TRACESESSIONPARSER_H diff --git a/lldb/source/Plugins/Trace/common/TraceSessionFileParser.cpp b/lldb/source/Plugins/Trace/common/TraceSessionFileParser.cpp deleted file mode 100644 --- a/lldb/source/Plugins/Trace/common/TraceSessionFileParser.cpp +++ /dev/null @@ -1,172 +0,0 @@ -//===-- TraceSessionFileParser.cpp ---------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===/ - -#include "TraceSessionFileParser.h" -#include "ThreadPostMortemTrace.h" - -#include - -#include "lldb/Core/Debugger.h" -#include "lldb/Core/Module.h" -#include "lldb/Target/Process.h" -#include "lldb/Target/Target.h" - -using namespace lldb; -using namespace lldb_private; -using namespace llvm; - -void TraceSessionFileParser::NormalizePath(lldb_private::FileSpec &file_spec) { - if (file_spec.IsRelative()) - file_spec.PrependPathComponent(m_session_file_dir); -} - -Error TraceSessionFileParser::ParseModule(lldb::TargetSP &target_sp, - const JSONModule &module) { - FileSpec system_file_spec(module.system_path); - NormalizePath(system_file_spec); - - FileSpec local_file_spec(module.file.hasValue() ? *module.file - : module.system_path); - NormalizePath(local_file_spec); - - ModuleSpec module_spec; - module_spec.GetFileSpec() = local_file_spec; - module_spec.GetPlatformFileSpec() = system_file_spec; - - if (module.uuid.hasValue()) - module_spec.GetUUID().SetFromStringRef(*module.uuid); - - Status error; - ModuleSP module_sp = - target_sp->GetOrCreateModule(module_spec, /*notify*/ false, &error); - - if (error.Fail()) - return error.ToError(); - - bool load_addr_changed = false; - module_sp->SetLoadAddress(*target_sp, module.load_address.value, false, - load_addr_changed); - return llvm::Error::success(); -} - -Error TraceSessionFileParser::CreateJSONError(json::Path::Root &root, - const json::Value &value) { - std::string err; - raw_string_ostream os(err); - root.printErrorContext(value, os); - return createStringError( - std::errc::invalid_argument, "%s\n\nContext:\n%s\n\nSchema:\n%s", - toString(root.getError()).c_str(), os.str().c_str(), m_schema.data()); -} - -std::string TraceSessionFileParser::BuildSchema(StringRef plugin_schema) { - std::ostringstream schema_builder; - schema_builder << "{\n \"trace\": "; - schema_builder << plugin_schema.data() << ","; - schema_builder << R"( - "processes": [ - { - "pid": integer, - "triple": string, // llvm-triple - "threads": [ - { - "tid": integer, - "traceFile": string - } - ], - "modules": [ - { - "systemPath": string, // original path of the module at runtime - "file"?: string, // copy of the file if not available at "systemPath" - "loadAddress": string, // string address in hex or decimal form - "uuid"?: string, - } - ] - } - ] - // Notes: - // All paths are either absolute or relative to the session file. -} -)"; - return schema_builder.str(); -} - -ThreadPostMortemTraceSP -TraceSessionFileParser::ParseThread(ProcessSP &process_sp, - const JSONThread &thread) { - lldb::tid_t tid = static_cast(thread.tid); - - FileSpec trace_file(thread.trace_file); - NormalizePath(trace_file); - - ThreadPostMortemTraceSP thread_sp = - std::make_shared(*process_sp, tid, trace_file); - process_sp->GetThreadList().AddThread(thread_sp); - return thread_sp; -} - -Expected -TraceSessionFileParser::ParseProcess(const JSONProcess &process) { - TargetSP target_sp; - Status error = m_debugger.GetTargetList().CreateTarget( - m_debugger, /*user_exe_path*/ StringRef(), process.triple, - eLoadDependentsNo, - /*platform_options*/ nullptr, target_sp); - - if (!target_sp) - return error.ToError(); - - ParsedProcess parsed_process; - parsed_process.target_sp = target_sp; - - ProcessSP process_sp = target_sp->CreateProcess( - /*listener*/ nullptr, "trace", - /*crash_file*/ nullptr, - /*can_connect*/ false); - - process_sp->SetID(static_cast(process.pid)); - - for (const JSONThread &thread : process.threads) - parsed_process.threads.push_back(ParseThread(process_sp, thread)); - - for (const JSONModule &module : process.modules) - if (Error err = ParseModule(target_sp, module)) - return std::move(err); - - if (!process.threads.empty()) - process_sp->GetThreadList().SetSelectedThreadByIndexID(0); - - // We invoke DidAttach to create a correct stopped state for the process and - // its threads. - ArchSpec process_arch; - process_sp->DidAttach(process_arch); - - return parsed_process; -} - -Expected> -TraceSessionFileParser::ParseCommonSessionFile( - const JSONTraceSessionBase &session) { - std::vector parsed_processes; - - auto onError = [&]() { - // Delete all targets that were created so far in case of failures - for (ParsedProcess &parsed_process : parsed_processes) - m_debugger.GetTargetList().DeleteTarget(parsed_process.target_sp); - }; - - for (const JSONProcess &process : session.processes) { - if (Expected parsed_process = ParseProcess(process)) - parsed_processes.push_back(std::move(*parsed_process)); - else { - onError(); - return parsed_process.takeError(); - } - } - return parsed_processes; -} diff --git a/lldb/source/Plugins/Trace/common/TraceSessionSaver.h b/lldb/source/Plugins/Trace/common/TraceSessionSaver.h deleted file mode 100644 --- a/lldb/source/Plugins/Trace/common/TraceSessionSaver.h +++ /dev/null @@ -1,102 +0,0 @@ -//===-- SessionSaver.h ----------------------------------------*- C++ //-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLDB_TARGET_TRACESESSIONSAVER_H -#define LLDB_TARGET_TRACESESSIONSAVER_H - -#include "TraceJSONStructs.h" - -namespace lldb_private { - -class TraceSessionSaver { - -public: - /// Save the trace session description JSON object inside the given directory - /// as a file named \a trace.json. - /// - /// \param[in] trace_session_description - /// The trace's session, as JSON Object. - /// - /// \param[in] directory - /// The directory where the JSON file will be saved. - /// - /// \return - /// \a llvm::success if the operation was successful, or an \a llvm::Error - /// otherwise. - static llvm::Error - WriteSessionToFile(const llvm::json::Value &trace_session_description, - FileSpec directory); - - /// Build the processes section of the trace session description file. Besides - /// returning the processes information, this method saves to disk all modules - /// and raw traces corresponding to the traced threads of the given process. - /// - /// \param[in] live_process - /// The process being traced. - /// - /// \param[in] raw_thread_trace_data_kind - /// Identifier for the data kind of the raw trace for each thread. - /// - /// \param[in] directory - /// The directory where files will be saved when building the processes - /// section. - /// - /// \return - /// The processes section or \a llvm::Error in case of failures. - static llvm::Expected - BuildProcessesSection(Process &live_process, - llvm::StringRef raw_thread_trace_data_kind, - FileSpec directory); - - /// Build the threads sub-section of the trace session description file. - /// For each traced thread, its raw trace is also written to the file - /// \a thread_id_.trace inside of the given directory. - /// - /// \param[in] live_process - /// The process being traced. - /// - /// \param[in] raw_thread_trace_data_kind - /// Identifier for the data kind of the raw trace for each thread. - /// - /// \param[in] directory - /// The directory where files will be saved when building the threads - /// section. - /// - /// \return - /// The threads section or \a llvm::Error in case of failures. - static llvm::Expected> - BuildThreadsSection(Process &live_process, - llvm::StringRef raw_thread_trace_data_kind, - FileSpec directory); - - /// Build modules sub-section of the trace's session. The original modules - /// will be copied over to the \a folder. Invalid modules - /// are skipped. - /// Copying the modules has the benefit of making these trace session - /// directories self-contained, as the raw traces and modules are part of the - /// output directory and can be sent to another machine, where lldb can load - /// them and replicate exactly the same trace session. - /// - /// \param[in] live_process - /// The process being traced. - /// - /// \param[in] directory - /// The directory where the modules files will be saved when building - /// the modules section. - /// Example: If a module \a libbar.so exists in the path - /// \a /usr/lib/foo/libbar.so, then it will be copied to - /// \a /modules/usr/lib/foo/libbar.so. - /// - /// \return - /// The modules section or \a llvm::Error in case of failures. - static llvm::Expected> - BuildModulesSection(Process &live_process, FileSpec directory); -}; -} // namespace lldb_private - -#endif // LLDB_TARGET_TRACESESSIONSAVER_H diff --git a/lldb/source/Plugins/Trace/common/TraceSessionSaver.cpp b/lldb/source/Plugins/Trace/common/TraceSessionSaver.cpp deleted file mode 100644 --- a/lldb/source/Plugins/Trace/common/TraceSessionSaver.cpp +++ /dev/null @@ -1,148 +0,0 @@ -//===-- TraceSessionSaver.cpp ---------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "TraceSessionSaver.h" - -#include "lldb/Core/Module.h" -#include "lldb/Core/Value.h" -#include "lldb/Target/Process.h" -#include "lldb/Target/SectionLoadList.h" -#include "lldb/Target/Target.h" -#include "lldb/lldb-types.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/JSON.h" - -#include - -using namespace lldb; -using namespace lldb_private; -using namespace llvm; - -llvm::Error TraceSessionSaver::WriteSessionToFile( - const llvm::json::Value &trace_session_description, FileSpec directory) { - - FileSpec trace_path = directory; - trace_path.AppendPathComponent("trace.json"); - std::ofstream os(trace_path.GetPath()); - os << std::string(formatv("{0:2}", trace_session_description)); - os.close(); - if (!os) - return createStringError(inconvertibleErrorCode(), - formatv("couldn't write to the file {0}", - trace_path.GetPath().c_str())); - return Error::success(); -} - -llvm::Expected TraceSessionSaver::BuildProcessesSection( - Process &live_process, llvm::StringRef raw_thread_trace_data_kind, - FileSpec directory) { - - JSONTraceSessionBase json_session_description; - Expected> json_threads = - BuildThreadsSection(live_process, raw_thread_trace_data_kind, directory); - if (!json_threads) - return json_threads.takeError(); - - Expected> json_modules = - BuildModulesSection(live_process, directory); - if (!json_modules) - return json_modules.takeError(); - - json_session_description.processes.push_back(JSONProcess{ - static_cast(live_process.GetID()), - live_process.GetTarget().GetArchitecture().GetTriple().getTriple(), - json_threads.get(), json_modules.get()}); - return json_session_description; -} - -llvm::Expected> TraceSessionSaver::BuildThreadsSection( - Process &live_process, llvm::StringRef raw_thread_trace_data_kind, - FileSpec directory) { - std::vector json_threads; - for (ThreadSP thread_sp : live_process.Threads()) { - TraceSP trace_sp = live_process.GetTarget().GetTrace(); - lldb::tid_t tid = thread_sp->GetID(); - if (!trace_sp->IsTraced(tid)) - continue; - - // resolve the directory just in case - FileSystem::Instance().Resolve(directory); - FileSpec raw_trace_path = directory; - raw_trace_path.AppendPathComponent(std::to_string(tid) + ".trace"); - json_threads.push_back(JSONThread{static_cast(tid), - raw_trace_path.GetPath().c_str()}); - - llvm::Error err = - live_process.GetTarget().GetTrace()->OnThreadBinaryDataRead( - tid, raw_thread_trace_data_kind, - [&](llvm::ArrayRef data) -> llvm::Error { - std::basic_fstream raw_trace_fs = - std::fstream(raw_trace_path.GetPath().c_str(), - std::ios::out | std::ios::binary); - raw_trace_fs.write(reinterpret_cast(&data[0]), - data.size() * sizeof(uint8_t)); - raw_trace_fs.close(); - if (!raw_trace_fs) - return createStringError( - inconvertibleErrorCode(), - formatv("couldn't write to the file {0}", - raw_trace_path.GetPath().c_str())); - return Error::success(); - }); - if (err) - return std::move(err); - } - return json_threads; -} - -llvm::Expected> -TraceSessionSaver::BuildModulesSection(Process &live_process, - FileSpec directory) { - std::vector json_modules; - ModuleList module_list = live_process.GetTarget().GetImages(); - for (size_t i = 0; i < module_list.GetSize(); ++i) { - ModuleSP module_sp(module_list.GetModuleAtIndex(i)); - if (!module_sp) - continue; - std::string system_path = module_sp->GetPlatformFileSpec().GetPath(); - // TODO: support memory-only libraries like [vdso] - if (!module_sp->GetFileSpec().IsAbsolute()) - continue; - - std::string file = module_sp->GetFileSpec().GetPath(); - ObjectFile *objfile = module_sp->GetObjectFile(); - if (objfile == nullptr) - continue; - - lldb::addr_t load_addr = LLDB_INVALID_ADDRESS; - Address base_addr(objfile->GetBaseAddress()); - if (base_addr.IsValid() && - !live_process.GetTarget().GetSectionLoadList().IsEmpty()) - load_addr = base_addr.GetLoadAddress(&live_process.GetTarget()); - - if (load_addr == LLDB_INVALID_ADDRESS) - continue; - - FileSystem::Instance().Resolve(directory); - FileSpec path_to_copy_module = directory; - path_to_copy_module.AppendPathComponent("modules"); - path_to_copy_module.AppendPathComponent(system_path); - sys::fs::create_directories(path_to_copy_module.GetDirectory().AsCString()); - - if (std::error_code ec = llvm::sys::fs::copy_file( - system_path, path_to_copy_module.GetPath())) - return createStringError( - inconvertibleErrorCode(), - formatv("couldn't write to the file. {0}", ec.message())); - - json_modules.push_back( - JSONModule{system_path, path_to_copy_module.GetPath(), - JSONAddress{load_addr}, module_sp->GetUUID().GetAsString()}); - } - return json_modules; -} diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -146,8 +146,11 @@ llvm::Error OnThreadBufferRead(lldb::tid_t tid, OnBinaryDataReadCallback callback); + /// Get or fetch the cpu information from, for example, /proc/cpuinfo. llvm::Expected GetCPUInfo(); + /// Get or fetch the values used to convert to and from TSCs and nanos. + llvm::Optional GetPerfZeroTscConversion(); /// \return /// The timer object for this trace. @@ -158,12 +161,20 @@ llvm::Expected GetCPUInfoForLiveProcess(); + /// Postmortem trace constructor + /// + /// \param[in] session + /// The definition file for the postmortem session. + /// + /// \param[in] traces_proceses + /// The processes traced in the live session. + /// /// \param[in] trace_threads - /// ThreadTrace instances, which are not live-processes and whose trace - /// files are fixed. - TraceIntelPT( - const pt_cpu &cpu_info, - const std::vector &traced_threads); + /// The threads traced in the live session. They must belong to the + /// processes mentioned above. + TraceIntelPT(JSONTraceSession &session, + llvm::ArrayRef traced_processes, + llvm::ArrayRef traced_threads); /// Constructor for live processes TraceIntelPT(Process &live_process) diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -74,15 +74,28 @@ return instance; } -TraceIntelPT::TraceIntelPT( - const pt_cpu &cpu_info, - const std::vector &traced_threads) - : m_cpu_info(cpu_info) { +TraceIntelPT::TraceIntelPT(JSONTraceSession &session, + ArrayRef traced_processes, + ArrayRef traced_threads) + : Trace(traced_processes, session.GetCoreIds()), + m_cpu_info(session.cpu_info), + m_tsc_conversion(session.tsc_perf_zero_conversion) { for (const ThreadPostMortemTraceSP &thread : traced_threads) { m_thread_decoders.emplace(thread->GetID(), std::make_unique(thread, *this)); - SetPostMortemThreadDataFile(thread->GetID(), IntelPTDataKinds::kTraceBuffer, - thread->GetTraceFile()); + if (const Optional &trace_file = thread->GetTraceFile()) { + SetPostMortemThreadDataFile(thread->GetID(), + IntelPTDataKinds::kTraceBuffer, *trace_file); + } + } + if (session.cores) { + for (const JSONCore &core : *session.cores) { + SetPostMortemCoreDataFile(core.core_id, IntelPTDataKinds::kTraceBuffer, + FileSpec(core.trace_buffer)); + SetPostMortemCoreDataFile(core.core_id, + IntelPTDataKinds::kPerfContextSwitchTrace, + FileSpec(core.context_switch_trace)); + } } } @@ -240,6 +253,12 @@ return *m_cpu_info; } +llvm::Optional +TraceIntelPT::GetPerfZeroTscConversion() { + RefreshLiveProcessState(); + return m_tsc_conversion; +} + Error TraceIntelPT::DoRefreshLiveProcessState(TraceGetStateResponse state, StringRef json_response) { m_thread_decoders.clear(); diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.h @@ -9,67 +9,84 @@ #ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTJSONSTRUCTS_H #define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTJSONSTRUCTS_H -#include "../common/TraceJSONStructs.h" +#include "lldb/lldb-types.h" + +#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" + +#include "llvm/ADT/Optional.h" +#include "llvm/Support/JSON.h" + #include +#include namespace lldb_private { namespace trace_intel_pt { -struct JSONTraceIntelPTCPUInfo { - JSONTraceIntelPTCPUInfo() = default; - - JSONTraceIntelPTCPUInfo(pt_cpu cpu_info) { - family = static_cast(cpu_info.family); - model = static_cast(cpu_info.model); - stepping = static_cast(cpu_info.stepping); - vendor = cpu_info.vendor == pcv_intel ? "intel" : "Unknown"; - } +struct JSONModule { + std::string system_path; + llvm::Optional file; + uint64_t load_address; + llvm::Optional uuid; +}; - int64_t family; - int64_t model; - int64_t stepping; - std::string vendor; +struct JSONThread { + int64_t tid; + llvm::Optional trace_buffer; }; -struct JSONTraceIntelPTTrace { - std::string type; - JSONTraceIntelPTCPUInfo cpuInfo; +struct JSONProcess { + int64_t pid; + std::string triple; + std::vector threads; + std::vector modules; }; -struct JSONTraceIntelPTSession { - JSONTraceIntelPTTrace ipt_trace; - JSONTraceSessionBase session_base; +struct JSONCore { + lldb::core_id_t core_id; + std::string trace_buffer; + std::string context_switch_trace; }; -struct JSONTraceIntelPTSettings : JSONTracePluginSettings { - JSONTraceIntelPTCPUInfo cpuInfo; +struct JSONTraceSession { + std::string type; + pt_cpu cpu_info; + std::vector processes; + llvm::Optional> cores; + llvm::Optional tsc_perf_zero_conversion; + + llvm::Optional> GetCoreIds(); }; -} // namespace trace_intel_pt -} // namespace lldb_private +llvm::json::Value toJSON(const JSONModule &module); + +llvm::json::Value toJSON(const JSONThread &thread); -namespace llvm { -namespace json { +llvm::json::Value toJSON(const JSONProcess &process); -bool fromJSON( - const Value &value, - lldb_private::trace_intel_pt::JSONTraceIntelPTSettings &plugin_settings, - Path path); +llvm::json::Value toJSON(const JSONCore &core); -bool fromJSON(const llvm::json::Value &value, - lldb_private::trace_intel_pt::JSONTraceIntelPTCPUInfo &packet, +llvm::json::Value toJSON(const pt_cpu &cpu_info); + +llvm::json::Value toJSON(const JSONTraceSession &session); + +bool fromJSON(const llvm::json::Value &value, JSONModule &module, llvm::json::Path path); -llvm::json::Value -toJSON(const lldb_private::trace_intel_pt::JSONTraceIntelPTCPUInfo &cpu_info); +bool fromJSON(const llvm::json::Value &value, JSONThread &thread, + llvm::json::Path path); + +bool fromJSON(const llvm::json::Value &value, JSONProcess &process, + llvm::json::Path path); -llvm::json::Value -toJSON(const lldb_private::trace_intel_pt::JSONTraceIntelPTTrace &trace); +bool fromJSON(const llvm::json::Value &value, JSONCore &core, + llvm::json::Path path); -llvm::json::Value -toJSON(const lldb_private::trace_intel_pt::JSONTraceIntelPTSession &session); +bool fromJSON(const llvm::json::Value &value, pt_cpu &cpu_info, + llvm::json::Path path); -} // namespace json -} // namespace llvm +bool fromJSON(const llvm::json::Value &value, JSONTraceSession &session, + llvm::json::Path path); +} // namespace trace_intel_pt +} // namespace lldb_private #endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTJSONSTRUCTS_H diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.cpp @@ -15,45 +15,130 @@ using namespace lldb_private; using namespace lldb_private::trace_intel_pt; using namespace llvm; +using namespace llvm::json; -namespace llvm { -namespace json { +namespace lldb_private { +namespace trace_intel_pt { -bool fromJSON(const Value &value, JSONTraceIntelPTSettings &plugin_settings, - Path path) { +Optional> JSONTraceSession::GetCoreIds() { + if (!cores) + return None; + std::vector core_ids; + for (const JSONCore &core : *cores) + core_ids.push_back(core.core_id); + return core_ids; +} + +json::Value toJSON(const JSONModule &module) { + json::Object json_module; + json_module["systemPath"] = module.system_path; + if (module.file) + json_module["file"] = *module.file; + json_module["loadAddress"] = module.load_address; + if (module.uuid) + json_module["uuid"] = *module.uuid; + return std::move(json_module); +} + +bool fromJSON(const json::Value &value, JSONModule &module, Path path) { + ObjectMapper o(value, path); + return o && o.map("systemPath", module.system_path) && + o.map("file", module.file) && + o.map("loadAddress", module.load_address) && + o.map("uuid", module.uuid); +} + +json::Value toJSON(const JSONThread &thread) { + return json::Object{{"tid", thread.tid}, + {"traceBuffer", thread.trace_buffer}}; +} + +bool fromJSON(const json::Value &value, JSONThread &thread, Path path) { + ObjectMapper o(value, path); + return o && o.map("tid", thread.tid) && + o.map("traceBuffer", thread.trace_buffer); +} + +json::Value toJSON(const JSONProcess &process) { + return Object{ + {"pid", process.pid}, + {"triple", process.triple}, + {"threads", process.threads}, + {"modules", process.modules}, + }; +} + +bool fromJSON(const json::Value &value, JSONProcess &process, Path path) { ObjectMapper o(value, path); - return o && o.map("cpuInfo", plugin_settings.cpuInfo) && - fromJSON(value, (JSONTracePluginSettings &)plugin_settings, path); + return o && o.map("pid", process.pid) && o.map("triple", process.triple) && + o.map("threads", process.threads) && o.map("modules", process.modules); +} + +json::Value toJSON(const JSONCore &core) { + return Object{ + {"coreId", core.core_id}, + {"traceBuffer", core.trace_buffer}, + {"contextSwitchTrace", core.context_switch_trace}, + }; } -bool fromJSON(const json::Value &value, JSONTraceIntelPTCPUInfo &cpu_info, - Path path) { +bool fromJSON(const json::Value &value, JSONCore &core, Path path) { ObjectMapper o(value, path); - return o && o.map("vendor", cpu_info.vendor) && - o.map("family", cpu_info.family) && o.map("model", cpu_info.model) && - o.map("stepping", cpu_info.stepping); + uint64_t core_id; + if (!o || !o.map("coreId", core_id) || + !o.map("traceBuffer", core.trace_buffer) || + !o.map("contextSwitchTrace", core.context_switch_trace)) + return false; + core.core_id = core_id; + return true; } -Value toJSON(const JSONTraceIntelPTCPUInfo &cpu_info) { - return Value(Object{{"family", cpu_info.family}, - {"model", cpu_info.model}, - {"stepping", cpu_info.stepping}, - {"vendor", cpu_info.vendor}}); +json::Value toJSON(const pt_cpu &cpu_info) { + return Object{ + {"vendor", cpu_info.vendor == pcv_intel ? "GenuineIntel" : "Unknown"}, + {"family", cpu_info.family}, + {"model", cpu_info.model}, + {"stepping", cpu_info.stepping}, + }; } -llvm::json::Value toJSON(const JSONTraceIntelPTTrace &trace) { - llvm::json::Object json_trace; - json_trace["type"] = trace.type; - json_trace["cpuInfo"] = toJSON(trace.cpuInfo); - return std::move(json_trace); +bool fromJSON(const json::Value &value, pt_cpu &cpu_info, Path path) { + ObjectMapper o(value, path); + std::string vendor; + uint64_t family, model, stepping; + if (!o || !o.map("vendor", vendor) || !o.map("family", family) || + !o.map("model", model) || !o.map("stepping", stepping)) + return false; + cpu_info.vendor = vendor == "GenuineIntel" ? pcv_intel : pcv_unknown; + cpu_info.family = family; + cpu_info.model = model; + cpu_info.stepping = stepping; + return true; } -llvm::json::Value toJSON(const JSONTraceIntelPTSession &session) { - llvm::json::Object json_session; - json_session["trace"] = toJSON(session.ipt_trace); - json_session["processes"] = toJSON(session.session_base); - return std::move(json_session); +json::Value toJSON(const JSONTraceSession &session) { + return Object{{"type", session.type}, + {"processes", session.processes}, + // We have to do this because the compiler fails at doing it + // automatically because pt_cpu is not in a namespace + {"cpuInfo", toJSON(session.cpu_info)}, + {"cores", session.cores}, + {"tscPerfZeroConversion", session.tsc_perf_zero_conversion}}; +} + +bool fromJSON(const json::Value &value, JSONTraceSession &session, Path path) { + ObjectMapper o(value, path); + if (!o || !o.map("processes", session.processes) || + !o.map("type", session.type) || !o.map("cores", session.cores) || + !o.map("tscPerfZeroConversion", session.tsc_perf_zero_conversion)) + return false; + // We have to do this because the compiler fails at doing it automatically + // because pt_cpu is not in a namespace + if (!fromJSON(*value.getAsObject()->get("cpuInfo"), session.cpu_info, + path.field("cpuInfo"))) + return false; + return true; } -} // namespace json -} // namespace llvm +} // namespace trace_intel_pt +} // namespace lldb_private diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h @@ -9,7 +9,7 @@ #ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONFILEPARSER_H #define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONFILEPARSER_H -#include "../common/TraceSessionFileParser.h" +#include "../common/ThreadPostMortemTrace.h" #include "TraceIntelPTJSONStructs.h" namespace lldb_private { @@ -17,16 +17,28 @@ class TraceIntelPT; -class TraceIntelPTSessionFileParser : public TraceSessionFileParser { +class TraceIntelPTSessionFileParser { public: + /// Helper struct holding the objects created when parsing a process + struct ParsedProcess { + lldb::TargetSP target_sp; + std::vector threads; + }; - /// See \a TraceSessionFileParser::TraceSessionFileParser for the description - /// of these fields. + /// \param[in] debugger + /// The debugger that will own the targets to create. + /// + /// \param[in] trace_session_file + /// The contents of the main trace session definition file that follows the + /// schema of the intel pt trace plug-in. + /// + /// \param[in] session_file_dir + /// The folder where the trace session file is located. TraceIntelPTSessionFileParser(Debugger &debugger, const llvm::json::Value &trace_session_file, llvm::StringRef session_file_dir) - : TraceSessionFileParser(debugger, session_file_dir, GetSchema()), - m_trace_session_file(trace_session_file) {} + : m_debugger(debugger), m_trace_session_file(trace_session_file), + m_session_file_dir(session_file_dir) {} /// \return /// The JSON schema for the session data. @@ -41,13 +53,41 @@ llvm::Expected Parse(); lldb::TraceSP - CreateTraceIntelPTInstance(const pt_cpu &cpu_info, + CreateTraceIntelPTInstance(JSONTraceSession &session, std::vector &parsed_processes); private: - static pt_cpu ParsePTCPU(const JSONTraceIntelPTCPUInfo &cpu_info); + /// Resolve non-absolute paths relative to the session file folder. It + /// modifies the given file_spec. + void NormalizePath(lldb_private::FileSpec &file_spec); + + lldb::ThreadPostMortemTraceSP ParseThread(lldb::ProcessSP &process_sp, + const JSONThread &thread); + + llvm::Expected ParseProcess(const JSONProcess &process); + + llvm::Error ParseModule(lldb::TargetSP &target_sp, const JSONModule &module); + + /// Create a user-friendly error message upon a JSON-parsing failure using the + /// \a json::ObjectMapper functionality. + /// + /// \param[in] root + /// The \a llvm::json::Path::Root used to parse the JSON \a value. + /// + /// \param[in] value + /// The json value that failed to parse. + /// + /// \return + /// An \a llvm::Error containing the user-friendly error message. + llvm::Error CreateJSONError(llvm::json::Path::Root &root, + const llvm::json::Value &value); + + llvm::Expected> + ParseSessionFile(const JSONTraceSession &session); + Debugger &m_debugger; const llvm::json::Value &m_trace_session_file; + std::string m_session_file_dir; }; } // namespace trace_intel_pt diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp @@ -10,44 +10,221 @@ #include "../common/ThreadPostMortemTrace.h" #include "TraceIntelPT.h" +#include "TraceIntelPTJSONStructs.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" using namespace lldb; using namespace lldb_private; using namespace lldb_private::trace_intel_pt; using namespace llvm; +void TraceIntelPTSessionFileParser::NormalizePath( + lldb_private::FileSpec &file_spec) { + if (file_spec.IsRelative()) + file_spec.PrependPathComponent(m_session_file_dir); +} + +Error TraceIntelPTSessionFileParser::ParseModule(lldb::TargetSP &target_sp, + const JSONModule &module) { + FileSpec system_file_spec(module.system_path); + NormalizePath(system_file_spec); + + FileSpec local_file_spec(module.file.hasValue() ? *module.file + : module.system_path); + NormalizePath(local_file_spec); + + ModuleSpec module_spec; + module_spec.GetFileSpec() = local_file_spec; + module_spec.GetPlatformFileSpec() = system_file_spec; + + if (module.uuid.hasValue()) + module_spec.GetUUID().SetFromStringRef(*module.uuid); + + Status error; + ModuleSP module_sp = + target_sp->GetOrCreateModule(module_spec, /*notify*/ false, &error); + + if (error.Fail()) + return error.ToError(); + + bool load_addr_changed = false; + module_sp->SetLoadAddress(*target_sp, module.load_address, false, + load_addr_changed); + return llvm::Error::success(); +} + +Error TraceIntelPTSessionFileParser::CreateJSONError(json::Path::Root &root, + const json::Value &value) { + std::string err; + raw_string_ostream os(err); + root.printErrorContext(value, os); + return createStringError( + std::errc::invalid_argument, "%s\n\nContext:\n%s\n\nSchema:\n%s", + toString(root.getError()).c_str(), os.str().c_str(), GetSchema().data()); +} + +ThreadPostMortemTraceSP +TraceIntelPTSessionFileParser::ParseThread(ProcessSP &process_sp, + const JSONThread &thread) { + lldb::tid_t tid = static_cast(thread.tid); + + Optional trace_file; + if (thread.trace_buffer) { + trace_file.emplace(*thread.trace_buffer); + NormalizePath(*trace_file); + } + + ThreadPostMortemTraceSP thread_sp = + std::make_shared(*process_sp, tid, trace_file); + process_sp->GetThreadList().AddThread(thread_sp); + return thread_sp; +} + +Expected +TraceIntelPTSessionFileParser::ParseProcess(const JSONProcess &process) { + TargetSP target_sp; + Status error = m_debugger.GetTargetList().CreateTarget( + m_debugger, /*user_exe_path*/ StringRef(), process.triple, + eLoadDependentsNo, + /*platform_options*/ nullptr, target_sp); + + if (!target_sp) + return error.ToError(); + + ParsedProcess parsed_process; + parsed_process.target_sp = target_sp; + + ProcessSP process_sp = target_sp->CreateProcess( + /*listener*/ nullptr, "trace", + /*crash_file*/ nullptr, + /*can_connect*/ false); + + process_sp->SetID(static_cast(process.pid)); + + for (const JSONThread &thread : process.threads) + parsed_process.threads.push_back(ParseThread(process_sp, thread)); + + for (const JSONModule &module : process.modules) + if (Error err = ParseModule(target_sp, module)) + return std::move(err); + + if (!process.threads.empty()) + process_sp->GetThreadList().SetSelectedThreadByIndexID(0); + + // We invoke DidAttach to create a correct stopped state for the process and + // its threads. + ArchSpec process_arch; + process_sp->DidAttach(process_arch); + + return parsed_process; +} + +Expected> +TraceIntelPTSessionFileParser::ParseSessionFile( + const JSONTraceSession &session) { + std::vector parsed_processes; + + auto HandleError = [&](Error &&err) { + // Delete all targets that were created so far in case of failures + for (ParsedProcess &parsed_process : parsed_processes) + m_debugger.GetTargetList().DeleteTarget(parsed_process.target_sp); + return std::move(err); + }; + + for (const JSONProcess &process : session.processes) { + if (Expected parsed_process = ParseProcess(process)) + parsed_processes.push_back(std::move(*parsed_process)); + else + return HandleError(parsed_process.takeError()); + } + return parsed_processes; +} + StringRef TraceIntelPTSessionFileParser::GetSchema() { static std::string schema; if (schema.empty()) { - schema = TraceSessionFileParser::BuildSchema(R"({ - "type": "intel-pt", - "cpuInfo": { - "vendor": "intel" | "unknown", - "family": integer, - "model": integer, - "stepping": integer + schema = R"({ + "type": "intel-pt", + "cpuInfo": { + // CPU information gotten from, for example, /proc/cpuinfo. + + "vendor": "GenuineIntel" | "unknown", + "family": integer, + "model": integer, + "stepping": integer + }, + "processes": [ + { + "pid": integer, + "triple": string, + // clang/llvm target triple. + "threads": [ + { + "tid": integer, + "traceBuffer"?: string + // Path to the raw Intel PT buffer file for this thread. + } + ], + "modules": [ + { + "systemPath": string, + // Original path of the module at runtime. + "file"?: string, + // Path to a copy of the file if not available at "systemPath". + "loadAddress": integer, + // Lowest address of the sections of the module loaded on memory. + "uuid"?: string, + // Build UUID for the file for sanity checks. + } + ] + } + ], + "cores"?: [ + { + "coreId": integer, + // Id of this CPU core. + "traceBuffer": string, + // Path to the raw Intel PT buffer for this core. + "contextSwitchTrace": string, + // Path to the raw perf_event_open context switch trace file for this core. } - })"); + ], + "tscPerfZeroConversion"?: { + // Values used to convert between TSCs and nanoseconds. See the time_zero + // section in https://man7.org/linux/man-pages/man2/perf_event_open.2.html + // for for information. + + "timeMult": integer, + "timeShift": integer, + "timeZero": integer, } - return schema; } -pt_cpu TraceIntelPTSessionFileParser::ParsePTCPU( - const JSONTraceIntelPTCPUInfo &cpu_info) { - return {cpu_info.vendor.compare("intel") == 0 ? pcv_intel : pcv_unknown, - static_cast(cpu_info.family), - static_cast(cpu_info.model), - static_cast(cpu_info.stepping)}; +Notes: + +- All paths are either absolute or relative to folder containing the session file. +- "cores" is provided if and only if processes[].threads[].traceBuffer is not provided. +- "tscPerfZeroConversion" must be provided if "cores" is provided. + })"; + } + return schema; } TraceSP TraceIntelPTSessionFileParser::CreateTraceIntelPTInstance( - const pt_cpu &cpu_info, std::vector &parsed_processes) { + JSONTraceSession &session, std::vector &parsed_processes) { std::vector threads; - for (const ParsedProcess &parsed_process : parsed_processes) + std::vector processes; + for (const ParsedProcess &parsed_process : parsed_processes) { + processes.push_back(parsed_process.target_sp->GetProcessSP()); threads.insert(threads.end(), parsed_process.threads.begin(), parsed_process.threads.end()); + } - TraceSP trace_instance(new TraceIntelPT(cpu_info, threads)); + TraceSP trace_instance(new TraceIntelPT(session, processes, threads)); for (const ParsedProcess &parsed_process : parsed_processes) parsed_process.target_sp->SetTrace(trace_instance); @@ -56,14 +233,13 @@ Expected TraceIntelPTSessionFileParser::Parse() { json::Path::Root root("traceSession"); - JSONTraceSession session; - if (!json::fromJSON(m_trace_session_file, session, root)) + JSONTraceSession session; + if (!fromJSON(m_trace_session_file, session, root)) return CreateJSONError(root, m_trace_session_file); if (Expected> parsed_processes = - ParseCommonSessionFile(session)) - return CreateTraceIntelPTInstance(ParsePTCPU(session.trace.cpuInfo), - *parsed_processes); + ParseSessionFile(session)) + return CreateTraceIntelPTInstance(session, *parsed_processes); else return parsed_processes.takeError(); } diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.h @@ -11,15 +11,12 @@ #include "TraceIntelPT.h" -#include "../common/TraceJSONStructs.h" +#include "TraceIntelPTJSONStructs.h" namespace lldb_private { namespace trace_intel_pt { -class TraceIntelPT; - class TraceIntelPTSessionSaver { - public: /// Save the Intel PT trace of a live process to the specified directory, /// which will be created if needed. This will also create a file @@ -38,17 +35,6 @@ /// \a llvm::success if the operation was successful, or an \a llvm::Error /// otherwise. llvm::Error SaveToDisk(TraceIntelPT &trace_ipt, FileSpec directory); - -private: - /// Build trace section of the intel-pt trace session description file. - /// - /// \param[in] trace_ipt - /// The Intel PT trace. - /// - /// \return - /// The trace section an \a llvm::Error in case of failures. - llvm::Expected - BuildTraceSection(TraceIntelPT &trace_ipt); }; } // namespace trace_intel_pt diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.cpp @@ -7,7 +7,6 @@ //===----------------------------------------------------------------------===// #include "TraceIntelPTSessionSaver.h" -#include "../common/TraceSessionSaver.h" #include "TraceIntelPT.h" #include "TraceIntelPTJSONStructs.h" #include "lldb/Core/Module.h" @@ -31,42 +30,280 @@ using namespace lldb_private::trace_intel_pt; using namespace llvm; -llvm::Error TraceIntelPTSessionSaver::SaveToDisk(TraceIntelPT &trace_ipt, - FileSpec directory) { - Process *live_process = trace_ipt.GetLiveProcess(); - if (live_process == nullptr) +static llvm::Error WriteBytesToDisk(FileSpec &output_file, + ArrayRef data) { + std::basic_fstream out_fs = std::fstream( + output_file.GetPath().c_str(), std::ios::out | std::ios::binary); + out_fs.write(reinterpret_cast(&data[0]), + data.size() * sizeof(uint8_t)); + out_fs.close(); + if (!out_fs) return createStringError(inconvertibleErrorCode(), - "Saving a trace requires a live process."); + formatv("couldn't write to the file {0}", + output_file.GetPath().c_str())); + return Error::success(); +} - if (std::error_code ec = - sys::fs::create_directories(directory.GetPath().c_str())) - return llvm::errorCodeToError(ec); +/// Save the trace session description JSON object inside the given directory +/// as a file named \a trace.json. +/// +/// \param[in] trace_session_json +/// The trace's session, as JSON Object. +/// +/// \param[in] directory +/// The directory where the JSON file will be saved. +/// +/// \return +/// \a llvm::Success if the operation was successful, or an \a llvm::Error +/// otherwise. +static llvm::Error +WriteSessionToFile(const llvm::json::Value &trace_session_json, + const FileSpec &directory) { + FileSpec trace_path = directory; + trace_path.AppendPathComponent("trace.json"); + std::ofstream os(trace_path.GetPath()); + os << std::string(formatv("{0:2}", trace_session_json)); + os.close(); + if (!os) + return createStringError(inconvertibleErrorCode(), + formatv("couldn't write to the file {0}", + trace_path.GetPath().c_str())); + return Error::success(); +} + +/// Build the threads sub-section of the trace session description file. +/// Any associated binary files are created inside the given directory. +/// +/// \param[in] process +/// The process being traced. +/// +/// \param[in] directory +/// The directory where files will be saved when building the threads +/// section. +/// +/// \return +/// The threads section or \a llvm::Error in case of failures. +static llvm::Expected> +BuildThreadsSection(Process &process, FileSpec directory) { + std::vector json_threads; + TraceSP trace_sp = process.GetTarget().GetTrace(); + + FileSpec threads_dir = directory; + threads_dir.AppendPathComponent("threads"); + FileSystem::Instance().Resolve(threads_dir); + sys::fs::create_directories(threads_dir.GetCString()); + + for (ThreadSP thread_sp : process.Threads()) { + lldb::tid_t tid = thread_sp->GetID(); + if (!trace_sp->IsTraced(tid)) + continue; + + JSONThread json_thread; + json_thread.tid = tid; + + if (trace_sp->GetTracedCores().empty()) { + FileSpec output_file = threads_dir; + output_file.AppendPathComponent(std::to_string(tid) + ".intelpt_trace"); + json_thread.trace_buffer = output_file.GetPath(); + + llvm::Error err = process.GetTarget().GetTrace()->OnThreadBinaryDataRead( + tid, IntelPTDataKinds::kTraceBuffer, + [&](llvm::ArrayRef data) -> llvm::Error { + return WriteBytesToDisk(output_file, data); + }); + if (err) + return std::move(err); + } + + json_threads.push_back(std::move(json_thread)); + } + return json_threads; +} + +static llvm::Expected>> +BuildCoresSection(TraceIntelPT &trace_ipt, FileSpec directory) { + if (trace_ipt.GetTracedCores().empty()) + return None; + + std::vector json_cores; + FileSpec cores_dir = directory; + cores_dir.AppendPathComponent("cores"); + FileSystem::Instance().Resolve(cores_dir); + sys::fs::create_directories(cores_dir.GetCString()); + + for (lldb::core_id_t core_id : trace_ipt.GetTracedCores()) { + JSONCore json_core; + json_core.core_id = core_id; + + { + FileSpec output_trace = cores_dir; + output_trace.AppendPathComponent(std::to_string(core_id) + + ".intelpt_trace"); + json_core.trace_buffer = output_trace.GetPath(); + + llvm::Error err = trace_ipt.OnCoreBinaryDataRead( + core_id, IntelPTDataKinds::kTraceBuffer, + [&](llvm::ArrayRef data) -> llvm::Error { + return WriteBytesToDisk(output_trace, data); + }); + if (err) + return std::move(err); + } - llvm::Expected json_intel_pt_trace = - BuildTraceSection(trace_ipt); - if (!json_intel_pt_trace) - return json_intel_pt_trace.takeError(); + { + FileSpec output_context_switch_trace = cores_dir; + output_context_switch_trace.AppendPathComponent( + std::to_string(core_id) + ".perf_context_switch_trace"); + json_core.context_switch_trace = output_context_switch_trace.GetPath(); - llvm::Expected json_session_description = - TraceSessionSaver::BuildProcessesSection( - *live_process, IntelPTDataKinds::kTraceBuffer, directory); + llvm::Error err = trace_ipt.OnCoreBinaryDataRead( + core_id, IntelPTDataKinds::kPerfContextSwitchTrace, + [&](llvm::ArrayRef data) -> llvm::Error { + return WriteBytesToDisk(output_context_switch_trace, data); + }); + if (err) + return std::move(err); + } + json_cores.push_back(std::move(json_core)); + } + return json_cores; +} + +/// Build modules sub-section of the trace's session. The original modules +/// will be copied over to the \a folder. Invalid modules +/// are skipped. +/// Copying the modules has the benefit of making these trace session +/// directories self-contained, as the raw traces and modules are part of the +/// output directory and can be sent to another machine, where lldb can load +/// them and replicate exactly the same trace session. +/// +/// \param[in] process +/// The process being traced. +/// +/// \param[in] directory +/// The directory where the modules files will be saved when building +/// the modules section. +/// Example: If a module \a libbar.so exists in the path +/// \a /usr/lib/foo/libbar.so, then it will be copied to +/// \a /modules/usr/lib/foo/libbar.so. +/// +/// \return +/// The modules section or \a llvm::Error in case of failures. +static llvm::Expected> +BuildModulesSection(Process &process, FileSpec directory) { + std::vector json_modules; + ModuleList module_list = process.GetTarget().GetImages(); + for (size_t i = 0; i < module_list.GetSize(); ++i) { + ModuleSP module_sp(module_list.GetModuleAtIndex(i)); + if (!module_sp) + continue; + std::string system_path = module_sp->GetPlatformFileSpec().GetPath(); + // TODO: support memory-only libraries like [vdso] + if (!module_sp->GetFileSpec().IsAbsolute()) + continue; + + std::string file = module_sp->GetFileSpec().GetPath(); + ObjectFile *objfile = module_sp->GetObjectFile(); + if (objfile == nullptr) + continue; + + lldb::addr_t load_addr = LLDB_INVALID_ADDRESS; + Address base_addr(objfile->GetBaseAddress()); + if (base_addr.IsValid() && + !process.GetTarget().GetSectionLoadList().IsEmpty()) + load_addr = base_addr.GetLoadAddress(&process.GetTarget()); + + if (load_addr == LLDB_INVALID_ADDRESS) + continue; - if (!json_session_description) - return json_session_description.takeError(); + FileSystem::Instance().Resolve(directory); + FileSpec path_to_copy_module = directory; + path_to_copy_module.AppendPathComponent("modules"); + path_to_copy_module.AppendPathComponent(system_path); + sys::fs::create_directories(path_to_copy_module.GetDirectory().AsCString()); - JSONTraceIntelPTSession json_intel_pt_session{json_intel_pt_trace.get(), - json_session_description.get()}; + if (std::error_code ec = llvm::sys::fs::copy_file( + system_path, path_to_copy_module.GetPath())) + return createStringError( + inconvertibleErrorCode(), + formatv("couldn't write to the file. {0}", ec.message())); - return TraceSessionSaver::WriteSessionToFile( - llvm::json::toJSON(json_intel_pt_session), directory); + json_modules.push_back(JSONModule{system_path, + path_to_copy_module.GetPath(), load_addr, + module_sp->GetUUID().GetAsString()}); + } + return json_modules; } -llvm::Expected -TraceIntelPTSessionSaver::BuildTraceSection(TraceIntelPT &trace_ipt) { - llvm::Expected cpu_info = trace_ipt.GetCPUInfo(); +/// Build the processes section of the trace session description file. Besides +/// returning the processes information, this method saves to disk all modules +/// and raw traces corresponding to the traced threads of the given process. +/// +/// \param[in] process +/// The process being traced. +/// +/// \param[in] directory +/// The directory where files will be saved when building the processes +/// section. +/// +/// \return +/// The processes section or \a llvm::Error in case of failures. +static llvm::Expected +BuildProcessSection(Process &process, const FileSpec &directory) { + Expected> json_threads = + BuildThreadsSection(process, directory); + if (!json_threads) + return json_threads.takeError(); + + Expected> json_modules = + BuildModulesSection(process, directory); + if (!json_modules) + return json_modules.takeError(); + + return JSONProcess{ + static_cast(process.GetID()), + process.GetTarget().GetArchitecture().GetTriple().getTriple(), + json_threads.get(), json_modules.get()}; +} + +/// See BuildProcessSection() +static llvm::Expected> +BuildProcessesSection(TraceIntelPT &trace_ipt, const FileSpec &directory) { + std::vector processes; + for (Process *process : trace_ipt.GetAllProcesses()) { + if (llvm::Expected json_process = + BuildProcessSection(*process, directory)) + processes.push_back(std::move(*json_process)); + else + return json_process.takeError(); + } + return processes; +} + +Error TraceIntelPTSessionSaver::SaveToDisk(TraceIntelPT &trace_ipt, + FileSpec directory) { + if (std::error_code ec = + sys::fs::create_directories(directory.GetPath().c_str())) + return llvm::errorCodeToError(ec); + + Expected cpu_info = trace_ipt.GetCPUInfo(); if (!cpu_info) return cpu_info.takeError(); - return JSONTraceIntelPTTrace{"intel-pt", - JSONTraceIntelPTCPUInfo(cpu_info.get())}; + Expected> json_processes = + BuildProcessesSection(trace_ipt, directory); + + if (!json_processes) + return json_processes.takeError(); + + Expected>> json_cores = + BuildCoresSection(trace_ipt, directory); + if (!json_cores) + return json_cores.takeError(); + + JSONTraceSession json_intel_pt_session{"intel-pt", *cpu_info, *json_processes, + *json_cores, + trace_ipt.GetPerfZeroTscConversion()}; + + return WriteSessionToFile(toJSON(json_intel_pt_session), directory); } diff --git a/lldb/source/Target/Trace.cpp b/lldb/source/Target/Trace.cpp --- a/lldb/source/Target/Trace.cpp +++ b/lldb/source/Target/Trace.cpp @@ -27,26 +27,16 @@ // Helper structs used to extract the type of a trace session json without // having to parse the entire object. -struct JSONSimplePluginSettings { - std::string type; -}; - struct JSONSimpleTraceSession { - JSONSimplePluginSettings trace; + std::string type; }; namespace llvm { namespace json { -bool fromJSON(const Value &value, JSONSimplePluginSettings &plugin_settings, - Path path) { - json::ObjectMapper o(value, path); - return o && o.map("type", plugin_settings.type); -} - bool fromJSON(const Value &value, JSONSimpleTraceSession &session, Path path) { json::ObjectMapper o(value, path); - return o && o.map("trace", session.trace); + return o && o.map("type", session.type); } } // namespace json @@ -69,10 +59,10 @@ return root.getError(); if (auto create_callback = - PluginManager::GetTraceCreateCallback(json_session.trace.type)) + PluginManager::GetTraceCreateCallback(json_session.type)) return create_callback(trace_session_file, session_file_dir, debugger); - return createInvalidPlugInError(json_session.trace.type); + return createInvalidPlugInError(json_session.type); } Expected Trace::FindPluginForLiveProcess(llvm::StringRef name, @@ -136,6 +126,18 @@ return single_thread_data_it->second; } +Optional Trace::GetLiveCoreBinaryDataSize(lldb::core_id_t core_id, + llvm::StringRef kind) { + auto it = m_live_core_data.find(core_id); + if (it == m_live_core_data.end()) + return None; + std::unordered_map &single_core_data = it->second; + auto single_thread_data_it = single_core_data.find(kind.str()); + if (single_thread_data_it == single_core_data.end()) + return None; + return single_thread_data_it->second; +} + Optional Trace::GetLiveProcessBinaryDataSize(llvm::StringRef kind) { auto data_it = m_live_process_data.find(kind.str()); if (data_it == m_live_process_data.end()) @@ -155,8 +157,26 @@ "Tracing data \"%s\" is not available for thread %" PRIu64 ".", kind.data(), tid); - TraceGetBinaryDataRequest request{GetPluginName().str(), kind.str(), tid, 0, - *size}; + TraceGetBinaryDataRequest request{GetPluginName().str(), kind.str(), tid, + /*core_id=*/None, /*offset=*/0, *size}; + return m_live_process->TraceGetBinaryData(request); +} + +Expected> +Trace::GetLiveCoreBinaryData(lldb::core_id_t core_id, llvm::StringRef kind) { + if (!m_live_process) + return createStringError(inconvertibleErrorCode(), + "Tracing requires a live process."); + llvm::Optional size = GetLiveCoreBinaryDataSize(core_id, kind); + if (!size) + return createStringError( + inconvertibleErrorCode(), + "Tracing data \"%s\" is not available for core_id %" PRIu64 ".", + kind.data(), core_id); + + TraceGetBinaryDataRequest request{GetPluginName().str(), kind.str(), + /*tid=*/None, core_id, + /*offset=*/0, *size}; return m_live_process->TraceGetBinaryData(request); } @@ -171,8 +191,9 @@ inconvertibleErrorCode(), "Tracing data \"%s\" is not available for the process.", kind.data()); - TraceGetBinaryDataRequest request{GetPluginName().str(), kind.str(), None, 0, - *size}; + TraceGetBinaryDataRequest request{GetPluginName().str(), kind.str(), + /*tid=*/None, /*core_id*/ None, + /*offset=*/0, *size}; return m_live_process->TraceGetBinaryData(request); } @@ -190,6 +211,7 @@ m_stop_id = new_stop_id; m_live_thread_data.clear(); m_live_refresh_error.reset(); + m_cores.reset(); auto HandleError = [&](Error &&err) -> const char * { m_live_refresh_error = toString(std::move(err)); @@ -205,8 +227,10 @@ if (!live_process_state) return HandleError(live_process_state.takeError()); - for (std::string &warning : live_process_state->warnings) - LLDB_LOG(log, "Warning when fetching the trace state: {0}", warning); + if (live_process_state->warnings) { + for (std::string &warning : *live_process_state->warnings) + LLDB_LOG(log, "== Warning when fetching the trace state: {0}", warning); + } for (const TraceThreadState &thread_state : live_process_state->traced_threads) { @@ -214,6 +238,21 @@ m_live_thread_data[thread_state.tid][item.kind] = item.size; } + LLDB_LOG(log, "== Found {0} threads being traced", + live_process_state->traced_threads.size()); + + if (live_process_state->cores) { + m_cores.emplace(); + for (const TraceCoreState &core_state : *live_process_state->cores) { + m_cores->push_back(core_state.core_id); + for (const TraceBinaryData &item : core_state.binary_data) + m_live_core_data[core_state.core_id][item.kind] = item.size; + } + LLDB_LOG(log, "== Found {0} cpu cores being traced", + live_process_state->cores->size()); + } + + for (const TraceBinaryData &item : live_process_state->process_binary_data) m_live_process_data[item.kind] = item.size; @@ -224,8 +263,25 @@ return nullptr; } +Trace::Trace(ArrayRef postmortem_processes, + Optional> postmortem_cores) { + for (ProcessSP process_sp : postmortem_processes) + m_postmortem_processes.push_back(process_sp.get()); + m_cores = postmortem_cores; +} + Process *Trace::GetLiveProcess() { return m_live_process; } +ArrayRef Trace::GetPostMortemProcesses() { + return m_postmortem_processes; +} + +std::vector Trace::GetAllProcesses() { + if (Process *proc = GetLiveProcess()) + return {proc}; + return GetPostMortemProcesses(); +} + uint32_t Trace::GetStopID() { RefreshLiveProcessState(); return m_stop_id; @@ -252,11 +308,39 @@ return it2->second; } +llvm::Expected +Trace::GetPostMortemCoreDataFile(lldb::core_id_t core_id, + llvm::StringRef kind) { + auto NotFoundError = [&]() { + return createStringError( + inconvertibleErrorCode(), + formatv("The core with id={0} doesn't have the tracing data {1}", + core_id, kind)); + }; + + auto it = m_postmortem_core_data.find(core_id); + if (it == m_postmortem_core_data.end()) + return NotFoundError(); + + std::unordered_map &data_kind_to_file_spec_map = + it->second; + auto it2 = data_kind_to_file_spec_map.find(kind.str()); + if (it2 == data_kind_to_file_spec_map.end()) + return NotFoundError(); + return it2->second; +} + void Trace::SetPostMortemThreadDataFile(lldb::tid_t tid, llvm::StringRef kind, FileSpec file_spec) { m_postmortem_thread_data[tid][kind.str()] = file_spec; } +void Trace::SetPostMortemCoreDataFile(lldb::core_id_t core_id, + llvm::StringRef kind, + FileSpec file_spec) { + m_postmortem_core_data[core_id][kind.str()] = file_spec; +} + llvm::Error Trace::OnLiveThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind, OnBinaryDataReadCallback callback) { @@ -266,14 +350,19 @@ return callback(*data); } -llvm::Error -Trace::OnPostMortemThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind, - OnBinaryDataReadCallback callback) { - Expected file = GetPostMortemThreadDataFile(tid, kind); - if (!file) - return file.takeError(); +llvm::Error Trace::OnLiveCoreBinaryDataRead(lldb::core_id_t core_id, + llvm::StringRef kind, + OnBinaryDataReadCallback callback) { + Expected> data = GetLiveCoreBinaryData(core_id, kind); + if (!data) + return data.takeError(); + return callback(*data); +} + +llvm::Error Trace::OnDataFileRead(FileSpec file, + OnBinaryDataReadCallback callback) { ErrorOr> trace_or_error = - MemoryBuffer::getFile(file->GetPath()); + MemoryBuffer::getFile(file.GetPath()); if (std::error_code err = trace_or_error.getError()) return errorCodeToError(err); @@ -284,10 +373,47 @@ return callback(array_ref); } +llvm::Error +Trace::OnPostMortemThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind, + OnBinaryDataReadCallback callback) { + Expected file = GetPostMortemThreadDataFile(tid, kind); + if (!file) + return file.takeError(); + return OnDataFileRead(*file, callback); +} + +llvm::Error +Trace::OnPostMortemCoreBinaryDataRead(lldb::core_id_t core_id, + llvm::StringRef kind, + OnBinaryDataReadCallback callback) { + Expected file = GetPostMortemCoreDataFile(core_id, kind); + if (!file) + return file.takeError(); + return OnDataFileRead(*file, callback); +} + llvm::Error Trace::OnThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind, OnBinaryDataReadCallback callback) { + RefreshLiveProcessState(); if (m_live_process) return OnLiveThreadBinaryDataRead(tid, kind, callback); else return OnPostMortemThreadBinaryDataRead(tid, kind, callback); } + +llvm::Error Trace::OnCoreBinaryDataRead(lldb::core_id_t core_id, + llvm::StringRef kind, + OnBinaryDataReadCallback callback) { + RefreshLiveProcessState(); + if (m_live_process) + return OnLiveCoreBinaryDataRead(core_id, kind, callback); + else + return OnPostMortemCoreBinaryDataRead(core_id, kind, callback); +} + +ArrayRef Trace::GetTracedCores() { + RefreshLiveProcessState(); + if (m_cores) + return *m_cores; + return {}; +} diff --git a/lldb/source/Utility/TraceGDBRemotePackets.cpp b/lldb/source/Utility/TraceGDBRemotePackets.cpp --- a/lldb/source/Utility/TraceGDBRemotePackets.cpp +++ b/lldb/source/Utility/TraceGDBRemotePackets.cpp @@ -101,7 +101,7 @@ return o && o.map("tracedThreads", packet.traced_threads) && o.map("processBinaryData", packet.process_binary_data) && o.map("cores", packet.cores) && - o.mapOptional("warnings", packet.warnings); + o.map("warnings", packet.warnings); } json::Value toJSON(const TraceGetStateResponse &packet) { @@ -111,6 +111,12 @@ {"warnings", packet.warnings}}); } +void TraceGetStateResponse::AddWarning(StringRef warning) { + if (!warnings) + warnings.emplace(); + warnings->push_back(warning.data()); +} + bool fromJSON(const json::Value &value, TraceCoreState &packet, json::Path path) { ObjectMapper o(value, path); @@ -135,6 +141,7 @@ {"kind", packet.kind}, {"offset", packet.offset}, {"tid", packet.tid}, + {"coreId", packet.core_id}, {"size", packet.size}}); } @@ -143,7 +150,7 @@ ObjectMapper o(value, path); return o && o.map("type", packet.type) && o.map("kind", packet.kind) && o.map("tid", packet.tid) && o.map("offset", packet.offset) && - o.map("size", packet.size); + o.map("size", packet.size) && o.map("coreId", packet.core_id); } /// \} diff --git a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp --- a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp +++ b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp @@ -60,7 +60,6 @@ json::Value toJSON(const LinuxPerfZeroTscConversion &packet) { return json::Value(json::Object{ - {"kind", "tscPerfZeroConversion"}, {"timeMult", packet.time_mult}, {"timeShift", packet.time_shift}, {"timeZero", packet.time_zero}, diff --git a/lldb/test/API/commands/trace/TestTraceLoad.py b/lldb/test/API/commands/trace/TestTraceLoad.py --- a/lldb/test/API/commands/trace/TestTraceLoad.py +++ b/lldb/test/API/commands/trace/TestTraceLoad.py @@ -62,23 +62,24 @@ Context: { + "cpuInfo": { ... }, "processes": [ /* error: expected object */ 123 ], - "trace": { ... } + "type": "intel-pt" } Schema: { - "trace": { - "type": "intel-pt", - "cpuInfo": { - "vendor": "intel" | "unknown", - "family": integer, - "model": integer, - "stepping": integer - } + "type": "intel-pt", + "cpuInfo": { + // CPU information gotten from, for example, /proc/cpuinfo. + + "vendor": "GenuineIntel" | "unknown", + "family": integer, + "model": integer, + "stepping": integer },''']) # Now we test a missing field in the global session file @@ -87,25 +88,22 @@ # Now we test a missing field in the intel-pt settings self.expect("trace load -v " + os.path.join(src_dir, "intelpt-trace", "trace_bad4.json"), error=True, - substrs=['''error: missing value at traceSession.trace.cpuInfo.family + substrs=['''error: missing value at traceSession.cpuInfo.family Context: { + "cpuInfo": /* error: missing value */ { + "model": 79, + "stepping": 1, + "vendor": "GenuineIntel" + }, "processes": [], - "trace": { - "cpuInfo": /* error: missing value */ { - "model": 79, - "stepping": 1, - "vendor": "intel" - }, - "type": "intel-pt" - } + "type": "intel-pt" }''', "Schema"]) # Now we test an incorrect load address in the intel-pt settings self.expect("trace load -v " + os.path.join(src_dir, "intelpt-trace", "trace_bad5.json"), error=True, - substrs=['error: expected numeric string at traceSession.processes[0].modules[0].loadAddress', - '"loadAddress": /* error: expected numeric string */ 400000,', "Schema"]) + substrs=['error: missing value at traceSession.processes[1].pid', "Schema"]) # The following wrong schema will have a valid target and an invalid one. In the case of failure, # no targets should be created. diff --git a/lldb/test/API/commands/trace/TestTraceSave.py b/lldb/test/API/commands/trace/TestTraceSave.py --- a/lldb/test/API/commands/trace/TestTraceSave.py +++ b/lldb/test/API/commands/trace/TestTraceSave.py @@ -1,9 +1,15 @@ import lldb +import json from intelpt_testcase import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil from lldbsuite.test.decorators import * +def find(predicate, seq): + for item in seq: + if predicate(item): + return item + class TestTraceSave(TraceIntelPTTestCaseBase): mydir = TestBase.compute_mydir(__file__) @@ -54,10 +60,79 @@ # Check the output when not doing live tracing self.expect("process trace save -d " + - os.path.join(self.getBuildDir(), "intelpt-trace", "trace_not_live_dir"), - substrs=["error: Saving a trace requires a live process."], - error=True) + os.path.join(self.getBuildDir(), "intelpt-trace", "trace_not_live_dir")) + + def testSaveMultiCoreTrace(self): + ''' + This test starts a per-core tracing session, then saves the session to disk, and + finally it loads it again. + ''' + self.skipIfPerCoreTracingIsNotSupported() + + self.expect("target create " + + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")) + self.expect("b main") + self.expect("r") + self.expect("process trace start --per-core-tracing") + self.expect("b 7") + output_dir = os.path.join(self.getBuildDir(), "intelpt-trace", "trace_save") + self.expect("process trace save -d " + output_dir) + + def checkSessionBundle(session_file_path): + with open(session_file_path) as session_file: + session = json.load(session_file) + # We expect tsc conversion info + self.assertTrue("tscPerfZeroConversion" in session) + # We expect at least one core + self.assertGreater(len(session["cores"]), 0) + + # We expect the required trace files to be created + for core in session["cores"]: + core_files_prefix = os.path.join(output_dir, "cores", str(core["coreId"])) + self.assertTrue(os.path.exists(core_files_prefix + ".intelpt_trace")) + self.assertTrue(os.path.exists(core_files_prefix + ".perf_context_switch_trace")) + + # We expect at least one one process + self.assertGreater(len(session["processes"]), 0) + for process in session["processes"]: + # We expect at least one thread + self.assertGreater(len(process["threads"]), 0) + # We don't expect thread traces + for thread in process["threads"]: + self.assertTrue(("traceBuffer" not in thread) or (thread["traceBuffer"] is None)) + + original_trace_session_file = os.path.join(output_dir, "trace.json") + checkSessionBundle(original_trace_session_file) + + output_dir = os.path.join(self.getBuildDir(), "intelpt-trace", "trace_save") + self.expect("trace load " + os.path.join(output_dir, "trace.json")) + output_copy_dir = os.path.join(self.getBuildDir(), "intelpt-trace", "copy_trace_save") + self.expect("process trace save -d " + output_copy_dir) + + # We now check that the new bundle is correct on its own + copied_trace_session_file = os.path.join(output_copy_dir, "trace.json") + checkSessionBundle(copied_trace_session_file) + + # We finally check that the new bundle has the same information as the original one + with open(original_trace_session_file) as original_file: + original = json.load(original_file) + with open(copied_trace_session_file) as copy_file: + copy = json.load(copy_file) + + self.assertEqual(len(original["processes"]), len(copy["processes"])) + + for process in original["processes"]: + copied_process = find(lambda proc : proc["pid"] == process["pid"], copy["processes"]) + self.assertTrue(copied_process is not None) + + for thread in process["threads"]: + copied_thread = find(lambda thr : thr["tid"] == thread["tid"], copied_process["threads"]) + self.assertTrue(copied_thread is not None) + + for core in original["cores"]: + copied_core = find(lambda cor : cor["coreId"] == core["coreId"], copy["cores"]) + self.assertTrue(copied_core is not None) def testSaveTrace(self): self.expect("target create " + diff --git a/lldb/test/API/commands/trace/TestTraceSchema.py b/lldb/test/API/commands/trace/TestTraceSchema.py --- a/lldb/test/API/commands/trace/TestTraceSchema.py +++ b/lldb/test/API/commands/trace/TestTraceSchema.py @@ -9,7 +9,7 @@ mydir = TestBase.compute_mydir(__file__) def testSchema(self): - self.expect("trace schema intel-pt", substrs=["trace", "triple", "threads", "traceFile"]) + self.expect("trace schema intel-pt", substrs=["triple", "threads", "traceBuffer"]) def testInvalidPluginSchema(self): self.expect("trace schema invalid-plugin", error=True, @@ -17,12 +17,12 @@ def testAllSchemas(self): self.expect("trace schema all", substrs=['''{ - "trace": { - "type": "intel-pt", - "cpuInfo": { - "vendor": "intel" | "unknown", - "family": integer, - "model": integer, - "stepping": integer - } + "type": "intel-pt", + "cpuInfo": { + // CPU information gotten from, for example, /proc/cpuinfo. + + "vendor": "GenuineIntel" | "unknown", + "family": integer, + "model": integer, + "stepping": integer },''']) diff --git a/lldb/test/API/commands/trace/intelpt-trace-multi-file/multi-file-no-ld.json b/lldb/test/API/commands/trace/intelpt-trace-multi-file/multi-file-no-ld.json --- a/lldb/test/API/commands/trace/intelpt-trace-multi-file/multi-file-no-ld.json +++ b/lldb/test/API/commands/trace/intelpt-trace-multi-file/multi-file-no-ld.json @@ -1,12 +1,10 @@ { - "trace": { - "type": "intel-pt", - "cpuInfo": { - "vendor": "intel", - "family": 6, - "model": 79, - "stepping": 1 - } + "type": "intel-pt", + "cpuInfo": { + "vendor": "GenuineIntel", + "family": 6, + "model": 79, + "stepping": 1 }, "processes": [ { @@ -15,26 +13,26 @@ "threads": [ { "tid": 815455, - "traceFile": "multi-file.trace" + "traceBuffer": "multi-file.trace" } ], "modules": [ { "file": "a.out", "systemPath": "a.out", - "loadAddress": "0x0000000000400000", + "loadAddress": 4194304, "uuid": "D2414468-7112-B7C5-408D-FF07E30D5B17-A5BFD2C4" }, { "file": "libfoo.so", "systemPath": "libfoo.so", - "loadAddress": "0x00007ffff7bd9000", + "loadAddress": 140737349783552, "uuid": "B30FFEDA-8BB2-3D08-4580-C5937ED11E2B-21BE778C" }, { "file": "libbar.so", "systemPath": "libbar.so", - "loadAddress": "0x00007ffff79d7000", + "loadAddress": 140737347678208, "uuid": "6633B038-EA73-D1A6-FF9A-7D0C0EDF733D-95FEA2CC" } ] diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace.json b/lldb/test/API/commands/trace/intelpt-trace/trace.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace.json @@ -1,12 +1,10 @@ { - "trace": { - "type": "intel-pt", - "cpuInfo": { - "vendor": "intel", - "family": 6, - "model": 79, - "stepping": 1 - } + "type": "intel-pt", + "cpuInfo": { + "vendor": "GenuineIntel", + "family": 6, + "model": 79, + "stepping": 1 }, "processes": [ { @@ -15,14 +13,14 @@ "threads": [ { "tid": 3842849, - "traceFile": "3842849.trace" + "traceBuffer": "3842849.trace" } ], "modules": [ { "file": "a.out", "systemPath": "a.out", - "loadAddress": "0x0000000000400000", + "loadAddress": 4194304, "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" } ] diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_2threads.json b/lldb/test/API/commands/trace/intelpt-trace/trace_2threads.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_2threads.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_2threads.json @@ -1,12 +1,10 @@ { - "trace": { - "type": "intel-pt", - "cpuInfo": { - "vendor": "intel", - "family": 6, - "model": 79, - "stepping": 1 - } + "type": "intel-pt", + "cpuInfo": { + "vendor": "GenuineIntel", + "family": 6, + "model": 79, + "stepping": 1 }, "processes": [ { @@ -15,18 +13,18 @@ "threads": [ { "tid": 3842849, - "traceFile": "3842849.trace" + "traceBuffer": "3842849.trace" }, { "tid": 3842850, - "traceFile": "3842849.trace" + "traceBuffer": "3842849.trace" } ], "modules": [ { "file": "a.out", "systemPath": "a.out", - "loadAddress": "0x0000000000400000", + "loadAddress": 4194304, "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" } ] diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_bad.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad.json @@ -1,12 +1,10 @@ { - "trace": { - "type": "intel-pt", - "cpuInfo": { - "vendor": "intel", - "family": 6, - "model": 79, - "stepping": 1 - } + "type": "intel-pt", + "cpuInfo": { + "vendor": "GenuineIntel", + "family": 6, + "model": 79, + "stepping": 1 }, "processes": [ 123 diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json @@ -1,12 +1,10 @@ { - "trace": { - "type": "intel-pt", - "cpuInfo": { - "vendor": "intel", - "family": 6, - "model": 79, - "stepping": 1 - } + "type": "intel-pt", + "cpuInfo": { + "vendor": "GenuineIntel", + "family": 6, + "model": 79, + "stepping": 1 }, "processes": [ { @@ -15,14 +13,14 @@ "threads": [ { "tid": 5678, - "traceFile": "3842849.trace" + "traceBuffer": "3842849.trace" } ], "modules": [ { "file": "a.out", "systemPath": "a.out", - "loadAddress": "0x0000000000400000", + "loadAddress": 4194304, "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" } ] @@ -32,7 +30,7 @@ "threads": [ { "tid": 56789, - "traceFile": "3842849.trace" + "traceBuffer": "3842849.trace" } ], "modules": [] diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad3.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad3.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_bad3.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad3.json @@ -1,12 +1,10 @@ { - "trace": { - "type": "intel-pt", - "cpuInfo": { - "vendor": "intel", - "family": 6, - "model": 79, - "stepping": 1 - } + "type": "intel-pt", + "cpuInfo": { + "vendor": "GenuineIntel", + "family": 6, + "model": 79, + "stepping": 1 }, "processes": [ { @@ -15,14 +13,14 @@ "threads": [ { "tid": 5678, - "traceFile": "3842849.trace" + "traceBuffer": "3842849.trace" } ], "modules": [ { "file": "a.out", "systemPath": "a.out", - "loadAddress": "0x0000000000400000", + "loadAddress": 4194304, "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" } ] diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad4.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad4.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_bad4.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad4.json @@ -1,11 +1,9 @@ { - "trace": { - "type": "intel-pt", - "cpuInfo": { - "vendor": "intel", - "model": 79, - "stepping": 1 - } + "type": "intel-pt", + "cpuInfo": { + "vendor": "GenuineIntel", + "model": 79, + "stepping": 1 }, "processes": [ ] diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad5.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad5.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_bad5.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad5.json @@ -1,12 +1,10 @@ { - "trace": { - "type": "intel-pt", - "cpuInfo": { - "vendor": "intel", - "family": 6, - "model": 79, - "stepping": 1 - } + "type": "intel-pt", + "cpuInfo": { + "vendor": "GenuineIntel", + "family": 6, + "model": 79, + "stepping": 1 }, "processes": [ { @@ -15,7 +13,7 @@ "threads": [ { "tid": 5678, - "traceFile": "3842849.trace" + "traceBuffer": "3842849.trace" } ], "modules": [ diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json @@ -1,12 +1,10 @@ { - "trace": { - "type": "intel-pt", - "cpuInfo": { - "vendor": "intel", - "family": 6, - "model": 79, - "stepping": 1 - } + "type": "intel-pt", + "cpuInfo": { + "vendor": "GenuineIntel", + "family": 6, + "model": 79, + "stepping": 1 }, "processes": [ { @@ -15,14 +13,14 @@ "threads": [ { "tid": 3842849, - "traceFile": "3842849.trace" + "traceBuffer": "3842849.trace" } ], "modules": [ { "file": "a.out", "systemPath": "a.out", - "loadAddress": "0x0000000000FFFFF0", + "loadAddress": 16777200, "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" } ] diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json b/lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json --- a/lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json @@ -1,12 +1,10 @@ { - "trace": { - "type": "intel-pt", - "cpuInfo": { - "vendor": "intel", - "family": 2123123, - "model": 12123123, - "stepping": 1231231 - } + "type": "intel-pt", + "cpuInfo": { + "vendor": "GenuineIntel", + "family": 2123123, + "model": 12123123, + "stepping": 1231231 }, "processes": [ { @@ -15,14 +13,14 @@ "threads": [ { "tid": 3842849, - "traceFile": "3842849.trace" + "traceBuffer": "3842849.trace" } ], "modules": [ { "file": "a.out", "systemPath": "a.out", - "loadAddress": "0x0000000000400000", + "loadAddress": 4194304, "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" } ] diff --git a/lldb/unittests/Process/Linux/PerfTests.cpp b/lldb/unittests/Process/Linux/PerfTests.cpp --- a/lldb/unittests/Process/Linux/PerfTests.cpp +++ b/lldb/unittests/Process/Linux/PerfTests.cpp @@ -6,8 +6,6 @@ // //===----------------------------------------------------------------------===// -#ifdef __x86_64__ - #include "Perf.h" #include "llvm/Support/Error.h" @@ -85,136 +83,3 @@ ASSERT_LT(converted_tsc_diff.count(), (SLEEP_NANOS + acceptable_overhead).count()); } - -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::ArrayRef src(reinterpret_cast(cyc_buf), - cyc_buf_size); - ReadCyclicBuffer(dst, src, cyc_start, offset); - return dst.size(); -} - -TEST(CyclicBuffer, EdgeCases) { - size_t bytes_read; - uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'}; - - // We will always leave the last bytes untouched - // so that string comparisons work. - char smaller_buffer[4] = {}; - - // empty buffer to read into - bytes_read = ReadCylicBufferWrapper(smaller_buffer, 0, cyclic_buffer, - sizeof(cyclic_buffer), 3, 0); - ASSERT_EQ(0u, bytes_read); - - // empty cyclic buffer - bytes_read = ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer), - cyclic_buffer, 0, 3, 0); - ASSERT_EQ(0u, bytes_read); - - // bigger offset - bytes_read = - ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer), - cyclic_buffer, sizeof(cyclic_buffer), 3, 6); - ASSERT_EQ(0u, bytes_read); - - // wrong offset - bytes_read = - ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer), - cyclic_buffer, sizeof(cyclic_buffer), 3, 7); - ASSERT_EQ(0u, bytes_read); - - // wrong start - bytes_read = - ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer), - cyclic_buffer, sizeof(cyclic_buffer), 3, 7); - ASSERT_EQ(0u, 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 (size_t i = 0; i < sizeof(cyclic); i++) { - // We will always leave the last bytes untouched - // so that string comparisons 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; - uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'}; - - // We will always leave the last bytes untouched - // so that string comparisons work. - char smaller_buffer[4] = {}; - bytes_read = - ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1), - cyclic_buffer, sizeof(cyclic_buffer), 3, 0); - ASSERT_EQ(3u, 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(3u, 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(3u, 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(3u, 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(2u, 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(1u, 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 (size_t i = 0; i < sizeof(cyclic); i++) { - // We will always leave the last bytes untouched - // so that string comparisons 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)); - } -} - -#endif // __x86_64__ diff --git a/lldb/unittests/Utility/TraceGDBRemotePacketsTest.cpp b/lldb/unittests/Utility/TraceGDBRemotePacketsTest.cpp --- a/lldb/unittests/Utility/TraceGDBRemotePacketsTest.cpp +++ b/lldb/unittests/Utility/TraceGDBRemotePacketsTest.cpp @@ -38,8 +38,7 @@ // Create TraceIntelPTGetStateResponse. TraceIntelPTGetStateResponse response; - response.tsc_conversion = std::make_unique( - test_time_mult, test_time_shift, test_time_zero); + response.tsc_perf_zero_conversion = LinuxPerfZeroTscConversion{test_time_mult, test_time_shift, test_time_zero}; // Serialize then deserialize. Expected deserialized_response = @@ -57,9 +56,9 @@ const uint64_t EXPECTED_NANOS = 9223372039007304983u; uint64_t pre_serialization_conversion = - response.tsc_conversion->Convert(TSC).count(); + response.tsc_perf_zero_conversion->ToNanos(TSC).count(); uint64_t post_serialization_conversion = - deserialized_response->tsc_conversion->Convert(TSC).count(); + deserialized_response->tsc_perf_zero_conversion->ToNanos(TSC).count(); // Check equality: // Ensure that both the TraceGetStateResponse and TraceIntelPTGetStateResponse @@ -95,7 +94,7 @@ // portions of the JSON representation are unchanged. ASSERT_EQ(toJSON(response), toJSON(*deserialized_response)); // Ensure that the tsc_conversion's are nullptr. - ASSERT_EQ(response.tsc_conversion.get(), nullptr); - ASSERT_EQ(response.tsc_conversion.get(), - deserialized_response->tsc_conversion.get()); + ASSERT_EQ(response.tsc_perf_zero_conversion, None); + ASSERT_EQ(response.tsc_perf_zero_conversion, + deserialized_response->tsc_perf_zero_conversion); }