wallace updated this revision to Diff 299524. wallace added a comment. Herald added a subscriber: dang.
Some updates, especially regarding the indexing and dump ordering, which I discussed at lenght with some coworkers: - I've addressed most of the issues, except for the disassembly and the yaml object files, which I'll do tomorrow. - I've changed the indexing to have [0] be the oldest instruction, and [N] the most recent one. This matches with most people's expectations on the order of lines in a text file. - I've changed the instruction dump to be thread trace dump instructions --count <> --position <> And it prints in reverse order, for example [10] [11] [12] [13] [14] And after a repeat command, it prints [5] [6] [7] [8] [9] and so on The common feedback I got is that this is the most intuitive way to understand the text. - I've added a position member to DecodedThread, which in the future we'll move as we perform reverse debugging. For now, it's default value is the end of the trace. I'm using it for the instruction dump. - Now when there are errors, I'm not printing its index and I'm adding a whitespace, like [ 0] 0x40064f [ 1] 0x400540 [ 2] 0x400546 [ 3] 0x40054b [ 4] 0x400510 [ 5] 0x400516 error: no memory mapped at this address 0x7ffff7df1950 [ 7] 0x400654 [ 8] 0x400657 [ 9] 0x40065a [10] 0x40065f Also the feedback I got about this is that showing the index of the error can confuse some people, and that the whitespace makes it very clear that there's missing information and that the error is not inoffensive. - I also made the requested 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/Commands/CommandObjectThread.cpp lldb/source/Commands/Options.td 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/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.trace 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/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']) - - # 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 + [ 1] 0x400518 + [ 2] 0x40051f + [ 3] 0x400529 + [ 4] 0x40052d + [ 5] 0x400521 + [ 6] 0x400525 + [ 7] 0x400529 + [ 8] 0x40052d + [ 9] 0x400521 + [10] 0x400525 + [11] 0x400529 + [12] 0x40052d + [13] 0x400521 + [14] 0x400525 + [15] 0x400529 + [16] 0x40052d + [17] 0x400521 + [18] 0x400525 + [19] 0x400529 + [20] 0x40052d''']) + + # We check if we can pass count and position + self.expect("thread trace dump instructions --count 5 --position 10", + substrs=['''thread #1: tid = 3842849, total instructions = 21 + [ 6] 0x400525 + [ 7] 0x400529 + [ 8] 0x40052d + [ 9] 0x400521 + [10] 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 + [ 1] 0x400518''']) # 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,106 @@ 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''']) - - # We use custom --count and --start-position, saving the command to history for later + self.expect("thread trace dump instructions 1 2 --count 2", + substrs=['''thread #1: tid = 3842849, total instructions = 21 + [19] 0x400529 + [20] 0x40052d +thread #2: tid = 3842850, total instructions = 21 + [19] 0x400529 + [20] 0x40052d''']) + + # We use custom --count and --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 --position 20", result, True) + self.assertIn('''thread #1: tid = 3842849, total instructions = 21 + [17] 0x400521 + [18] 0x400525 + [19] 0x400529 + [20] 0x40052d +thread #2: tid = 3842850, total instructions = 21 + [17] 0x400521 + [18] 0x400525 + [19] 0x400529 + [20] 0x40052d''', result.GetOutput()) + + # We use a repeat command and ensure the previous count is used and the position has moved to the next position - # 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 + [13] 0x400521 + [14] 0x400525 + [15] 0x400529 + [16] 0x40052d +thread #2: tid = 3842850, total instructions = 21 + [13] 0x400521 + [14] 0x400525 + [15] 0x400529 + [16] 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 + [ 9] 0x400521 + [10] 0x400525 + [11] 0x400529 + [12] 0x40052d +thread #2: tid = 3842850, total instructions = 21 + [ 9] 0x400521 + [10] 0x400525 + [11] 0x400529 + [12] 0x40052d''', result.GetOutput()) + + def testInvalidBounds(self): + self.expect("trace load -v " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json")) + + # The output should be work when too many instructions are asked + self.expect("thread trace dump instructions --count 20 --position 2", + substrs=['''thread #1: tid = 3842849, total instructions = 21 + [0] 0x400511 + [1] 0x400518 + [2] 0x40051f''']) + + # Should print no instructions if the position is out of bounds + self.expect("thread trace dump instructions --position 23", + endstr='thread #1: tid = 3842849, total instructions = 21\n') + + # Should fail with negative bounds + self.expect("thread trace dump instructions --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 + error: no memory mapped at this address 0x400511 + error: no memory mapped at this address 0x400518''']) + + 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 = 1 + error: 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] 0x40064f + [ 1] 0x400540 + [ 2] 0x400546 + [ 3] 0x40054b + [ 4] 0x400510 + [ 5] 0x400516 + error: no memory mapped at this address 0x7ffff7df1950 + + [ 7] 0x400654 + [ 8] 0x400657 + [ 9] 0x40065a + [10] 0x40065f''']) 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,44 @@ return createInvalidPlugInError(name); } +static int GetNumberOfDigits(size_t num) { + return num == 0 ? 1 : static_cast<int>(log10(num)) + 1; +} + 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 end_position) { + size_t instructions_count = GetInstructionCount(thread); + s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = %zu\n", + thread.GetIndexID(), thread.GetID(), instructions_count); + + if (count == 0 || end_position >= instructions_count) + return; + + size_t start_position = + end_position + 1 < count ? 0 : end_position + 1 - count; + + // Used for formatting the instruction indices + int digits_count = GetNumberOfDigits(end_position); + + bool was_prev_instruction_an_error = false; + TraverseInstructions( + thread, start_position, TraceDirection::Forwards, + [&](size_t index, Expected<lldb::addr_t> load_address) -> bool { + if (load_address) { + // We print an empty line after a sequence of errors to show more + // clearly that there's a gap in the trace + if (was_prev_instruction_an_error) + s.Printf("\n"); + + s.Printf(" [%*zu] 0x%" PRIx64, digits_count, index, *load_address); + + was_prev_instruction_an_error = false; + } else { + s.Printf(" %s", toString(load_address.takeError()).c_str()); + was_prev_instruction_an_error = true; + } + + s.Printf("\n"); + return index < end_position; + }); } 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; + + size_t GetInstructionCount(const Thread &thread) override; + + size_t GetCurrentPosition(const Thread &thread) override; + private: friend class TraceIntelPTSessionFileParser; @@ -68,8 +73,20 @@ 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 a \b nullptr + /// if the thread's trace is not managed by this class. + 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,46 @@ : 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(thread->GetProcess()->GetID(), thread->GetID()), + std::forward_as_tuple(thread, pt_cpu)); +} + +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 nullptr; + return &it->second.Decode(); +} + +size_t TraceIntelPT::GetCurrentPosition(const Thread &thread) { + const DecodedThread *decoded_thread = Decode(thread); + if (!decoded_thread) + return 0; + return decoded_thread->GetCurrentPosition(); +} + +void TraceIntelPT::TraverseInstructions( + const Thread &thread, size_t position, TraceDirection direction, + std::function<bool(size_t index, Expected<lldb::addr_t> load_addr)> + callback) { + const DecodedThread *decoded_thread = Decode(thread); + if (!decoded_thread) + return; + + ArrayRef<IntelPTInstruction> instructions = decoded_thread->GetInstructions(); + + ssize_t delta = direction == TraceDirection::Forwards ? 1 : -1; + for (ssize_t i = position; i < (ssize_t)instructions.size() && i >= 0; + i += delta) + if (!callback(i, instructions[i].GetLoadAddress())) + break; +} + +size_t TraceIntelPT::GetInstructionCount(const Thread &thread) { + if (const DecodedThread *decoded_thread = Decode(thread)) + return decoded_thread->GetInstructions().size(); + else + return 0; } Index: lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h @@ -0,0 +1,52 @@ +//===-- 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 { + +/// \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. + const DecodedThread &Decode(); + +private: + ThreadTraceDecoder(const ThreadTraceDecoder &other) = delete; + ThreadTraceDecoder &operator=(const ThreadTraceDecoder &other) = delete; + + std::shared_ptr<ThreadTrace> m_trace_thread; + pt_cpu m_pt_cpu; + llvm::Optional<DecodedThread> m_decoded_thread; +}; + +} // 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,209 @@ +//===-- 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. +static 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. +static 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(insn, errcode); + break; + } + + instructions.emplace_back(insn); + } + } + + return instructions; +} + +/// 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 = static_cast<Process *>(context); + + Status error; + int bytes_read = process->ReadMemory(pc, buffer, size, error); + if (error.Fail()) + return -pte_nomap; + return bytes_read; +} + +static std::vector<IntelPTInstruction> +CreateDecoderAndDecode(Process &process, const pt_cpu &pt_cpu, + const FileSpec &trace_file) { + ErrorOr<std::unique_ptr<MemoryBuffer>> trace_or_error = + MemoryBuffer::getFile(trace_file.GetPath()); + if (std::error_code err = trace_or_error.getError()) + return {IntelPTInstruction(errorCodeToError(err))}; + + MemoryBuffer &trace = **trace_or_error; + + pt_config config; + pt_config_init(&config); + config.cpu = pt_cpu; + + if (int errcode = pt_cpu_errata(&config.errata, &config.cpu)) + return {IntelPTInstruction(errcode)}; + + // The libipt library does not modify the trace buffer, hence the following + // cast is safe. + 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 {IntelPTInstruction(-pte_nomem)}; + + pt_image *image = pt_insn_get_image(decoder); + + int errcode = pt_image_set_callback(image, ReadProcessMemory, &process); + assert(errcode == 0); + (void)errcode; + + std::vector<IntelPTInstruction> instructions = DecodeInstructions(*decoder); + + pt_insn_free_decoder(decoder); + return instructions; +} + +const DecodedThread &ThreadTraceDecoder::Decode() { + if (!m_decoded_thread.hasValue()) { + m_decoded_thread = DecodedThread( + CreateDecoderAndDecode(*m_trace_thread->GetProcess(), m_pt_cpu, + m_trace_thread->GetTraceFile())); + } + + return *m_decoded_thread; +} Index: lldb/source/Plugins/Trace/intel-pt/DecodedThread.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/DecodedThread.h @@ -0,0 +1,106 @@ +//===-- 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 "llvm/Support/Error.h" + +#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, int libipt_error_code = 0) + : m_pt_insn(pt_insn), m_libipt_error_code(libipt_error_code), + m_custom_error() {} + + IntelPTInstruction(int libipt_error_code) + : m_pt_insn(), m_libipt_error_code(libipt_error_code), m_custom_error() {} + + IntelPTInstruction(llvm::Error custom_error) + : m_pt_insn(), m_libipt_error_code(0), + m_custom_error(llvm::toString(std::move(custom_error))) {} + + /// 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 a descriptive error message if this is an error. + /// + /// \return + /// The error message. + std::string GetErrorMessage() const; + +private: + pt_insn m_pt_insn; + int m_libipt_error_code; + std::string m_custom_error; +}; + +/// \class DecodedThread +/// Class holding the instructions and function call hierarchy obtained from +/// decoding a trace. +class DecodedThread { +public: + DecodedThread(std::vector<IntelPTInstruction> &&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; + + /// \return + /// The index of the current position or 0 if there are no instructions. + size_t GetCurrentPosition() const; + + /// Change the position for this trace. If this value is to high, the new + /// position will be set as the last instruction of the trace. + /// + /// \return + /// The effective new position. + size_t SetPosition(size_t new_position); + +private: + size_t GetLastPosition() const; + + std::vector<IntelPTInstruction> m_instructions; + size_t m_position; +}; + +} // 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,59 @@ +//===-- 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" + +#include "lldb/Utility/StreamString.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 || !m_custom_error.empty(); +} + +Expected<lldb::addr_t> IntelPTInstruction::GetLoadAddress() const { + if (IsError()) + return createStringError(std::errc::invalid_argument, + GetErrorMessage().data()); + return m_pt_insn.ip; +} + +std::string IntelPTInstruction::GetErrorMessage() const { + StreamString message; + message.Printf("error: "); + + if (!m_custom_error.empty()) + message << m_custom_error; + else { + message << pt_errstr(pt_errcode(m_libipt_error_code)); + if (m_libipt_error_code == -pte_nomap) + message.Printf(" 0x%" PRIx64, m_pt_insn.ip); + } + + return message.GetData(); +} + +size_t DecodedThread::GetLastPosition() const { + return m_instructions.empty() ? 0 : m_instructions.size() - 1; +} + +DecodedThread::DecodedThread(std::vector<IntelPTInstruction> &&instructions) + : m_instructions(std::move(instructions)), m_position(GetLastPosition()) {} + +ArrayRef<IntelPTInstruction> DecodedThread::GetInstructions() const { + return makeArrayRef(m_instructions); +} + +size_t DecodedThread::GetCurrentPosition() const { return m_position; } + +size_t DecodedThread::SetPosition(size_t new_position) { + m_position = std::min(new_position, GetLastPosition()); + return m_position; +} 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/source/Commands/Options.td =================================================================== --- lldb/source/Commands/Options.td +++ lldb/source/Commands/Options.td @@ -1008,16 +1008,12 @@ let Command = "thread trace dump instructions" in { def thread_trace_dump_instructions_count : Option<"count", "c">, Group<1>, Arg<"Count">, - Desc<"The number of instructions to display starting at the current " - "position in reverse order chronologically.">; - def thread_trace_dump_instructions_start_position: - Option<"start-position", "s">, + Desc<"The number of instructions to display ending at the current position.">; + def thread_trace_dump_instructions_position: + Option<"position", "p">, Group<1>, Arg<"Index">, - Desc<"The position of the first instruction to print. Defaults to the " - "current position, i.e. where the thread is stopped. The instructions are " - "indexed in reverse order, which means that a start position of 0 refers " - "to the last instruction chronologically.">; + Desc<"The position to use instead of the current position of the trace.">; } let Command = "type summary add" in { Index: lldb/source/Commands/CommandObjectThread.cpp =================================================================== --- lldb/source/Commands/CommandObjectThread.cpp +++ lldb/source/Commands/CommandObjectThread.cpp @@ -2200,15 +2200,15 @@ m_count = count; break; } - case 's': { - int32_t start_position; - if (option_arg.empty() || option_arg.getAsInteger(0, start_position) || - start_position < 0) + case 'p': { + int32_t position; + if (option_arg.empty() || option_arg.getAsInteger(0, position) || + position < 0) error.SetErrorStringWithFormat( "invalid integer value for option '%s'", option_arg.str().c_str()); else - m_start_position = start_position; + m_position = position; break; } default: @@ -2219,19 +2219,18 @@ void OptionParsingStarting(ExecutionContext *execution_context) override { m_count = kDefaultCount; - m_start_position = kDefaultStartPosition; + m_position = llvm::None; } llvm::ArrayRef<OptionDefinition> GetDefinitions() override { return llvm::makeArrayRef(g_thread_trace_dump_instructions_options); } - static const uint32_t kDefaultCount = 20; - static const uint32_t kDefaultStartPosition = 0; + static const size_t kDefaultCount = 20; // Instance variables to hold the values for command options. - uint32_t m_count; - uint32_t m_start_position; + size_t m_count; + llvm::Optional<ssize_t> m_position; }; CommandObjectTraceDumpInstructions(CommandInterpreter &interpreter) @@ -2253,30 +2252,24 @@ uint32_t index) override { current_command_args.GetCommandString(m_repeat_command); m_create_repeat_command_just_invoked = true; + m_consecutive_repetitions = 0; return m_repeat_command.c_str(); } protected: bool DoExecute(Args &args, CommandReturnObject &result) override { + if (IsRepeatCommand()) + m_consecutive_repetitions++; bool status = CommandObjectIterateOverThreads::DoExecute(args, result); - PrepareRepeatArguments(); - return status; - } - void PrepareRepeatArguments() { - m_repeat_start_position = m_options.m_count + GetStartPosition(); m_create_repeat_command_just_invoked = false; + return status; } bool IsRepeatCommand() { return !m_repeat_command.empty() && !m_create_repeat_command_just_invoked; } - uint32_t GetStartPosition() { - return IsRepeatCommand() ? m_repeat_start_position - : m_options.m_start_position; - } - bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override { const TraceSP &trace_sp = m_exe_ctx.GetTargetSP()->GetTrace(); if (!trace_sp) { @@ -2287,8 +2280,15 @@ ThreadSP thread_sp = m_exe_ctx.GetProcessPtr()->GetThreadList().FindThreadByID(tid); - trace_sp->DumpTraceInstructions(*thread_sp, result.GetOutputStream(), - m_options.m_count, GetStartPosition()); + size_t count = m_options.m_count; + ssize_t position = m_options.m_position.getValueOr( + trace_sp->GetCurrentPosition(*thread_sp)) - + m_consecutive_repetitions * count; + if (position < 0) + result.SetError("error: no more data"); + else + trace_sp->DumpTraceInstructions(*thread_sp, result.GetOutputStream(), + count, position); return true; } @@ -2297,7 +2297,7 @@ // Repeat command helpers std::string m_repeat_command; bool m_create_repeat_command_just_invoked; - uint32_t m_repeat_start_position; + size_t m_consecutive_repetitions = 0; }; // CommandObjectMultiwordTraceDump 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 @@ -98,12 +103,16 @@ /// The JSON schema of this Trace plug-in. virtual llvm::StringRef GetSchema() = 0; - /// Dump \a count instructions of the given thread's \a Trace starting at the - /// \a start_position position in reverse order. + /// \return + /// The current position of the thread's trace of \b 0 if empty. + virtual size_t GetCurrentPosition(const Thread &thread) = 0; + + /// Dump \a count instructions of the given thread's trace ending at the + /// given \a end_position position. /// - /// The instructions are indexed in reverse order, which means that the \a - /// start_position 0 represents the last instruction of the trace - /// chronologically. + /// The instructions are printed along with their indices or positions, which + /// are increasing chronologically. This means that the \a index 0 represents + /// the oldest instruction of the trace chronologically. /// /// \param[in] thread /// The thread whose trace will be dumped. @@ -114,10 +123,50 @@ /// \param[in] count /// The number of instructions to print. /// - /// \param[in] start_position - /// The position of the first instruction to print. + /// \param[in] end_position + /// The position of the last instruction to print. void DumpTraceInstructions(Thread &thread, Stream &s, size_t count, - size_t start_position) const; + size_t end_position); + + /// Run the provided callback on the instructions of the trace of the given + /// thread. + /// + /// The instructions will be traversed starting at the given \a 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 incrementing indices. If + /// \b TraceDirection::Backwards, the traversal is done backwards + /// chronologically, i.e. with decrementing indices. + /// + /// \param[in] callback + /// The callback to execute on each instruction. If it returns \b false, + /// the iteration stops. + 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; + + /// Get the number of available instructions in the trace of the given thread. + /// + /// \param[in] thread + /// The thread whose trace will be inspected. + /// + /// \return + /// The total number of instructions in the trace. + virtual size_t GetInstructionCount(const Thread &thread) = 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