wallace updated this revision to Diff 298751. wallace marked 2 inline comments as done. wallace added a comment. Herald added a project: LLVM. Herald added a subscriber: llvm-commits.
- C hanged the instruction iterator to the proposed signature - Added SupportsInstructionsCount to determine whether GetInstructionCount is supported by the plug-in. This count can be useful for debugging purposes, and at least for a while we won’t support lazy decoding of instructions. - Removed GetTraceErrorStatus following Greg’s idea - Added the requested test cases - Some small fixes here and there Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D89283/new/ https://reviews.llvm.org/D89283 Files: lldb/include/lldb/Target/ProcessTrace.h lldb/include/lldb/Target/Trace.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/TraceIntelPT.cpp lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h lldb/source/Target/ProcessTrace.cpp lldb/source/Target/Trace.cpp lldb/source/Target/TraceSessionFileParser.cpp lldb/test/API/commands/trace/TestTraceDumpInstructions.py lldb/test/API/commands/trace/intelpt-trace-multi-file/a.out lldb/test/API/commands/trace/intelpt-trace-multi-file/bar.cpp lldb/test/API/commands/trace/intelpt-trace-multi-file/bar.h lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.cpp lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.h lldb/test/API/commands/trace/intelpt-trace-multi-file/ld-2.17.so lldb/test/API/commands/trace/intelpt-trace-multi-file/libbar.so lldb/test/API/commands/trace/intelpt-trace-multi-file/libfoo.so lldb/test/API/commands/trace/intelpt-trace-multi-file/main.cpp lldb/test/API/commands/trace/intelpt-trace-multi-file/multi-file-no-ld.json lldb/test/API/commands/trace/intelpt-trace-multi-file/multi-file.json lldb/test/API/commands/trace/intelpt-trace-multi-file/multi-file.trace lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json lldb/tools/intel-features/intel-pt/Decoder.cpp llvm/include/llvm/Support/Error.h
Index: llvm/include/llvm/Support/Error.h =================================================================== --- llvm/include/llvm/Support/Error.h +++ llvm/include/llvm/Support/Error.h @@ -1007,6 +1007,17 @@ handleAllErrors(std::move(Err), [](const ErrorInfoBase &) {}); } +/// Consume an Expected without doing anything. This method should be used +/// only where the expected result is irrelevant. +/// +/// Uses of this method are potentially indicative of design problems: If it's +/// legitimate to do nothing while processing an "expected", the +/// expected-producer might be more clearly refactored to return an Optional<T>. +template <typename T> inline void consumeExpected(Expected<T> E) { + if (!E) + consumeError(E.takeError()); +} + /// Convert an Expected to an Optional without doing anything. This method /// should be used only where an error can be considered a reasonable and /// expected return value. Index: lldb/tools/intel-features/intel-pt/Decoder.cpp =================================================================== --- lldb/tools/intel-features/intel-pt/Decoder.cpp +++ lldb/tools/intel-features/intel-pt/Decoder.cpp @@ -248,6 +248,8 @@ } } +#include <fstream> + void Decoder::ReadTraceDataAndImageInfo(lldb::SBProcess &sbprocess, lldb::tid_t tid, lldb::SBError &sberror, ThreadTraceInfo &threadTraceInfo) { @@ -255,6 +257,7 @@ // 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; @@ -295,6 +298,11 @@ } std::fill(pt_buffer.begin() + bytes_written, pt_buffer.end(), 0); + std::ofstream of("/tmp/multi-file.trace", std::ios::binary | std::ios::out); + for (auto bait : pt_buffer) + of << bait; + of.close(); + // Get information of all the modules of the inferior lldb::SBTarget sbtarget = sbprocess.GetTarget(); ReadExecuteSectionInfos &readExecuteSectionInfos = 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/intelpt-trace-multi-file/multi-file.json =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace-multi-file/multi-file.json @@ -0,0 +1,49 @@ +{ + "trace": { + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel", + "family": 6, + "model": 79, + "stepping": 1 + } + }, + "processes": [ + { + "pid": 815455, + "triple": "x86_64-*-linux", + "threads": [ + { + "tid": 815455, + "traceFile": "multi-file.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000400000", + "uuid": "729BF711-43CB-0017-AADF-83304FEA5EC4-D46B45DD" + }, + { + "file": "libfoo.so", + "systemPath": "libfoo.so", + "loadAddress": "0x00007ffff7bd9000", + "uuid": "B30FFEDA-8BB2-3D08-4580-C5937ED11E2B-21BE778C" + }, + { + "file": "libbar.so", + "systemPath": "libbar.so", + "loadAddress": "0x00007ffff79d7000", + "uuid": "6633B038-EA73-D1A6-FF9A-7D0C0EDF733D-95FEA2CC" + }, + { + "file": "ld-2.17.so", + "systemPath": "ld-2.17.so", + "loadAddress": "0x00007ffff7ddb000", + "uuid": "27FFD1FB-C695-69C7-76E6-66474EED7233-95E6D727" + } + ] + } + ] +} Index: lldb/test/API/commands/trace/intelpt-trace-multi-file/multi-file-no-ld.json =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace-multi-file/multi-file-no-ld.json @@ -0,0 +1,43 @@ +{ + "trace": { + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel", + "family": 6, + "model": 79, + "stepping": 1 + } + }, + "processes": [ + { + "pid": 815455, + "triple": "x86_64-*-linux", + "threads": [ + { + "tid": 815455, + "traceFile": "multi-file.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000400000", + "uuid": "729BF711-43CB-0017-AADF-83304FEA5EC4-D46B45DD" + }, + { + "file": "libfoo.so", + "systemPath": "libfoo.so", + "loadAddress": "0x00007ffff7bd9000", + "uuid": "B30FFEDA-8BB2-3D08-4580-C5937ED11E2B-21BE778C" + }, + { + "file": "libbar.so", + "systemPath": "libbar.so", + "loadAddress": "0x00007ffff79d7000", + "uuid": "6633B038-EA73-D1A6-FF9A-7D0C0EDF733D-95FEA2CC" + } + ] + } + ] +} Index: lldb/test/API/commands/trace/intelpt-trace-multi-file/main.cpp =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace-multi-file/main.cpp @@ -0,0 +1,7 @@ +#include "foo.h" + +int main() { + int res = foo(); + res++; + return res; +} Index: lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.h =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.h @@ -0,0 +1 @@ +int foo(); Index: lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.cpp =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.cpp @@ -0,0 +1,7 @@ +#include "bar.h" + +int foo() { + int y = bar(); + y++; + return y; +} Index: lldb/test/API/commands/trace/intelpt-trace-multi-file/bar.h =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace-multi-file/bar.h @@ -0,0 +1 @@ +int bar(); Index: lldb/test/API/commands/trace/intelpt-trace-multi-file/bar.cpp =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace-multi-file/bar.cpp @@ -0,0 +1,5 @@ +int bar() { + int x = 1; + x++; + return x; +} Index: lldb/test/API/commands/trace/TestTraceDumpInstructions.py =================================================================== --- lldb/test/API/commands/trace/TestTraceDumpInstructions.py +++ lldb/test/API/commands/trace/TestTraceDumpInstructions.py @@ -12,7 +12,7 @@ TestBase.setUp(self) if 'intel-pt' not in configuration.enabled_plugins: self.skipTest("The intel-pt test plugin is not enabled") - + def testErrorMessages(self): # We first check the output when there are no targets self.expect("thread trace dump instructions", @@ -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 = 21 + [ 0] 0x40052d + [ 1] 0x400529 + [ 2] 0x400525 + [ 3] 0x400521 + [ 4] 0x40052d + [ 5] 0x400529 + [ 6] 0x400525 + [ 7] 0x400521 + [ 8] 0x40052d + [ 9] 0x400529 + [10] 0x400525 + [11] 0x400521 + [12] 0x40052d + [13] 0x400529 + [14] 0x400525 + [15] 0x400521 + [16] 0x40052d + [17] 0x400529 + [18] 0x40051f + [19] 0x400518''']) # 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 = 21 + [10] 0x400525 + [11] 0x400521 + [12] 0x40052d + [13] 0x400529 + [14] 0x400525''']) # 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 = 21 + [ 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,141 @@ 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 = 21 + [0] 0x40052d + [1] 0x400529 +thread #2: tid = 3842850, total instructions = 21 + [0] 0x40052d + [1] 0x400529''']) # 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 = 21 + [5] 0x400529 + [6] 0x400525 + [7] 0x400521 + [8] 0x40052d +thread #2: tid = 3842850, total instructions = 21 + [5] 0x400529 + [6] 0x400525 + [7] 0x400521 + [8] 0x40052d''', 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 = 21 + [ 9] 0x400529 + [10] 0x400525 + [11] 0x400521 + [12] 0x40052d +thread #2: tid = 3842850, total instructions = 21 + [ 9] 0x400529 + [10] 0x400525 + [11] 0x400521 + [12] 0x40052d''', 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 = 21 + [13] 0x400529 + [14] 0x400525 + [15] 0x400521 + [16] 0x40052d +thread #2: tid = 3842850, total instructions = 21 + [13] 0x400529 + [14] 0x400525 + [15] 0x400521 + [16] 0x40052d''', result.GetOutput()) + + def testInvalidBounds(self): + self.expect("trace load -v " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json")) + + # The output should be truncated if asking for too many instructions + self.expect("thread trace dump instructions --count 20 --start-position 20", + substrs=['''thread #1: tid = 3842849, total instructions = 21 + [20] 0x400511''']) + + # Should print no instructions if the start-position is out of bounds + self.expect("thread trace dump instructions --start-position 23", + endstr='thread #1: tid = 3842849, total instructions = 21\n') + + # Should fail with negative bounds + self.expect("thread trace dump instructions --start-position -1", error=True) + self.expect("thread trace dump instructions --count -1", error=True) + + def testWrongImage(self): + self.expect("trace load " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_bad_image.json")) + self.expect("thread trace dump instructions", + substrs=['''thread #1: tid = 3842849, total instructions = 2 + [0] no memory mapped at this address + [1] no memory mapped at this address''']) + + def testWrongCPU(self): + self.expect("trace load " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_wrong_cpu.json")) + self.expect("thread trace dump instructions", + substrs=['''thread #1: tid = 3842849, total instructions = 0 + [0] Intel PT decoding error -27: 'unknown cpu''']) + + def testMultiFileTraceWithMissingModule(self): + self.expect("trace load " + os.path.join(self.getSourceDir(), "intelpt-trace-multi-file", "multi-file-no-ld.json")) + + # The trace will be composed of the first and last instructions of main.cpp, with an unmapped region in between + # corresponding to the dynamic linker, which is missing in the json file. Because of this error, the decoder moves + # to the next synchronization point, skipping the instructions of bar.cpp and foo.cpp. + self.expect("thread trace dump instructions", + substrs=['''thread #1: tid = 815455, total instructions = 11 + [ 0] 0x40065f + [ 1] 0x40065a + [ 2] 0x400657 + [ 3] 0x400654 + [ 4] no memory mapped at this address + [ 5] 0x400516 + [ 6] 0x400510 + [ 7] 0x40054b + [ 8] 0x400546 + [ 9] 0x400540 + [10] 0x40064f''']) + + def testMultiFileTrace(self): + self.expect("trace load " + os.path.join(self.getSourceDir(), "intelpt-trace-multi-file", "multi-file.json")) + + # last instructions of main.cpp + self.expect("thread trace dump instructions --count 3", + substrs=['''thread #1: tid = 815455, total instructions = 1153''', ''' + [0] 0x40065f + [1] 0x40065a + [2] 0x400657''']) + + # bar.cpp + self.expect("thread trace dump instructions --count 3 --start-position 4", + substrs=[''' + [4] 0x7ffff7bd9703 + [5] 0x7ffff7bd9702 + [6] 0x7ffff7bd96fe''']) + + # foo.cpp + self.expect("thread trace dump instructions --count 3 --start-position 13", + substrs=[''' + [13] 0x7ffff79d76a9 + [14] 0x7ffff79d76a6 + [15] 0x7ffff79d76a3''']) + + # ld + self.expect("thread trace dump instructions --count 3 --start-position 21", + substrs=[ ''' + [21] 0x7ffff7df1a16 + [22] 0x7ffff7df1a12 + [23] 0x7ffff7df1a0e''']) + + # first instructions of main.cpp + self.expect("thread trace dump instructions --count 3 --start-position 1150", + substrs=[ ''' + [1150] 0x400546 + [1151] 0x400540 + [1152] 0x40064f''']) Index: lldb/source/Target/TraceSessionFileParser.cpp =================================================================== --- lldb/source/Target/TraceSessionFileParser.cpp +++ lldb/source/Target/TraceSessionFileParser.cpp @@ -37,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); @@ -45,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, 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,47 @@ 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) { + s.Printf("thread #%u: tid = %" PRIu64 "", thread.GetIndexID(), + thread.GetID()); + + // Used later for formatting the instruction indices + int digits_count = GetNumberOfDigits(start_position + count); + + if (SupportsInstructionsCount()) { + size_t instructions_count = GetInstructionCount(thread); + s.Printf(", total instructions = %zu", instructions_count); + digits_count = + std::min(digits_count, GetNumberOfDigits(instructions_count)); + } + + s.Printf("\n"); + + TraverseInstructions( + thread, start_position, TraceDirection::Backwards, + [&](size_t index, Expected<lldb::addr_t> load_address) -> bool { + if (index >= start_position + count) { + consumeExpected(std::move(load_address)); + return false; + } + + s.Printf(" [%*zu]", digits_count, index); + if (load_address) + s.Printf(" 0x%" PRIx64, *load_address); + else + s.Printf(" %s", toString(load_address.takeError()).c_str()); + s.Printf("\n"); + + return true; + }); } Index: lldb/source/Target/ProcessTrace.cpp =================================================================== --- lldb/source/Target/ProcessTrace.cpp +++ lldb/source/Target/ProcessTrace.cpp @@ -12,6 +12,8 @@ #include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Core/Section.h" +#include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.h" using namespace lldb; @@ -121,5 +123,9 @@ size_t ProcessTrace::DoReadMemory(addr_t addr, void *buf, size_t size, Status &error) { - return 0; + Address resolved_address; + GetTarget().GetSectionLoadList().ResolveLoadAddress(addr, resolved_address); + + return GetTarget().ReadMemoryFromFileCache(resolved_address, buf, size, + error); } 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 { @@ -59,6 +55,15 @@ llvm::StringRef GetSchema() override; + void TraverseInstructions( + const Thread &thread, size_t position, TraceDirection direction, + std::function<bool(size_t index, llvm::Expected<lldb::addr_t> load_addr)> + callback) override; + + bool SupportsInstructionsCount() override; + + size_t GetInstructionCount(const Thread &thread) override; + private: friend class TraceIntelPTSessionFileParser; @@ -68,8 +73,21 @@ TraceIntelPT(const pt_cpu &pt_cpu, const std::vector<std::shared_ptr<ThreadTrace>> &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 ThreadTrace, 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::map<std::pair<lldb::pid_t, lldb::tid_t>, std::shared_ptr<ThreadTrace>> + std::map<std::pair<lldb::pid_t, lldb::tid_t>, ThreadTraceDecoder> m_trace_threads; }; 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 @@ -64,5 +64,49 @@ : m_pt_cpu(pt_cpu) { for (const std::shared_ptr<ThreadTrace> &thread : traced_threads) m_trace_threads.emplace( - std::make_pair(thread->GetProcess()->GetID(), thread->GetID()), thread); + std::piecewise_construct, + std::forward_as_tuple( + std::make_pair(thread->GetProcess()->GetID(), thread->GetID())), + std::forward_as_tuple(thread, 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::TraverseInstructions( + const Thread &thread, size_t position, TraceDirection direction, + std::function<bool(size_t index, Expected<lldb::addr_t> load_addr)> + callback) { + Expected<const DecodedThread &> decoded_thread = Decode(thread); + if (!decoded_thread) { + callback(position, decoded_thread.takeError()); + return; + } + + const std::vector<IntelPTInstruction> &instructions = + decoded_thread->GetInstructions(); + + int delta = direction == TraceDirection::Forwards ? -1 : 1; + for (uint64_t i = position; i < instructions.size() && i >= 0; i += delta) + if (!callback(i, instructions[i].GetLoadAddress())) + break; +} + +bool TraceIntelPT::SupportsInstructionsCount() { return true; } + +size_t TraceIntelPT::GetInstructionCount(const Thread &thread) { + if (Expected<const DecodedThread &> decoded_thread = Decode(thread)) + return decoded_thread->GetInstructions().size(); + else { + consumeError(decoded_thread.takeError()); + return 0; + } } 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::ThreadTrace decoder that stores the output from decoding, +/// avoiding recomputations, as decoding is expensive. +class ThreadTraceDecoder { +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. + ThreadTraceDecoder(const std::shared_ptr<ThreadTrace> &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: + ThreadTraceDecoder(const ThreadTraceDecoder &other) = delete; + + std::shared_ptr<ThreadTrace> 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,232 @@ +//===-- 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 "llvm/Support/MemoryBuffer.h" + +#include "lldb/Core/Module.h" +#include "lldb/Core/Section.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/ThreadTrace.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +/// Move the decoder forward to the next synchronization point (i.e. next PSB +/// packet). +/// +/// Once the decoder is at that sync. point, it can start decoding instructions. +/// +/// \return +/// A negative number with the libipt error if we couldn't synchronize. +/// Otherwise, a positive number with the synchronization status will be +/// returned. +int FindNextSynchronizationPoint(pt_insn_decoder &decoder) { + // 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. + int errcode = pt_insn_sync_forward(&decoder); + + if (errcode != -pte_eos && errcode < 0) { + uint64_t decoder_offset = 0; + int errcode_off = pt_insn_get_offset(&decoder, &decoder_offset); + if (errcode_off >= 0) { // we could get the offset + while (true) { + errcode = pt_insn_sync_forward(&decoder); + if (errcode >= 0 || errcode == -pte_eos) + break; + + uint64_t new_decoder_offset = 0; + errcode_off = pt_insn_get_offset(&decoder, &new_decoder_offset); + if (errcode_off < 0) + break; // We can't further synchronize. + 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, + // stopping in this situation. + break; + } + // We'll try again starting from a new offset. + decoder_offset = new_decoder_offset; + } + } + } + + return errcode; +} + +/// Before querying instructions, we need to query the events associated that +/// instruction e.g. timing events like ptev_tick, or paging events like +/// ptev_paging. +/// +/// \return +/// 0 if there were no errors processing the events, or a negative libipt +/// error code in case of errors. +int ProcessPTEvents(pt_insn_decoder &decoder, int errcode) { + while (errcode & pts_event_pending) { + pt_event event; + errcode = pt_insn_event(&decoder, &event, sizeof(event)); + if (errcode < 0) + return errcode; + } + return 0; +}; + +/// 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. +/// +/// 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 +/// +/// \param[in] decoder +/// A configured libipt \a pt_insn_decoder. +/// +/// \return +/// The decoded instructions. +static std::vector<IntelPTInstruction> +DecodeInstructions(pt_insn_decoder &decoder) { + std::vector<IntelPTInstruction> instructions; + + while (true) { + int errcode = FindNextSynchronizationPoint(decoder); + if (errcode == -pte_eos) + break; + + if (errcode < 0) { + instructions.emplace_back(errcode); + break; + } + + // We have synchronized, so we can start decoding + // instructions and events. + while (true) { + errcode = ProcessPTEvents(decoder, errcode); + if (errcode < 0) { + instructions.emplace_back(errcode); + break; + } + pt_insn insn; + + errcode = pt_insn_next(&decoder, &insn, sizeof(insn)); + if (errcode == -pte_eos) + break; + + if (errcode < 0) { + instructions.emplace_back(errcode); + break; + } + + instructions.emplace_back(insn); + } + } + + return instructions; +} + +static Error CreateLibiptError(int errcode) { + return createStringError(std::errc::invalid_argument, + "Intel PT decoding error %d: '%s'", errcode, + pt_errstr(pt_errcode(errcode))); +} + +/// Callback used by libipt for reading the process memory. +/// +/// More information can be found in +/// https://github.com/intel/libipt/blob/master/doc/man/pt_image_set_callback.3.md +static int ReadProcessMemory(uint8_t *buffer, size_t size, + const pt_asid * /* unused */, uint64_t pc, + void *context) { + Process *process = reinterpret_cast<Process *>(context); + + Status error; + int bytes_read = process->ReadMemory(pc, buffer, size, error); + if (error.Fail()) + return -pte_nomap; + return bytes_read; +} + +static Expected<std::vector<IntelPTInstruction>> +CreateDecoderAndDecode(Process &process, const pt_cpu &pt_cpu, + MemoryBuffer &trace) { + pt_config config; + pt_config_init(&config); + config.cpu = pt_cpu; + + if (int errcode = pt_cpu_errata(&config.errata, &config.cpu)) + return CreateLibiptError(errcode); + + config.begin = + reinterpret_cast<uint8_t *>(const_cast<char *>(trace.getBufferStart())); + config.end = + reinterpret_cast<uint8_t *>(const_cast<char *>(trace.getBufferEnd())); + + pt_insn_decoder *decoder = pt_insn_alloc_decoder(&config); + if (!decoder) + return CreateLibiptError(pte_nomem); + + pt_image *image = pt_insn_get_image(decoder); + if (int error = pt_image_set_callback(image, ReadProcessMemory, &process)) + return CreateLibiptError(error); + + std::vector<IntelPTInstruction> instructions = DecodeInstructions(*decoder); + // 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 instructions; +} + +Expected<DecodedThread> +IntelPTDecoder::DecodeSingleThreadTraceFile(const FileSpec &trace_file) { + ErrorOr<std::unique_ptr<MemoryBuffer>> buffer_or_error = + MemoryBuffer::getFile(trace_file.GetPath()); + if (std::error_code err = buffer_or_error.getError()) + return errorCodeToError(err); + + if (Expected<std::vector<IntelPTInstruction>> instructions = + CreateDecoderAndDecode(m_process, m_pt_cpu, **buffer_or_error)) + return DecodedThread(std::move(*instructions)); + else + return instructions.takeError(); +} + +Expected<const DecodedThread &> ThreadTraceDecoder::Decode() { + if (!m_decoded_thread.hasValue() && !m_error_message.hasValue()) { + IntelPTDecoder decoder(*m_trace_thread->GetProcess(), m_pt_cpu); + + if (llvm::Expected<DecodedThread> decoded_thread = + decoder.DecodeSingleThreadTraceFile(m_trace_thread->GetTraceFile())) + m_decoded_thread = *decoded_thread; + else + // We create a copy of the error message, as we'll create a new instance + // of llvm::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: + 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; + + /// Get the instruction pointer if this is not an error. + /// + /// \return + /// The instruction pointer address. + llvm::Expected<lldb::addr_t> GetLoadAddress() const; + + /// 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; + +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(std::move(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. + llvm::ArrayRef<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,32 @@ +//===-- 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; +using namespace llvm; + +bool IntelPTInstruction::IsError() const { return m_libipt_error_code < 0; } + +Expected<lldb::addr_t> IntelPTInstruction::GetLoadAddress() const { + if (IsError()) + return createStringError(std::errc::invalid_argument, + GetErrorMessage().data()); + 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())); +} + +ArrayRef<IntelPTInstruction> DecodedThread::GetInstructions() const { + return makeArrayRef(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,6 +10,8 @@ find_library(LIBIPT_LIBRARY ipt PATHS ${LIBIPT_LIBRARY_PATH} REQUIRED) add_lldb_library(lldbPluginTraceIntelPT PLUGIN + DecodedThread.cpp + IntelPTDecoder.cpp TraceIntelPT.cpp TraceIntelPTSessionFileParser.cpp Index: lldb/include/lldb/Target/Trace.h =================================================================== --- lldb/include/lldb/Target/Trace.h +++ lldb/include/lldb/Target/Trace.h @@ -35,6 +35,11 @@ class Trace : public PluginInterface, public std::enable_shared_from_this<Trace> { public: + enum class TraceDirection { + Forwards = 0, + Backwards, + }; + /// 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 +122,61 @@ /// \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] position + /// The instruction position to start iterating on. + /// + /// \param[in] direction + /// If \b TraceDirection::Forwards, then then instructions will be + /// traversed forwards chronologically, i.e. with decrementing indices. If + /// \b TraceDirection::Backwards, the traversal is done backwards + /// chronologically, i.e. with incrementing indices. + /// + /// \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. + virtual void TraverseInstructions( + const Thread &thread, size_t position, TraceDirection direction, + std::function<bool(size_t index, llvm::Expected<lldb::addr_t> load_addr)> + callback) = 0; + + /// Determine whether the trace plug-in supports the \a GetInstructionCount + /// method. + /// + /// Some trace plug-ins might prefer to provide instructions lazily on the + /// fly, being unable to provide the total number of instructions in a single + /// call. + /// + /// \return + /// \b true if invoking \a GetInstructionCount will return a correct + /// value, \b false otherwise. + virtual bool SupportsInstructionsCount() { return false; } + + /// 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) { return 0; } }; } // namespace lldb_private Index: lldb/include/lldb/Target/ProcessTrace.h =================================================================== --- lldb/include/lldb/Target/ProcessTrace.h +++ lldb/include/lldb/Target/ProcessTrace.h @@ -9,6 +9,7 @@ #ifndef LLDB_TARGET_PROCESSTRACE_H #define LLDB_TARGET_PROCESSTRACE_H +#include "lldb/Target/MemoryRegionInfo.h" #include "lldb/Target/Process.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/Status.h"
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits