wallace updated this revision to Diff 298311. wallace added a comment. cleanup
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/Trace.h lldb/source/Plugins/Process/Trace/ProcessTrace.cpp lldb/source/Plugins/Process/Trace/ProcessTrace.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/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
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,40 @@ 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''']) # 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 +83,125 @@ 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 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 + 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,41 @@ 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 = GetTraceErrorStatus(thread)) + s.Printf(" %s\n", llvm::toString(std::move(err)).c_str()); + + ForEachInstruction( + thread, + [&](size_t index, Expected<lldb::addr_t> &load_address) -> bool { + if (index >= start_position + count) + 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; + }, + start_position); } 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,16 @@ llvm::StringRef GetSchema() override; + void ForEachInstruction( + const Thread &thread, + std::function<bool(size_t index, llvm::Expected<lldb::addr_t> &load_addr)> + callback, + size_t from = 0) override; + + size_t GetInstructionCount(const Thread &thread) override; + + llvm::Error GetTraceErrorStatus(const Thread &thread) override; + private: friend class TraceIntelPTSessionFileParser; @@ -68,8 +74,21 @@ 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::map<std::pair<lldb::pid_t, lldb::tid_t>, std::shared_ptr<TraceThread>> + std::map<std::pair<lldb::pid_t, lldb::tid_t>, TraceThreadDecoder> 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,54 @@ : m_pt_cpu(pt_cpu) { for (const std::shared_ptr<TraceThread> &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::ForEachInstruction( + const Thread &thread, + std::function<bool(size_t index, Expected<lldb::addr_t> &load_addr)> + 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++) { + Expected<lldb::addr_t> load_addr = instructions[i].GetLoadAddress(); + // We check the expected here to avoid forcing the callback check it. + (void)(bool) load_addr; + + if (!callback(i, load_addr)) + break; + } +} + +size_t TraceIntelPT::GetInstructionCount(const Thread &thread) { + if (Expected<const DecodedThread &> decoded_thread = Decode(thread)) + return decoded_thread->GetInstructions().size(); + else + return 0; +} + +Error TraceIntelPT::GetTraceErrorStatus(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/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,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/TraceThread.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 &> TraceThreadDecoder::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/source/Plugins/Process/Trace/ProcessTrace.h =================================================================== --- lldb/source/Plugins/Process/Trace/ProcessTrace.h +++ lldb/source/Plugins/Process/Trace/ProcessTrace.h @@ -9,6 +9,7 @@ #ifndef LLDB_SOURCE_PLUGINS_PROCESS_TRACE_PROCESSTRACE_H #define LLDB_SOURCE_PLUGINS_PROCESS_TRACE_PROCESSTRACE_H +#include "lldb/Target/MemoryRegionInfo.h" #include "lldb/Target/Process.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/Status.h" @@ -78,6 +79,13 @@ bool UpdateThreadList(ThreadList &old_thread_list, ThreadList &new_thread_list) override; + +private: + /// Create a map from virtual memory ranges to the sections that contain them. + const std::map<MemoryRegionInfo::RangeType, lldb::SectionSP> &GetSectionMap(); + + llvm::Optional<std::map<MemoryRegionInfo::RangeType, lldb::SectionSP>> + m_section_map; }; } // namespace process_trace Index: lldb/source/Plugins/Process/Trace/ProcessTrace.cpp =================================================================== --- lldb/source/Plugins/Process/Trace/ProcessTrace.cpp +++ lldb/source/Plugins/Process/Trace/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; @@ -109,6 +111,33 @@ return GetTarget().GetArchitecture(); } +const std::map<MemoryRegionInfo::RangeType, SectionSP> & +ProcessTrace::GetSectionMap() { + if (m_section_map) + return *m_section_map; + + m_section_map.emplace(); + + ModuleList &modules = GetTarget().GetImages(); + SectionLoadList &load_list = GetTarget().GetSectionLoadList(); + modules.ForEach([&](const ModuleSP &module_sp) { + SectionList *sections = module_sp->GetSectionList(); + for (size_t i = 0; i < sections->GetSize(); i++) { + SectionSP section_sp = sections->GetSectionAtIndex(i); + + addr_t load_addr = load_list.GetSectionLoadAddress(section_sp); + if (load_addr == LLDB_INVALID_ADDRESS) + continue; + MemoryRegionInfo::RangeType section_range(load_addr, + section_sp->GetByteSize()); + m_section_map->insert(std::make_pair(section_range, section_sp)); + } + return true; + }); + + return *m_section_map; +} + bool ProcessTrace::GetProcessInfo(ProcessInstanceInfo &info) { info.Clear(); info.SetProcessID(GetID()); @@ -124,5 +153,25 @@ size_t ProcessTrace::DoReadMemory(addr_t addr, void *buf, size_t size, Status &error) { + const std::map<MemoryRegionInfo::RangeType, SectionSP> §ion_map = + GetSectionMap(); + + MemoryRegionInfo::RangeType address_range(addr, size); + // Doing (upper_bound - 1) gives us the maximum range whose base address is <= + // addr + auto it = section_map.upper_bound(address_range); + if (it != section_map.begin()) { + --it; + const SectionSP §ion = it->second; + const MemoryRegionInfo::RangeType §ion_range = it->first; + + if (section_range.Contains(address_range) && section->GetObjectFile()) { + section->GetObjectFile()->CopyData(addr - section_range.GetRangeBase(), + size, buf); + return size; + } + } + + error.SetErrorString("could not parse memory info"); return 0; } Index: lldb/include/lldb/Target/Trace.h =================================================================== --- lldb/include/lldb/Target/Trace.h +++ lldb/include/lldb/Target/Trace.h @@ -117,7 +117,58 @@ /// \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, llvm::Expected<lldb::addr_t> &load_addr)> + 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 GetTraceErrorStatus(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