forked from OSchip/llvm-project
961 lines
36 KiB
C++
961 lines
36 KiB
C++
//===-- Decoder.cpp ---------------------------------------------*- 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "Decoder.h"
|
|
|
|
// C/C++ Includes
|
|
#include <cinttypes>
|
|
#include <cstring>
|
|
|
|
#include "lldb/API/SBModule.h"
|
|
#include "lldb/API/SBProcess.h"
|
|
#include "lldb/API/SBThread.h"
|
|
|
|
using namespace ptdecoder_private;
|
|
|
|
// This function removes entries of all the processes/threads which were once
|
|
// registered in the class but are not alive anymore because they died or
|
|
// finished executing.
|
|
void Decoder::RemoveDeadProcessesAndThreads(lldb::SBProcess &sbprocess) {
|
|
lldb::SBTarget sbtarget = sbprocess.GetTarget();
|
|
lldb::SBDebugger sbdebugger = sbtarget.GetDebugger();
|
|
uint32_t num_targets = sbdebugger.GetNumTargets();
|
|
|
|
auto itr_process = m_mapProcessUID_mapThreadID_TraceInfo.begin();
|
|
while (itr_process != m_mapProcessUID_mapThreadID_TraceInfo.end()) {
|
|
bool process_found = false;
|
|
lldb::SBTarget target;
|
|
lldb::SBProcess process;
|
|
for (uint32_t i = 0; i < num_targets; i++) {
|
|
target = sbdebugger.GetTargetAtIndex(i);
|
|
process = target.GetProcess();
|
|
if (process.GetUniqueID() == itr_process->first) {
|
|
process_found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Remove the process's entry if it was not found in SBDebugger
|
|
if (!process_found) {
|
|
itr_process = m_mapProcessUID_mapThreadID_TraceInfo.erase(itr_process);
|
|
continue;
|
|
}
|
|
|
|
// If the state of the process is exited or detached then remove process's
|
|
// entry. If not then remove entry for all those registered threads of this
|
|
// process that are not alive anymore.
|
|
lldb::StateType state = process.GetState();
|
|
if ((state == lldb::StateType::eStateDetached) ||
|
|
(state == lldb::StateType::eStateExited))
|
|
itr_process = m_mapProcessUID_mapThreadID_TraceInfo.erase(itr_process);
|
|
else {
|
|
auto itr_thread = itr_process->second.begin();
|
|
while (itr_thread != itr_process->second.end()) {
|
|
if (itr_thread->first == LLDB_INVALID_THREAD_ID) {
|
|
++itr_thread;
|
|
continue;
|
|
}
|
|
|
|
lldb::SBThread thread = process.GetThreadByID(itr_thread->first);
|
|
if (!thread.IsValid())
|
|
itr_thread = itr_process->second.erase(itr_thread);
|
|
else
|
|
++itr_thread;
|
|
}
|
|
++itr_process;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Decoder::StartProcessorTrace(lldb::SBProcess &sbprocess,
|
|
lldb::SBTraceOptions &sbtraceoptions,
|
|
lldb::SBError &sberror) {
|
|
sberror.Clear();
|
|
CheckDebuggerID(sbprocess, sberror);
|
|
if (!sberror.Success())
|
|
return;
|
|
|
|
std::lock_guard<std::mutex> guard(
|
|
m_mapProcessUID_mapThreadID_TraceInfo_mutex);
|
|
RemoveDeadProcessesAndThreads(sbprocess);
|
|
|
|
if (sbtraceoptions.getType() != lldb::TraceType::eTraceTypeProcessorTrace) {
|
|
sberror.SetErrorStringWithFormat("SBTraceOptions::TraceType not set to "
|
|
"eTraceTypeProcessorTrace; ProcessID = "
|
|
"%" PRIu64,
|
|
sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
lldb::SBStructuredData sbstructdata = sbtraceoptions.getTraceParams(sberror);
|
|
if (!sberror.Success())
|
|
return;
|
|
|
|
const char *trace_tech_key = "trace-tech";
|
|
std::string trace_tech_value("intel-pt");
|
|
lldb::SBStructuredData value = sbstructdata.GetValueForKey(trace_tech_key);
|
|
if (!value.IsValid()) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"key \"%s\" not set in custom trace parameters", trace_tech_key);
|
|
return;
|
|
}
|
|
|
|
char string_value[9];
|
|
size_t bytes_written = value.GetStringValue(
|
|
string_value, sizeof(string_value) / sizeof(*string_value));
|
|
if (!bytes_written ||
|
|
(bytes_written > (sizeof(string_value) / sizeof(*string_value)))) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"key \"%s\" not set in custom trace parameters", trace_tech_key);
|
|
return;
|
|
}
|
|
|
|
std::size_t pos =
|
|
trace_tech_value.find((const char *)string_value, 0, bytes_written);
|
|
if ((pos == std::string::npos)) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"key \"%s\" not set to \"%s\" in custom trace parameters",
|
|
trace_tech_key, trace_tech_value.c_str());
|
|
return;
|
|
}
|
|
|
|
// Start Tracing
|
|
lldb::SBError error;
|
|
uint32_t unique_id = sbprocess.GetUniqueID();
|
|
lldb::tid_t tid = sbtraceoptions.getThreadID();
|
|
lldb::SBTrace trace = sbprocess.StartTrace(sbtraceoptions, error);
|
|
if (!error.Success()) {
|
|
if (tid == LLDB_INVALID_THREAD_ID)
|
|
sberror.SetErrorStringWithFormat("%s; ProcessID = %" PRIu64,
|
|
error.GetCString(),
|
|
sbprocess.GetProcessID());
|
|
else
|
|
sberror.SetErrorStringWithFormat(
|
|
"%s; thread_id = %" PRIu64 ", ProcessID = %" PRIu64,
|
|
error.GetCString(), tid, sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
|
|
MapThreadID_TraceInfo &mapThreadID_TraceInfo =
|
|
m_mapProcessUID_mapThreadID_TraceInfo[unique_id];
|
|
ThreadTraceInfo &trace_info = mapThreadID_TraceInfo[tid];
|
|
trace_info.SetUniqueTraceInstance(trace);
|
|
trace_info.SetStopID(sbprocess.GetStopID());
|
|
}
|
|
|
|
void Decoder::StopProcessorTrace(lldb::SBProcess &sbprocess,
|
|
lldb::SBError &sberror, lldb::tid_t tid) {
|
|
sberror.Clear();
|
|
CheckDebuggerID(sbprocess, sberror);
|
|
if (!sberror.Success()) {
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> guard(
|
|
m_mapProcessUID_mapThreadID_TraceInfo_mutex);
|
|
RemoveDeadProcessesAndThreads(sbprocess);
|
|
|
|
uint32_t unique_id = sbprocess.GetUniqueID();
|
|
auto itr_process = m_mapProcessUID_mapThreadID_TraceInfo.find(unique_id);
|
|
if (itr_process == m_mapProcessUID_mapThreadID_TraceInfo.end()) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"tracing not active for this process; ProcessID = %" PRIu64,
|
|
sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
|
|
lldb::SBError error;
|
|
if (tid == LLDB_INVALID_THREAD_ID) {
|
|
// This implies to stop tracing on the whole process
|
|
lldb::user_id_t id_to_be_ignored = LLDB_INVALID_UID;
|
|
auto itr_thread = itr_process->second.begin();
|
|
while (itr_thread != itr_process->second.end()) {
|
|
// In the case when user started trace on the entire process and then
|
|
// registered newly spawned threads of this process in the class later,
|
|
// these newly spawned threads will have same trace id. If we stopped
|
|
// trace on the entire process then tracing stops automatically for these
|
|
// newly spawned registered threads. Stopping trace on them again will
|
|
// return error and therefore we need to skip stopping trace on them
|
|
// again.
|
|
lldb::SBTrace &trace = itr_thread->second.GetUniqueTraceInstance();
|
|
lldb::user_id_t lldb_pt_user_id = trace.GetTraceUID();
|
|
if (lldb_pt_user_id != id_to_be_ignored) {
|
|
trace.StopTrace(error, itr_thread->first);
|
|
if (!error.Success()) {
|
|
std::string error_string(error.GetCString());
|
|
if ((error_string.find("tracing not active for this process") ==
|
|
std::string::npos) &&
|
|
(error_string.find("tracing not active for this thread") ==
|
|
std::string::npos)) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"%s; thread id=%" PRIu64 ", ProcessID = %" PRIu64,
|
|
error_string.c_str(), itr_thread->first,
|
|
sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (itr_thread->first == LLDB_INVALID_THREAD_ID)
|
|
id_to_be_ignored = lldb_pt_user_id;
|
|
}
|
|
itr_thread = itr_process->second.erase(itr_thread);
|
|
}
|
|
m_mapProcessUID_mapThreadID_TraceInfo.erase(itr_process);
|
|
} else {
|
|
// This implies to stop tracing on a single thread.
|
|
// if 'tid' is registered in the class then get the trace id and stop trace
|
|
// on it. If it is not then check if tracing was ever started on the entire
|
|
// process (because there is a possibility that trace is still running for
|
|
// 'tid' but it was not registered in the class because user had started
|
|
// trace on the whole process and 'tid' spawned later). In that case, get
|
|
// the trace id of the process trace instance and stop trace on this thread.
|
|
// If tracing was never started on the entire process then return error
|
|
// because there is no way tracing is active on 'tid'.
|
|
MapThreadID_TraceInfo &mapThreadID_TraceInfo = itr_process->second;
|
|
lldb::SBTrace trace;
|
|
auto itr = mapThreadID_TraceInfo.find(tid);
|
|
if (itr != mapThreadID_TraceInfo.end()) {
|
|
trace = itr->second.GetUniqueTraceInstance();
|
|
} else {
|
|
auto itr = mapThreadID_TraceInfo.find(LLDB_INVALID_THREAD_ID);
|
|
if (itr != mapThreadID_TraceInfo.end()) {
|
|
trace = itr->second.GetUniqueTraceInstance();
|
|
} else {
|
|
sberror.SetErrorStringWithFormat(
|
|
"tracing not active for this thread; thread id=%" PRIu64
|
|
", ProcessID = %" PRIu64,
|
|
tid, sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Stop Tracing
|
|
trace.StopTrace(error, tid);
|
|
if (!error.Success()) {
|
|
std::string error_string(error.GetCString());
|
|
sberror.SetErrorStringWithFormat(
|
|
"%s; thread id=%" PRIu64 ", ProcessID = %" PRIu64,
|
|
error_string.c_str(), tid, sbprocess.GetProcessID());
|
|
if (error_string.find("tracing not active") == std::string::npos)
|
|
return;
|
|
}
|
|
// Delete the entry of 'tid' from this class (if any)
|
|
mapThreadID_TraceInfo.erase(tid);
|
|
}
|
|
}
|
|
|
|
void Decoder::ReadTraceDataAndImageInfo(lldb::SBProcess &sbprocess,
|
|
lldb::tid_t tid, lldb::SBError &sberror,
|
|
ThreadTraceInfo &threadTraceInfo) {
|
|
// Allocate trace data buffer and parse cpu info for 'tid' if it is registered
|
|
// for the first time in class
|
|
lldb::SBTrace &trace = threadTraceInfo.GetUniqueTraceInstance();
|
|
Buffer &pt_buffer = threadTraceInfo.GetPTBuffer();
|
|
lldb::SBError error;
|
|
if (pt_buffer.size() == 0) {
|
|
lldb::SBTraceOptions traceoptions;
|
|
traceoptions.setThreadID(tid);
|
|
trace.GetTraceConfig(traceoptions, error);
|
|
if (!error.Success()) {
|
|
sberror.SetErrorStringWithFormat("%s; ProcessID = %" PRIu64,
|
|
error.GetCString(),
|
|
sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
if (traceoptions.getType() != lldb::TraceType::eTraceTypeProcessorTrace) {
|
|
sberror.SetErrorStringWithFormat("invalid TraceType received from LLDB "
|
|
"for this thread; thread id=%" PRIu64
|
|
", ProcessID = %" PRIu64,
|
|
tid, sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
|
|
threadTraceInfo.AllocatePTBuffer(traceoptions.getTraceBufferSize());
|
|
lldb::SBStructuredData sbstructdata = traceoptions.getTraceParams(sberror);
|
|
if (!sberror.Success())
|
|
return;
|
|
CPUInfo &pt_cpu = threadTraceInfo.GetCPUInfo();
|
|
ParseCPUInfo(pt_cpu, sbstructdata, sberror);
|
|
if (!sberror.Success())
|
|
return;
|
|
}
|
|
|
|
// Call LLDB API to get raw trace data for this thread
|
|
size_t bytes_written = trace.GetTraceData(error, (void *)pt_buffer.data(),
|
|
pt_buffer.size(), 0, tid);
|
|
if (!error.Success()) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"%s; thread_id = %" PRIu64 ", ProcessID = %" PRIu64,
|
|
error.GetCString(), tid, sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
std::fill(pt_buffer.begin() + bytes_written, pt_buffer.end(), 0);
|
|
|
|
// Get information of all the modules of the inferior
|
|
lldb::SBTarget sbtarget = sbprocess.GetTarget();
|
|
ReadExecuteSectionInfos &readExecuteSectionInfos =
|
|
threadTraceInfo.GetReadExecuteSectionInfos();
|
|
GetTargetModulesInfo(sbtarget, readExecuteSectionInfos, sberror);
|
|
if (!sberror.Success())
|
|
return;
|
|
}
|
|
|
|
void Decoder::DecodeProcessorTrace(lldb::SBProcess &sbprocess, lldb::tid_t tid,
|
|
lldb::SBError &sberror,
|
|
ThreadTraceInfo &threadTraceInfo) {
|
|
// Initialize instruction decoder
|
|
struct pt_insn_decoder *decoder = nullptr;
|
|
struct pt_config config;
|
|
Buffer &pt_buffer = threadTraceInfo.GetPTBuffer();
|
|
CPUInfo &pt_cpu = threadTraceInfo.GetCPUInfo();
|
|
ReadExecuteSectionInfos &readExecuteSectionInfos =
|
|
threadTraceInfo.GetReadExecuteSectionInfos();
|
|
|
|
InitializePTInstDecoder(&decoder, &config, pt_cpu, pt_buffer,
|
|
readExecuteSectionInfos, sberror);
|
|
if (!sberror.Success())
|
|
return;
|
|
|
|
// Start raw trace decoding
|
|
Instructions &instruction_list = threadTraceInfo.GetInstructionLog();
|
|
instruction_list.clear();
|
|
DecodeTrace(decoder, instruction_list, sberror);
|
|
}
|
|
|
|
// Raw trace decoding requires information of Read & Execute sections of each
|
|
// module of the inferior. This function updates internal state of the class to
|
|
// store this information.
|
|
void Decoder::GetTargetModulesInfo(
|
|
lldb::SBTarget &sbtarget, ReadExecuteSectionInfos &readExecuteSectionInfos,
|
|
lldb::SBError &sberror) {
|
|
if (!sbtarget.IsValid()) {
|
|
sberror.SetErrorStringWithFormat("Can't get target's modules info from "
|
|
"LLDB; process has an invalid target");
|
|
return;
|
|
}
|
|
|
|
lldb::SBFileSpec target_file_spec = sbtarget.GetExecutable();
|
|
if (!target_file_spec.IsValid()) {
|
|
sberror.SetErrorStringWithFormat("Target has an invalid file spec");
|
|
return;
|
|
}
|
|
|
|
uint32_t num_modules = sbtarget.GetNumModules();
|
|
readExecuteSectionInfos.clear();
|
|
|
|
// Store information of all RX sections of each module of inferior
|
|
for (uint32_t i = 0; i < num_modules; i++) {
|
|
lldb::SBModule module = sbtarget.GetModuleAtIndex(i);
|
|
if (!module.IsValid()) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"Can't get module info [ %" PRIu32
|
|
" ] of target \"%s\" from LLDB, invalid module",
|
|
i, target_file_spec.GetFilename());
|
|
return;
|
|
}
|
|
|
|
lldb::SBFileSpec module_file_spec = module.GetPlatformFileSpec();
|
|
if (!module_file_spec.IsValid()) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"Can't get module info [ %" PRIu32
|
|
" ] of target \"%s\" from LLDB, invalid file spec",
|
|
i, target_file_spec.GetFilename());
|
|
return;
|
|
}
|
|
|
|
const char *image(module_file_spec.GetFilename());
|
|
lldb::SBError error;
|
|
char image_complete_path[1024];
|
|
uint32_t path_length = module_file_spec.GetPath(
|
|
image_complete_path, sizeof(image_complete_path));
|
|
size_t num_sections = module.GetNumSections();
|
|
|
|
// Store information of only RX sections
|
|
for (size_t idx = 0; idx < num_sections; idx++) {
|
|
lldb::SBSection section = module.GetSectionAtIndex(idx);
|
|
uint32_t section_permission = section.GetPermissions();
|
|
if ((section_permission & lldb::Permissions::ePermissionsReadable) &&
|
|
(section_permission & lldb::Permissions::ePermissionsExecutable)) {
|
|
lldb::SBData section_data = section.GetSectionData();
|
|
if (!section_data.IsValid()) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"Can't get module info [ %" PRIu32 " ] \"%s\" of target "
|
|
"\"%s\" from LLDB, invalid "
|
|
"data in \"%s\" section",
|
|
i, image, target_file_spec.GetFilename(), section.GetName());
|
|
return;
|
|
}
|
|
|
|
// In case section has no data, skip it.
|
|
if (section_data.GetByteSize() == 0)
|
|
continue;
|
|
|
|
if (!path_length) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"Can't get module info [ %" PRIu32 " ] \"%s\" of target "
|
|
"\"%s\" from LLDB, module "
|
|
"has an invalid path length",
|
|
i, image, target_file_spec.GetFilename());
|
|
return;
|
|
}
|
|
|
|
std::string image_path(image_complete_path, path_length);
|
|
readExecuteSectionInfos.emplace_back(
|
|
section.GetLoadAddress(sbtarget), section.GetFileOffset(),
|
|
section_data.GetByteSize(), image_path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Raw trace decoding requires information of the target cpu on which inferior
|
|
// is running. This function gets the Trace Configuration from LLDB, parses it
|
|
// for cpu model, family, stepping and vendor id info and updates the internal
|
|
// state of the class to store this information.
|
|
void Decoder::ParseCPUInfo(CPUInfo &pt_cpu, lldb::SBStructuredData &s,
|
|
lldb::SBError &sberror) {
|
|
lldb::SBStructuredData custom_trace_params = s.GetValueForKey("intel-pt");
|
|
if (!custom_trace_params.IsValid()) {
|
|
sberror.SetErrorStringWithFormat("lldb couldn't provide cpuinfo");
|
|
return;
|
|
}
|
|
|
|
uint64_t family = 0, model = 0, stepping = 0;
|
|
char vendor[32];
|
|
const char *key_family = "cpu_family";
|
|
const char *key_model = "cpu_model";
|
|
const char *key_stepping = "cpu_stepping";
|
|
const char *key_vendor = "cpu_vendor";
|
|
|
|
// parse family
|
|
lldb::SBStructuredData struct_family =
|
|
custom_trace_params.GetValueForKey(key_family);
|
|
if (!struct_family.IsValid()) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"%s info missing in custom trace parameters", key_family);
|
|
return;
|
|
}
|
|
family = struct_family.GetIntegerValue(0x10000);
|
|
if (family > UINT16_MAX) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"invalid CPU family value extracted from custom trace parameters");
|
|
return;
|
|
}
|
|
pt_cpu.family = (uint16_t)family;
|
|
|
|
// parse model
|
|
lldb::SBStructuredData struct_model =
|
|
custom_trace_params.GetValueForKey(key_model);
|
|
if (!struct_model.IsValid()) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"%s info missing in custom trace parameters; family=%" PRIu16,
|
|
key_model, pt_cpu.family);
|
|
return;
|
|
}
|
|
model = struct_model.GetIntegerValue(0x100);
|
|
if (model > UINT8_MAX) {
|
|
sberror.SetErrorStringWithFormat("invalid CPU model value extracted from "
|
|
"custom trace parameters; family=%" PRIu16,
|
|
pt_cpu.family);
|
|
return;
|
|
}
|
|
pt_cpu.model = (uint8_t)model;
|
|
|
|
// parse stepping
|
|
lldb::SBStructuredData struct_stepping =
|
|
custom_trace_params.GetValueForKey(key_stepping);
|
|
if (!struct_stepping.IsValid()) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"%s info missing in custom trace parameters; family=%" PRIu16
|
|
", model=%" PRIu8,
|
|
key_stepping, pt_cpu.family, pt_cpu.model);
|
|
return;
|
|
}
|
|
stepping = struct_stepping.GetIntegerValue(0x100);
|
|
if (stepping > UINT8_MAX) {
|
|
sberror.SetErrorStringWithFormat("invalid CPU stepping value extracted "
|
|
"from custom trace parameters; "
|
|
"family=%" PRIu16 ", model=%" PRIu8,
|
|
pt_cpu.family, pt_cpu.model);
|
|
return;
|
|
}
|
|
pt_cpu.stepping = (uint8_t)stepping;
|
|
|
|
// parse vendor info
|
|
pt_cpu.vendor = pcv_unknown;
|
|
lldb::SBStructuredData struct_vendor =
|
|
custom_trace_params.GetValueForKey(key_vendor);
|
|
if (!struct_vendor.IsValid()) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"%s info missing in custom trace parameters; family=%" PRIu16
|
|
", model=%" PRIu8 ", stepping=%" PRIu8,
|
|
key_vendor, pt_cpu.family, pt_cpu.model, pt_cpu.stepping);
|
|
return;
|
|
}
|
|
auto length = struct_vendor.GetStringValue(vendor, sizeof(vendor));
|
|
if (length && strstr(vendor, "GenuineIntel"))
|
|
pt_cpu.vendor = pcv_intel;
|
|
}
|
|
|
|
// Initialize trace decoder with pt_config structure and populate its image
|
|
// structure with inferior's memory image information. pt_config structure is
|
|
// initialized with trace buffer and cpu info of the inferior before storing it
|
|
// in trace decoder.
|
|
void Decoder::InitializePTInstDecoder(
|
|
struct pt_insn_decoder **decoder, struct pt_config *config,
|
|
const CPUInfo &pt_cpu, Buffer &pt_buffer,
|
|
const ReadExecuteSectionInfos &readExecuteSectionInfos,
|
|
lldb::SBError &sberror) const {
|
|
if (!decoder || !config) {
|
|
sberror.SetErrorStringWithFormat("internal error");
|
|
return;
|
|
}
|
|
|
|
// Load cpu info of inferior's target in pt_config struct
|
|
pt_config_init(config);
|
|
config->cpu = pt_cpu;
|
|
int errcode = pt_cpu_errata(&(config->errata), &(config->cpu));
|
|
if (errcode < 0) {
|
|
sberror.SetErrorStringWithFormat("processor trace decoding library: "
|
|
"pt_cpu_errata() failed with error: "
|
|
"\"%s\"",
|
|
pt_errstr(pt_errcode(errcode)));
|
|
return;
|
|
}
|
|
|
|
// Load trace buffer's starting and end address in pt_config struct
|
|
config->begin = pt_buffer.data();
|
|
config->end = pt_buffer.data() + pt_buffer.size();
|
|
|
|
// Fill trace decoder with pt_config struct
|
|
*decoder = pt_insn_alloc_decoder(config);
|
|
if (*decoder == nullptr) {
|
|
sberror.SetErrorStringWithFormat("processor trace decoding library: "
|
|
"pt_insn_alloc_decoder() returned null "
|
|
"pointer");
|
|
return;
|
|
}
|
|
|
|
// Fill trace decoder's image with inferior's memory image information
|
|
struct pt_image *image = pt_insn_get_image(*decoder);
|
|
if (!image) {
|
|
sberror.SetErrorStringWithFormat("processor trace decoding library: "
|
|
"pt_insn_get_image() returned null "
|
|
"pointer");
|
|
pt_insn_free_decoder(*decoder);
|
|
return;
|
|
}
|
|
|
|
for (auto &itr : readExecuteSectionInfos) {
|
|
errcode = pt_image_add_file(image, itr.image_path.c_str(), itr.file_offset,
|
|
itr.size, nullptr, itr.load_address);
|
|
if (errcode < 0) {
|
|
sberror.SetErrorStringWithFormat("processor trace decoding library: "
|
|
"pt_image_add_file() failed with error: "
|
|
"\"%s\"",
|
|
pt_errstr(pt_errcode(errcode)));
|
|
pt_insn_free_decoder(*decoder);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Decoder::AppendErrorWithOffsetToInstructionList(
|
|
int errcode, uint64_t decoder_offset, Instructions &instruction_list,
|
|
lldb::SBError &sberror) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"processor trace decoding library: \"%s\" [decoder_offset] => "
|
|
"[0x%" PRIu64 "]",
|
|
pt_errstr(pt_errcode(errcode)), decoder_offset);
|
|
instruction_list.emplace_back(sberror.GetCString());
|
|
}
|
|
|
|
void Decoder::AppendErrorWithoutOffsetToInstructionList(
|
|
int errcode, Instructions &instruction_list, lldb::SBError &sberror) {
|
|
sberror.SetErrorStringWithFormat("processor trace decoding library: \"%s\"",
|
|
pt_errstr(pt_errcode(errcode)));
|
|
instruction_list.emplace_back(sberror.GetCString());
|
|
}
|
|
|
|
int Decoder::AppendErrorToInstructionList(int errcode, pt_insn_decoder *decoder,
|
|
Instructions &instruction_list,
|
|
lldb::SBError &sberror) {
|
|
uint64_t decoder_offset = 0;
|
|
int errcode_off = pt_insn_get_offset(decoder, &decoder_offset);
|
|
if (errcode_off < 0) {
|
|
AppendErrorWithoutOffsetToInstructionList(errcode, instruction_list,
|
|
sberror);
|
|
return errcode_off;
|
|
}
|
|
AppendErrorWithOffsetToInstructionList(errcode, decoder_offset,
|
|
instruction_list, sberror);
|
|
return 0;
|
|
}
|
|
|
|
int Decoder::HandlePTInstructionEvents(pt_insn_decoder *decoder, int errcode,
|
|
Instructions &instruction_list,
|
|
lldb::SBError &sberror) {
|
|
while (errcode & pts_event_pending) {
|
|
pt_event event;
|
|
errcode = pt_insn_event(decoder, &event, sizeof(event));
|
|
if (errcode < 0)
|
|
return errcode;
|
|
|
|
// The list of events are in
|
|
// https://github.com/intel/libipt/blob/master/doc/man/pt_qry_event.3.md
|
|
if (event.type == ptev_overflow) {
|
|
int append_errcode = AppendErrorToInstructionList(
|
|
errcode, decoder, instruction_list, sberror);
|
|
if (append_errcode < 0)
|
|
return append_errcode;
|
|
}
|
|
// Other events don't signal stream errors
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Start actual decoding of raw trace
|
|
void Decoder::DecodeTrace(struct pt_insn_decoder *decoder,
|
|
Instructions &instruction_list,
|
|
lldb::SBError &sberror) {
|
|
uint64_t decoder_offset = 0;
|
|
|
|
while (1) {
|
|
struct pt_insn insn;
|
|
|
|
// Try to sync the decoder. If it fails then get the decoder_offset and try
|
|
// to sync again. If the new_decoder_offset is same as decoder_offset then
|
|
// we will not succeed in syncing for any number of pt_insn_sync_forward()
|
|
// operations. Return in that case. Else keep resyncing until either end of
|
|
// trace stream is reached or pt_insn_sync_forward() passes.
|
|
int errcode = pt_insn_sync_forward(decoder);
|
|
if (errcode < 0) {
|
|
if (errcode == -pte_eos)
|
|
return;
|
|
|
|
int errcode_off = pt_insn_get_offset(decoder, &decoder_offset);
|
|
if (errcode_off < 0) {
|
|
AppendErrorWithoutOffsetToInstructionList(errcode, instruction_list,
|
|
sberror);
|
|
return;
|
|
}
|
|
|
|
sberror.SetErrorStringWithFormat(
|
|
"processor trace decoding library: \"%s\" [decoder_offset] => "
|
|
"[0x%" PRIu64 "]",
|
|
pt_errstr(pt_errcode(errcode)), decoder_offset);
|
|
instruction_list.emplace_back(sberror.GetCString());
|
|
while (1) {
|
|
errcode = pt_insn_sync_forward(decoder);
|
|
if (errcode >= 0)
|
|
break;
|
|
|
|
if (errcode == -pte_eos)
|
|
return;
|
|
|
|
uint64_t new_decoder_offset = 0;
|
|
errcode_off = pt_insn_get_offset(decoder, &new_decoder_offset);
|
|
if (errcode_off < 0) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"processor trace decoding library: \"%s\"",
|
|
pt_errstr(pt_errcode(errcode)));
|
|
instruction_list.emplace_back(sberror.GetCString());
|
|
return;
|
|
} else if (new_decoder_offset <= decoder_offset) {
|
|
// We tried resyncing the decoder and decoder didn't make any
|
|
// progress because the offset didn't change. We will not make any
|
|
// progress further. Hence, returning in this situation.
|
|
return;
|
|
}
|
|
AppendErrorWithOffsetToInstructionList(errcode, new_decoder_offset,
|
|
instruction_list, sberror);
|
|
decoder_offset = new_decoder_offset;
|
|
}
|
|
}
|
|
|
|
while (1) {
|
|
errcode = HandlePTInstructionEvents(decoder, errcode, instruction_list,
|
|
sberror);
|
|
if (errcode < 0) {
|
|
int append_errcode = AppendErrorToInstructionList(
|
|
errcode, decoder, instruction_list, sberror);
|
|
if (append_errcode < 0)
|
|
return;
|
|
break;
|
|
}
|
|
errcode = pt_insn_next(decoder, &insn, sizeof(insn));
|
|
if (errcode < 0) {
|
|
if (insn.iclass == ptic_error)
|
|
break;
|
|
|
|
instruction_list.emplace_back(insn);
|
|
|
|
if (errcode == -pte_eos)
|
|
return;
|
|
|
|
Diagnose(decoder, errcode, sberror, &insn);
|
|
instruction_list.emplace_back(sberror.GetCString());
|
|
break;
|
|
}
|
|
instruction_list.emplace_back(insn);
|
|
if (errcode & pts_eos)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Function to diagnose and indicate errors during raw trace decoding
|
|
void Decoder::Diagnose(struct pt_insn_decoder *decoder, int decode_error,
|
|
lldb::SBError &sberror, const struct pt_insn *insn) {
|
|
int errcode;
|
|
uint64_t offset;
|
|
|
|
errcode = pt_insn_get_offset(decoder, &offset);
|
|
if (insn) {
|
|
if (errcode < 0)
|
|
sberror.SetErrorStringWithFormat(
|
|
"processor trace decoding library: \"%s\" [decoder_offset, "
|
|
"last_successful_decoded_ip] => [?, 0x%" PRIu64 "]",
|
|
pt_errstr(pt_errcode(decode_error)), insn->ip);
|
|
else
|
|
sberror.SetErrorStringWithFormat(
|
|
"processor trace decoding library: \"%s\" [decoder_offset, "
|
|
"last_successful_decoded_ip] => [0x%" PRIu64 ", 0x%" PRIu64 "]",
|
|
pt_errstr(pt_errcode(decode_error)), offset, insn->ip);
|
|
} else {
|
|
if (errcode < 0)
|
|
sberror.SetErrorStringWithFormat(
|
|
"processor trace decoding library: \"%s\"",
|
|
pt_errstr(pt_errcode(decode_error)));
|
|
else
|
|
sberror.SetErrorStringWithFormat(
|
|
"processor trace decoding library: \"%s\" [decoder_offset] => "
|
|
"[0x%" PRIu64 "]",
|
|
pt_errstr(pt_errcode(decode_error)), offset);
|
|
}
|
|
}
|
|
|
|
void Decoder::GetInstructionLogAtOffset(lldb::SBProcess &sbprocess,
|
|
lldb::tid_t tid, uint32_t offset,
|
|
uint32_t count,
|
|
InstructionList &result_list,
|
|
lldb::SBError &sberror) {
|
|
sberror.Clear();
|
|
CheckDebuggerID(sbprocess, sberror);
|
|
if (!sberror.Success()) {
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> guard(
|
|
m_mapProcessUID_mapThreadID_TraceInfo_mutex);
|
|
RemoveDeadProcessesAndThreads(sbprocess);
|
|
|
|
ThreadTraceInfo *threadTraceInfo = nullptr;
|
|
FetchAndDecode(sbprocess, tid, sberror, &threadTraceInfo);
|
|
if (!sberror.Success()) {
|
|
return;
|
|
}
|
|
if (threadTraceInfo == nullptr) {
|
|
sberror.SetErrorStringWithFormat("internal error");
|
|
return;
|
|
}
|
|
|
|
// Return instruction log by populating 'result_list'
|
|
Instructions &insn_list = threadTraceInfo->GetInstructionLog();
|
|
uint64_t sum = (uint64_t)offset + 1;
|
|
if (((insn_list.size() <= offset) && (count <= sum) &&
|
|
((sum - count) >= insn_list.size())) ||
|
|
(count < 1)) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"Instruction Log not available for offset=%" PRIu32
|
|
" and count=%" PRIu32 ", ProcessID = %" PRIu64,
|
|
offset, count, sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
|
|
Instructions::iterator itr_first =
|
|
(insn_list.size() <= offset) ? insn_list.begin()
|
|
: insn_list.begin() + insn_list.size() - sum;
|
|
Instructions::iterator itr_last =
|
|
(count <= sum) ? insn_list.begin() + insn_list.size() - (sum - count)
|
|
: insn_list.end();
|
|
Instructions::iterator itr = itr_first;
|
|
while (itr != itr_last) {
|
|
result_list.AppendInstruction(*itr);
|
|
++itr;
|
|
}
|
|
}
|
|
|
|
void Decoder::GetProcessorTraceInfo(lldb::SBProcess &sbprocess, lldb::tid_t tid,
|
|
TraceOptions &options,
|
|
lldb::SBError &sberror) {
|
|
sberror.Clear();
|
|
CheckDebuggerID(sbprocess, sberror);
|
|
if (!sberror.Success()) {
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> guard(
|
|
m_mapProcessUID_mapThreadID_TraceInfo_mutex);
|
|
RemoveDeadProcessesAndThreads(sbprocess);
|
|
|
|
ThreadTraceInfo *threadTraceInfo = nullptr;
|
|
FetchAndDecode(sbprocess, tid, sberror, &threadTraceInfo);
|
|
if (!sberror.Success()) {
|
|
return;
|
|
}
|
|
if (threadTraceInfo == nullptr) {
|
|
sberror.SetErrorStringWithFormat("internal error");
|
|
return;
|
|
}
|
|
|
|
// Get SBTraceOptions from LLDB for 'tid', populate 'traceoptions' with it
|
|
lldb::SBTrace &trace = threadTraceInfo->GetUniqueTraceInstance();
|
|
lldb::SBTraceOptions traceoptions;
|
|
lldb::SBError error;
|
|
traceoptions.setThreadID(tid);
|
|
trace.GetTraceConfig(traceoptions, error);
|
|
if (!error.Success()) {
|
|
std::string error_string(error.GetCString());
|
|
if (error_string.find("tracing not active") != std::string::npos) {
|
|
uint32_t unique_id = sbprocess.GetUniqueID();
|
|
auto itr_process = m_mapProcessUID_mapThreadID_TraceInfo.find(unique_id);
|
|
if (itr_process == m_mapProcessUID_mapThreadID_TraceInfo.end())
|
|
return;
|
|
itr_process->second.erase(tid);
|
|
}
|
|
sberror.SetErrorStringWithFormat("%s; ProcessID = %" PRIu64,
|
|
error_string.c_str(),
|
|
sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
if (traceoptions.getType() != lldb::TraceType::eTraceTypeProcessorTrace) {
|
|
sberror.SetErrorStringWithFormat("invalid TraceType received from LLDB "
|
|
"for this thread; thread id=%" PRIu64
|
|
", ProcessID = %" PRIu64,
|
|
tid, sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
options.setType(traceoptions.getType());
|
|
options.setTraceBufferSize(traceoptions.getTraceBufferSize());
|
|
options.setMetaDataBufferSize(traceoptions.getMetaDataBufferSize());
|
|
lldb::SBStructuredData sbstructdata = traceoptions.getTraceParams(sberror);
|
|
if (!sberror.Success())
|
|
return;
|
|
options.setTraceParams(sbstructdata);
|
|
options.setInstructionLogSize(threadTraceInfo->GetInstructionLog().size());
|
|
}
|
|
|
|
void Decoder::FetchAndDecode(lldb::SBProcess &sbprocess, lldb::tid_t tid,
|
|
lldb::SBError &sberror,
|
|
ThreadTraceInfo **threadTraceInfo) {
|
|
// Return with error if 'sbprocess' is not registered in the class
|
|
uint32_t unique_id = sbprocess.GetUniqueID();
|
|
auto itr_process = m_mapProcessUID_mapThreadID_TraceInfo.find(unique_id);
|
|
if (itr_process == m_mapProcessUID_mapThreadID_TraceInfo.end()) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"tracing not active for this process; ProcessID = %" PRIu64,
|
|
sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
|
|
if (tid == LLDB_INVALID_THREAD_ID) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"invalid thread id provided; thread_id = %" PRIu64
|
|
", ProcessID = %" PRIu64,
|
|
tid, sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
|
|
// Check whether 'tid' thread is registered in the class. If it is then in
|
|
// case StopID didn't change then return without doing anything (no need to
|
|
// read and decode trace data then). Otherwise, save new StopID and proceed
|
|
// with reading and decoding trace.
|
|
if (threadTraceInfo == nullptr) {
|
|
sberror.SetErrorStringWithFormat("internal error");
|
|
return;
|
|
}
|
|
|
|
MapThreadID_TraceInfo &mapThreadID_TraceInfo = itr_process->second;
|
|
auto itr_thread = mapThreadID_TraceInfo.find(tid);
|
|
if (itr_thread != mapThreadID_TraceInfo.end()) {
|
|
if (itr_thread->second.GetStopID() == sbprocess.GetStopID()) {
|
|
*threadTraceInfo = &(itr_thread->second);
|
|
return;
|
|
}
|
|
itr_thread->second.SetStopID(sbprocess.GetStopID());
|
|
} else {
|
|
// Implies 'tid' is not registered in the class. If tracing was never
|
|
// started on the entire process then return an error. Else try to register
|
|
// this thread and proceed with reading and decoding trace.
|
|
lldb::SBError error;
|
|
itr_thread = mapThreadID_TraceInfo.find(LLDB_INVALID_THREAD_ID);
|
|
if (itr_thread == mapThreadID_TraceInfo.end()) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"tracing not active for this thread; ProcessID = %" PRIu64,
|
|
sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
|
|
lldb::SBTrace &trace = itr_thread->second.GetUniqueTraceInstance();
|
|
ThreadTraceInfo &trace_info = mapThreadID_TraceInfo[tid];
|
|
trace_info.SetUniqueTraceInstance(trace);
|
|
trace_info.SetStopID(sbprocess.GetStopID());
|
|
itr_thread = mapThreadID_TraceInfo.find(tid);
|
|
}
|
|
|
|
// Get raw trace data and inferior image from LLDB for the registered thread
|
|
ReadTraceDataAndImageInfo(sbprocess, tid, sberror, itr_thread->second);
|
|
if (!sberror.Success()) {
|
|
std::string error_string(sberror.GetCString());
|
|
if (error_string.find("tracing not active") != std::string::npos)
|
|
mapThreadID_TraceInfo.erase(itr_thread);
|
|
return;
|
|
}
|
|
// Decode raw trace data
|
|
DecodeProcessorTrace(sbprocess, tid, sberror, itr_thread->second);
|
|
if (!sberror.Success()) {
|
|
return;
|
|
}
|
|
*threadTraceInfo = &(itr_thread->second);
|
|
}
|
|
|
|
// This function checks whether the provided SBProcess instance belongs to same
|
|
// SBDebugger with which this tool instance is associated.
|
|
void Decoder::CheckDebuggerID(lldb::SBProcess &sbprocess,
|
|
lldb::SBError &sberror) {
|
|
if (!sbprocess.IsValid()) {
|
|
sberror.SetErrorStringWithFormat("invalid process instance");
|
|
return;
|
|
}
|
|
|
|
lldb::SBTarget sbtarget = sbprocess.GetTarget();
|
|
if (!sbtarget.IsValid()) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"process contains an invalid target; ProcessID = %" PRIu64,
|
|
sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
|
|
lldb::SBDebugger sbdebugger = sbtarget.GetDebugger();
|
|
if (!sbdebugger.IsValid()) {
|
|
sberror.SetErrorStringWithFormat("process's target contains an invalid "
|
|
"debugger instance; ProcessID = %" PRIu64,
|
|
sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
|
|
if (sbdebugger.GetID() != m_debugger_user_id) {
|
|
sberror.SetErrorStringWithFormat(
|
|
"process belongs to a different SBDebugger instance than the one for "
|
|
"which the tool is instantiated; ProcessID = %" PRIu64,
|
|
sbprocess.GetProcessID());
|
|
return;
|
|
}
|
|
}
|