wallace created this revision. Herald added subscribers: lldb-commits, mgorny. Herald added a reviewer: JDevlieghere. Herald added a project: LLDB. wallace requested review of this revision.
This diff finally implements trace decoding! The current interface is $ trace load /path/to/trace/session/file.json $ thread trace dump instructions thread #1: tid = 3842849, total instructions = 22 [ 0] 0x40052d [ 1] 0x40052d ... [19] 0x400521 $ # simply enter, which is a repeat command [20] 0x40052d [21] 0x400529 ... This doesn't do any disassembly, which will be done in the next diff. Changes: - Renamed ThreadIntelPT to TreaceThread, which is a top-level class. I noticed that this should work for any trace plugin and there's nothing intel-pt specific to it. - With that ThreadThread change, I was able to move most of the json file parsing logic to the base class TraceSessionFileParser, which makes adding new plug-ins easier. - Added an IntelPTDecoder class, which is a wrapper for libipt, which is the actual library that performs the decoding. - Added TraceThreadDecoder class that decodes TraceThreads and memoizes the result to avoid repeating the decoding step. - Added a DecodedThread class, which represents the output from decoding and that for the time being only stored the list of reconstructed instructions. Later it'll contain the function call hierarchy, which will enable reconstructing backtraces. - Added basic APIs for accessing the trace in Trace.h: - GetInstructionCount, which counts the number of instructions traced for a given thread - IsTraceFailed, which returns an Error if decoding a thread failed - ForEachInstruction, which iterates on the instructions traced for a given thread, concealing the internal storage of threads, as plug-ins can decide to generate the instructions on the fly or to store them all in a vector, like I do. - DumpTraceInstructions was updated to print the instructions or show an error message if decoding was impossible. - Tests included Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D89283 Files: lldb/include/lldb/Target/Trace.h lldb/include/lldb/Target/TraceSessionFileParser.h lldb/include/lldb/Target/TraceThread.h lldb/include/lldb/lldb-forward.h lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp lldb/source/Plugins/Trace/intel-pt/DecodedThread.h lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h lldb/source/Plugins/Trace/intel-pt/ThreadIntelPT.cpp lldb/source/Plugins/Trace/intel-pt/ThreadIntelPT.h lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h lldb/source/Target/CMakeLists.txt lldb/source/Target/Thread.cpp lldb/source/Target/Trace.cpp lldb/source/Target/TraceSessionFileParser.cpp lldb/source/Target/TraceThread.cpp lldb/test/API/commands/trace/TestTraceDumpInstructions.py lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json
Index: lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json @@ -0,0 +1,31 @@ +{ + "trace": { + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel", + "family": 2123123, + "model": 12123123, + "stepping": 1231231 + } + }, + "processes": [ + { + "pid": 1234, + "triple": "x86_64-*-linux", + "threads": [ + { + "tid": 3842849, + "traceFile": "3842849.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000400000", + "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" + } + ] + } + ] +} Index: lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json @@ -0,0 +1,31 @@ +{ + "trace": { + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel", + "family": 6, + "model": 79, + "stepping": 1 + } + }, + "processes": [ + { + "pid": 1234, + "triple": "x86_64-*-linux", + "threads": [ + { + "tid": 3842849, + "traceFile": "3842849.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000FFFFF0", + "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" + } + ] + } + ] +} Index: lldb/test/API/commands/trace/TestTraceDumpInstructions.py =================================================================== --- lldb/test/API/commands/trace/TestTraceDumpInstructions.py +++ lldb/test/API/commands/trace/TestTraceDumpInstructions.py @@ -39,18 +39,41 @@ substrs=["intel-pt"]) self.expect("thread trace dump instructions", - substrs=['thread #1: tid = 3842849, total instructions = 1000', - 'would print 20 instructions from position 0']) + substrs=['''thread #1: tid = 3842849, total instructions = 22 + [ 0] 0x40052d + [ 1] 0x40052d + [ 2] 0x400529 + [ 3] 0x400525 + [ 4] 0x400521 + [ 5] 0x40052d + [ 6] 0x400529 + [ 7] 0x400525 + [ 8] 0x400521 + [ 9] 0x40052d + [10] 0x400529 + [11] 0x400525 + [12] 0x400521 + [13] 0x40052d + [14] 0x400529 + [15] 0x400525 + [16] 0x400521 + [17] 0x40052d + [18] 0x400529 + [19] 0x40051f''']) # We check if we can pass count and offset self.expect("thread trace dump instructions --count 5 --start-position 10", - substrs=['thread #1: tid = 3842849, total instructions = 1000', - 'would print 5 instructions from position 10']) + substrs=['''thread #1: tid = 3842849, total instructions = 22 + [10] 0x400529 + [11] 0x400525 + [12] 0x400521 + [13] 0x40052d + [14] 0x400529''']) # We check if we can access the thread by index id self.expect("thread trace dump instructions 1", - substrs=['thread #1: tid = 3842849, total instructions = 1000', - 'would print 20 instructions from position 0']) + substrs=['''thread #1: tid = 3842849, total instructions = 22 + [ 0] 0x40052d''']) # We check that we get an error when using an invalid thread index id self.expect("thread trace dump instructions 10", error=True, @@ -61,32 +84,68 @@ self.expect("trace load -v " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_2threads.json")) # We print the instructions of two threads simultaneously - self.expect("thread trace dump instructions 1 2", - substrs=['''thread #1: tid = 3842849, total instructions = 1000 - would print 20 instructions from position 0 -thread #2: tid = 3842850, total instructions = 1000 - would print 20 instructions from position 0''']) + self.expect("thread trace dump instructions 1 2 --count 2", + substrs=['''thread #1: tid = 3842849, total instructions = 22 + [0] 0x40052d + [1] 0x40052d +thread #2: tid = 3842850, total instructions = 22 + [0] 0x40052d + [1] 0x40052d''']) # We use custom --count and --start-position, saving the command to history for later ci = self.dbg.GetCommandInterpreter() result = lldb.SBCommandReturnObject() - ci.HandleCommand("thread trace dump instructions 1 2 --count 12 --start-position 5", result, True) - self.assertIn('''thread #1: tid = 3842849, total instructions = 1000 - would print 12 instructions from position 5 -thread #2: tid = 3842850, total instructions = 1000 - would print 12 instructions from position 5''', result.GetOutput()) + ci.HandleCommand("thread trace dump instructions 1 2 --count 4 --start-position 5", result, True) + self.assertIn('''thread #1: tid = 3842849, total instructions = 22 + [5] 0x40052d + [6] 0x400529 + [7] 0x400525 + [8] 0x400521 +thread #2: tid = 3842850, total instructions = 22 + [5] 0x40052d + [6] 0x400529 + [7] 0x400525 + [8] 0x400521''', result.GetOutput()) # We use a repeat command and ensure the previous count is used and the start-position has moved to the next position + result = lldb.SBCommandReturnObject() ci.HandleCommand("", result) - self.assertIn('''thread #1: tid = 3842849, total instructions = 1000 - would print 12 instructions from position 17 -thread #2: tid = 3842850, total instructions = 1000 - would print 12 instructions from position 17''', result.GetOutput()) + self.assertIn('''thread #1: tid = 3842849, total instructions = 22 + [ 9] 0x40052d + [10] 0x400529 + [11] 0x400525 + [12] 0x400521 +thread #2: tid = 3842850, total instructions = 22 + [ 9] 0x40052d + [10] 0x400529 + [11] 0x400525 + [12] 0x400521''', result.GetOutput()) + result = lldb.SBCommandReturnObject() ci.HandleCommand("", result) - self.assertIn('''thread #1: tid = 3842849, total instructions = 1000 - would print 12 instructions from position 29 -thread #2: tid = 3842850, total instructions = 1000 - would print 12 instructions from position 29''', result.GetOutput()) + self.assertIn('''thread #1: tid = 3842849, total instructions = 22 + [13] 0x40052d + [14] 0x400529 + [15] 0x400525 + [16] 0x400521 +thread #2: tid = 3842850, total instructions = 22 + [13] 0x40052d + [14] 0x400529 + [15] 0x400525 + [16] 0x400521''', result.GetOutput()) + + def testWrongImage(self): + trace_definition_file = os.path.join(self.getSourceDir(), "intelpt-trace", "trace_bad_image.json") + self.expect("trace load " + trace_definition_file) + self.expect("thread trace dump instructions", + substrs=['''thread #1: tid = 3842849, total instructions = 3 + [0] no memory mapped at this address''']) + + def testWrongCPU(self): + trace_definition_file = os.path.join(self.getSourceDir(), "intelpt-trace", "trace_wrong_cpu.json") + self.expect("trace load " + trace_definition_file) + self.expect("thread trace dump instructions", + substrs=['''thread #1: tid = 3842849, total instructions = 0 + Intel PT decoding error -27: 'unknown cpu''']) Index: lldb/source/Target/TraceThread.cpp =================================================================== --- lldb/source/Target/TraceThread.cpp +++ lldb/source/Target/TraceThread.cpp @@ -1,4 +1,4 @@ -//===-- ThreadIntelPT.cpp -------------------------------------------------===// +//===-- TraceThread.cpp -------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "ThreadIntelPT.h" +#include "lldb/Target/TraceThread.h" #include <memory> @@ -16,11 +16,10 @@ using namespace lldb; using namespace lldb_private; -using namespace lldb_private::trace_intel_pt; -void ThreadIntelPT::RefreshStateAfterStop() {} +void TraceThread::RefreshStateAfterStop() {} -RegisterContextSP ThreadIntelPT::GetRegisterContext() { +RegisterContextSP TraceThread::GetRegisterContext() { if (!m_reg_context_sp) m_reg_context_sp = CreateRegisterContextForFrame(nullptr); @@ -28,11 +27,13 @@ } RegisterContextSP -ThreadIntelPT::CreateRegisterContextForFrame(StackFrame *frame) { +TraceThread::CreateRegisterContextForFrame(StackFrame *frame) { // Eventually this will calculate the register context based on the current // trace position. return std::make_shared<RegisterContextHistory>( *this, 0, GetProcess()->GetAddressByteSize(), LLDB_INVALID_ADDRESS); } -bool ThreadIntelPT::CalculateStopInfo() { return false; } +bool TraceThread::CalculateStopInfo() { return false; } + +const FileSpec &TraceThread::GetTraceFile() const { return m_trace_file; } Index: lldb/source/Target/TraceSessionFileParser.cpp =================================================================== --- lldb/source/Target/TraceSessionFileParser.cpp +++ lldb/source/Target/TraceSessionFileParser.cpp @@ -10,8 +10,11 @@ #include <sstream> +#include "lldb/Core/Debugger.h" #include "lldb/Core/Module.h" +#include "lldb/Target/Process.h" #include "lldb/Target/Target.h" +#include "lldb/Target/TraceThread.h" using namespace lldb; using namespace lldb_private; @@ -34,7 +37,6 @@ ModuleSpec module_spec; module_spec.GetFileSpec() = local_file_spec; module_spec.GetPlatformFileSpec() = system_file_spec; - module_spec.SetObjectOffset(module.load_address.value); if (module.uuid.hasValue()) module_spec.GetUUID().SetFromStringRef(*module.uuid); @@ -42,7 +44,14 @@ Status error; ModuleSP module_sp = target_sp->GetOrCreateModule(module_spec, /*notify*/ false, &error); - return error.ToError(); + + if (error.Fail()) + return error.ToError(); + + bool load_addr_changed = false; + module_sp->SetLoadAddress(*target_sp, module.load_address.value, false, + load_addr_changed); + return llvm::Error::success(); } Error TraceSessionFileParser::CreateJSONError(json::Path::Root &root, @@ -87,6 +96,55 @@ return schema_builder.str(); } +void TraceSessionFileParser::ParseThread(ProcessSP &process_sp, + const JSONThread &thread) { + lldb::tid_t tid = static_cast<lldb::tid_t>(thread.tid); + + FileSpec trace_file(thread.trace_file); + NormalizePath(trace_file); + + ThreadSP thread_sp = + std::make_shared<TraceThread>(*process_sp, tid, trace_file); + process_sp->GetThreadList().AddThread(thread_sp); +} + +Expected<TargetSP> +TraceSessionFileParser::ParseProcess(const JSONProcess &process) { + TargetSP target_sp; + Status error = m_debugger.GetTargetList().CreateTarget( + m_debugger, /*user_exe_path*/ StringRef(), process.triple, + eLoadDependentsNo, + /*platform_options*/ nullptr, target_sp); + + if (!target_sp) + return error.ToError(); + + m_debugger.GetTargetList().SetSelectedTarget(target_sp.get()); + + ProcessSP process_sp(target_sp->CreateProcess( + /*listener*/ nullptr, "trace", + /*crash_file*/ nullptr)); + process_sp->SetID(static_cast<lldb::pid_t>(process.pid)); + + for (const JSONThread &thread : process.threads) + ParseThread(process_sp, thread); + + for (const JSONModule &module : process.modules) { + if (Error err = ParseModule(target_sp, module)) + return std::move(err); + } + + if (!process.threads.empty()) + process_sp->GetThreadList().SetSelectedThreadByIndexID(0); + + // We invoke DidAttach to create a correct stopped state for the process and + // its threads. + ArchSpec process_arch; + process_sp->DidAttach(process_arch); + + return target_sp; +} + namespace llvm { namespace json { Index: lldb/source/Target/Trace.cpp =================================================================== --- lldb/source/Target/Trace.cpp +++ lldb/source/Target/Trace.cpp @@ -8,8 +8,6 @@ #include "lldb/Target/Trace.h" -#include <sstream> - #include "llvm/Support/Format.h" #include "lldb/Core/PluginManager.h" @@ -79,10 +77,40 @@ return createInvalidPlugInError(name); } +static int GetNumberOfDigits(size_t num) { + int digits_count = 0; + do { + digits_count++; + num /= 10; + } while (num); + return digits_count; +} + void Trace::DumpTraceInstructions(Thread &thread, Stream &s, size_t count, - size_t start_position) const { - s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = 1000\n", - thread.GetIndexID(), thread.GetID()); - s.Printf(" would print %zu instructions from position %zu\n", count, - start_position); + size_t start_position) { + size_t instruction_count = GetInstructionCount(thread); + s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = %zu\n", + thread.GetIndexID(), thread.GetID(), instruction_count); + + int digits_count = + GetNumberOfDigits(std::min(instruction_count, start_position + count)); + + if (Error err = IsTraceFailed(thread)) + s.Printf(" %s\n", llvm::toString(std::move(err)).c_str()); + + ForEachInstruction(thread, + [&](size_t index, const Trace::Instruction &insn) -> bool { + if (index >= start_position + count) + return false; + + s.Printf(" [%*zu]", digits_count, index); + if (insn.IsError()) + s.Printf(" %s", insn.GetErrorMessage().data()); + else + s.Printf(" 0x%" PRIx64, insn.GetLoadAddress()); + s.Printf("\n"); + + return true; + }, + start_position); } Index: lldb/source/Target/Thread.cpp =================================================================== --- lldb/source/Target/Thread.cpp +++ lldb/source/Target/Thread.cpp @@ -42,6 +42,7 @@ #include "lldb/Target/ThreadPlanStepThrough.h" #include "lldb/Target/ThreadPlanStepUntil.h" #include "lldb/Target/ThreadSpec.h" +#include "lldb/Target/Trace.h" #include "lldb/Target/UnwindLLDB.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/RegularExpression.h" @@ -51,6 +52,7 @@ #include "lldb/lldb-enumerations.h" #include <memory> +#include <sstream> using namespace lldb; using namespace lldb_private; Index: lldb/source/Target/CMakeLists.txt =================================================================== --- lldb/source/Target/CMakeLists.txt +++ lldb/source/Target/CMakeLists.txt @@ -67,6 +67,7 @@ ThreadSpec.cpp Trace.cpp TraceSessionFileParser.cpp + TraceThread.cpp UnixSignals.cpp UnwindAssembly.cpp UnwindLLDB.cpp Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h @@ -9,8 +9,6 @@ #ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONFILEPARSER_H #define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONFILEPARSER_H -#include "intel-pt.h" - #include "TraceIntelPT.h" #include "lldb/Target/TraceSessionFileParser.h" @@ -38,8 +36,8 @@ TraceIntelPTSessionFileParser(Debugger &debugger, const llvm::json::Value &trace_session_file, llvm::StringRef session_file_dir) - : TraceSessionFileParser(session_file_dir, GetSchema()), - m_debugger(debugger), m_trace_session_file(trace_session_file) {} + : TraceSessionFileParser(debugger, session_file_dir, GetSchema()), + m_trace_session_file(trace_session_file) {} /// \return /// The JSON schema for the session data. @@ -53,24 +51,14 @@ /// errors, return a null pointer. llvm::Expected<lldb::TraceSP> Parse(); -private: - llvm::Error ParseImpl(); - - llvm::Error ParseProcess(const TraceSessionFileParser::JSONProcess &process); + lldb::TraceSP + CreateTraceIntelPTInstance(const pt_cpu &pt_cpu, + std::vector<lldb::TargetSP> &targets); - void ParseThread(lldb::ProcessSP &process_sp, - const TraceSessionFileParser::JSONThread &thread); - - void ParsePTCPU(const JSONPTCPU &pt_cpu); +private: + pt_cpu ParsePTCPU(const JSONPTCPU &pt_cpu); - Debugger &m_debugger; const llvm::json::Value &m_trace_session_file; - - /// Objects created as product of the parsing - /// \{ - pt_cpu m_pt_cpu; - std::vector<lldb::TargetSP> m_targets; - /// \} }; } // namespace trace_intel_pt Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp @@ -8,10 +8,10 @@ #include "TraceIntelPTSessionFileParser.h" -#include "ThreadIntelPT.h" #include "lldb/Core/Debugger.h" -#include "lldb/Target/Process.h" #include "lldb/Target/Target.h" +#include "lldb/Target/ThreadList.h" +#include "lldb/Target/TraceThread.h" using namespace lldb; using namespace lldb_private; @@ -34,88 +34,54 @@ return schema; } -void TraceIntelPTSessionFileParser::ParseThread( - ProcessSP &process_sp, const TraceSessionFileParser::JSONThread &thread) { - lldb::tid_t tid = static_cast<lldb::tid_t>(thread.tid); - - FileSpec trace_file(thread.trace_file); - NormalizePath(trace_file); - - ThreadSP thread_sp = - std::make_shared<ThreadIntelPT>(*process_sp, tid, trace_file); - process_sp->GetThreadList().AddThread(thread_sp); +pt_cpu TraceIntelPTSessionFileParser::ParsePTCPU(const JSONPTCPU &pt_cpu) { + return {pt_cpu.vendor.compare("intel") == 0 ? pcv_intel : pcv_unknown, + static_cast<uint16_t>(pt_cpu.family), + static_cast<uint8_t>(pt_cpu.model), + static_cast<uint8_t>(pt_cpu.stepping)}; } -Error TraceIntelPTSessionFileParser::ParseProcess( - const TraceSessionFileParser::JSONProcess &process) { - TargetSP target_sp; - Status error = m_debugger.GetTargetList().CreateTarget( - m_debugger, /*user_exe_path*/ StringRef(), process.triple, - eLoadDependentsNo, - /*platform_options*/ nullptr, target_sp); - - if (!target_sp) - return error.ToError(); - - m_targets.push_back(target_sp); - m_debugger.GetTargetList().SetSelectedTarget(target_sp.get()); - - ProcessSP process_sp(target_sp->CreateProcess( - /*listener*/ nullptr, "trace", - /*crash_file*/ nullptr)); - process_sp->SetID(static_cast<lldb::pid_t>(process.pid)); - - for (const TraceSessionFileParser::JSONThread &thread : process.threads) - ParseThread(process_sp, thread); - - for (const TraceSessionFileParser::JSONModule &module : process.modules) { - if (Error err = ParseModule(target_sp, module)) - return err; +TraceSP TraceIntelPTSessionFileParser::CreateTraceIntelPTInstance( + const pt_cpu &pt_cpu, std::vector<TargetSP> &targets) { + std::vector<std::shared_ptr<TraceThread>> threads; + for (TargetSP &target_sp : targets) { + ThreadList &thread_list = target_sp->GetProcessSP()->GetThreadList(); + for (size_t i = 0; i < thread_list.GetSize(); i++) { + // The top-level parser creates TraceThreads, so this is safe + threads.push_back(std::static_pointer_cast<TraceThread>( + thread_list.GetThreadAtIndex(i))); + } } - if (!process.threads.empty()) - process_sp->GetThreadList().SetSelectedThreadByIndexID(0); - - // We invoke DidAttach to create a correct stopped state for the process and - // its threads. - ArchSpec process_arch; - process_sp->DidAttach(process_arch); - - return llvm::Error::success(); -} + TraceSP trace_instance(new TraceIntelPT(pt_cpu, threads)); + for (const TargetSP &target_sp : targets) + target_sp->SetTrace(trace_instance); -void TraceIntelPTSessionFileParser::ParsePTCPU(const JSONPTCPU &pt_cpu) { - m_pt_cpu = {pt_cpu.vendor.compare("intel") == 0 ? pcv_intel : pcv_unknown, - static_cast<uint16_t>(pt_cpu.family), - static_cast<uint8_t>(pt_cpu.model), - static_cast<uint8_t>(pt_cpu.stepping)}; + return trace_instance; } -Error TraceIntelPTSessionFileParser::ParseImpl() { +Expected<TraceSP> TraceIntelPTSessionFileParser::Parse() { json::Path::Root root("traceSession"); TraceSessionFileParser::JSONTraceSession<JSONTraceIntelPTSettings> session; - if (!json::fromJSON(m_trace_session_file, session, root)) { + if (!json::fromJSON(m_trace_session_file, session, root)) return CreateJSONError(root, m_trace_session_file); - } - - ParsePTCPU(session.trace.pt_cpu); - for (const TraceSessionFileParser::JSONProcess &process : session.processes) { - if (Error err = ParseProcess(process)) - return err; - } - return Error::success(); -} -Expected<TraceSP> TraceIntelPTSessionFileParser::Parse() { - if (Error err = ParseImpl()) { - // Delete all targets that were created - for (auto target_sp : m_targets) + std::vector<TargetSP> targets; + auto onError = [this, &targets]() { + // Delete all targets that were created so far in case of failures + for (TargetSP &target_sp : targets) m_debugger.GetTargetList().DeleteTarget(target_sp); - m_targets.clear(); - return std::move(err); - } + }; - return TraceIntelPT::CreateInstance(m_pt_cpu, m_targets); + for (const TraceSessionFileParser::JSONProcess &process : session.processes) { + if (Expected<TargetSP> target_sp = ParseProcess(process)) + targets.push_back(*target_sp); + else { + onError(); + return target_sp.takeError(); + } + } + return CreateTraceIntelPTInstance(ParsePTCPU(session.trace.pt_cpu), targets); } namespace llvm { Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -9,12 +9,8 @@ #ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPT_H #define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPT_H -#include "intel-pt.h" -#include "llvm/ADT/Optional.h" - +#include "IntelPTDecoder.h" #include "TraceIntelPTSessionFileParser.h" -#include "lldb/Target/Trace.h" -#include "lldb/lldb-private.h" namespace lldb_private { namespace trace_intel_pt { @@ -52,21 +48,6 @@ CreateInstance(const llvm::json::Value &trace_session_file, llvm::StringRef session_file_dir, Debugger &debugger); - /// Create an instance of this class. - /// - /// \param[in] pt_cpu - /// The libipt.h cpu information needed for decoding correctling the - /// traces. - /// - /// \param[in] targets - /// The list of targets to associate with this trace instance - /// - /// \return - /// An intel-pt trace instance. - static lldb::TraceSP - CreateInstance(const pt_cpu &pt_cpu, - const std::vector<lldb::TargetSP> &targets); - static ConstString GetPluginNameStatic(); uint32_t GetPluginVersion() override; @@ -74,15 +55,42 @@ llvm::StringRef GetSchema() override; + const pt_cpu &GetIntelPTCPU(); + + void ForEachInstruction( + const Thread &thread, + std::function<bool(size_t index, const Instruction &insn)> callback, + size_t from = 0) override; + + size_t GetInstructionCount(const Thread &thread) override; + + llvm::Error IsTraceFailed(const Thread &thread) override; + private: - TraceIntelPT(const pt_cpu &pt_cpu, const std::vector<lldb::TargetSP> &targets) - : m_pt_cpu(pt_cpu) { - for (const lldb::TargetSP &target_sp : targets) - m_targets.push_back(target_sp); - } + friend class TraceIntelPTSessionFileParser; + + /// \param[in] trace_threads + /// TraceThread instances, which are not live-processes and whose trace + /// files are fixed. + TraceIntelPT(const pt_cpu &pt_cpu, + const std::vector<std::shared_ptr<TraceThread>> &traced_threads); + + /// Decode the trace of the given thread that, i.e. recontruct the traced + /// instructions. That trace must be managed by this class. + /// + /// \param[in] thread + /// If \a thread is a \a TraceThread, then its internal trace file will be + /// decoded. Live threads are not currently supported. + /// + /// \return + /// A \a DecodedThread instance if decoding was successful, or an error if + /// the thread's trace is not managed by this class or if the trace couldn't + /// be decoded. + llvm::Expected<const DecodedThread &> Decode(const Thread &thread); pt_cpu m_pt_cpu; - std::vector<std::weak_ptr<Target>> m_targets; + std::map<std::pair<lldb::pid_t, lldb::tid_t>, TraceThreadDecoder> + m_trace_threads; }; } // namespace trace_intel_pt Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -11,6 +11,7 @@ #include "TraceIntelPTSessionFileParser.h" #include "lldb/Core/PluginManager.h" #include "lldb/Target/Target.h" +#include "lldb/Target/TraceThread.h" using namespace lldb; using namespace lldb_private; @@ -56,11 +57,57 @@ .Parse(); } -TraceSP TraceIntelPT::CreateInstance(const pt_cpu &pt_cpu, - const std::vector<TargetSP> &targets) { - TraceSP trace_instance(new TraceIntelPT(pt_cpu, targets)); - for (const TargetSP &target_sp : targets) - target_sp->SetTrace(trace_instance); +TraceIntelPT::TraceIntelPT( + const pt_cpu &pt_cpu, + const std::vector<std::shared_ptr<TraceThread>> &traced_threads) + : m_pt_cpu(pt_cpu) { + for (const std::shared_ptr<TraceThread> &thread : traced_threads) + m_trace_threads.emplace( + std::piecewise_construct, + std::forward_as_tuple( + std::make_pair(thread->GetProcess()->GetID(), thread->GetID())), + std::forward_as_tuple(thread, pt_cpu)); +} + +const pt_cpu &TraceIntelPT::GetIntelPTCPU() { return m_pt_cpu; } + +Expected<const DecodedThread &> TraceIntelPT::Decode(const Thread &thread) { + auto it = m_trace_threads.find( + std::make_pair(thread.GetProcess()->GetID(), thread.GetID())); + if (it == m_trace_threads.end()) + return createStringError(std::errc::invalid_argument, + "The thread %" PRIu64 " is not traced.", + thread.GetID()); + + return it->second.Decode(); +} + +void TraceIntelPT::ForEachInstruction( + const Thread &thread, + std::function<bool(size_t index, const Instruction &insn)> callback, + size_t from) { + Expected<const DecodedThread &> decoded_thread = Decode(thread); + if (!decoded_thread) + return; + + const std::vector<IntelPTInstruction> &instructions = + decoded_thread->GetInstructions(); + for (size_t i = from; i < instructions.size(); i++) { + if (!callback(i, instructions[i])) + break; + } +} + +size_t TraceIntelPT::GetInstructionCount(const Thread &thread) { + if (Expected<const DecodedThread &> decoded_thread = Decode(thread)) + return decoded_thread->GetInstructions().size(); + else + return 0; +} - return trace_instance; +Error TraceIntelPT::IsTraceFailed(const Thread &thread) { + if (Expected<const DecodedThread &> decoded_thread = Decode(thread)) + return Error::success(); + else + return decoded_thread.takeError(); } Index: lldb/source/Plugins/Trace/intel-pt/ThreadIntelPT.h =================================================================== --- lldb/source/Plugins/Trace/intel-pt/ThreadIntelPT.h +++ /dev/null @@ -1,54 +0,0 @@ -//===-- ThreadIntelPT.h -----------------------------------------*- 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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_THREADINTELPT_H -#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_THREADINTELPT_H - -#include "lldb/Target/Thread.h" - -namespace lldb_private { -namespace trace_intel_pt { - -class ThreadIntelPT : public Thread { -public: - /// Create an Intel PT-traced thread. - /// - /// \param[in] process - /// The process that owns this thread. - /// - /// \param[in] tid - /// The thread id of this thread. - /// - /// \param[in] trace_file - /// The trace file for this thread. - /// - /// \param[in] pt_cpu - /// The Intel CPU information required to decode the \a trace_file. - ThreadIntelPT(Process &process, lldb::tid_t tid, const FileSpec &trace_file) - : Thread(process, tid), m_trace_file(trace_file) {} - - void RefreshStateAfterStop() override; - - lldb::RegisterContextSP GetRegisterContext() override; - - lldb::RegisterContextSP - CreateRegisterContextForFrame(StackFrame *frame) override; - -protected: - bool CalculateStopInfo() override; - - lldb::RegisterContextSP m_thread_reg_ctx_sp; - -private: - FileSpec m_trace_file; -}; - -} // namespace trace_intel_pt -} // namespace lldb_private - -#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_THREADINTELPT_H Index: lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h @@ -0,0 +1,78 @@ +//===-- IntelPTDecoder.h --======--------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODER_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODER_H + +#include "intel-pt.h" + +#include "DecodedThread.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/FileSpec.h" + +namespace lldb_private { +namespace trace_intel_pt { + +class IntelPTDecoder { +public: + /// \param[in] process + /// The process associated with the traces to decode. + /// + /// \param[in] pt_cpu + /// The libipt \a pt_cpu information of the CPU where the traces were + /// recorded. + IntelPTDecoder(Process &process, const pt_cpu &pt_cpu) + : m_process(process), m_pt_cpu(pt_cpu) {} + + /// Decode a single-thread trace file. + /// + /// \param[in] trace_file + /// The binary trace file obtained from tracing a thread. + /// + /// \return + /// A \a DecodedTrace instance, or an error if decoding failed. + llvm::Expected<DecodedThread> + DecodeSingleThreadTraceFile(const FileSpec &trace_file); + +private: + Process &m_process; + pt_cpu m_pt_cpu; +}; + +/// \a lldb_private::TraceThread decoder that stores the output from decoding, +/// avoiding recomputations, as decoding is expensive. +class TraceThreadDecoder { +public: + /// \param[in] trace_thread + /// The thread whose trace file will be decoded. + /// + /// \param[in] pt_cpu + /// The libipt cpu used when recording the trace. + TraceThreadDecoder(const std::shared_ptr<TraceThread> &trace_thread, + const pt_cpu &pt_cpu) + : m_trace_thread(trace_thread), m_pt_cpu(pt_cpu), m_decoded_thread() {} + + /// Decode the thread and store the result internally. + /// + /// \return + /// A \a DecodedThread instance or an error in case of failures. + llvm::Expected<const DecodedThread &> Decode(); + +private: + TraceThreadDecoder(const TraceThreadDecoder &other) = delete; + + std::shared_ptr<TraceThread> m_trace_thread; + pt_cpu m_pt_cpu; + llvm::Optional<DecodedThread> m_decoded_thread; + llvm::Optional<std::string> m_error_message; +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODER_H Index: lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp @@ -0,0 +1,244 @@ +//===-- IntelPTDecoder.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 "IntelPTDecoder.h" + +#include <fstream> + +#include "lldb/Core/Module.h" +#include "lldb/Core/Section.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/TraceThread.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +/// Read the bytes from a trace file +static std::vector<uint8_t> ReadTraceFile(const FileSpec &trace_file) { + char file_path[PATH_MAX]; + trace_file.GetPath(file_path, sizeof(file_path)); + std::ifstream src(file_path, std::ios::in | std::ios_base::binary); + std::vector<uint8_t> trace((std::istreambuf_iterator<char>(src)), + std::istreambuf_iterator<char>()); + return trace; +} + +/// Get a list of the sections of the given process that are Read or Exec, +/// which are the only sections that could correspond to instructions traced. +std::vector<lldb::SectionSP> static GetReadExecSections(Process &process) { + Target &target = process.GetTarget(); + + ModuleList &module_list = target.GetImages(); + std::vector<SectionSP> sections; + for (size_t i = 0; i < module_list.GetSize(); i++) { + ModuleSP module_sp = module_list.GetModuleAtIndex(i); + SectionList *section_list = module_sp->GetSectionList(); + for (size_t j = 0; j < section_list->GetSize(); j++) { + SectionSP section_sp = section_list->GetSectionAtIndex(j); + if (!section_sp) + continue; + + uint32_t permissions = section_sp->GetPermissions(); + if ((permissions & Permissions::ePermissionsReadable) && + (permissions & Permissions::ePermissionsExecutable)) { + sections.push_back(section_sp); + } + } + } + return sections; +} + +/// Decode all the instructions from a configured decoder. +/// The decoding flow is based on +/// https://github.com/intel/libipt/blob/master/doc/howto_libipt.md#the-instruction-flow-decode-loop +/// but with some relaxation to allow for gaps in the trace. +/// +/// \param[in] decoder +/// A configured libipt \a pt_insn_decoder. +/// +/// \param[out] instructions +/// The instructions decoded. +void DecodeInstructions(pt_insn_decoder &decoder, + std::vector<IntelPTInstruction> &instructions) { + // Error codes returned by libipt while decoding are: + // - negative: actual errors + // - positive or zero: not an error, but a list of bits signaling the status + // of the decoder + auto handle_pt_events = [&](int errcode) -> int { + 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) + instructions.push_back(IntelPTInstruction(-pte_overflow)); + else if (event.type == ptev_enabled && + event.variant.enabled.resumed == 0 && !instructions.empty()) { + /// This means that tracing is enabled from a different IP where it + /// stopped before + instructions.push_back(IntelPTInstruction(-pte_noip)); + } + // Other events don't signal stream errors + } + return 0; + }; + + int errcode = 0; + while (true) { + // Try to sync the decoder. If it fails, then get + // the decoder_offset and try to sync again from + // the next synchronization point. If the + // new_decoder_offset is same as decoder_offset + // then we can't move to the next synchronization + // point. Otherwise, keep resyncing until either + // end of trace stream (eos) is reached or + // pt_insn_sync_forward() passes. + errcode = pt_insn_sync_forward(&decoder); + if (errcode == -pte_eos) + return; + + if (errcode < 0) { + // There's a gap in the trace because we + // couldn't synchronize. + instructions.push_back(IntelPTInstruction(errcode)); + + uint64_t decoder_offset = 0; + int errcode_off = pt_insn_get_offset(&decoder, &decoder_offset); + if (errcode_off < 0) { + // We can't further synchronize. + return; + } + + while (true) { + 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) { + // We can't further synchronize. + 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; + } + // We'll try again starting from a new offset. + decoder_offset = new_decoder_offset; + } + } + + // We have synchronized, so we can start decoding + // instructions and events. + while (true) { + errcode = handle_pt_events(errcode); + if (errcode < 0) { + instructions.push_back(IntelPTInstruction(errcode)); + break; + } + pt_insn insn; + errcode = pt_insn_next(&decoder, &insn, sizeof(insn)); + if (insn.iclass != ptic_error) + instructions.push_back(IntelPTInstruction(insn)); + + if (errcode == -pte_eos) + return; + + if (errcode < 0) { + instructions.push_back(IntelPTInstruction(errcode)); + break; + } + } + } +} + +/// \param[out] instructions +/// Container were decoded instructions will be stored. +/// +/// \return +/// A libipt error code in case of failure or 0 otherwise. +int CreateDecoderAndDecode(Process &process, const pt_cpu &pt_cpu, + std::vector<uint8_t> &trace, + std::vector<IntelPTInstruction> &instructions) { + pt_config config; + pt_config_init(&config); + config.cpu = pt_cpu; + + if (int errcode = pt_cpu_errata(&config.errata, &config.cpu)) + return errcode; + + config.begin = trace.data(); + config.end = trace.data() + trace.size(); + + pt_insn_decoder *decoder = pt_insn_alloc_decoder(&config); + if (decoder == nullptr) + return pte_nomem; + + pt_image *image = pt_insn_get_image(decoder); + for (SectionSP §ion_sp : GetReadExecSections(process)) { + char path[PATH_MAX]; + section_sp->GetModule()->GetPlatformFileSpec().GetPath(path, sizeof(path)); + if (int errcode = pt_image_add_file( + image, path, section_sp->GetFileOffset(), section_sp->GetByteSize(), + nullptr, section_sp->GetLoadBaseAddress(&process.GetTarget()))) { + pt_insn_free_decoder(decoder); + return errcode; + } + } + DecodeInstructions(*decoder, instructions); + // We'll need the instructions in reverse order chronologically, so we + // reverse them now + std::reverse(instructions.begin(), instructions.end()); + + pt_insn_free_decoder(decoder); + return pte_ok; +} + +Expected<DecodedThread> +IntelPTDecoder::DecodeSingleThreadTraceFile(const FileSpec &trace_file) { + std::vector<IntelPTInstruction> instructions; + std::vector<uint8_t> trace = ReadTraceFile(trace_file); + if (int errcode = + CreateDecoderAndDecode(m_process, m_pt_cpu, trace, instructions)) + return createStringError(std::errc::invalid_argument, + "Intel PT decoding error %d: '%s'", errcode, + pt_errstr(pt_errcode(errcode))); + return DecodedThread(std::move(instructions)); +} + +Expected<const DecodedThread &> TraceThreadDecoder::Decode() { + if (!m_decoded_thread.hasValue() && !m_error_message.hasValue()) { + if (llvm::Expected<DecodedThread> decoded_thread = + IntelPTDecoder(*m_trace_thread->GetProcess(), m_pt_cpu) + .DecodeSingleThreadTraceFile(m_trace_thread->GetTraceFile())) + m_decoded_thread = *decoded_thread; + else + // We create a copy of the error message, as we'll create a copy of the + // error whenever this function is invoked. We have to do this because + // llvm::Error is only movable, not copyable. + m_error_message = llvm::toString(decoded_thread.takeError()); + } + + if (m_decoded_thread.hasValue()) + return *m_decoded_thread; + else + return llvm::createStringError(std::errc::invalid_argument, + m_error_message->c_str()); +} Index: lldb/source/Plugins/Trace/intel-pt/DecodedThread.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/DecodedThread.h @@ -0,0 +1,93 @@ +//===-- DecodedThread.h -----------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODEDTHREAD_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODEDTHREAD_H + +#include <vector> + +#include "lldb/Target/Trace.h" + +#include "intel-pt.h" + +namespace lldb_private { +namespace trace_intel_pt { + +/// \class IntelPTInstruction +/// An instruction obtained from decoding a trace. It is either an actual +/// instruction or an error indicating a gap in the trace. +/// +/// Gaps in the trace can come in a few flavors: +/// - tracing gaps (e.g. tracing was paused and then resumed) +/// - tracing errors (e.g. buffer overflow) +/// - decoding errors (e.g. some memory region couldn't be decoded) +/// As mentioned, any gap is represented as an error in this class. +class IntelPTInstruction : public Trace::Instruction { +public: + IntelPTInstruction() = delete; + + IntelPTInstruction(const pt_insn &pt_insn) + : m_pt_insn(pt_insn), m_libipt_error_code(0) {} + + IntelPTInstruction(int libipt_error_code) + : m_libipt_error_code(libipt_error_code) {} + + /// Check if this object represents an error (i.e. a gap). + /// + /// \return + /// Whether this object represents an error. + bool IsError() const override; + + /// Get the instruction pointer if this is not an error. + /// + /// \return + /// The instruction pointer address. + lldb::addr_t GetLoadAddress() const override; + + /// Return the libipt error code. + /// + /// libipt error codes are negative numbers. + /// + /// \return + /// 0 if it is not an error, or the error value otherwise. + int GetErrorCode() const; + + /// Return a descriptive error message if this is an error. + /// + /// \return + /// The error message. + llvm::StringRef GetErrorMessage() const override; + +private: + pt_insn m_pt_insn; + int m_libipt_error_code; +}; + +/// \class DecodedThread +/// Class holding the instructions and function call hierarchy obtained from +/// decoding a trace. +class DecodedThread { +public: + DecodedThread(std::vector<IntelPTInstruction> &&instructions) + : m_instructions(instructions) {} + + /// Get the instructions from the decoded trace. Some of them might indicate + /// errors (i.e. gaps) in the trace. + /// + /// \return + /// The instructions of the trace. + const std::vector<IntelPTInstruction> &GetInstructions() const; + +private: + std::vector<IntelPTInstruction> m_instructions; +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODEDTHREAD_H Index: lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp @@ -0,0 +1,26 @@ +//===-- DecodedThread.cpp -------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "DecodedThread.h" + +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; + +bool IntelPTInstruction::IsError() const { return m_libipt_error_code < 0; } + +lldb::addr_t IntelPTInstruction::GetLoadAddress() const { return m_pt_insn.ip; } + +int IntelPTInstruction::GetErrorCode() const { return m_libipt_error_code; } + +llvm::StringRef IntelPTInstruction::GetErrorMessage() const { + return pt_errstr(pt_errcode(GetErrorCode())); +} + +const std::vector<IntelPTInstruction> &DecodedThread::GetInstructions() const { + return m_instructions; +} Index: lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt =================================================================== --- lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt +++ lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt @@ -10,9 +10,10 @@ find_library(LIBIPT_LIBRARY ipt PATHS ${LIBIPT_LIBRARY_PATH} REQUIRED) add_lldb_library(lldbPluginTraceIntelPT PLUGIN + DecodedThread.cpp + IntelPTDecoder.cpp TraceIntelPT.cpp TraceIntelPTSessionFileParser.cpp - ThreadIntelPT.cpp LINK_LIBS lldbCore Index: lldb/include/lldb/lldb-forward.h =================================================================== --- lldb/include/lldb/lldb-forward.h +++ lldb/include/lldb/lldb-forward.h @@ -228,6 +228,7 @@ class ThreadSpec; class Trace; class TraceSessionFileParser; +class TraceThread; class TraceOptions; class Type; class TypeAndOrName; Index: lldb/include/lldb/Target/TraceThread.h =================================================================== --- /dev/null +++ lldb/include/lldb/Target/TraceThread.h @@ -0,0 +1,59 @@ +//===-- TraceThread.h -------------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TARGET_TRACETHREAD_H +#define LLDB_TARGET_TRACETHREAD_H + +#include "lldb/Target/Thread.h" + +namespace lldb_private { + +/// \class TraceThread TraceThread.h +/// +/// Thread implementation used for representing threads gotten from trace +/// session files, which are similar to threads from core files. +/// +/// See \a TraceSessionFileParser for more information regarding trace session +/// files. +class TraceThread : public Thread { +public: + /// \param[in] process + /// The process who owns this thread. + /// + /// \param[in] tid + /// The tid of this thread. + /// + /// \param[in] trace_file. + /// The file that contains the list of instructions that were traced when + /// this thread was being executed. + TraceThread(Process &process, lldb::tid_t tid, const FileSpec trace_file) + : Thread(process, tid), m_trace_file(trace_file) {} + + void RefreshStateAfterStop() override; + + lldb::RegisterContextSP GetRegisterContext() override; + + lldb::RegisterContextSP + CreateRegisterContextForFrame(StackFrame *frame) override; + + /// \return + /// The trace file of this thread. + const FileSpec &GetTraceFile() const; + +protected: + bool CalculateStopInfo() override; + + lldb::RegisterContextSP m_thread_reg_ctx_sp; + +private: + FileSpec m_trace_file; +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_TRACETHREAD_H Index: lldb/include/lldb/Target/TraceSessionFileParser.h =================================================================== --- lldb/include/lldb/Target/TraceSessionFileParser.h +++ lldb/include/lldb/Target/TraceSessionFileParser.h @@ -24,6 +24,8 @@ /// See \a Trace::FindPlugin for more information regarding these JSON files. class TraceSessionFileParser { public: + virtual ~TraceSessionFileParser() = default; + /// C++ structs representing the JSON trace session. /// \{ struct JSONAddress { @@ -61,9 +63,10 @@ }; /// \} - TraceSessionFileParser(llvm::StringRef session_file_dir, + TraceSessionFileParser(Debugger &debugger, llvm::StringRef session_file_dir, llvm::StringRef schema) - : m_session_file_dir(session_file_dir), m_schema(schema) {} + : m_debugger(debugger), m_session_file_dir(session_file_dir), + m_schema(schema) {} /// Build the full schema for a Trace plug-in. /// @@ -80,6 +83,10 @@ /// modifies the given file_spec. void NormalizePath(lldb_private::FileSpec &file_spec); + void ParseThread(lldb::ProcessSP &process_sp, const JSONThread &thread); + + llvm::Expected<lldb::TargetSP> ParseProcess(const JSONProcess &process); + llvm::Error ParseModule(lldb::TargetSP &target_sp, const JSONModule &module); /// Create a user-friendly error message upon a JSON-parsing failure using the @@ -96,6 +103,7 @@ llvm::Error CreateJSONError(llvm::json::Path::Root &root, const llvm::json::Value &value); + Debugger &m_debugger; std::string m_session_file_dir; llvm::StringRef m_schema; }; Index: lldb/include/lldb/Target/Trace.h =================================================================== --- lldb/include/lldb/Target/Trace.h +++ lldb/include/lldb/Target/Trace.h @@ -35,6 +35,29 @@ class Trace : public PluginInterface, public std::enable_shared_from_this<Trace> { public: + /// Basic instruction gotten from a trace. + /// + /// Some instructions might represent errors (i.e. gaps) in the trace, as + /// there can be collection or reconstruction problems. Thus, users of this + /// class should always check if the instruction corresponds to an error or + /// not. + struct Instruction { + virtual ~Instruction() = default; + + /// \return + /// \b true if this instruction corresponds to an error, or \b false + /// otherwise. + virtual bool IsError() const = 0; + + /// \return + /// An error message if this instructions corresponds to an error. + virtual llvm::StringRef GetErrorMessage() const = 0; + + /// \return + /// The load address of this instruction if it is not an error. + virtual lldb::addr_t GetLoadAddress() const = 0; + }; + /// Dump the trace data that this plug-in has access to. /// /// This function will dump all of the trace data for all threads in a user @@ -117,7 +140,57 @@ /// \param[in] start_position /// The position of the first instruction to print. void DumpTraceInstructions(Thread &thread, Stream &s, size_t count, - size_t start_position) const; + size_t start_position); + + /// Run the provided callback on the instructions of the trace of the given + /// thread. + /// + /// The instructions will be traversed starting at the \a from position + /// sequentially until the callback returns \b false, in which case no more + /// instructions are inspected. + /// + /// The purpose of this method is to allow inspecting traced instructions + /// without exposing the internal representation of how they are stored on + /// memory. + /// + /// \param[in] thread + /// The thread whose trace will be traversed. + /// + /// \param[in] callback + /// The callback to execute on each instruction. If it returns \b true for + /// the \a i-th instruction, then the instruction with index \a i + 1 will + /// be inspected. If it returns \b false, no more instructions will be + /// inspected. + /// + /// \param[in] from + /// The first index to execute the callback on. + virtual void ForEachInstruction( + const Thread &thread, + std::function<bool(size_t index, const Instruction &insn)> callback, + size_t from = 0) = 0; + + /// Get the number of instructions of the trace of the given thread. + /// + /// \param[in] thread + /// The thread whose trace will be inspected. + /// + /// \return + /// The total number of instructions of the trace. + virtual size_t GetInstructionCount(const Thread &thread) = 0; + + /// Get any errors in case the trace of the given thread can't be + /// reconstructed. + /// + /// If this returns an actual error, then no instructions are available in the + /// trace. + /// + /// \param[in] thread + /// The thread whose trace will be inspected. + /// + /// \return + /// An \a llvm::Error::success instance if the trace can be reconstructed, + /// or an actual Error in case of failures. + virtual llvm::Error IsTraceFailed(const Thread &thread) = 0; }; } // namespace lldb_private
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits