wallace created this revision. wallace added a reviewer: clayborg. Herald added subscribers: lldb-commits, dang. Herald added a reviewer: JDevlieghere. Herald added a project: LLDB. wallace requested review of this revision.
Depends on D87589 <https://reviews.llvm.org/D87589>. In D87589 <https://reviews.llvm.org/D87589> I added the basic instruction decoding functionality and the Dump action was printing the raw strings. I'm now pretty printing the instructions, with an output like the following: pid: '1234', tid: '1981309' a.out`main [57] 0x400549 <+13>: movl %eax, -0x4(%rbp) a.out`bar() [56] 0x40053b <+46>: retq [55] 0x40053a <+45>: leave [54] 0x400537 <+42>: movl -0x4(%rbp), %eax [53] 0x400535 <+40>: jle 0x400525 ; <+24> at main.cpp:7 [52] 0x400531 <+36>: cmpl $0x3, -0x8(%rbp) [51] 0x40052d <+32>: addl $0x1, -0x8(%rbp) [50] 0x40052a <+29>: addl %eax, -0x4(%rbp) a.out`foo() [49] 0x400567 <+15>: retq [48] 0x400566 <+14>: popq %rbp [47] 0x400563 <+11>: movl -0x4(%rbp), %eax [46] 0x40055c <+4>: movl $0x2a, -0x4(%rbp) [45] 0x400559 <+1>: movq %rsp, %rbp [44] 0x400558 <+0>: pushq %rbp There's also a --raw flag that prints simply the instructions. An important remark is that the decoder can fail to decode some instructions, which we are printing, for example: pid: '1234', tid: '3842849' [4] 0x400529 <+28>: cmpl $0x3, -0x8(%rbp) [3] error -13. 'no memory mapped at this address' [2] 0x40052d <+32>: jle 0x400521 As an implementation note, I'm using lldb's Disassembler. I couldn't move the entire printing logic to the disassembler because it assumes that all instuctions printed are valid, which conflicts with what the intel-pt decoder outputs. Finally, the instruction dumping command is as follows: trace dump -i [-rv] [-c <count>] [-o <offset>] [-t <thread-id>] Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D87730 Files: lldb/include/lldb/Core/Disassembler.h lldb/include/lldb/Target/Trace.h lldb/source/Commands/CommandObjectTrace.cpp lldb/source/Commands/Options.td lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp lldb/test/API/commands/trace/TestTraceDump.py lldb/test/API/commands/trace/intelpt-trace-multi-function/a.out lldb/test/API/commands/trace/intelpt-trace-multi-function/main.cpp lldb/test/API/commands/trace/intelpt-trace-multi-function/multi-function.trace lldb/test/API/commands/trace/intelpt-trace-multi-function/other.cpp lldb/test/API/commands/trace/intelpt-trace-multi-function/other.h lldb/test/API/commands/trace/intelpt-trace-multi-function/trace.json
Index: lldb/test/API/commands/trace/intelpt-trace-multi-function/trace.json =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace-multi-function/trace.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": 1981309, + "traceFile": "multi-function.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000400000", + "uuid": "13A5952D-66DE-EE86-C44F-CD91816AE259-CD06AB8D" + } + ] + } + ] +} Index: lldb/test/API/commands/trace/intelpt-trace-multi-function/other.h =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace-multi-function/other.h @@ -0,0 +1 @@ +int foo(); Index: lldb/test/API/commands/trace/intelpt-trace-multi-function/other.cpp =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace-multi-function/other.cpp @@ -0,0 +1,4 @@ +int foo() { + int x = 42; + return x; +} Index: lldb/test/API/commands/trace/intelpt-trace-multi-function/main.cpp =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace-multi-function/main.cpp @@ -0,0 +1,16 @@ +#include "other.h" + +int bar() { + int ret = 0; + + for (int i = 0; i < 4; i++) + ret += foo(); + + return ret; +} + +int main() { + int ret = bar(); + + return ret > 0; +} Index: lldb/test/API/commands/trace/TestTraceDump.py =================================================================== --- lldb/test/API/commands/trace/TestTraceDump.py +++ lldb/test/API/commands/trace/TestTraceDump.py @@ -51,51 +51,113 @@ trace_definition_file = os.path.join(src_dir, "intelpt-trace", "trace2.json") self.expect("trace load " + trace_definition_file) + # Dump the instructions of the currently selected thread in raw mode + self.expect("trace dump -i -r", substrs=['''Instructions: +pid: '2', tid: '21' + 0x40052d + 0x400529 + 0x400525 + 0x400521 + 0x40052d + 0x400529 + 0x400525 + 0x400521 + 0x40052d + 0x400529''']) + # Dump the instructions of the currently selected thread self.expect("trace dump -i", substrs=['''Instructions: pid: '2', tid: '21' - 4195629 - 4195625 - 4195621 - 4195617 - 4195629 - 4195625 - 4195621 - 4195617 - 4195629 - 4195625''']) + a.out`main + [21] 0x40052d <+32>: jle 0x400521 ; <+20> at main.cpp:5 + [20] 0x400529 <+28>: cmpl $0x3, -0x8(%rbp) + [19] 0x400525 <+24>: addl $0x1, -0x8(%rbp) + [18] 0x400521 <+20>: xorl $0x1, -0x4(%rbp) + [17] 0x40052d <+32>: jle 0x400521 ; <+20> at main.cpp:5 + [16] 0x400529 <+28>: cmpl $0x3, -0x8(%rbp) + [15] 0x400525 <+24>: addl $0x1, -0x8(%rbp) + [14] 0x400521 <+20>: xorl $0x1, -0x4(%rbp) + [13] 0x40052d <+32>: jle 0x400521 ; <+20> at main.cpp:5 + [12] 0x400529 <+28>: cmpl $0x3, -0x8(%rbp)''']) # Dump the instructions of one specific thread self.expect("trace dump -i -t 22", substrs=['''Instructions: pid: '2', tid: '22' - 4195629 - 4195625 - 4195621 - 4195617 - 4195629 - 4195625 - 4195621 - 4195617 - 4195629 - 4195625''']) + a.out`main + [21] 0x40052d <+32>: jle 0x400521 ; <+20> at main.cpp:5 + [20] 0x400529 <+28>: cmpl $0x3, -0x8(%rbp) + [19] 0x400525 <+24>: addl $0x1, -0x8(%rbp) + [18] 0x400521 <+20>: xorl $0x1, -0x4(%rbp) + [17] 0x40052d <+32>: jle 0x400521 ; <+20> at main.cpp:5 + [16] 0x400529 <+28>: cmpl $0x3, -0x8(%rbp) + [15] 0x400525 <+24>: addl $0x1, -0x8(%rbp) + [14] 0x400521 <+20>: xorl $0x1, -0x4(%rbp) + [13] 0x40052d <+32>: jle 0x400521 ; <+20> at main.cpp:5 + [12] 0x400529 <+28>: cmpl $0x3, -0x8(%rbp)''']) # Dump the instructions of two threads self.expect("trace dump -i -t 22 -t 21", substrs=[ 'Instructions:', - "pid: '2', tid: '21'\n 4195629", - "pid: '2', tid: '22'\n 4195629" + "pid: '2', tid: '21'\n a.out`main\n [21] 0x40052d <+32>: jle 0x400521", + "pid: '2', tid: '22'\n a.out`main\n [21] 0x40052d <+32>: jle 0x400521" ]) # Dump specific instructions using --offset and --count self.expect("trace dump -i -t 22 -c 3 -o 3", substrs=['''Instructions: pid: '2', tid: '22' - 4195617 - 4195629 - 4195625''']) + a.out`main + [18] 0x400521 <+20>: xorl $0x1, -0x4(%rbp) + [17] 0x40052d <+32>: jle 0x400521 ; <+20> at main.cpp:5 + [16] 0x400529 <+28>: cmpl $0x3, -0x8(%rbp)''']) # Check errors of a failed decoding trace_definition_file = os.path.join(src_dir, "intelpt-trace", "trace_bad_image.json") self.expect("trace load " + trace_definition_file) self.expect("trace dump -i", substrs=['''Instructions: pid: '1234', tid: '3842849' - error -13. 'no memory mapped at this address''']) + [3] error -13. 'no memory mapped at this address''']) + + def testDumpMultiFunctionTrace(self): + self.expect("trace dump", substrs=["error: no trace data in this target"]) + + src_dir = self.getSourceDir() + trace_definition_file = os.path.join(src_dir, "intelpt-trace-multi-function", "trace.json") + self.expect("trace load " + trace_definition_file) + self.expect("trace dump -i -c 30", substrs=['''Instructions: +pid: '1234', tid: '1981309' + a.out`main + [57] 0x400549 <+13>: movl %eax, -0x4(%rbp) + a.out`bar() + [56] 0x40053b <+46>: retq + [55] 0x40053a <+45>: leave + [54] 0x400537 <+42>: movl -0x4(%rbp), %eax + [53] 0x400535 <+40>: jle 0x400525 ; <+24> at main.cpp:7 + [52] 0x400531 <+36>: cmpl $0x3, -0x8(%rbp) + [51] 0x40052d <+32>: addl $0x1, -0x8(%rbp) + [50] 0x40052a <+29>: addl %eax, -0x4(%rbp) + a.out`foo() + [49] 0x400567 <+15>: retq + [48] 0x400566 <+14>: popq %rbp + [47] 0x400563 <+11>: movl -0x4(%rbp), %eax + [46] 0x40055c <+4>: movl $0x2a, -0x4(%rbp) + [45] 0x400559 <+1>: movq %rsp, %rbp + [44] 0x400558 <+0>: pushq %rbp + a.out`bar() + [43] 0x400525 <+24>: callq 0x400558 ; foo at other.cpp:1 + [42] 0x400535 <+40>: jle 0x400525 ; <+24> at main.cpp:7 + [41] 0x400531 <+36>: cmpl $0x3, -0x8(%rbp) + [40] 0x40052d <+32>: addl $0x1, -0x8(%rbp) + [39] 0x40052a <+29>: addl %eax, -0x4(%rbp) + a.out`foo() + [38] 0x400567 <+15>: retq + [37] 0x400566 <+14>: popq %rbp + [36] 0x400563 <+11>: movl -0x4(%rbp), %eax + [35] 0x40055c <+4>: movl $0x2a, -0x4(%rbp) + [34] 0x400559 <+1>: movq %rsp, %rbp + [33] 0x400558 <+0>: pushq %rbp + a.out`bar() + [32] 0x400525 <+24>: callq 0x400558 ; foo at other.cpp:1 + [31] 0x400535 <+40>: jle 0x400525 ; <+24> at main.cpp:7 + [30] 0x400531 <+36>: cmpl $0x3, -0x8(%rbp) + [29] 0x40052d <+32>: addl $0x1, -0x8(%rbp) + [28] 0x40052a <+29>: addl %eax, -0x4(%rbp)''']) 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 @@ -9,13 +9,17 @@ #include "TraceIntelPT.h" #include <algorithm> +#include <sstream> #include "llvm/Support/Format.h" #include "llvm/Support/FormatVariadic.h" #include "TraceIntelPTSettingsParser.h" #include "lldb/Core/Disassembler.h" +#include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Target/Process.h" using namespace lldb; @@ -56,19 +60,127 @@ void TraceIntelPT::DumpSettingsFile(Stream &s) const { s << "Settings:\n"; - s.Indent(); std::string str; llvm::raw_string_ostream OS(str); json::Object obj = GetSettings(); OS << llvm::formatv("{0:2}", json::Value(std::move(obj))); OS.flush(); s << OS.str(); - s.IndentLess(); s << "\n\nSettings directory:\n"; - s.Indent(); s << GetSettingsDir() << "\n\n"; - s.IndentLess(); +} + +void DumpInstructionSymbolHeader(Stream &s, const ModuleSP &module_sp, + Symbol *symbol) { + if (module_sp) + s.Format("{0:F}`", module_sp->GetObjectFile()->GetFileSpec()); + else + s << "(unknown module)`"; + + if (symbol == nullptr) + s << "(unknown symbol)"; + else + s << symbol->GetDisplayName().AsCString(); + s << "\n"; +} + +/// Disassemble an instruction given an address and return a string like the +/// following 0x400525 <+24>: addl $0x1, -0x8(%rbp) +/// +/// This uses \a lldb_private::Disassembler, which outputs a prefix line like +/// "a.out`main" if there's symbol information of the address. We don't need +/// that prefix, so we remove it if present. +/// +/// \return +/// The portion of the Disassembler output corresponding exactly to the +/// instruction, or an empty string if disassembling failed. +std::string DisassembleInstruction(Target &target, const Address &addr) { + ExecutionContext exe_ctx; + target.CalculateExecutionContext(exe_ctx); + CommandReturnObject result(/*colors*/ true); + Disassembler::Limit limit = {Disassembler::Limit::Instructions, 1}; + if (Disassembler::Disassemble( + target.GetDebugger(), target.GetArchitecture(), + /*plugin_name*/ nullptr, /*flavor_string*/ nullptr, exe_ctx, addr, + limit, /*show_mixed*/ false, + /*context_lines*/ 0, /*options*/ 0, result.GetOutputStream())) { + StringRef full_disassembly = result.GetOutputData().trim(); + return full_disassembly.substr(full_disassembly.find_last_of('\n') + 1) + .str(); + } + return ""; +} + +/// Dump an instruction and print a module`symbol header if this instruction +/// belongs to a different symbol than the previous instruction. +/// +/// \return +/// A pointer to the symbol containing this instruction, or nullptr in case +/// of an error +Symbol *DumpInstruction(Stream &s, const IntelPTInstruction &insn, + Target &target, Symbol *prev_symbol, size_t index, + bool raw) { + Symbol *symbol = nullptr; + + s << "\n"; + if (insn.IsError()) { + s.Indent(); + s.Printf("[%zu] ", index); + s.Printf("error %d. '%s'", insn.GetErrorCode(), insn.GetErrorMessage()); + return symbol; + } + if (raw) { + s.Indent(); + s.Printf("0x%" PRIx64, insn.GetIP()); + return symbol; + } + + Address addr; + ModuleSP module_sp; + std::string disassembly; + if (target.ResolveLoadAddress(insn.GetIP(), addr)) { + symbol = addr.CalculateSymbolContextSymbol(); + module_sp = addr.CalculateSymbolContextModule(); + disassembly = DisassembleInstruction(target, addr); + } + + if (symbol != prev_symbol) { + s.Indent(); + DumpInstructionSymbolHeader(s, module_sp, symbol); + } + + s.Indent(); + s.Printf("[%zu] ", index); + if (!disassembly.empty()) + s << disassembly; + else + s.Printf("%" PRIx64, insn.GetIP()); + return symbol; +} + +void DumpInstructions(Stream &s, IntelPTThread &thread, + const TraceDumpOptions &options) { + if (llvm::Expected<const DecodedTrace &> trace = + thread.Decode(*options.process)) { + const std::vector<IntelPTInstruction> &instructions = + trace->GetInstructions(); + + int to = std::max((int)instructions.size() - (int)options.offset, 0); + int from = std::max((int)to - (int)options.count, 0); + + Target &target = options.process->GetTarget(); + Symbol *prev_symbol = nullptr; + + for (int i = to - 1; i >= from; i--) { + prev_symbol = DumpInstruction(s, instructions[i], target, prev_symbol, i, + options.raw); + } + } else { + s << "\n"; + s.Indent(); + s << llvm::toString(trace.takeError()); + } } void TraceIntelPT::Dump(Stream &s, const TraceDumpOptions &options) const { @@ -81,36 +193,18 @@ s << "Instructions:"; } - auto dump_instructions = [&](IntelPTThread &thread) { - if (llvm::Expected<const DecodedTrace &> trace = - thread.Decode(*options.process)) { - const std::vector<IntelPTInstruction> &instructions = - trace->GetInstructions(); - - int to = std::max((int)instructions.size() - (int)options.offset, 0); - int from = std::max((int)to - (int)options.count, 0); - - for (int i = to - 1; i >= from; i--) { - const IntelPTInstruction &insn = instructions[i]; - if (insn.IsError()) - s.Printf("\n error %d. '%s'", insn.GetErrorCode(), - insn.GetErrorMessage()); - else - s.Printf("\n %" PRIu64, insn.GetIP()); - } - } else { - s << "\n" << llvm::toString(trace.takeError()); - } - }; - auto dump_thread = [&](lldb::pid_t pid, IntelPTThread &thread) { s.Printf("\npid: '%" PRIu64 "', tid: '%" PRIu64 "'", pid, thread.GetThreadID()); - if (options.category == TraceDumpCategory::GenericInfo) - s << "\n " << thread.GetTraceFile(); - else if (options.category == TraceDumpCategory::Instructions) - dump_instructions(thread); + s.IndentMore(); + if (options.category == TraceDumpCategory::GenericInfo) { + s << "\n"; + s.Indent(); + s << thread.GetTraceFile(); + } else if (options.category == TraceDumpCategory::Instructions) + DumpInstructions(s, thread, options); + s.IndentLess(); }; // We go through all the processes and threads even when there are filters as Index: lldb/source/Commands/Options.td =================================================================== --- lldb/source/Commands/Options.td +++ lldb/source/Commands/Options.td @@ -1181,7 +1181,7 @@ Desc<"Show verbose trace information.">; def trace_dump_thread_id : Option<"thread-id", "t">, Groups<[1,2]>, Arg<"ThreadID">, Desc<"The thread id to dump trace information of. Defaults to the currently selected thread. Can be specified multiple times">; - def trace_dump_instructions: Option<"instructions", "i">, Group<2>, + def trace_dump_instructions: Option<"instructions", "i">, Group<2>, Required, Desc<"Display the last instructions from the trace.">; def trace_dump_count : Option<"count", "c">, Group<2>, Arg<"Count">, @@ -1189,6 +1189,8 @@ def trace_dump_offset: Option<"offset", "o">, Group<2>, Arg<"Offset">, Desc<"Offset from the last position to start displaying instructions from. Defaults to 0.">; + def trace_dump_raw: Option<"raw", "r">, Group<2>, + Desc<"Print raw addresses without any additional information.">; } let Command = "trace schema" in { Index: lldb/source/Commands/CommandObjectTrace.cpp =================================================================== --- lldb/source/Commands/CommandObjectTrace.cpp +++ lldb/source/Commands/CommandObjectTrace.cpp @@ -183,6 +183,10 @@ m_dump_options.offset = offset; break; } + case 'r': { + m_dump_options.raw = true; + break; + } default: llvm_unreachable("Unimplemented option"); } Index: lldb/include/lldb/Target/Trace.h =================================================================== --- lldb/include/lldb/Target/Trace.h +++ lldb/include/lldb/Target/Trace.h @@ -29,6 +29,7 @@ Process *process = nullptr; // Must be specified std::set<lldb::tid_t> tids; // Thread IDs, if empty dump all threads bool verbose = false; + bool raw = false; TraceDumpCategory category = TraceDumpCategory::GenericInfo; size_t count = 10; Index: lldb/include/lldb/Core/Disassembler.h =================================================================== --- lldb/include/lldb/Core/Disassembler.h +++ lldb/include/lldb/Core/Disassembler.h @@ -282,16 +282,16 @@ /// @param[in] ignore_calls /// It true, then fine the first branch instruction that isn't /// a function call (a branch that calls and returns to the next - /// instruction). If false, find the instruction index of any + /// instruction). If false, find the instruction index of any /// branch in the list. - /// + /// /// @param[out] found_calls - /// If non-null, this will be set to true if any calls were found in + /// If non-null, this will be set to true if any calls were found in /// extending the range. - /// + /// /// @return /// The instruction index of the first branch that is at or past - /// \a start. Returns UINT32_MAX if no matching branches are + /// \a start. Returns UINT32_MAX if no matching branches are /// found. //------------------------------------------------------------------ uint32_t GetIndexOfNextBranchInstruction(uint32_t start,
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits