344 lines
12 KiB
C++
344 lines
12 KiB
C++
//===-- TraceIntelPT.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 "TraceIntelPT.h"
|
|
|
|
#include "../common/ThreadPostMortemTrace.h"
|
|
#include "CommandObjectTraceStartIntelPT.h"
|
|
#include "DecodedThread.h"
|
|
#include "TraceIntelPTConstants.h"
|
|
#include "TraceIntelPTSessionFileParser.h"
|
|
#include "lldb/Core/PluginManager.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;
|
|
|
|
LLDB_PLUGIN_DEFINE(TraceIntelPT)
|
|
|
|
lldb::CommandObjectSP
|
|
TraceIntelPT::GetProcessTraceStartCommand(CommandInterpreter &interpreter) {
|
|
return CommandObjectSP(
|
|
new CommandObjectProcessTraceStartIntelPT(*this, interpreter));
|
|
}
|
|
|
|
lldb::CommandObjectSP
|
|
TraceIntelPT::GetThreadTraceStartCommand(CommandInterpreter &interpreter) {
|
|
return CommandObjectSP(
|
|
new CommandObjectThreadTraceStartIntelPT(*this, interpreter));
|
|
}
|
|
|
|
void TraceIntelPT::Initialize() {
|
|
PluginManager::RegisterPlugin(GetPluginNameStatic(), "Intel Processor Trace",
|
|
CreateInstanceForSessionFile,
|
|
CreateInstanceForLiveProcess,
|
|
TraceIntelPTSessionFileParser::GetSchema());
|
|
}
|
|
|
|
void TraceIntelPT::Terminate() {
|
|
PluginManager::UnregisterPlugin(CreateInstanceForSessionFile);
|
|
}
|
|
|
|
ConstString TraceIntelPT::GetPluginNameStatic() {
|
|
static ConstString g_name("intel-pt");
|
|
return g_name;
|
|
}
|
|
|
|
StringRef TraceIntelPT::GetSchema() {
|
|
return TraceIntelPTSessionFileParser::GetSchema();
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// PluginInterface protocol
|
|
//------------------------------------------------------------------
|
|
|
|
ConstString TraceIntelPT::GetPluginName() { return GetPluginNameStatic(); }
|
|
|
|
uint32_t TraceIntelPT::GetPluginVersion() { return 1; }
|
|
|
|
void TraceIntelPT::Dump(Stream *s) const {}
|
|
|
|
Expected<TraceSP> TraceIntelPT::CreateInstanceForSessionFile(
|
|
const json::Value &trace_session_file, StringRef session_file_dir,
|
|
Debugger &debugger) {
|
|
return TraceIntelPTSessionFileParser(debugger, trace_session_file,
|
|
session_file_dir)
|
|
.Parse();
|
|
}
|
|
|
|
Expected<TraceSP> TraceIntelPT::CreateInstanceForLiveProcess(Process &process) {
|
|
TraceSP instance(new TraceIntelPT(process));
|
|
process.GetTarget().SetTrace(instance);
|
|
return instance;
|
|
}
|
|
|
|
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.get(), std::make_unique<PostMortemThreadDecoder>(thread, *this));
|
|
}
|
|
|
|
DecodedThreadSP TraceIntelPT::Decode(Thread &thread) {
|
|
RefreshLiveProcessState();
|
|
if (m_live_refresh_error.hasValue())
|
|
return std::make_shared<DecodedThread>(
|
|
thread.shared_from_this(),
|
|
createStringError(inconvertibleErrorCode(), *m_live_refresh_error));
|
|
|
|
auto it = m_thread_decoders.find(&thread);
|
|
if (it == m_thread_decoders.end())
|
|
return std::make_shared<DecodedThread>(
|
|
thread.shared_from_this(),
|
|
createStringError(inconvertibleErrorCode(), "thread not traced"));
|
|
return it->second->Decode();
|
|
}
|
|
|
|
lldb::TraceCursorUP TraceIntelPT::GetCursor(Thread &thread) {
|
|
return Decode(thread)->GetCursor();
|
|
}
|
|
|
|
void TraceIntelPT::DumpTraceInfo(Thread &thread, Stream &s, bool verbose) {
|
|
Optional<size_t> raw_size = GetRawTraceSize(thread);
|
|
s.Printf("\nthread #%u: tid = %" PRIu64, thread.GetIndexID(), thread.GetID());
|
|
if (!raw_size) {
|
|
s.Printf(", not traced\n");
|
|
return;
|
|
}
|
|
s.Printf("\n Raw trace size: %zu bytes\n", *raw_size);
|
|
return;
|
|
}
|
|
|
|
Optional<size_t> TraceIntelPT::GetRawTraceSize(Thread &thread) {
|
|
if (IsTraced(thread))
|
|
return Decode(thread)->GetRawTraceSize();
|
|
else
|
|
return None;
|
|
}
|
|
|
|
Expected<pt_cpu> TraceIntelPT::GetCPUInfoForLiveProcess() {
|
|
Expected<std::vector<uint8_t>> cpu_info = GetLiveProcessBinaryData("cpuInfo");
|
|
if (!cpu_info)
|
|
return cpu_info.takeError();
|
|
|
|
int64_t cpu_family = -1;
|
|
int64_t model = -1;
|
|
int64_t stepping = -1;
|
|
std::string vendor_id;
|
|
|
|
StringRef rest(reinterpret_cast<const char *>(cpu_info->data()),
|
|
cpu_info->size());
|
|
while (!rest.empty()) {
|
|
StringRef line;
|
|
std::tie(line, rest) = rest.split('\n');
|
|
|
|
SmallVector<StringRef, 2> columns;
|
|
line.split(columns, StringRef(":"), -1, false);
|
|
|
|
if (columns.size() < 2)
|
|
continue; // continue searching
|
|
|
|
columns[1] = columns[1].trim(" ");
|
|
if (columns[0].contains("cpu family") &&
|
|
columns[1].getAsInteger(10, cpu_family))
|
|
continue;
|
|
|
|
else if (columns[0].contains("model") && columns[1].getAsInteger(10, model))
|
|
continue;
|
|
|
|
else if (columns[0].contains("stepping") &&
|
|
columns[1].getAsInteger(10, stepping))
|
|
continue;
|
|
|
|
else if (columns[0].contains("vendor_id")) {
|
|
vendor_id = columns[1].str();
|
|
if (!vendor_id.empty())
|
|
continue;
|
|
}
|
|
|
|
if ((cpu_family != -1) && (model != -1) && (stepping != -1) &&
|
|
(!vendor_id.empty())) {
|
|
return pt_cpu{vendor_id == "GenuineIntel" ? pcv_intel : pcv_unknown,
|
|
static_cast<uint16_t>(cpu_family),
|
|
static_cast<uint8_t>(model),
|
|
static_cast<uint8_t>(stepping)};
|
|
}
|
|
}
|
|
return createStringError(inconvertibleErrorCode(),
|
|
"Failed parsing the target's /proc/cpuinfo file");
|
|
}
|
|
|
|
Expected<pt_cpu> TraceIntelPT::GetCPUInfo() {
|
|
if (!m_cpu_info) {
|
|
if (llvm::Expected<pt_cpu> cpu_info = GetCPUInfoForLiveProcess())
|
|
m_cpu_info = *cpu_info;
|
|
else
|
|
return cpu_info.takeError();
|
|
}
|
|
return *m_cpu_info;
|
|
}
|
|
|
|
void TraceIntelPT::DoRefreshLiveProcessState(
|
|
Expected<TraceGetStateResponse> state) {
|
|
m_thread_decoders.clear();
|
|
|
|
if (!state) {
|
|
m_live_refresh_error = toString(state.takeError());
|
|
return;
|
|
}
|
|
|
|
for (const TraceThreadState &thread_state : state->tracedThreads) {
|
|
Thread &thread =
|
|
*m_live_process->GetThreadList().FindThreadByID(thread_state.tid);
|
|
m_thread_decoders.emplace(
|
|
&thread, std::make_unique<LiveThreadDecoder>(thread, *this));
|
|
}
|
|
}
|
|
|
|
bool TraceIntelPT::IsTraced(const Thread &thread) {
|
|
RefreshLiveProcessState();
|
|
return m_thread_decoders.count(&thread);
|
|
}
|
|
|
|
// The information here should match the description of the intel-pt section
|
|
// of the jLLDBTraceStart packet in the lldb/docs/lldb-gdb-remote.txt
|
|
// documentation file. Similarly, it should match the CLI help messages of the
|
|
// TraceIntelPTOptions.td file.
|
|
const char *TraceIntelPT::GetStartConfigurationHelp() {
|
|
return R"(Parameters:
|
|
|
|
Note: If a parameter is not specified, a default value will be used.
|
|
|
|
- int threadBufferSize (defaults to 4096 bytes):
|
|
[process and thread tracing]
|
|
Trace size in bytes per thread. It must be a power of 2 greater
|
|
than or equal to 4096 (2^12). The trace is circular keeping the
|
|
the most recent data.
|
|
|
|
- boolean enableTsc (default to false):
|
|
[process and thread tracing]
|
|
Whether to use enable TSC timestamps or not. This is supported on
|
|
all devices that support intel-pt.
|
|
|
|
- psbPeriod (defaults to null):
|
|
[process and thread tracing]
|
|
This value defines the period in which PSB packets will be generated.
|
|
A PSB packet is a synchronization packet that contains a TSC
|
|
timestamp and the current absolute instruction pointer.
|
|
|
|
This parameter can only be used if
|
|
|
|
/sys/bus/event_source/devices/intel_pt/caps/psb_cyc
|
|
|
|
is 1. Otherwise, the PSB period will be defined by the processor.
|
|
|
|
If supported, valid values for this period can be found in
|
|
|
|
/sys/bus/event_source/devices/intel_pt/caps/psb_periods
|
|
|
|
which contains a hexadecimal number, whose bits represent
|
|
valid values e.g. if bit 2 is set, then value 2 is valid.
|
|
|
|
The psb_period value is converted to the approximate number of
|
|
raw trace bytes between PSB packets as:
|
|
|
|
2 ^ (value + 11)
|
|
|
|
e.g. value 3 means 16KiB between PSB packets. Defaults to 0 if
|
|
supported.
|
|
|
|
- int processBufferSizeLimit (defaults to 500 MB):
|
|
[process tracing only]
|
|
Maximum total trace size per process in bytes. This limit applies
|
|
to the sum of the sizes of all thread traces of this process,
|
|
excluding the ones created explicitly with "thread tracing".
|
|
Whenever a thread is attempted to be traced due to this command
|
|
and the limit would be reached, the process is stopped with a
|
|
"processor trace" reason, so that the user can retrace the process
|
|
if needed.)";
|
|
}
|
|
|
|
Error TraceIntelPT::Start(size_t thread_buffer_size,
|
|
size_t total_buffer_size_limit, bool enable_tsc,
|
|
Optional<size_t> psb_period) {
|
|
TraceIntelPTStartRequest request;
|
|
request.threadBufferSize = thread_buffer_size;
|
|
request.processBufferSizeLimit = total_buffer_size_limit;
|
|
request.enableTsc = enable_tsc;
|
|
request.psbPeriod = psb_period.map([](size_t val) { return (int64_t)val; });
|
|
request.type = GetPluginName().AsCString();
|
|
return Trace::Start(toJSON(request));
|
|
}
|
|
|
|
Error TraceIntelPT::Start(StructuredData::ObjectSP configuration) {
|
|
size_t thread_buffer_size = kDefaultThreadBufferSize;
|
|
size_t process_buffer_size_limit = kDefaultProcessBufferSizeLimit;
|
|
bool enable_tsc = kDefaultEnableTscValue;
|
|
Optional<size_t> psb_period = kDefaultPsbPeriod;
|
|
|
|
if (configuration) {
|
|
if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) {
|
|
dict->GetValueForKeyAsInteger("threadBufferSize", thread_buffer_size);
|
|
dict->GetValueForKeyAsInteger("processBufferSizeLimit",
|
|
process_buffer_size_limit);
|
|
dict->GetValueForKeyAsBoolean("enableTsc", enable_tsc);
|
|
dict->GetValueForKeyAsInteger("psbPeriod", psb_period);
|
|
} else {
|
|
return createStringError(inconvertibleErrorCode(),
|
|
"configuration object is not a dictionary");
|
|
}
|
|
}
|
|
|
|
return Start(thread_buffer_size, process_buffer_size_limit, enable_tsc,
|
|
psb_period);
|
|
}
|
|
|
|
llvm::Error TraceIntelPT::Start(llvm::ArrayRef<lldb::tid_t> tids,
|
|
size_t thread_buffer_size, bool enable_tsc,
|
|
Optional<size_t> psb_period) {
|
|
TraceIntelPTStartRequest request;
|
|
request.threadBufferSize = thread_buffer_size;
|
|
request.enableTsc = enable_tsc;
|
|
request.psbPeriod = psb_period.map([](size_t val) { return (int64_t)val; });
|
|
request.type = GetPluginName().AsCString();
|
|
request.tids.emplace();
|
|
for (lldb::tid_t tid : tids)
|
|
request.tids->push_back(tid);
|
|
return Trace::Start(toJSON(request));
|
|
}
|
|
|
|
Error TraceIntelPT::Start(llvm::ArrayRef<lldb::tid_t> tids,
|
|
StructuredData::ObjectSP configuration) {
|
|
size_t thread_buffer_size = kDefaultThreadBufferSize;
|
|
bool enable_tsc = kDefaultEnableTscValue;
|
|
Optional<size_t> psb_period = kDefaultPsbPeriod;
|
|
|
|
if (configuration) {
|
|
if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) {
|
|
dict->GetValueForKeyAsInteger("threadBufferSize", thread_buffer_size);
|
|
dict->GetValueForKeyAsBoolean("enableTsc", enable_tsc);
|
|
dict->GetValueForKeyAsInteger("psbPeriod", psb_period);
|
|
} else {
|
|
return createStringError(inconvertibleErrorCode(),
|
|
"configuration object is not a dictionary");
|
|
}
|
|
}
|
|
|
|
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");
|
|
}
|