blob: 21642c75327a9a9c6378963046e932052d23986d [file] [log] [blame]
//===-- 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;
}
}