| //===-- Decoder.cpp ---------------------------------------------*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| // Project includes |
| #include "Decoder.h" |
| |
| // C/C++ Includes |
| #include <cinttypes> |
| #include <cstring> |
| |
| // Other libraries and framework includes |
| #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; |
| } |
| } |
| } |
| |
| // 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) { |
| sberror.SetErrorStringWithFormat( |
| "processor trace decoding library: \"%s\"", |
| pt_errstr(pt_errcode(errcode))); |
| instruction_list.emplace_back(sberror.GetCString()); |
| 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; |
| } |
| sberror.SetErrorStringWithFormat( |
| "processor trace decoding library: \"%s\" [decoder_offset] => " |
| "[0x%" PRIu64 "]", |
| pt_errstr(pt_errcode(errcode)), new_decoder_offset); |
| instruction_list.emplace_back(sberror.GetCString()); |
| decoder_offset = new_decoder_offset; |
| } |
| } |
| |
| while (1) { |
| 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; |
| } |
| } |