[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:
Walter Erquinigo 2022-05-18 21:36:34 -07:00
parent bab0910f77
commit fc5ef57c7d
50 changed files with 1435 additions and 1331 deletions

View File

@ -534,7 +534,7 @@ read packet: OK/E<error code>;AAAAAAAAA
// "tscPerfZeroConversion": {
// "timeMult": <decimal integer>,
// "timeShift": <decimal integer>,
// "timeSero": <decimal integer>,
// "timeZero": <decimal integer>,
// }
//----------------------------------------------------------------------

View File

@ -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/

View File

@ -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;
};

View File

@ -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.

View File

@ -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() {

View File

@ -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;
}

View File

@ -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

View File

@ -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>

View File

@ -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)

View File

@ -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>;

View File

@ -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));

View File

@ -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();
}

View File

@ -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

View File

@ -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.

View File

@ -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.
///

View File

@ -1,8 +1,5 @@
add_lldb_library(lldbPluginTraceCommon
ThreadPostMortemTrace.cpp
TraceJSONStructs.cpp
TraceSessionFileParser.cpp
TraceSessionSaver.cpp
LINK_LIBS
lldbCore

View File

@ -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;
}

View 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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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();

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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 {};
}

View File

@ -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);
}
/// \}

View File

@ -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},

View File

@ -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.

View File

@ -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 " +

View File

@ -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
},'''])

View File

@ -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"
}
]

View File

@ -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"
}
]

View File

@ -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"
}
]

View File

@ -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

View File

@ -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": []

View File

@ -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"
}
]

View File

@ -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": [
]

View File

@ -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": [

View File

@ -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"
}
]

View File

@ -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"
}
]

View File

@ -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__

View File

@ -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);
}