forked from OSchip/llvm-project
[trace][intelpt] Support system-wide tracing [12] - Support multi-core trace load and save
:q! This diff is massive, but it's because it connects the client with lldb-server and also ensures that the postmortem case works. - Flatten the postmortem trace schema. The reason is that the schema has become quite complex due to the new multicore case, which defeats the original purpose of having a schema that could work for every trace plug-in. At this point, it's better that each trace plug-in defines it's own full schema. This means that the only common field is "type". -- Because of this new approach, I merged the "common" trace load and saving functionalities into the IntelPT one. This simplified the code quite a bit. If we eventually implement another trace plug-in, we can see then what we could reuse. -- The new schema, which is flattened, has now better comments and is parsed better. A change I did was to disallow hex addresses, because they are a bit error prone. I'm asking now to print the address in decimal. -- Renamed "intel" to "GenuineIntel" in the schema because that's what you see in /proc/cpuinfo. - Implemented reading the context switch trace data buffer. I had to do some refactors to do that cleanly. -- A major change that I did here was to simplify the perf_event circular buffer reading logic. It was too complex. Maybe the original Intel author had something different in mind. - Implemented all the necessary bits to read trace.json files with per-core data. - Implemented all the necessary bits to save to disk per-core trace session. - Added a test that ensures that parsing and saving to disk works. Differential Revision: https://reviews.llvm.org/D126015
This commit is contained in:
parent
bab0910f77
commit
fc5ef57c7d
|
@ -534,7 +534,7 @@ read packet: OK/E<error code>;AAAAAAAAA
|
|||
// "tscPerfZeroConversion": {
|
||||
// "timeMult": <decimal integer>,
|
||||
// "timeShift": <decimal integer>,
|
||||
// "timeSero": <decimal integer>,
|
||||
// "timeZero": <decimal integer>,
|
||||
// }
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -182,14 +182,12 @@ For example
|
|||
::
|
||||
|
||||
{
|
||||
"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 @@ For example
|
|||
"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 @@ References
|
|||
- 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/
|
||||
.. _blog: https://engineering.fb.com/2021/04/27/developer-tools/reverse-debugging/
|
||||
|
|
|
@ -240,6 +240,7 @@ public:
|
|||
|
||||
using OnBinaryDataReadCallback =
|
||||
std::function<llvm::Error(llvm::ArrayRef<uint8_t> 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 @@ public:
|
|||
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<Process *> GetAllProcesses();
|
||||
|
||||
/// \return
|
||||
/// The list of cores being traced. Might be empty depending on the
|
||||
/// plugin.
|
||||
llvm::ArrayRef<lldb::core_id_t> 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<Process *> 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 @@ protected:
|
|||
llvm::Expected<FileSpec> 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<FileSpec> 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 @@ protected:
|
|||
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 @@ protected:
|
|||
llvm::Expected<std::vector<uint8_t>>
|
||||
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<std::vector<uint8_t>>
|
||||
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 @@ protected:
|
|||
llvm::Optional<size_t> GetLiveThreadBinaryDataSize(lldb::tid_t tid,
|
||||
llvm::StringRef kind);
|
||||
|
||||
/// Get the size of the data returned by \a GetLiveCoreBinaryData
|
||||
llvm::Optional<size_t> GetLiveCoreBinaryDataSize(lldb::core_id_t core_id,
|
||||
llvm::StringRef kind);
|
||||
|
||||
/// Get the size of the data returned by \a GetLiveProcessBinaryData
|
||||
llvm::Optional<size_t> GetLiveProcessBinaryDataSize(llvm::StringRef kind);
|
||||
|
||||
/// Constructor for post mortem processes
|
||||
Trace() = default;
|
||||
Trace(llvm::ArrayRef<lldb::ProcessSP> postmortem_processes,
|
||||
llvm::Optional<std::vector<lldb::core_id_t>> postmortem_cores);
|
||||
|
||||
/// Constructor for a live process
|
||||
Trace(Process &live_process) : m_live_process(&live_process) {}
|
||||
|
@ -400,6 +503,10 @@ private:
|
|||
/// 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<Process *> 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 @@ private:
|
|||
llvm::DenseMap<lldb::tid_t, std::unordered_map<std::string, size_t>>
|
||||
m_live_thread_data;
|
||||
|
||||
/// core id -> data kind -> size
|
||||
llvm::DenseMap<lldb::core_id_t, std::unordered_map<std::string, size_t>>
|
||||
m_live_core_data;
|
||||
/// data kind -> size
|
||||
std::unordered_map<std::string, size_t> m_live_process_data;
|
||||
/// \}
|
||||
|
||||
/// The list of cores being traced. Might be \b None depending on the plug-in.
|
||||
llvm::Optional<std::vector<lldb::core_id_t>> 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<lldb::tid_t, std::unordered_map<std::string, FileSpec>>
|
||||
m_postmortem_thread_data;
|
||||
|
||||
/// core id -> data kind -> file
|
||||
llvm::DenseMap<lldb::core_id_t, std::unordered_map<std::string, FileSpec>>
|
||||
m_postmortem_core_data;
|
||||
|
||||
/// \}
|
||||
|
||||
llvm::Optional<std::string> m_live_refresh_error;
|
||||
};
|
||||
|
||||
|
|
|
@ -133,7 +133,9 @@ struct TraceGetStateResponse {
|
|||
std::vector<TraceThreadState> traced_threads;
|
||||
std::vector<TraceBinaryData> process_binary_data;
|
||||
llvm::Optional<std::vector<TraceCoreState>> cores;
|
||||
std::vector<std::string> warnings;
|
||||
llvm::Optional<std::vector<std::string>> warnings;
|
||||
|
||||
void AddWarning(llvm::StringRef warning);
|
||||
};
|
||||
|
||||
bool fromJSON(const llvm::json::Value &value, TraceGetStateResponse &packet,
|
||||
|
@ -151,6 +153,8 @@ struct TraceGetBinaryDataRequest {
|
|||
std::string kind;
|
||||
/// Optional tid if the data is related to a thread.
|
||||
llvm::Optional<lldb::tid_t> tid;
|
||||
/// Optional core id if the data is related to a cpu core.
|
||||
llvm::Optional<lldb::tid_t> core_id;
|
||||
/// Offset in bytes from where to start reading the data.
|
||||
uint64_t offset;
|
||||
/// Number of bytes to read.
|
||||
|
|
|
@ -182,32 +182,39 @@ Expected<json::Value> IntelPTCollector::GetState() {
|
|||
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<std::vector<uint8_t>>
|
||||
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 (m_process_trace_up && m_process_trace_up->TracesThread(*request.tid))
|
||||
return m_process_trace_up->GetBinaryData(request);
|
||||
|
||||
if (Expected<IntelPTSingleBufferTrace &> trace =
|
||||
m_thread_traces.GetTracedThread(*request.tid))
|
||||
return trace->GetTraceBuffer(request.offset, request.size);
|
||||
else
|
||||
return trace.takeError();
|
||||
} else if (request.kind == IntelPTDataKinds::kProcFsCpuInfo) {
|
||||
if (request.kind == IntelPTDataKinds::kProcFsCpuInfo)
|
||||
return GetProcfsCpuInfo();
|
||||
|
||||
if (m_process_trace_up) {
|
||||
Expected<Optional<std::vector<uint8_t>>> data =
|
||||
m_process_trace_up->TryGetBinaryData(request);
|
||||
if (!data)
|
||||
return data.takeError();
|
||||
if (*data)
|
||||
return **data;
|
||||
}
|
||||
return createStringError(inconvertibleErrorCode(),
|
||||
"Unsuported trace binary data kind: %s",
|
||||
request.kind.c_str());
|
||||
|
||||
{
|
||||
Expected<Optional<std::vector<uint8_t>>> data =
|
||||
m_thread_traces.TryGetBinaryData(request);
|
||||
if (!data)
|
||||
return data.takeError();
|
||||
if (*data)
|
||||
return **data;
|
||||
}
|
||||
|
||||
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() {
|
||||
|
|
|
@ -70,8 +70,8 @@ static Expected<PerfEvent> CreateContextSwitchTracePerfEvent(
|
|||
if (Expected<PerfEvent> 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 @@ Error IntelPTMultiCoreTrace::TraceStop(lldb::tid_t tid) {
|
|||
"per-core process tracing is enabled.");
|
||||
}
|
||||
|
||||
Expected<std::vector<uint8_t>>
|
||||
IntelPTMultiCoreTrace::GetBinaryData(const TraceGetBinaryDataRequest &request) {
|
||||
return createStringError(inconvertibleErrorCode(), "Unimplemented");
|
||||
Expected<Optional<std::vector<uint8_t>>>
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -80,8 +80,8 @@ public:
|
|||
|
||||
llvm::Error TraceStop(lldb::tid_t tid) override;
|
||||
|
||||
llvm::Expected<std::vector<uint8_t>>
|
||||
GetBinaryData(const TraceGetBinaryDataRequest &request) override;
|
||||
llvm::Expected<llvm::Optional<std::vector<uint8_t>>>
|
||||
TryGetBinaryData(const TraceGetBinaryDataRequest &request) override;
|
||||
|
||||
private:
|
||||
/// This assumes that all underlying perf_events for each core are part of the
|
||||
|
|
|
@ -46,13 +46,10 @@ TraceIntelPTGetStateResponse IntelPTPerThreadProcessTrace::GetState() {
|
|||
return state;
|
||||
}
|
||||
|
||||
Expected<std::vector<uint8_t>> IntelPTPerThreadProcessTrace::GetBinaryData(
|
||||
Expected<llvm::Optional<std::vector<uint8_t>>>
|
||||
IntelPTPerThreadProcessTrace::TryGetBinaryData(
|
||||
const TraceGetBinaryDataRequest &request) {
|
||||
if (Expected<IntelPTSingleBufferTrace &> 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<IntelPTProcessTraceUP>
|
||||
|
|
|
@ -44,8 +44,8 @@ public:
|
|||
|
||||
TraceIntelPTGetStateResponse GetState() override;
|
||||
|
||||
llvm::Expected<std::vector<uint8_t>>
|
||||
GetBinaryData(const TraceGetBinaryDataRequest &request) override;
|
||||
llvm::Expected<llvm::Optional<std::vector<uint8_t>>>
|
||||
TryGetBinaryData(const TraceGetBinaryDataRequest &request) override;
|
||||
|
||||
private:
|
||||
IntelPTPerThreadProcessTrace(const TraceIntelPTStartRequest &request)
|
||||
|
|
|
@ -36,9 +36,12 @@ public:
|
|||
/// \copydoc IntelPTThreadTraceCollection::TraceStop()
|
||||
virtual llvm::Error TraceStop(lldb::tid_t tid) = 0;
|
||||
|
||||
/// Get binary data owned by this instance.
|
||||
virtual llvm::Expected<std::vector<uint8_t>>
|
||||
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<llvm::Optional<std::vector<uint8_t>>>
|
||||
TryGetBinaryData(const TraceGetBinaryDataRequest &request) = 0;
|
||||
};
|
||||
|
||||
using IntelPTProcessTraceUP = std::unique_ptr<IntelPTProcessTrace>;
|
||||
|
|
|
@ -270,7 +270,8 @@ IntelPTSingleBufferTrace::Start(const TraceIntelPTStartRequest &request,
|
|||
|
||||
if (Expected<PerfEvent> 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));
|
||||
|
|
|
@ -71,3 +71,21 @@ void IntelPTThreadTraceCollection::Clear() {
|
|||
size_t IntelPTThreadTraceCollection::GetTracedThreadsCount() const {
|
||||
return m_thread_traces.size();
|
||||
}
|
||||
|
||||
llvm::Expected<llvm::Optional<std::vector<uint8_t>>>
|
||||
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<IntelPTSingleBufferTrace &> trace =
|
||||
GetTracedThread(*request.tid))
|
||||
return trace->GetTraceBuffer(request.offset, request.size);
|
||||
else
|
||||
return trace.takeError();
|
||||
}
|
||||
|
|
|
@ -59,6 +59,10 @@ public:
|
|||
|
||||
size_t GetTracedThreadsCount() const;
|
||||
|
||||
/// \copydoc IntelPTProcessTrace::TryGetBinaryData()
|
||||
llvm::Expected<llvm::Optional<std::vector<uint8_t>>>
|
||||
TryGetBinaryData(const TraceGetBinaryDataRequest &request);
|
||||
|
||||
private:
|
||||
llvm::DenseMap<lldb::tid_t, IntelPTSingleBufferTrace> m_thread_traces;
|
||||
/// Total actual thread buffer size in bytes
|
||||
|
|
|
@ -24,54 +24,6 @@ using namespace lldb_private;
|
|||
using namespace process_linux;
|
||||
using namespace llvm;
|
||||
|
||||
void lldb_private::process_linux::ReadCyclicBuffer(
|
||||
llvm::MutableArrayRef<uint8_t> &dst, llvm::ArrayRef<uint8_t> 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<ArrayRef<uint8_t>, 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<LinuxPerfZeroTscConversion>
|
||||
lldb_private::process_linux::LoadPerfTscConversionParameters() {
|
||||
lldb::pid_t pid = getpid();
|
||||
|
@ -84,8 +36,10 @@ lldb_private::process_linux::LoadPerfTscConversionParameters() {
|
|||
Expected<PerfEvent> 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 @@ PerfEvent::DoMmap(void *addr, size_t length, int prot, int flags,
|
|||
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<resource_handle::MmapUP> mmap_metadata_data =
|
||||
DoMmap(nullptr, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, 0,
|
||||
"metadata and data buffer")) {
|
||||
if (Expected<resource_handle::MmapUP> 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 @@ llvm::Error PerfEvent::MmapAuxBuffer(size_t num_aux_pages) {
|
|||
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::MmapAuxBuffer(size_t num_aux_pages) {
|
|||
}
|
||||
|
||||
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::Error PerfEvent::MmapMetadataAndBuffers(size_t num_data_pages,
|
|||
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 @@ ArrayRef<uint8_t> PerfEvent::GetAuxBuffer() const {
|
|||
static_cast<size_t>(mmap_metadata.aux_size)};
|
||||
}
|
||||
|
||||
Expected<std::vector<uint8_t>>
|
||||
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<uint8_t> data = GetDataBuffer();
|
||||
uint64_t data_head = mmap_metadata.data_head;
|
||||
uint64_t data_size = mmap_metadata.data_size;
|
||||
std::vector<uint8_t> 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<std::vector<uint8_t>>
|
||||
PerfEvent::ReadFlushedOutAuxCyclicBuffer(size_t offset, size_t size) {
|
||||
CollectionState previous_state = m_collection_state;
|
||||
if (Error err = DisableWithIoctl())
|
||||
return std::move(err);
|
||||
|
||||
std::vector<uint8_t> 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<uint8_t> data = GetAuxBuffer();
|
||||
uint64_t aux_head = mmap_metadata.aux_head;
|
||||
uint64_t aux_size = mmap_metadata.aux_size;
|
||||
std::vector<uint8_t> output;
|
||||
output.reserve(size);
|
||||
|
||||
/**
|
||||
* When configured as ring buffer, the aux buffer keeps wrapping around
|
||||
|
@ -247,14 +260,28 @@ PerfEvent::ReadFlushedOutAuxCyclicBuffer(size_t offset, size_t size) {
|
|||
*
|
||||
* */
|
||||
|
||||
MutableArrayRef<uint8_t> buffer(data);
|
||||
ReadCyclicBuffer(buffer, GetAuxBuffer(), static_cast<size_t>(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 @@ Error PerfEvent::EnableWithIoctl() {
|
|||
|
||||
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.
|
||||
|
|
|
@ -74,24 +74,6 @@ using MmapUP = std::unique_ptr<void, resource_handle::MmapDeleter>;
|
|||
|
||||
} // 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<uint8_t> &dst,
|
||||
llvm::ArrayRef<uint8_t> 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 @@ public:
|
|||
/// 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 @@ public:
|
|||
llvm::Expected<std::vector<uint8_t>>
|
||||
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<std::vector<uint8_t>>
|
||||
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 @@ private:
|
|||
/// 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.
|
||||
///
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
add_lldb_library(lldbPluginTraceCommon
|
||||
ThreadPostMortemTrace.cpp
|
||||
TraceJSONStructs.cpp
|
||||
TraceSessionFileParser.cpp
|
||||
TraceSessionSaver.cpp
|
||||
|
||||
LINK_LIBS
|
||||
lldbCore
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
using namespace lldb;
|
||||
using namespace lldb_private;
|
||||
using namespace llvm;
|
||||
|
||||
void ThreadPostMortemTrace::RefreshStateAfterStop() {}
|
||||
|
||||
|
@ -36,6 +37,6 @@ ThreadPostMortemTrace::CreateRegisterContextForFrame(StackFrame *frame) {
|
|||
|
||||
bool ThreadPostMortemTrace::CalculateStopInfo() { return false; }
|
||||
|
||||
const FileSpec &ThreadPostMortemTrace::GetTraceFile() const {
|
||||
const Optional<FileSpec> &ThreadPostMortemTrace::GetTraceFile() const {
|
||||
return m_trace_file;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ public:
|
|||
/// 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<FileSpec> &trace_file)
|
||||
: Thread(process, tid), m_trace_file(trace_file) {}
|
||||
|
||||
void RefreshStateAfterStop() override;
|
||||
|
@ -44,7 +44,7 @@ public:
|
|||
|
||||
/// \return
|
||||
/// The trace file of this thread.
|
||||
const FileSpec &GetTraceFile() const;
|
||||
const llvm::Optional<FileSpec> &GetTraceFile() const;
|
||||
|
||||
protected:
|
||||
bool CalculateStopInfo() override;
|
||||
|
@ -52,7 +52,7 @@ protected:
|
|||
lldb::RegisterContextSP m_thread_reg_ctx_sp;
|
||||
|
||||
private:
|
||||
FileSpec m_trace_file;
|
||||
llvm::Optional<FileSpec> m_trace_file;
|
||||
};
|
||||
|
||||
} // namespace lldb_private
|
||||
|
|
|
@ -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 <sstream>
|
||||
|
||||
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<StringRef> 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
|
|
@ -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<std::string> file;
|
||||
JSONAddress load_address;
|
||||
llvm::Optional<std::string> uuid;
|
||||
};
|
||||
|
||||
struct JSONThread {
|
||||
int64_t tid;
|
||||
std::string trace_file;
|
||||
};
|
||||
|
||||
struct JSONProcess {
|
||||
int64_t pid;
|
||||
std::string triple;
|
||||
std::vector<JSONThread> threads;
|
||||
std::vector<JSONModule> modules;
|
||||
};
|
||||
|
||||
struct JSONTracePluginSettings {
|
||||
std::string type;
|
||||
};
|
||||
|
||||
struct JSONTraceSessionBase {
|
||||
std::vector<JSONProcess> processes;
|
||||
};
|
||||
|
||||
/// The trace plug-in implementation should provide its own TPluginSettings,
|
||||
/// which corresponds to the "trace" section of the schema.
|
||||
template <class TPluginSettings>
|
||||
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 <class TPluginSettings>
|
||||
bool fromJSON(const Value &value,
|
||||
lldb_private::JSONTraceSession<TPluginSettings> &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
|
|
@ -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 <sstream>
|
||||
|
||||
#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<lldb::tid_t>(thread.tid);
|
||||
|
||||
FileSpec trace_file(thread.trace_file);
|
||||
NormalizePath(trace_file);
|
||||
|
||||
ThreadPostMortemTraceSP thread_sp =
|
||||
std::make_shared<ThreadPostMortemTrace>(*process_sp, tid, trace_file);
|
||||
process_sp->GetThreadList().AddThread(thread_sp);
|
||||
return thread_sp;
|
||||
}
|
||||
|
||||
Expected<TraceSessionFileParser::ParsedProcess>
|
||||
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<lldb::pid_t>(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<std::vector<TraceSessionFileParser::ParsedProcess>>
|
||||
TraceSessionFileParser::ParseCommonSessionFile(
|
||||
const JSONTraceSessionBase &session) {
|
||||
std::vector<ParsedProcess> 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<ParsedProcess> parsed_process = ParseProcess(process))
|
||||
parsed_processes.push_back(std::move(*parsed_process));
|
||||
else {
|
||||
onError();
|
||||
return parsed_process.takeError();
|
||||
}
|
||||
}
|
||||
return parsed_processes;
|
||||
}
|
|
@ -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<lldb::ThreadPostMortemTraceSP> 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<std::vector<ParsedProcess>>
|
||||
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<ParsedProcess> 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
|
|
@ -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 <fstream>
|
||||
|
||||
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<JSONTraceSessionBase> TraceSessionSaver::BuildProcessesSection(
|
||||
Process &live_process, llvm::StringRef raw_thread_trace_data_kind,
|
||||
FileSpec directory) {
|
||||
|
||||
JSONTraceSessionBase json_session_description;
|
||||
Expected<std::vector<JSONThread>> json_threads =
|
||||
BuildThreadsSection(live_process, raw_thread_trace_data_kind, directory);
|
||||
if (!json_threads)
|
||||
return json_threads.takeError();
|
||||
|
||||
Expected<std::vector<JSONModule>> json_modules =
|
||||
BuildModulesSection(live_process, directory);
|
||||
if (!json_modules)
|
||||
return json_modules.takeError();
|
||||
|
||||
json_session_description.processes.push_back(JSONProcess{
|
||||
static_cast<int64_t>(live_process.GetID()),
|
||||
live_process.GetTarget().GetArchitecture().GetTriple().getTriple(),
|
||||
json_threads.get(), json_modules.get()});
|
||||
return json_session_description;
|
||||
}
|
||||
|
||||
llvm::Expected<std::vector<JSONThread>> TraceSessionSaver::BuildThreadsSection(
|
||||
Process &live_process, llvm::StringRef raw_thread_trace_data_kind,
|
||||
FileSpec directory) {
|
||||
std::vector<JSONThread> 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<int64_t>(tid),
|
||||
raw_trace_path.GetPath().c_str()});
|
||||
|
||||
llvm::Error err =
|
||||
live_process.GetTarget().GetTrace()->OnThreadBinaryDataRead(
|
||||
tid, raw_thread_trace_data_kind,
|
||||
[&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {
|
||||
std::basic_fstream<char> raw_trace_fs =
|
||||
std::fstream(raw_trace_path.GetPath().c_str(),
|
||||
std::ios::out | std::ios::binary);
|
||||
raw_trace_fs.write(reinterpret_cast<const char *>(&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<std::vector<JSONModule>>
|
||||
TraceSessionSaver::BuildModulesSection(Process &live_process,
|
||||
FileSpec directory) {
|
||||
std::vector<JSONModule> 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;
|
||||
}
|
|
@ -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<JSONTraceSessionBase>
|
||||
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<std::vector<JSONThread>>
|
||||
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 <directory/modules> 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 <directory>/modules/usr/lib/foo/libbar.so.
|
||||
///
|
||||
/// \return
|
||||
/// The modules section or \a llvm::Error in case of failures.
|
||||
static llvm::Expected<std::vector<JSONModule>>
|
||||
BuildModulesSection(Process &live_process, FileSpec directory);
|
||||
};
|
||||
} // namespace lldb_private
|
||||
|
||||
#endif // LLDB_TARGET_TRACESESSIONSAVER_H
|
|
@ -74,15 +74,28 @@ Expected<TraceSP> TraceIntelPT::CreateInstanceForLiveProcess(Process &process) {
|
|||
return instance;
|
||||
}
|
||||
|
||||
TraceIntelPT::TraceIntelPT(
|
||||
const pt_cpu &cpu_info,
|
||||
const std::vector<ThreadPostMortemTraceSP> &traced_threads)
|
||||
: m_cpu_info(cpu_info) {
|
||||
TraceIntelPT::TraceIntelPT(JSONTraceSession &session,
|
||||
ArrayRef<ProcessSP> traced_processes,
|
||||
ArrayRef<ThreadPostMortemTraceSP> 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<ThreadDecoder>(thread, *this));
|
||||
SetPostMortemThreadDataFile(thread->GetID(), IntelPTDataKinds::kTraceBuffer,
|
||||
thread->GetTraceFile());
|
||||
if (const Optional<FileSpec> &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 @@ Expected<pt_cpu> TraceIntelPT::GetCPUInfo() {
|
|||
return *m_cpu_info;
|
||||
}
|
||||
|
||||
llvm::Optional<LinuxPerfZeroTscConversion>
|
||||
TraceIntelPT::GetPerfZeroTscConversion() {
|
||||
RefreshLiveProcessState();
|
||||
return m_tsc_conversion;
|
||||
}
|
||||
|
||||
Error TraceIntelPT::DoRefreshLiveProcessState(TraceGetStateResponse state,
|
||||
StringRef json_response) {
|
||||
m_thread_decoders.clear();
|
||||
|
|
|
@ -146,8 +146,11 @@ public:
|
|||
llvm::Error OnThreadBufferRead(lldb::tid_t tid,
|
||||
OnBinaryDataReadCallback callback);
|
||||
|
||||
/// Get or fetch the cpu information from, for example, /proc/cpuinfo.
|
||||
llvm::Expected<pt_cpu> GetCPUInfo();
|
||||
|
||||
/// Get or fetch the values used to convert to and from TSCs and nanos.
|
||||
llvm::Optional<LinuxPerfZeroTscConversion> GetPerfZeroTscConversion();
|
||||
|
||||
/// \return
|
||||
/// The timer object for this trace.
|
||||
|
@ -158,12 +161,20 @@ private:
|
|||
|
||||
llvm::Expected<pt_cpu> 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<lldb::ThreadPostMortemTraceSP> &traced_threads);
|
||||
/// The threads traced in the live session. They must belong to the
|
||||
/// processes mentioned above.
|
||||
TraceIntelPT(JSONTraceSession &session,
|
||||
llvm::ArrayRef<lldb::ProcessSP> traced_processes,
|
||||
llvm::ArrayRef<lldb::ThreadPostMortemTraceSP> traced_threads);
|
||||
|
||||
/// Constructor for live processes
|
||||
TraceIntelPT(Process &live_process)
|
||||
|
|
|
@ -15,45 +15,130 @@ using namespace lldb;
|
|||
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<std::vector<lldb::core_id_t>> JSONTraceSession::GetCoreIds() {
|
||||
if (!cores)
|
||||
return None;
|
||||
std::vector<lldb::core_id_t> 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("cpuInfo", plugin_settings.cpuInfo) &&
|
||||
fromJSON(value, (JSONTracePluginSettings &)plugin_settings, 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 json::Value &value, JSONTraceIntelPTCPUInfo &cpu_info,
|
||||
Path path) {
|
||||
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("vendor", cpu_info.vendor) &&
|
||||
o.map("family", cpu_info.family) && o.map("model", cpu_info.model) &&
|
||||
o.map("stepping", cpu_info.stepping);
|
||||
return o && o.map("tid", thread.tid) &&
|
||||
o.map("traceBuffer", thread.trace_buffer);
|
||||
}
|
||||
|
||||
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 JSONProcess &process) {
|
||||
return Object{
|
||||
{"pid", process.pid},
|
||||
{"triple", process.triple},
|
||||
{"threads", process.threads},
|
||||
{"modules", process.modules},
|
||||
};
|
||||
}
|
||||
|
||||
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, 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);
|
||||
}
|
||||
|
||||
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 JSONCore &core) {
|
||||
return Object{
|
||||
{"coreId", core.core_id},
|
||||
{"traceBuffer", core.trace_buffer},
|
||||
{"contextSwitchTrace", core.context_switch_trace},
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace json
|
||||
} // namespace llvm
|
||||
bool fromJSON(const json::Value &value, JSONCore &core, Path path) {
|
||||
ObjectMapper o(value, path);
|
||||
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;
|
||||
}
|
||||
|
||||
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},
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 trace_intel_pt
|
||||
} // namespace lldb_private
|
||||
|
|
|
@ -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 <intel-pt.h>
|
||||
#include <vector>
|
||||
|
||||
namespace lldb_private {
|
||||
namespace trace_intel_pt {
|
||||
|
||||
struct JSONTraceIntelPTCPUInfo {
|
||||
JSONTraceIntelPTCPUInfo() = default;
|
||||
|
||||
JSONTraceIntelPTCPUInfo(pt_cpu cpu_info) {
|
||||
family = static_cast<int64_t>(cpu_info.family);
|
||||
model = static_cast<int64_t>(cpu_info.model);
|
||||
stepping = static_cast<int64_t>(cpu_info.stepping);
|
||||
vendor = cpu_info.vendor == pcv_intel ? "intel" : "Unknown";
|
||||
}
|
||||
|
||||
int64_t family;
|
||||
int64_t model;
|
||||
int64_t stepping;
|
||||
std::string vendor;
|
||||
struct JSONModule {
|
||||
std::string system_path;
|
||||
llvm::Optional<std::string> file;
|
||||
uint64_t load_address;
|
||||
llvm::Optional<std::string> uuid;
|
||||
};
|
||||
|
||||
struct JSONTraceIntelPTTrace {
|
||||
struct JSONThread {
|
||||
int64_t tid;
|
||||
llvm::Optional<std::string> trace_buffer;
|
||||
};
|
||||
|
||||
struct JSONProcess {
|
||||
int64_t pid;
|
||||
std::string triple;
|
||||
std::vector<JSONThread> threads;
|
||||
std::vector<JSONModule> modules;
|
||||
};
|
||||
|
||||
struct JSONCore {
|
||||
lldb::core_id_t core_id;
|
||||
std::string trace_buffer;
|
||||
std::string context_switch_trace;
|
||||
};
|
||||
|
||||
struct JSONTraceSession {
|
||||
std::string type;
|
||||
JSONTraceIntelPTCPUInfo cpuInfo;
|
||||
pt_cpu cpu_info;
|
||||
std::vector<JSONProcess> processes;
|
||||
llvm::Optional<std::vector<JSONCore>> cores;
|
||||
llvm::Optional<LinuxPerfZeroTscConversion> tsc_perf_zero_conversion;
|
||||
|
||||
llvm::Optional<std::vector<lldb::core_id_t>> GetCoreIds();
|
||||
};
|
||||
|
||||
struct JSONTraceIntelPTSession {
|
||||
JSONTraceIntelPTTrace ipt_trace;
|
||||
JSONTraceSessionBase session_base;
|
||||
};
|
||||
llvm::json::Value toJSON(const JSONModule &module);
|
||||
|
||||
struct JSONTraceIntelPTSettings : JSONTracePluginSettings {
|
||||
JSONTraceIntelPTCPUInfo cpuInfo;
|
||||
};
|
||||
llvm::json::Value toJSON(const JSONThread &thread);
|
||||
|
||||
llvm::json::Value toJSON(const JSONProcess &process);
|
||||
|
||||
llvm::json::Value toJSON(const JSONCore &core);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
bool fromJSON(const llvm::json::Value &value, JSONCore &core,
|
||||
llvm::json::Path path);
|
||||
|
||||
bool fromJSON(const llvm::json::Value &value, pt_cpu &cpu_info,
|
||||
llvm::json::Path path);
|
||||
|
||||
bool fromJSON(const llvm::json::Value &value, JSONTraceSession &session,
|
||||
llvm::json::Path path);
|
||||
} // namespace trace_intel_pt
|
||||
} // namespace lldb_private
|
||||
|
||||
namespace llvm {
|
||||
namespace json {
|
||||
|
||||
bool fromJSON(
|
||||
const Value &value,
|
||||
lldb_private::trace_intel_pt::JSONTraceIntelPTSettings &plugin_settings,
|
||||
Path path);
|
||||
|
||||
bool fromJSON(const llvm::json::Value &value,
|
||||
lldb_private::trace_intel_pt::JSONTraceIntelPTCPUInfo &packet,
|
||||
llvm::json::Path path);
|
||||
|
||||
llvm::json::Value
|
||||
toJSON(const lldb_private::trace_intel_pt::JSONTraceIntelPTCPUInfo &cpu_info);
|
||||
|
||||
llvm::json::Value
|
||||
toJSON(const lldb_private::trace_intel_pt::JSONTraceIntelPTTrace &trace);
|
||||
|
||||
llvm::json::Value
|
||||
toJSON(const lldb_private::trace_intel_pt::JSONTraceIntelPTSession &session);
|
||||
|
||||
} // namespace json
|
||||
} // namespace llvm
|
||||
|
||||
#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTJSONSTRUCTS_H
|
||||
|
|
|
@ -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<lldb::tid_t>(thread.tid);
|
||||
|
||||
Optional<FileSpec> trace_file;
|
||||
if (thread.trace_buffer) {
|
||||
trace_file.emplace(*thread.trace_buffer);
|
||||
NormalizePath(*trace_file);
|
||||
}
|
||||
|
||||
ThreadPostMortemTraceSP thread_sp =
|
||||
std::make_shared<ThreadPostMortemTrace>(*process_sp, tid, trace_file);
|
||||
process_sp->GetThreadList().AddThread(thread_sp);
|
||||
return thread_sp;
|
||||
}
|
||||
|
||||
Expected<TraceIntelPTSessionFileParser::ParsedProcess>
|
||||
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<lldb::pid_t>(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<std::vector<TraceIntelPTSessionFileParser::ParsedProcess>>
|
||||
TraceIntelPTSessionFileParser::ParseSessionFile(
|
||||
const JSONTraceSession &session) {
|
||||
std::vector<ParsedProcess> 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<ParsedProcess> 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,
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pt_cpu TraceIntelPTSessionFileParser::ParsePTCPU(
|
||||
const JSONTraceIntelPTCPUInfo &cpu_info) {
|
||||
return {cpu_info.vendor.compare("intel") == 0 ? pcv_intel : pcv_unknown,
|
||||
static_cast<uint16_t>(cpu_info.family),
|
||||
static_cast<uint8_t>(cpu_info.model),
|
||||
static_cast<uint8_t>(cpu_info.stepping)};
|
||||
}
|
||||
|
||||
TraceSP TraceIntelPTSessionFileParser::CreateTraceIntelPTInstance(
|
||||
const pt_cpu &cpu_info, std::vector<ParsedProcess> &parsed_processes) {
|
||||
JSONTraceSession &session, std::vector<ParsedProcess> &parsed_processes) {
|
||||
std::vector<ThreadPostMortemTraceSP> threads;
|
||||
for (const ParsedProcess &parsed_process : parsed_processes)
|
||||
std::vector<ProcessSP> 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 @@ TraceSP TraceIntelPTSessionFileParser::CreateTraceIntelPTInstance(
|
|||
|
||||
Expected<TraceSP> TraceIntelPTSessionFileParser::Parse() {
|
||||
json::Path::Root root("traceSession");
|
||||
JSONTraceSession<JSONTraceIntelPTSettings> 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<std::vector<ParsedProcess>> parsed_processes =
|
||||
ParseCommonSessionFile(session))
|
||||
return CreateTraceIntelPTInstance(ParsePTCPU(session.trace.cpuInfo),
|
||||
*parsed_processes);
|
||||
ParseSessionFile(session))
|
||||
return CreateTraceIntelPTInstance(session, *parsed_processes);
|
||||
else
|
||||
return parsed_processes.takeError();
|
||||
}
|
||||
|
|
|
@ -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 @@ namespace trace_intel_pt {
|
|||
|
||||
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<lldb::ThreadPostMortemTraceSP> 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 @@ public:
|
|||
llvm::Expected<lldb::TraceSP> Parse();
|
||||
|
||||
lldb::TraceSP
|
||||
CreateTraceIntelPTInstance(const pt_cpu &cpu_info,
|
||||
CreateTraceIntelPTInstance(JSONTraceSession &session,
|
||||
std::vector<ParsedProcess> &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<ParsedProcess> 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<std::vector<ParsedProcess>>
|
||||
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
|
||||
|
|
|
@ -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;
|
|||
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<uint8_t> data) {
|
||||
std::basic_fstream<char> out_fs = std::fstream(
|
||||
output_file.GetPath().c_str(), std::ios::out | std::ios::binary);
|
||||
out_fs.write(reinterpret_cast<const char *>(&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();
|
||||
}
|
||||
|
||||
/// 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<std::vector<JSONThread>>
|
||||
BuildThreadsSection(Process &process, FileSpec directory) {
|
||||
std::vector<JSONThread> 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<uint8_t> 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<llvm::Optional<std::vector<JSONCore>>>
|
||||
BuildCoresSection(TraceIntelPT &trace_ipt, FileSpec directory) {
|
||||
if (trace_ipt.GetTracedCores().empty())
|
||||
return None;
|
||||
|
||||
std::vector<JSONCore> 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<uint8_t> data) -> llvm::Error {
|
||||
return WriteBytesToDisk(output_trace, data);
|
||||
});
|
||||
if (err)
|
||||
return std::move(err);
|
||||
}
|
||||
|
||||
{
|
||||
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::Error err = trace_ipt.OnCoreBinaryDataRead(
|
||||
core_id, IntelPTDataKinds::kPerfContextSwitchTrace,
|
||||
[&](llvm::ArrayRef<uint8_t> 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 <directory/modules> 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 <directory>/modules/usr/lib/foo/libbar.so.
|
||||
///
|
||||
/// \return
|
||||
/// The modules section or \a llvm::Error in case of failures.
|
||||
static llvm::Expected<std::vector<JSONModule>>
|
||||
BuildModulesSection(Process &process, FileSpec directory) {
|
||||
std::vector<JSONModule> 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;
|
||||
|
||||
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(), load_addr,
|
||||
module_sp->GetUUID().GetAsString()});
|
||||
}
|
||||
return json_modules;
|
||||
}
|
||||
|
||||
/// 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<JSONProcess>
|
||||
BuildProcessSection(Process &process, const FileSpec &directory) {
|
||||
Expected<std::vector<JSONThread>> json_threads =
|
||||
BuildThreadsSection(process, directory);
|
||||
if (!json_threads)
|
||||
return json_threads.takeError();
|
||||
|
||||
Expected<std::vector<JSONModule>> json_modules =
|
||||
BuildModulesSection(process, directory);
|
||||
if (!json_modules)
|
||||
return json_modules.takeError();
|
||||
|
||||
return JSONProcess{
|
||||
static_cast<int64_t>(process.GetID()),
|
||||
process.GetTarget().GetArchitecture().GetTriple().getTriple(),
|
||||
json_threads.get(), json_modules.get()};
|
||||
}
|
||||
|
||||
/// See BuildProcessSection()
|
||||
static llvm::Expected<std::vector<JSONProcess>>
|
||||
BuildProcessesSection(TraceIntelPT &trace_ipt, const FileSpec &directory) {
|
||||
std::vector<JSONProcess> processes;
|
||||
for (Process *process : trace_ipt.GetAllProcesses()) {
|
||||
if (llvm::Expected<JSONProcess> 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);
|
||||
|
||||
llvm::Expected<JSONTraceIntelPTTrace> json_intel_pt_trace =
|
||||
BuildTraceSection(trace_ipt);
|
||||
if (!json_intel_pt_trace)
|
||||
return json_intel_pt_trace.takeError();
|
||||
|
||||
llvm::Expected<JSONTraceSessionBase> json_session_description =
|
||||
TraceSessionSaver::BuildProcessesSection(
|
||||
*live_process, IntelPTDataKinds::kTraceBuffer, directory);
|
||||
|
||||
if (!json_session_description)
|
||||
return json_session_description.takeError();
|
||||
|
||||
JSONTraceIntelPTSession json_intel_pt_session{json_intel_pt_trace.get(),
|
||||
json_session_description.get()};
|
||||
|
||||
return TraceSessionSaver::WriteSessionToFile(
|
||||
llvm::json::toJSON(json_intel_pt_session), directory);
|
||||
}
|
||||
|
||||
llvm::Expected<JSONTraceIntelPTTrace>
|
||||
TraceIntelPTSessionSaver::BuildTraceSection(TraceIntelPT &trace_ipt) {
|
||||
llvm::Expected<pt_cpu> cpu_info = trace_ipt.GetCPUInfo();
|
||||
Expected<pt_cpu> cpu_info = trace_ipt.GetCPUInfo();
|
||||
if (!cpu_info)
|
||||
return cpu_info.takeError();
|
||||
|
||||
return JSONTraceIntelPTTrace{"intel-pt",
|
||||
JSONTraceIntelPTCPUInfo(cpu_info.get())};
|
||||
Expected<std::vector<JSONProcess>> json_processes =
|
||||
BuildProcessesSection(trace_ipt, directory);
|
||||
|
||||
if (!json_processes)
|
||||
return json_processes.takeError();
|
||||
|
||||
Expected<Optional<std::vector<JSONCore>>> 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);
|
||||
}
|
||||
|
|
|
@ -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 @@ public:
|
|||
/// \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<JSONTraceIntelPTTrace>
|
||||
BuildTraceSection(TraceIntelPT &trace_ipt);
|
||||
};
|
||||
|
||||
} // namespace trace_intel_pt
|
||||
|
|
|
@ -27,26 +27,16 @@ using namespace llvm;
|
|||
// 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 @@ Trace::FindPluginForPostMortemProcess(Debugger &debugger,
|
|||
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<lldb::TraceSP> Trace::FindPluginForLiveProcess(llvm::StringRef name,
|
||||
|
@ -136,6 +126,18 @@ Optional<size_t> Trace::GetLiveThreadBinaryDataSize(lldb::tid_t tid,
|
|||
return single_thread_data_it->second;
|
||||
}
|
||||
|
||||
Optional<size_t> 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<std::string, size_t> &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<size_t> 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 @@ Trace::GetLiveThreadBinaryData(lldb::tid_t tid, llvm::StringRef kind) {
|
|||
"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<std::vector<uint8_t>>
|
||||
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_t> 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 @@ Trace::GetLiveProcessBinaryData(llvm::StringRef kind) {
|
|||
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 @@ const char *Trace::RefreshLiveProcessState() {
|
|||
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 @@ const char *Trace::RefreshLiveProcessState() {
|
|||
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 @@ const char *Trace::RefreshLiveProcessState() {
|
|||
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 @@ const char *Trace::RefreshLiveProcessState() {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
Trace::Trace(ArrayRef<ProcessSP> postmortem_processes,
|
||||
Optional<std::vector<lldb::core_id_t>> 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<Process *> Trace::GetPostMortemProcesses() {
|
||||
return m_postmortem_processes;
|
||||
}
|
||||
|
||||
std::vector<Process *> Trace::GetAllProcesses() {
|
||||
if (Process *proc = GetLiveProcess())
|
||||
return {proc};
|
||||
return GetPostMortemProcesses();
|
||||
}
|
||||
|
||||
uint32_t Trace::GetStopID() {
|
||||
RefreshLiveProcessState();
|
||||
return m_stop_id;
|
||||
|
@ -252,11 +308,39 @@ Trace::GetPostMortemThreadDataFile(lldb::tid_t tid, llvm::StringRef kind) {
|
|||
return it2->second;
|
||||
}
|
||||
|
||||
llvm::Expected<FileSpec>
|
||||
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<std::string, FileSpec> &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 @@ Trace::OnLiveThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind,
|
|||
return callback(*data);
|
||||
}
|
||||
|
||||
llvm::Error
|
||||
Trace::OnPostMortemThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind,
|
||||
OnBinaryDataReadCallback callback) {
|
||||
Expected<FileSpec> 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<std::vector<uint8_t>> data = GetLiveCoreBinaryData(core_id, kind);
|
||||
if (!data)
|
||||
return data.takeError();
|
||||
return callback(*data);
|
||||
}
|
||||
|
||||
llvm::Error Trace::OnDataFileRead(FileSpec file,
|
||||
OnBinaryDataReadCallback callback) {
|
||||
ErrorOr<std::unique_ptr<MemoryBuffer>> 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 @@ Trace::OnPostMortemThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind,
|
|||
return callback(array_ref);
|
||||
}
|
||||
|
||||
llvm::Error
|
||||
Trace::OnPostMortemThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind,
|
||||
OnBinaryDataReadCallback callback) {
|
||||
Expected<FileSpec> 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<FileSpec> 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<lldb::core_id_t> Trace::GetTracedCores() {
|
||||
RefreshLiveProcessState();
|
||||
if (m_cores)
|
||||
return *m_cores;
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ bool fromJSON(const json::Value &value, TraceGetStateResponse &packet,
|
|||
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 @@ json::Value toJSON(const TraceGetStateResponse &packet) {
|
|||
{"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 @@ json::Value toJSON(const TraceGetBinaryDataRequest &packet) {
|
|||
{"kind", packet.kind},
|
||||
{"offset", packet.offset},
|
||||
{"tid", packet.tid},
|
||||
{"coreId", packet.core_id},
|
||||
{"size", packet.size}});
|
||||
}
|
||||
|
||||
|
@ -143,7 +150,7 @@ bool fromJSON(const json::Value &value, TraceGetBinaryDataRequest &packet,
|
|||
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);
|
||||
}
|
||||
/// \}
|
||||
|
||||
|
|
|
@ -60,7 +60,6 @@ std::chrono::nanoseconds LinuxPerfZeroTscConversion::ToNanos(uint64_t tsc) {
|
|||
|
||||
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},
|
||||
|
|
|
@ -62,23 +62,24 @@ thread #1: tid = 3842849
|
|||
|
||||
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 @@ Schema:
|
|||
|
||||
# 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.
|
||||
|
|
|
@ -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 @@ class TestTraceSave(TraceIntelPTTestCaseBase):
|
|||
|
||||
# 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 " +
|
||||
|
|
|
@ -9,7 +9,7 @@ class TestTraceLoad(TraceIntelPTTestCaseBase):
|
|||
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 @@ class TestTraceLoad(TraceIntelPTTestCaseBase):
|
|||
|
||||
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
|
||||
},'''])
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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": []
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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": [
|
||||
]
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifdef __x86_64__
|
||||
|
||||
#include "Perf.h"
|
||||
|
||||
#include "llvm/Support/Error.h"
|
||||
|
@ -85,136 +83,3 @@ TEST(Perf, TscConversion) {
|
|||
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<uint8_t> dst(reinterpret_cast<uint8_t *>(buf),
|
||||
buf_size);
|
||||
llvm::ArrayRef<uint8_t> src(reinterpret_cast<uint8_t *>(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__
|
||||
|
|
|
@ -38,8 +38,7 @@ TEST(TraceGDBRemotePacketsTest, IntelPTGetStateResponse) {
|
|||
|
||||
// Create TraceIntelPTGetStateResponse.
|
||||
TraceIntelPTGetStateResponse response;
|
||||
response.tsc_conversion = std::make_unique<LinuxPerfZeroTscConversion>(
|
||||
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<TraceIntelPTGetStateResponse> deserialized_response =
|
||||
|
@ -57,9 +56,9 @@ TEST(TraceGDBRemotePacketsTest, IntelPTGetStateResponse) {
|
|||
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 @@ TEST(TraceGDBRemotePacketsTest, IntelPTGetStateResponseEmpty) {
|
|||
// 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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue