[trace][intel pt] Create a common accessor for live and postmortem data

Some parts of the code have to distinguish between live and postmortem threads
to figure out how to get some data, e.g. thread trace buffers. This makes the
code less generic and more error prone. An example of that is that we have
two different decoders: LiveThreadDecoder and PostMortemThreadDecoder. They
exist because getting the trace bufer is different for each case.

The problem doesn't stop there. Soon we'll have even more kinds of data, like
the context switch trace, whose fetching will be different for live and post-
mortem processes.

As a way to fix this, I'm creating a common API for accessing thread data,
which is able to figure out how to handle the postmortem and live cases on
behalf of the caller. As a result of that, I was able to eliminate the two
decoders and unify them into a simpler one. Not only that, our TraceSave
functionality only worked for live threads, but now it can also work for
postmortem processes, which might be useful now, but it might in the future.

This common API is OnThreadBinaryDataRead. More information in the inline
documentation.

Differential Revision: https://reviews.llvm.org/D123281
This commit is contained in:
Walter Erquinigo 2022-04-06 15:17:23 -07:00
parent 6423b50235
commit e0cfe20ad2
11 changed files with 228 additions and 172 deletions

View file

@ -233,15 +233,76 @@ public:
/// \a llvm::Error otherwise.
llvm::Error Stop();
/// Get the trace file of the given post mortem thread.
llvm::Expected<const FileSpec &> GetPostMortemTraceFile(lldb::tid_t tid);
/// \return
/// The stop ID of the live process being traced, or an invalid stop ID
/// if the trace is in an error or invalid state.
uint32_t GetStopID();
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
/// the different data fetching procedures that exist for live and post mortem
/// threads.
///
/// The fetched data is not persisted after the callback is invoked.
///
/// \param[in] tid
/// The tid 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
/// An \a llvm::Error if the data couldn't be fetched, or the return value
/// of the callback, otherwise.
llvm::Error OnThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind,
OnBinaryDataReadCallback callback);
protected:
/// Implementation of \a OnThreadBinaryDataRead() for live threads.
llvm::Error OnLiveThreadBinaryDataRead(lldb::tid_t tid, 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);
/// Get the file path containing data of a postmortem thread given a data
/// identifier.
///
/// \param[in] tid
/// The thread 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> GetPostMortemThreadDataFile(lldb::tid_t tid,
llvm::StringRef kind);
/// Associate a given thread with a data file using a data identifier.
///
/// \param[in] tid
/// The thread 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 SetPostMortemThreadDataFile(lldb::tid_t tid, llvm::StringRef kind,
FileSpec file_spec);
/// Get binary data of a live thread given a data identifier.
///
/// \param[in] tid
@ -315,11 +376,25 @@ protected:
uint32_t m_stop_id = LLDB_INVALID_STOP_ID;
/// Process traced by this object if doing live tracing. Otherwise it's null.
Process *m_live_process = nullptr;
/// 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.
/// \{
/// tid -> data kind -> size
std::map<lldb::tid_t, std::unordered_map<std::string, size_t>>
llvm::DenseMap<lldb::tid_t, std::unordered_map<std::string, size_t>>
m_live_thread_data;
/// data kind -> size
std::unordered_map<std::string, size_t> m_live_process_data;
/// \}
/// 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;
};
} // namespace lldb_private

View file

@ -39,15 +39,12 @@ llvm::Error TraceSessionSaver::WriteSessionToFile(
}
llvm::Expected<JSONTraceSessionBase> TraceSessionSaver::BuildProcessesSection(
Process &live_process,
std::function<
llvm::Expected<llvm::Optional<std::vector<uint8_t>>>(lldb::tid_t tid)>
raw_trace_fetcher,
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_trace_fetcher, directory);
BuildThreadsSection(live_process, raw_thread_trace_data_kind, directory);
if (!json_threads)
return json_threads.takeError();
@ -64,39 +61,41 @@ llvm::Expected<JSONTraceSessionBase> TraceSessionSaver::BuildProcessesSection(
}
llvm::Expected<std::vector<JSONThread>> TraceSessionSaver::BuildThreadsSection(
Process &live_process,
std::function<
llvm::Expected<llvm::Optional<std::vector<uint8_t>>>(lldb::tid_t tid)>
raw_trace_fetcher,
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(thread_sp->GetID()) +
".trace");
json_threads.push_back(JSONThread{static_cast<int64_t>(thread_sp->GetID()),
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::Expected<llvm::Optional<std::vector<uint8_t>>> raw_trace =
raw_trace_fetcher(thread_sp->GetID());
if (!raw_trace)
return raw_trace.takeError();
if (!raw_trace.get())
continue;
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 *>(&raw_trace.get()->at(0)),
raw_trace.get()->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()));
}
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;
}

View file

@ -39,11 +39,8 @@ public:
/// \param[in] live_process
/// The process being traced.
///
/// \param[in] raw_trace_fetcher
/// Callback function that receives a thread ID and returns its raw trace.
/// This callback should return \a None if the thread is not being traced.
/// Otherwise, it should return the raw trace in bytes or an
/// \a llvm::Error in case of failures.
/// \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
@ -51,12 +48,10 @@ public:
///
/// \return
/// The processes section or \a llvm::Error in case of failures.
static llvm::Expected<JSONTraceSessionBase> BuildProcessesSection(
Process &live_process,
std::function<
llvm::Expected<llvm::Optional<std::vector<uint8_t>>>(lldb::tid_t tid)>
raw_trace_fetcher,
FileSpec directory);
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
@ -65,11 +60,8 @@ public:
/// \param[in] live_process
/// The process being traced.
///
/// \param[in] raw_trace_fetcher
/// Callback function that receives a thread ID and returns its raw trace.
/// This callback should return \a None if the thread is not being traced.
/// Otherwise, it should return the raw trace in bytes or an
/// \a llvm::Error in case of failures.
/// \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
@ -77,12 +69,10 @@ public:
///
/// \return
/// The threads section or \a llvm::Error in case of failures.
static llvm::Expected<std::vector<JSONThread>> BuildThreadsSection(
Process &live_process,
std::function<
llvm::Expected<llvm::Optional<std::vector<uint8_t>>>(lldb::tid_t tid)>
raw_trace_fetcher,
FileSpec directory);
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

View file

@ -255,7 +255,7 @@ using PtInsnDecoderUP =
static Expected<PtInsnDecoderUP>
CreateInstructionDecoder(DecodedThread &decoded_thread,
TraceIntelPT &trace_intel_pt,
MutableArrayRef<uint8_t> buffer) {
ArrayRef<uint8_t> buffer) {
Expected<pt_cpu> cpu_info = trace_intel_pt.GetCPUInfo();
if (!cpu_info)
return cpu_info.takeError();
@ -268,8 +268,10 @@ CreateInstructionDecoder(DecodedThread &decoded_thread,
if (IsLibiptError(status = pt_cpu_errata(&config.errata, &config.cpu)))
return make_error<IntelPTError>(status);
config.begin = buffer.data();
config.end = buffer.data() + buffer.size();
// The libipt library does not modify the trace buffer, hence the
// following casts are safe.
config.begin = const_cast<uint8_t *>(buffer.data());
config.end = const_cast<uint8_t *>(buffer.data() + buffer.size());
pt_insn_decoder *decoder_ptr = pt_insn_alloc_decoder(&config);
if (!decoder_ptr)
@ -285,9 +287,11 @@ CreateInstructionDecoder(DecodedThread &decoded_thread,
return decoder_up;
}
void lldb_private::trace_intel_pt::DecodeTrace(
DecodedThread &decoded_thread, TraceIntelPT &trace_intel_pt,
MutableArrayRef<uint8_t> buffer) {
void lldb_private::trace_intel_pt::DecodeTrace(DecodedThread &decoded_thread,
TraceIntelPT &trace_intel_pt,
ArrayRef<uint8_t> buffer) {
decoded_thread.SetRawTraceSize(buffer.size());
Expected<PtInsnDecoderUP> decoder_up =
CreateInstructionDecoder(decoded_thread, trace_intel_pt, buffer);
if (!decoder_up)

View file

@ -21,7 +21,7 @@ namespace trace_intel_pt {
/// instructions and errors in \p decoded_thread. It uses the low level libipt
/// library underneath.
void DecodeTrace(DecodedThread &decoded_thread, TraceIntelPT &trace_intel_pt,
llvm::MutableArrayRef<uint8_t> buffer);
llvm::ArrayRef<uint8_t> buffer);
} // namespace trace_intel_pt
} // namespace lldb_private

View file

@ -20,7 +20,8 @@ using namespace lldb_private;
using namespace lldb_private::trace_intel_pt;
using namespace llvm;
// ThreadDecoder ====================
ThreadDecoder::ThreadDecoder(const ThreadSP &thread_sp, TraceIntelPT &trace)
: m_thread_sp(thread_sp), m_trace(trace) {}
DecodedThreadSP ThreadDecoder::Decode() {
if (!m_decoded_thread.hasValue())
@ -28,52 +29,16 @@ DecodedThreadSP ThreadDecoder::Decode() {
return *m_decoded_thread;
}
// LiveThreadDecoder ====================
LiveThreadDecoder::LiveThreadDecoder(Thread &thread, TraceIntelPT &trace)
: m_thread_sp(thread.shared_from_this()), m_trace(trace) {}
DecodedThreadSP LiveThreadDecoder::DoDecode() {
DecodedThreadSP ThreadDecoder::DoDecode() {
DecodedThreadSP decoded_thread_sp =
std::make_shared<DecodedThread>(m_thread_sp);
Expected<std::vector<uint8_t>> buffer =
m_trace.GetLiveThreadBuffer(m_thread_sp->GetID());
if (!buffer) {
decoded_thread_sp->AppendError(buffer.takeError());
return decoded_thread_sp;
}
decoded_thread_sp->SetRawTraceSize(buffer->size());
DecodeTrace(*decoded_thread_sp, m_trace, MutableArrayRef<uint8_t>(*buffer));
return decoded_thread_sp;
}
// PostMortemThreadDecoder =======================
PostMortemThreadDecoder::PostMortemThreadDecoder(
const lldb::ThreadPostMortemTraceSP &trace_thread, TraceIntelPT &trace)
: m_trace_thread(trace_thread), m_trace(trace) {}
DecodedThreadSP PostMortemThreadDecoder::DoDecode() {
DecodedThreadSP decoded_thread_sp =
std::make_shared<DecodedThread>(m_trace_thread);
ErrorOr<std::unique_ptr<MemoryBuffer>> trace_or_error =
MemoryBuffer::getFile(m_trace_thread->GetTraceFile().GetPath());
if (std::error_code err = trace_or_error.getError()) {
decoded_thread_sp->AppendError(errorCodeToError(err));
return decoded_thread_sp;
}
MemoryBuffer &trace = **trace_or_error;
MutableArrayRef<uint8_t> trace_data(
// The libipt library does not modify the trace buffer, hence the
// following cast is safe.
reinterpret_cast<uint8_t *>(const_cast<char *>(trace.getBufferStart())),
trace.getBufferSize());
decoded_thread_sp->SetRawTraceSize(trace_data.size());
DecodeTrace(*decoded_thread_sp, m_trace, trace_data);
Error err = m_trace.OnThreadBufferRead(
m_thread_sp->GetID(), [&](llvm::ArrayRef<uint8_t> data) {
DecodeTrace(*decoded_thread_sp, m_trace, data);
return Error::success();
});
if (err)
decoded_thread_sp->AppendError(std::move(err));
return decoded_thread_sp;
}

View file

@ -19,12 +19,15 @@
namespace lldb_private {
namespace trace_intel_pt {
/// Base class that handles the decoding of a thread and caches the result.
/// Class that handles the decoding of a thread and caches the result.
class ThreadDecoder {
public:
virtual ~ThreadDecoder() = default;
ThreadDecoder() = default;
/// \param[in] thread_sp
/// The thread whose trace buffer will be decoded.
///
/// \param[in] trace
/// The main Trace object who owns this decoder and its data.
ThreadDecoder(const lldb::ThreadSP &thread_sp, TraceIntelPT &trace);
/// Decode the thread and store the result internally, to avoid
/// recomputations.
@ -36,49 +39,12 @@ public:
ThreadDecoder(const ThreadDecoder &other) = delete;
ThreadDecoder &operator=(const ThreadDecoder &other) = delete;
protected:
/// Decode the thread.
///
/// \return
/// A \a DecodedThread instance.
virtual DecodedThreadSP DoDecode() = 0;
llvm::Optional<DecodedThreadSP> m_decoded_thread;
};
/// Decoder implementation for \a lldb_private::ThreadPostMortemTrace, which are
/// non-live processes that come trace session files.
class PostMortemThreadDecoder : public ThreadDecoder {
public:
/// \param[in] trace_thread
/// The thread whose trace file will be decoded.
///
/// \param[in] trace
/// The main Trace object who owns this decoder and its data.
PostMortemThreadDecoder(const lldb::ThreadPostMortemTraceSP &trace_thread,
TraceIntelPT &trace);
private:
DecodedThreadSP DoDecode() override;
lldb::ThreadPostMortemTraceSP m_trace_thread;
TraceIntelPT &m_trace;
};
class LiveThreadDecoder : public ThreadDecoder {
public:
/// \param[in] thread
/// The thread whose traces will be decoded.
///
/// \param[in] trace
/// The main Trace object who owns this decoder and its data.
LiveThreadDecoder(Thread &thread, TraceIntelPT &trace);
private:
DecodedThreadSP DoDecode() override;
DecodedThreadSP DoDecode();
lldb::ThreadSP m_thread_sp;
TraceIntelPT &m_trace;
llvm::Optional<DecodedThreadSP> m_decoded_thread;
};
} // namespace trace_intel_pt

View file

@ -78,10 +78,12 @@ TraceIntelPT::TraceIntelPT(
const pt_cpu &cpu_info,
const std::vector<ThreadPostMortemTraceSP> &traced_threads)
: m_cpu_info(cpu_info) {
for (const ThreadPostMortemTraceSP &thread : traced_threads)
m_thread_decoders.emplace(
thread->GetID(),
std::make_unique<PostMortemThreadDecoder>(thread, *this));
for (const ThreadPostMortemTraceSP &thread : traced_threads) {
m_thread_decoders.emplace(thread->GetID(),
std::make_unique<ThreadDecoder>(thread, *this));
SetPostMortemThreadDataFile(thread->GetID(), "threadTraceBuffer",
thread->GetTraceFile());
}
}
DecodedThreadSP TraceIntelPT::Decode(Thread &thread) {
@ -213,10 +215,10 @@ void TraceIntelPT::DoRefreshLiveProcessState(
}
for (const TraceThreadState &thread_state : state->tracedThreads) {
Thread &thread =
*m_live_process->GetThreadList().FindThreadByID(thread_state.tid);
ThreadSP thread_sp =
m_live_process->GetThreadList().FindThreadByID(thread_state.tid);
m_thread_decoders.emplace(
thread_state.tid, std::make_unique<LiveThreadDecoder>(thread, *this));
thread_state.tid, std::make_unique<ThreadDecoder>(thread_sp, *this));
}
}
@ -352,7 +354,7 @@ Error TraceIntelPT::Start(llvm::ArrayRef<lldb::tid_t> tids,
return Start(tids, thread_buffer_size, enable_tsc, psb_period);
}
Expected<std::vector<uint8_t>>
TraceIntelPT::GetLiveThreadBuffer(lldb::tid_t tid) {
return Trace::GetLiveThreadBinaryData(tid, "threadTraceBuffer");
Error TraceIntelPT::OnThreadBufferRead(lldb::tid_t tid,
OnBinaryDataReadCallback callback) {
return OnThreadBinaryDataRead(tid, "threadTraceBuffer", callback);
}

View file

@ -137,8 +137,9 @@ public:
StructuredData::ObjectSP configuration =
StructuredData::ObjectSP()) override;
/// Get the thread buffer content for a live thread
llvm::Expected<std::vector<uint8_t>> GetLiveThreadBuffer(lldb::tid_t tid);
/// See \a Trace::OnThreadBinaryDataRead().
llvm::Error OnThreadBufferRead(lldb::tid_t tid,
OnBinaryDataReadCallback callback);
llvm::Expected<pt_cpu> GetCPUInfo();

View file

@ -48,15 +48,8 @@ llvm::Error TraceIntelPTSessionSaver::SaveToDisk(TraceIntelPT &trace_ipt,
return json_intel_pt_trace.takeError();
llvm::Expected<JSONTraceSessionBase> json_session_description =
TraceSessionSaver::BuildProcessesSection(
*live_process,
[&](lldb::tid_t tid)
-> llvm::Expected<llvm::Optional<std::vector<uint8_t>>> {
if (!trace_ipt.IsTraced(tid))
return None;
return trace_ipt.GetLiveThreadBuffer(tid);
},
directory);
TraceSessionSaver::BuildProcessesSection(*live_process,
"threadTraceBuffer", directory);
if (!json_session_description)
return json_session_description.takeError();

View file

@ -215,3 +215,64 @@ uint32_t Trace::GetStopID() {
RefreshLiveProcessState();
return m_stop_id;
}
llvm::Expected<FileSpec>
Trace::GetPostMortemThreadDataFile(lldb::tid_t tid, llvm::StringRef kind) {
auto NotFoundError = [&]() {
return createStringError(
inconvertibleErrorCode(),
formatv("The thread with tid={0} doesn't have the tracing data {1}",
tid, kind));
};
auto it = m_postmortem_thread_data.find(tid);
if (it == m_postmortem_thread_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;
}
llvm::Error
Trace::OnLiveThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind,
OnBinaryDataReadCallback callback) {
Expected<std::vector<uint8_t>> data = GetLiveThreadBinaryData(tid, kind);
if (!data)
return data.takeError();
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();
ErrorOr<std::unique_ptr<MemoryBuffer>> trace_or_error =
MemoryBuffer::getFile(file->GetPath());
if (std::error_code err = trace_or_error.getError())
return errorCodeToError(err);
MemoryBuffer &data = **trace_or_error;
ArrayRef<uint8_t> array_ref(
reinterpret_cast<const uint8_t *>(data.getBufferStart()),
data.getBufferSize());
return callback(array_ref);
}
llvm::Error Trace::OnThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind,
OnBinaryDataReadCallback callback) {
if (m_live_process)
return OnLiveThreadBinaryDataRead(tid, kind, callback);
else
return OnPostMortemThreadBinaryDataRead(tid, kind, callback);
}