forked from OSchip/llvm-project
[trace] Add an option to dump instructions in json and to a file
In order to provide simple scripting support on top of instruction traces, a simple solution is to enhance the `dump instructions` command and allow printing in json and directly to a file. The format is verbose and not space efficient, but it's not supposed to be used for really large traces, in which case the TraceCursor API is the way to go. - add a -j option for printing the dump in json - add a -J option for pretty printing the json output - add a -F option for specifying an output file - add a -a option for dumping all the instructions available starting at the initial point configured with the other flags - add tests for all cases - refactored the instruction dumper and abstracted the actual "printing" logic. There are two writer implementations: CLI and JSON. This made the dumper itself much more readable and maintanable sample output: ``` (lldb) thread trace dump instructions -t -a --id 100 -J [ { "id": 100, "tsc": "43591204528448966" "loadAddress": "0x407a91", "module": "a.out", "symbol": "void std::deque<Foo, std::allocator<Foo>>::_M_push_back_aux<Foo>(Foo&&)", "mnemonic": "movq", "source": "/usr/include/c++/8/bits/deque.tcc", "line": 492, "column": 30 }, ... ``` Differential Revision: https://reviews.llvm.org/D128316
This commit is contained in:
parent
89a1d03e2b
commit
efbfde0dd0
|
@ -173,6 +173,11 @@ public:
|
|||
/// its position.
|
||||
virtual bool GoToId(lldb::user_id_t id) = 0;
|
||||
|
||||
/// \return
|
||||
/// \b true if and only if there's an instruction item with the given \p
|
||||
/// id.
|
||||
virtual bool HasId(lldb::user_id_t id) const = 0;
|
||||
|
||||
/// \return
|
||||
/// A unique identifier for the instruction or error this cursor is
|
||||
/// pointing to.
|
||||
|
|
|
@ -15,17 +15,6 @@
|
|||
|
||||
namespace lldb_private {
|
||||
|
||||
/// Helper struct that holds symbol, disassembly and address information of an
|
||||
/// instruction.
|
||||
struct InstructionSymbolInfo {
|
||||
SymbolContext sc;
|
||||
Address address;
|
||||
lldb::addr_t load_address;
|
||||
lldb::DisassemblerSP disassembler;
|
||||
lldb::InstructionSP instruction;
|
||||
lldb_private::ExecutionContext exe_ctx;
|
||||
};
|
||||
|
||||
/// Class that holds the configuration used by \a TraceInstructionDumper for
|
||||
/// traversing and dumping instructions.
|
||||
struct TraceInstructionDumperOptions {
|
||||
|
@ -36,6 +25,10 @@ struct TraceInstructionDumperOptions {
|
|||
/// Dump only instruction addresses without disassembly nor symbol
|
||||
/// information.
|
||||
bool raw = false;
|
||||
/// Dump in json format.
|
||||
bool json = false;
|
||||
/// When dumping in JSON format, pretty print the output.
|
||||
bool pretty_print_json = false;
|
||||
/// For each instruction, print the corresponding timestamp counter if
|
||||
/// available.
|
||||
bool show_tsc = false;
|
||||
|
@ -52,6 +45,42 @@ struct TraceInstructionDumperOptions {
|
|||
/// state and granularity.
|
||||
class TraceInstructionDumper {
|
||||
public:
|
||||
/// Helper struct that holds symbol, disassembly and address information of an
|
||||
/// instruction.
|
||||
struct SymbolInfo {
|
||||
SymbolContext sc;
|
||||
Address address;
|
||||
lldb::DisassemblerSP disassembler;
|
||||
lldb::InstructionSP instruction;
|
||||
lldb_private::ExecutionContext exe_ctx;
|
||||
};
|
||||
|
||||
/// Helper struct that holds all the information we know about an instruction
|
||||
struct InstructionEntry {
|
||||
lldb::user_id_t id;
|
||||
lldb::addr_t load_address;
|
||||
llvm::Optional<uint64_t> tsc;
|
||||
llvm::Optional<llvm::StringRef> error;
|
||||
llvm::Optional<SymbolInfo> symbol_info;
|
||||
llvm::Optional<SymbolInfo> prev_symbol_info;
|
||||
};
|
||||
|
||||
/// Interface used to abstract away the format in which the instruction
|
||||
/// information will be dumped.
|
||||
class OutputWriter {
|
||||
public:
|
||||
virtual ~OutputWriter() = default;
|
||||
|
||||
/// Indicate a user-level info message. It's not part of the actual trace.
|
||||
virtual void InfoMessage(llvm::StringRef text) {}
|
||||
|
||||
/// Dump a trace event.
|
||||
virtual void Event(llvm::StringRef text) = 0;
|
||||
|
||||
/// Dump an instruction or a trace error.
|
||||
virtual void Instruction(const InstructionEntry &insn) = 0;
|
||||
};
|
||||
|
||||
/// Create a instruction dumper for the cursor.
|
||||
///
|
||||
/// \param[in] cursor
|
||||
|
@ -83,46 +112,22 @@ public:
|
|||
/// \b true if there's still more data to traverse in the trace.
|
||||
bool HasMoreData();
|
||||
|
||||
private:
|
||||
/// Indicate to the dumper that no more data is available in the trace.
|
||||
/// This will prevent further iterations.
|
||||
void SetNoMoreData();
|
||||
|
||||
/// Move the cursor one step.
|
||||
///
|
||||
/// \return
|
||||
/// \b true if the cursor moved.
|
||||
bool TryMoveOneStep();
|
||||
private:
|
||||
/// Create an instruction entry for the current position without symbol
|
||||
/// information.
|
||||
InstructionEntry CreatRawInstructionEntry();
|
||||
|
||||
void PrintEvents();
|
||||
|
||||
void PrintMissingInstructionsMessage();
|
||||
|
||||
void PrintInstructionHeader();
|
||||
|
||||
void DumpInstructionDisassembly(const InstructionSymbolInfo &insn);
|
||||
|
||||
/// Dump the symbol context of the given instruction address if it's different
|
||||
/// from the symbol context of the previous instruction in the trace.
|
||||
///
|
||||
/// \param[in] prev_sc
|
||||
/// The symbol context of the previous instruction in the trace.
|
||||
///
|
||||
/// \param[in] address
|
||||
/// The address whose symbol information will be dumped.
|
||||
///
|
||||
/// \return
|
||||
/// The symbol context of the current address, which might differ from the
|
||||
/// previous one.
|
||||
void DumpInstructionSymbolContext(
|
||||
const llvm::Optional<InstructionSymbolInfo> &prev_insn,
|
||||
const InstructionSymbolInfo &insn);
|
||||
|
||||
lldb::TraceCursorUP m_cursor_up;
|
||||
TraceInstructionDumperOptions m_options;
|
||||
Stream &m_s;
|
||||
/// If \b true, all the instructions have been traversed.
|
||||
bool m_no_more_data = false;
|
||||
std::unique_ptr<OutputWriter> m_writer_up;
|
||||
};
|
||||
|
||||
} // namespace lldb_private
|
||||
|
|
|
@ -2128,6 +2128,10 @@ public:
|
|||
m_count = count;
|
||||
break;
|
||||
}
|
||||
case 'a': {
|
||||
m_count = std::numeric_limits<decltype(m_count)>::max();
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
int32_t skip;
|
||||
if (option_arg.empty() || option_arg.getAsInteger(0, skip) || skip < 0)
|
||||
|
@ -2148,6 +2152,10 @@ public:
|
|||
m_dumper_options.id = id;
|
||||
break;
|
||||
}
|
||||
case 'F': {
|
||||
m_output_file.emplace(option_arg);
|
||||
break;
|
||||
}
|
||||
case 'r': {
|
||||
m_dumper_options.raw = true;
|
||||
break;
|
||||
|
@ -2164,6 +2172,15 @@ public:
|
|||
m_dumper_options.show_events = true;
|
||||
break;
|
||||
}
|
||||
case 'j': {
|
||||
m_dumper_options.json = true;
|
||||
break;
|
||||
}
|
||||
case 'J': {
|
||||
m_dumper_options.pretty_print_json = true;
|
||||
m_dumper_options.json = true;
|
||||
break;
|
||||
}
|
||||
case 'C': {
|
||||
m_continue = true;
|
||||
break;
|
||||
|
@ -2177,6 +2194,7 @@ public:
|
|||
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
||||
m_count = kDefaultCount;
|
||||
m_continue = false;
|
||||
m_output_file = llvm::None;
|
||||
m_dumper_options = {};
|
||||
}
|
||||
|
||||
|
@ -2189,6 +2207,7 @@ public:
|
|||
// Instance variables to hold the values for command options.
|
||||
size_t m_count;
|
||||
size_t m_continue;
|
||||
llvm::Optional<FileSpec> m_output_file;
|
||||
TraceInstructionDumperOptions m_dumper_options;
|
||||
};
|
||||
|
||||
|
@ -2238,27 +2257,44 @@ protected:
|
|||
|
||||
bool DoExecute(Args &args, CommandReturnObject &result) override {
|
||||
ThreadSP thread_sp = GetThread(args, result);
|
||||
if (!thread_sp)
|
||||
if (!thread_sp) {
|
||||
result.AppendError("invalid thread\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
Stream &s = result.GetOutputStream();
|
||||
s.Printf("thread #%u: tid = %" PRIu64 "\n", thread_sp->GetIndexID(),
|
||||
thread_sp->GetID());
|
||||
|
||||
if (m_options.m_continue) {
|
||||
if (!m_last_id) {
|
||||
result.AppendMessage(" no more data\n");
|
||||
return true;
|
||||
}
|
||||
if (m_options.m_continue && m_last_id) {
|
||||
// We set up the options to continue one instruction past where
|
||||
// the previous iteration stopped.
|
||||
m_options.m_dumper_options.skip = 1;
|
||||
m_options.m_dumper_options.id = m_last_id;
|
||||
}
|
||||
|
||||
const TraceSP &trace_sp = m_exe_ctx.GetTargetSP()->GetTrace();
|
||||
TraceInstructionDumper dumper(trace_sp->GetCursor(*thread_sp), s,
|
||||
m_options.m_dumper_options);
|
||||
TraceCursorUP cursor_up =
|
||||
m_exe_ctx.GetTargetSP()->GetTrace()->GetCursor(*thread_sp);
|
||||
|
||||
if (m_options.m_dumper_options.id &&
|
||||
!cursor_up->HasId(*m_options.m_dumper_options.id)) {
|
||||
result.AppendError("invalid instruction id\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
llvm::Optional<StreamFile> out_file;
|
||||
if (m_options.m_output_file) {
|
||||
out_file.emplace(m_options.m_output_file->GetPath().c_str(),
|
||||
File::eOpenOptionWriteOnly | File::eOpenOptionCanCreate,
|
||||
lldb::eFilePermissionsFileDefault);
|
||||
}
|
||||
|
||||
TraceInstructionDumper dumper(
|
||||
std::move(cursor_up), out_file ? *out_file : result.GetOutputStream(),
|
||||
m_options.m_dumper_options);
|
||||
|
||||
if (m_options.m_continue && !m_last_id) {
|
||||
// We need to tell the dumper to stop processing data when
|
||||
// we already ran out of instructions in a previous command
|
||||
dumper.SetNoMoreData();
|
||||
}
|
||||
|
||||
m_last_id = dumper.DumpInstructions(m_options.m_count);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1120,6 +1120,9 @@ let Command = "thread trace dump instructions" in {
|
|||
Arg<"Count">,
|
||||
Desc<"The number of instructions to display starting at the most recent "
|
||||
"instruction, or the oldest if --forwards is provided.">;
|
||||
def thread_trace_dump_instructions_all : Option<"all", "a">, Group<1>,
|
||||
Desc<"From the starting point of the trace, dump all instructions "
|
||||
"available.">;
|
||||
def thread_trace_dump_instructions_id: Option<"id", "i">, Group<1>,
|
||||
Arg<"Index">,
|
||||
Desc<"Custom starting instruction id from where to start traversing. This "
|
||||
|
@ -1128,14 +1131,22 @@ let Command = "thread trace dump instructions" in {
|
|||
Arg<"Index">,
|
||||
Desc<"How many instruction to skip from the starting position of the trace "
|
||||
"before starting the traversal.">;
|
||||
def thread_trace_dump_instructions_raw : Option<"raw", "r">,
|
||||
Group<1>,
|
||||
def thread_trace_dump_instructions_raw : Option<"raw", "r">, Group<1>,
|
||||
Desc<"Dump only instruction address without disassembly nor symbol "
|
||||
"information.">;
|
||||
def thread_trace_dump_instructions_file : Option<"file", "F">, Group<1>,
|
||||
Arg<"Filename">,
|
||||
Desc<"Dump the instruction to a file instead of the standard output.">;
|
||||
def thread_trace_dump_instructions_json: Option<"json", "j">,
|
||||
Group<1>,
|
||||
Desc<"Dump in simple JSON format.">;
|
||||
def thread_trace_dump_instructions_pretty_print: Option<"pretty-json", "J">,
|
||||
Group<1>,
|
||||
Desc<"Dump in JSON format but pretty printing the output for easier readability.">;
|
||||
def thread_trace_dump_instructions_show_tsc : Option<"tsc", "t">, Group<1>,
|
||||
Desc<"For each instruction, print the corresponding timestamp counter if "
|
||||
"available.">;
|
||||
def thread_trace_dump_instructions_hide_events : Option<"events", "e">,
|
||||
def thread_trace_dump_instructions_show_events : Option<"events", "e">,
|
||||
Group<1>,
|
||||
Desc<"Dump the events that happened during the execution of the target.">;
|
||||
def thread_trace_dump_instructions_continue: Option<"continue", "C">,
|
||||
|
|
|
@ -118,7 +118,7 @@ TraceCursorIntelPT::GetInstructionControlFlowType() {
|
|||
}
|
||||
|
||||
bool TraceCursorIntelPT::GoToId(user_id_t id) {
|
||||
if (m_decoded_thread_sp->GetInstructionsCount() <= id)
|
||||
if (!HasId(id))
|
||||
return false;
|
||||
m_pos = id;
|
||||
m_tsc_range = m_decoded_thread_sp->CalculateTscRange(m_pos, m_tsc_range);
|
||||
|
@ -126,4 +126,8 @@ bool TraceCursorIntelPT::GoToId(user_id_t id) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool TraceCursorIntelPT::HasId(lldb::user_id_t id) const {
|
||||
return id < m_decoded_thread_sp->GetInstructionsCount();
|
||||
}
|
||||
|
||||
user_id_t TraceCursorIntelPT::GetId() const { return m_pos; }
|
||||
|
|
|
@ -41,6 +41,8 @@ public:
|
|||
|
||||
lldb::user_id_t GetId() const override;
|
||||
|
||||
bool HasId(lldb::user_id_t id) const override;
|
||||
|
||||
private:
|
||||
size_t GetInternalInstructionSize();
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "lldb/Target/TraceInstructionDumper.h"
|
||||
|
||||
#include "lldb/Core/Module.h"
|
||||
#include "lldb/Symbol/CompileUnit.h"
|
||||
#include "lldb/Symbol/Function.h"
|
||||
#include "lldb/Target/ExecutionContext.h"
|
||||
#include "lldb/Target/Process.h"
|
||||
|
@ -18,41 +19,23 @@ using namespace lldb;
|
|||
using namespace lldb_private;
|
||||
using namespace llvm;
|
||||
|
||||
TraceInstructionDumper::TraceInstructionDumper(
|
||||
lldb::TraceCursorUP &&cursor_up, Stream &s,
|
||||
const TraceInstructionDumperOptions &options)
|
||||
: m_cursor_up(std::move(cursor_up)), m_options(options), m_s(s) {
|
||||
// We first set the cursor in its initial position
|
||||
if (m_options.id) {
|
||||
if (!m_cursor_up->GoToId(*m_options.id)) {
|
||||
s.PutCString(" invalid instruction id\n");
|
||||
SetNoMoreData();
|
||||
return;
|
||||
}
|
||||
} else if (m_options.forwards) {
|
||||
m_cursor_up->Seek(0, TraceCursor::SeekType::Beginning);
|
||||
} else {
|
||||
m_cursor_up->Seek(0, TraceCursor::SeekType::End);
|
||||
}
|
||||
|
||||
m_cursor_up->SetForwards(m_options.forwards);
|
||||
if (m_options.skip) {
|
||||
uint64_t to_skip = *m_options.skip;
|
||||
if (m_cursor_up->Seek((m_options.forwards ? 1 : -1) * to_skip,
|
||||
TraceCursor::SeekType::Current) < to_skip) {
|
||||
// This happens when the skip value was more than the number of
|
||||
// available instructions.
|
||||
SetNoMoreData();
|
||||
}
|
||||
}
|
||||
/// \return
|
||||
/// The given string or \b None if it's empty.
|
||||
static Optional<const char *> ToOptionalString(const char *s) {
|
||||
if (!s)
|
||||
return None;
|
||||
return s;
|
||||
}
|
||||
|
||||
bool TraceInstructionDumper::TryMoveOneStep() {
|
||||
if (!m_cursor_up->Next()) {
|
||||
SetNoMoreData();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
/// \return
|
||||
/// The module name (basename if the module is a file, or the actual name if
|
||||
/// it's a virtual module), or \b nullptr if no name nor module was found.
|
||||
static const char *
|
||||
GetModuleName(const TraceInstructionDumper::InstructionEntry &insn) {
|
||||
if (!insn.symbol_info || !insn.symbol_info->sc.module_sp)
|
||||
return nullptr;
|
||||
return insn.symbol_info->sc.module_sp->GetFileSpec()
|
||||
.GetFilename()
|
||||
.AsCString();
|
||||
}
|
||||
|
||||
// This custom LineEntry validator is neded because some line_entries have
|
||||
|
@ -73,7 +56,7 @@ static bool FileLineAndColumnMatches(const LineEntry &a, const LineEntry &b) {
|
|||
return a.file == b.file;
|
||||
}
|
||||
|
||||
/// Compare the symbol contexts of the provided \a InstructionSymbolInfo
|
||||
/// Compare the symbol contexts of the provided \a SymbolInfo
|
||||
/// objects.
|
||||
///
|
||||
/// \return
|
||||
|
@ -83,9 +66,9 @@ static bool FileLineAndColumnMatches(const LineEntry &a, const LineEntry &b) {
|
|||
/// - symbol
|
||||
/// - function
|
||||
/// - line
|
||||
static bool
|
||||
IsSameInstructionSymbolContext(const InstructionSymbolInfo &prev_insn,
|
||||
const InstructionSymbolInfo &insn) {
|
||||
static bool IsSameInstructionSymbolContext(
|
||||
const TraceInstructionDumper::SymbolInfo &prev_insn,
|
||||
const TraceInstructionDumper::SymbolInfo &insn) {
|
||||
// module checks
|
||||
if (insn.sc.module_sp != prev_insn.sc.module_sp)
|
||||
return false;
|
||||
|
@ -109,63 +92,210 @@ IsSameInstructionSymbolContext(const InstructionSymbolInfo &prev_insn,
|
|||
return curr_line_valid == prev_line_valid;
|
||||
}
|
||||
|
||||
void TraceInstructionDumper::DumpInstructionSymbolContext(
|
||||
const Optional<InstructionSymbolInfo> &prev_insn,
|
||||
const InstructionSymbolInfo &insn) {
|
||||
if (prev_insn && IsSameInstructionSymbolContext(*prev_insn, insn))
|
||||
return;
|
||||
class OutputWriterCLI : public TraceInstructionDumper::OutputWriter {
|
||||
public:
|
||||
OutputWriterCLI(Stream &s, const TraceInstructionDumperOptions &options)
|
||||
: m_s(s), m_options(options){};
|
||||
|
||||
m_s << " ";
|
||||
void InfoMessage(StringRef text) override { m_s << " " << text << "\n"; }
|
||||
|
||||
if (!insn.sc.module_sp)
|
||||
m_s << "(none)";
|
||||
else if (!insn.sc.function && !insn.sc.symbol)
|
||||
m_s.Format("{0}`(none)",
|
||||
insn.sc.module_sp->GetFileSpec().GetFilename().AsCString());
|
||||
void Event(StringRef text) override { m_s.Format(" [{0}]\n", text); }
|
||||
|
||||
void
|
||||
Instruction(const TraceInstructionDumper::InstructionEntry &insn) override {
|
||||
if (insn.symbol_info) {
|
||||
if (!insn.prev_symbol_info ||
|
||||
!IsSameInstructionSymbolContext(*insn.prev_symbol_info,
|
||||
*insn.symbol_info)) {
|
||||
m_s << " ";
|
||||
const char *module_name = GetModuleName(insn);
|
||||
if (!module_name)
|
||||
m_s << "(none)";
|
||||
else if (!insn.symbol_info->sc.function && !insn.symbol_info->sc.symbol)
|
||||
m_s.Format("{0}`(none)", module_name);
|
||||
else
|
||||
insn.symbol_info->sc.DumpStopContext(
|
||||
&m_s, insn.symbol_info->exe_ctx.GetTargetPtr(),
|
||||
insn.symbol_info->address,
|
||||
/*show_fullpaths=*/false,
|
||||
/*show_module=*/true, /*show_inlined_frames=*/false,
|
||||
/*show_function_arguments=*/true,
|
||||
/*show_function_name=*/true);
|
||||
m_s << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (insn.error && !m_was_prev_instruction_an_error)
|
||||
InfoMessage("...missing instructions");
|
||||
|
||||
m_s.Format(" {0}: ", insn.id);
|
||||
|
||||
if (m_options.show_tsc) {
|
||||
m_s << "[tsc=";
|
||||
|
||||
if (insn.tsc)
|
||||
m_s.Format("{0}", *insn.tsc);
|
||||
else
|
||||
m_s << "unavailable";
|
||||
|
||||
m_s << "] ";
|
||||
}
|
||||
|
||||
if (insn.error) {
|
||||
m_s << *insn.error;
|
||||
m_was_prev_instruction_an_error = true;
|
||||
} else {
|
||||
m_s.Format("{0:x+16}", insn.load_address);
|
||||
if (insn.symbol_info) {
|
||||
m_s << " ";
|
||||
insn.symbol_info->instruction->Dump(&m_s, /*max_opcode_byte_size=*/0,
|
||||
/*show_address=*/false,
|
||||
/*show_bytes=*/false,
|
||||
&insn.symbol_info->exe_ctx,
|
||||
&insn.symbol_info->sc,
|
||||
/*prev_sym_ctx=*/nullptr,
|
||||
/*disassembly_addr_format=*/nullptr,
|
||||
/*max_address_text_size=*/0);
|
||||
}
|
||||
m_was_prev_instruction_an_error = false;
|
||||
}
|
||||
m_s << "\n";
|
||||
}
|
||||
|
||||
private:
|
||||
Stream &m_s;
|
||||
TraceInstructionDumperOptions m_options;
|
||||
bool m_was_prev_instruction_an_error = false;
|
||||
};
|
||||
|
||||
class OutputWriterJSON : public TraceInstructionDumper::OutputWriter {
|
||||
/* schema:
|
||||
error_message: string
|
||||
| {
|
||||
"event": string
|
||||
} | {
|
||||
"id": decimal,
|
||||
"tsc"?: string decimal,
|
||||
"error": string,
|
||||
| {
|
||||
"id": decimal,
|
||||
"tsc"?: string decimal,
|
||||
"module"?: string,
|
||||
"symbol"?: string,
|
||||
"line"?: decimal,
|
||||
"column"?: decimal,
|
||||
"source"?: string,
|
||||
"mnemonic"?: string,
|
||||
}
|
||||
*/
|
||||
public:
|
||||
OutputWriterJSON(Stream &s, const TraceInstructionDumperOptions &options)
|
||||
: m_s(s), m_options(options),
|
||||
m_j(m_s.AsRawOstream(),
|
||||
/*IndentSize=*/options.pretty_print_json ? 2 : 0) {
|
||||
m_j.arrayBegin();
|
||||
};
|
||||
|
||||
~OutputWriterJSON() { m_j.arrayEnd(); }
|
||||
|
||||
void Event(StringRef text) override {
|
||||
m_j.object([&] { m_j.attribute("event", text); });
|
||||
}
|
||||
|
||||
void
|
||||
Instruction(const TraceInstructionDumper::InstructionEntry &insn) override {
|
||||
m_j.object([&] {
|
||||
m_j.attribute("id", insn.id);
|
||||
if (m_options.show_tsc)
|
||||
m_j.attribute(
|
||||
"tsc",
|
||||
insn.tsc ? Optional<std::string>(std::to_string(*insn.tsc)) : None);
|
||||
|
||||
if (insn.error) {
|
||||
m_j.attribute("error", *insn.error);
|
||||
return;
|
||||
}
|
||||
|
||||
m_j.attribute("loadAddress", formatv("{0:x}", insn.load_address));
|
||||
if (insn.symbol_info) {
|
||||
m_j.attribute("module", ToOptionalString(GetModuleName(insn)));
|
||||
m_j.attribute("symbol",
|
||||
ToOptionalString(
|
||||
insn.symbol_info->sc.GetFunctionName().AsCString()));
|
||||
m_j.attribute(
|
||||
"mnemonic",
|
||||
ToOptionalString(insn.symbol_info->instruction->GetMnemonic(
|
||||
&insn.symbol_info->exe_ctx)));
|
||||
|
||||
if (IsLineEntryValid(insn.symbol_info->sc.line_entry)) {
|
||||
m_j.attribute(
|
||||
"source",
|
||||
ToOptionalString(
|
||||
insn.symbol_info->sc.line_entry.file.GetPath().c_str()));
|
||||
m_j.attribute("line", insn.symbol_info->sc.line_entry.line);
|
||||
m_j.attribute("column", insn.symbol_info->sc.line_entry.column);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
Stream &m_s;
|
||||
TraceInstructionDumperOptions m_options;
|
||||
json::OStream m_j;
|
||||
};
|
||||
|
||||
static std::unique_ptr<TraceInstructionDumper::OutputWriter>
|
||||
CreateWriter(Stream &s, const TraceInstructionDumperOptions &options) {
|
||||
if (options.json)
|
||||
return std::unique_ptr<TraceInstructionDumper::OutputWriter>(
|
||||
new OutputWriterJSON(s, options));
|
||||
else
|
||||
insn.sc.DumpStopContext(&m_s, insn.exe_ctx.GetTargetPtr(), insn.address,
|
||||
/*show_fullpaths=*/false,
|
||||
/*show_module=*/true, /*show_inlined_frames=*/false,
|
||||
/*show_function_arguments=*/true,
|
||||
/*show_function_name=*/true);
|
||||
m_s << "\n";
|
||||
return std::unique_ptr<TraceInstructionDumper::OutputWriter>(
|
||||
new OutputWriterCLI(s, options));
|
||||
}
|
||||
|
||||
void TraceInstructionDumper::DumpInstructionDisassembly(
|
||||
const InstructionSymbolInfo &insn) {
|
||||
if (!insn.instruction)
|
||||
return;
|
||||
m_s << " ";
|
||||
insn.instruction->Dump(&m_s, /*max_opcode_byte_size=*/0,
|
||||
/*show_address=*/false,
|
||||
/*show_bytes=*/false, &insn.exe_ctx, &insn.sc,
|
||||
/*prev_sym_ctx=*/nullptr,
|
||||
/*disassembly_addr_format=*/nullptr,
|
||||
/*max_address_text_size=*/0);
|
||||
TraceInstructionDumper::TraceInstructionDumper(
|
||||
lldb::TraceCursorUP &&cursor_up, Stream &s,
|
||||
const TraceInstructionDumperOptions &options)
|
||||
: m_cursor_up(std::move(cursor_up)), m_options(options),
|
||||
m_writer_up(CreateWriter(s, m_options)) {
|
||||
|
||||
if (m_options.id) {
|
||||
if (!m_cursor_up->GoToId(*m_options.id)) {
|
||||
m_writer_up->InfoMessage("invalid instruction id");
|
||||
SetNoMoreData();
|
||||
}
|
||||
} else if (m_options.forwards) {
|
||||
m_cursor_up->Seek(0, TraceCursor::SeekType::Beginning);
|
||||
} else {
|
||||
m_cursor_up->Seek(0, TraceCursor::SeekType::End);
|
||||
}
|
||||
|
||||
m_cursor_up->SetForwards(m_options.forwards);
|
||||
if (m_options.skip) {
|
||||
uint64_t to_skip = *m_options.skip;
|
||||
if (m_cursor_up->Seek((m_options.forwards ? 1 : -1) * to_skip,
|
||||
TraceCursor::SeekType::Current) < to_skip) {
|
||||
// This happens when the skip value was more than the number of
|
||||
// available instructions.
|
||||
SetNoMoreData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TraceInstructionDumper::SetNoMoreData() { m_no_more_data = true; }
|
||||
|
||||
bool TraceInstructionDumper::HasMoreData() { return !m_no_more_data; }
|
||||
|
||||
void TraceInstructionDumper::PrintMissingInstructionsMessage() {
|
||||
m_s << " ...missing instructions\n";
|
||||
}
|
||||
TraceInstructionDumper::InstructionEntry
|
||||
TraceInstructionDumper::CreatRawInstructionEntry() {
|
||||
InstructionEntry insn;
|
||||
insn.id = m_cursor_up->GetId();
|
||||
|
||||
void TraceInstructionDumper::PrintInstructionHeader() {
|
||||
m_s.Format(" {0}: ", m_cursor_up->GetId());
|
||||
|
||||
if (m_options.show_tsc) {
|
||||
m_s << "[tsc=";
|
||||
|
||||
if (Optional<uint64_t> timestamp =
|
||||
m_cursor_up->GetCounter(lldb::eTraceCounterTSC))
|
||||
m_s.Format("{0:x+16}", *timestamp);
|
||||
else
|
||||
m_s << "unavailable";
|
||||
|
||||
m_s << "] ";
|
||||
}
|
||||
if (m_options.show_tsc)
|
||||
insn.tsc = m_cursor_up->GetCounter(lldb::eTraceCounterTSC);
|
||||
return insn;
|
||||
}
|
||||
|
||||
void TraceInstructionDumper::PrintEvents() {
|
||||
|
@ -174,20 +304,21 @@ void TraceInstructionDumper::PrintEvents() {
|
|||
|
||||
trace_event_utils::ForEachEvent(
|
||||
m_cursor_up->GetEvents(), [&](TraceEvents event) {
|
||||
m_s.Format(" [{0}]\n", trace_event_utils::EventToDisplayString(event));
|
||||
m_writer_up->Event(trace_event_utils::EventToDisplayString(event));
|
||||
});
|
||||
}
|
||||
|
||||
/// Find the symbol context for the given address reusing the previous
|
||||
/// instruction's symbol context when possible.
|
||||
static SymbolContext
|
||||
CalculateSymbolContext(const Address &address,
|
||||
const InstructionSymbolInfo &prev_insn_info) {
|
||||
static SymbolContext CalculateSymbolContext(
|
||||
const Address &address,
|
||||
const TraceInstructionDumper::SymbolInfo &prev_symbol_info) {
|
||||
AddressRange range;
|
||||
if (prev_insn_info.sc.GetAddressRange(eSymbolContextEverything, 0,
|
||||
/*inline_block_range*/ false, range) &&
|
||||
if (prev_symbol_info.sc.GetAddressRange(eSymbolContextEverything, 0,
|
||||
/*inline_block_range*/ false,
|
||||
range) &&
|
||||
range.Contains(address))
|
||||
return prev_insn_info.sc;
|
||||
return prev_symbol_info.sc;
|
||||
|
||||
SymbolContext sc;
|
||||
address.CalculateSymbolContext(&sc, eSymbolContextEverything);
|
||||
|
@ -197,49 +328,48 @@ CalculateSymbolContext(const Address &address,
|
|||
/// Find the disassembler for the given address reusing the previous
|
||||
/// instruction's disassembler when possible.
|
||||
static std::tuple<DisassemblerSP, InstructionSP>
|
||||
CalculateDisass(const InstructionSymbolInfo &insn_info,
|
||||
const InstructionSymbolInfo &prev_insn_info,
|
||||
CalculateDisass(const TraceInstructionDumper::SymbolInfo &symbol_info,
|
||||
const TraceInstructionDumper::SymbolInfo &prev_symbol_info,
|
||||
const ExecutionContext &exe_ctx) {
|
||||
if (prev_insn_info.disassembler) {
|
||||
if (prev_symbol_info.disassembler) {
|
||||
if (InstructionSP instruction =
|
||||
prev_insn_info.disassembler->GetInstructionList()
|
||||
.GetInstructionAtAddress(insn_info.address))
|
||||
return std::make_tuple(prev_insn_info.disassembler, instruction);
|
||||
prev_symbol_info.disassembler->GetInstructionList()
|
||||
.GetInstructionAtAddress(symbol_info.address))
|
||||
return std::make_tuple(prev_symbol_info.disassembler, instruction);
|
||||
}
|
||||
|
||||
if (insn_info.sc.function) {
|
||||
if (symbol_info.sc.function) {
|
||||
if (DisassemblerSP disassembler =
|
||||
insn_info.sc.function->GetInstructions(exe_ctx, nullptr)) {
|
||||
symbol_info.sc.function->GetInstructions(exe_ctx, nullptr)) {
|
||||
if (InstructionSP instruction =
|
||||
disassembler->GetInstructionList().GetInstructionAtAddress(
|
||||
insn_info.address))
|
||||
symbol_info.address))
|
||||
return std::make_tuple(disassembler, instruction);
|
||||
}
|
||||
}
|
||||
// We fallback to a single instruction disassembler
|
||||
Target &target = exe_ctx.GetTargetRef();
|
||||
const ArchSpec arch = target.GetArchitecture();
|
||||
AddressRange range(insn_info.address, arch.GetMaximumOpcodeByteSize());
|
||||
AddressRange range(symbol_info.address, arch.GetMaximumOpcodeByteSize());
|
||||
DisassemblerSP disassembler =
|
||||
Disassembler::DisassembleRange(arch, /*plugin_name*/ nullptr,
|
||||
/*flavor*/ nullptr, target, range);
|
||||
return std::make_tuple(
|
||||
disassembler,
|
||||
disassembler ? disassembler->GetInstructionList().GetInstructionAtAddress(
|
||||
insn_info.address)
|
||||
symbol_info.address)
|
||||
: InstructionSP());
|
||||
}
|
||||
|
||||
Optional<lldb::user_id_t>
|
||||
TraceInstructionDumper::DumpInstructions(size_t count) {
|
||||
ThreadSP thread_sp = m_cursor_up->GetExecutionContextRef().GetThreadSP();
|
||||
if (!thread_sp) {
|
||||
m_s << "invalid thread";
|
||||
return None;
|
||||
}
|
||||
|
||||
bool was_prev_instruction_an_error = false;
|
||||
InstructionSymbolInfo prev_insn_info;
|
||||
m_writer_up->InfoMessage(formatv("thread #{0}: tid = {1}",
|
||||
thread_sp->GetIndexID(), thread_sp->GetID())
|
||||
.str());
|
||||
|
||||
SymbolInfo prev_symbol_info;
|
||||
Optional<lldb::user_id_t> last_id;
|
||||
|
||||
ExecutionContext exe_ctx;
|
||||
|
@ -247,63 +377,50 @@ TraceInstructionDumper::DumpInstructions(size_t count) {
|
|||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (!HasMoreData()) {
|
||||
m_s << " no more data\n";
|
||||
m_writer_up->InfoMessage("no more data");
|
||||
break;
|
||||
}
|
||||
last_id = m_cursor_up->GetId();
|
||||
|
||||
if (m_options.forwards) {
|
||||
// When moving forwards, we first print the event before printing
|
||||
// the actual instruction.
|
||||
PrintEvents();
|
||||
}
|
||||
|
||||
InstructionEntry insn = CreatRawInstructionEntry();
|
||||
|
||||
if (const char *err = m_cursor_up->GetError()) {
|
||||
if (!m_cursor_up->IsForwards() && !was_prev_instruction_an_error)
|
||||
PrintMissingInstructionsMessage();
|
||||
|
||||
was_prev_instruction_an_error = true;
|
||||
|
||||
PrintInstructionHeader();
|
||||
m_s << err;
|
||||
insn.error = err;
|
||||
m_writer_up->Instruction(insn);
|
||||
} else {
|
||||
if (m_cursor_up->IsForwards() && was_prev_instruction_an_error)
|
||||
PrintMissingInstructionsMessage();
|
||||
|
||||
was_prev_instruction_an_error = false;
|
||||
|
||||
InstructionSymbolInfo insn_info;
|
||||
insn.load_address = m_cursor_up->GetLoadAddress();
|
||||
|
||||
if (!m_options.raw) {
|
||||
insn_info.load_address = m_cursor_up->GetLoadAddress();
|
||||
insn_info.exe_ctx = exe_ctx;
|
||||
insn_info.address.SetLoadAddress(insn_info.load_address,
|
||||
exe_ctx.GetTargetPtr());
|
||||
insn_info.sc =
|
||||
CalculateSymbolContext(insn_info.address, prev_insn_info);
|
||||
std::tie(insn_info.disassembler, insn_info.instruction) =
|
||||
CalculateDisass(insn_info, prev_insn_info, exe_ctx);
|
||||
|
||||
DumpInstructionSymbolContext(prev_insn_info, insn_info);
|
||||
SymbolInfo symbol_info;
|
||||
symbol_info.exe_ctx = exe_ctx;
|
||||
symbol_info.address.SetLoadAddress(insn.load_address,
|
||||
exe_ctx.GetTargetPtr());
|
||||
symbol_info.sc =
|
||||
CalculateSymbolContext(symbol_info.address, prev_symbol_info);
|
||||
std::tie(symbol_info.disassembler, symbol_info.instruction) =
|
||||
CalculateDisass(symbol_info, prev_symbol_info, exe_ctx);
|
||||
insn.prev_symbol_info = prev_symbol_info;
|
||||
insn.symbol_info = symbol_info;
|
||||
prev_symbol_info = symbol_info;
|
||||
}
|
||||
|
||||
PrintInstructionHeader();
|
||||
m_s.Format("{0:x+16}", m_cursor_up->GetLoadAddress());
|
||||
|
||||
if (!m_options.raw)
|
||||
DumpInstructionDisassembly(insn_info);
|
||||
|
||||
prev_insn_info = insn_info;
|
||||
m_writer_up->Instruction(insn);
|
||||
}
|
||||
|
||||
m_s << "\n";
|
||||
|
||||
if (!m_options.forwards) {
|
||||
// If we move backwards, we print the events after printing
|
||||
// the actual instruction so that reading chronologically
|
||||
// makes sense.
|
||||
PrintEvents();
|
||||
}
|
||||
TryMoveOneStep();
|
||||
|
||||
if (!m_cursor_up->Next())
|
||||
SetNoMoreData();
|
||||
}
|
||||
return last_id;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,71 @@ class TestTraceDumpInstructions(TraceIntelPTTestCaseBase):
|
|||
substrs=["error: Process is not being traced"],
|
||||
error=True)
|
||||
|
||||
def testRawDumpInstructionsInJSON(self):
|
||||
self.expect("trace load -v " +
|
||||
os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
|
||||
substrs=["intel-pt"])
|
||||
|
||||
self.expect("thread trace dump instructions --raw --count 5 --forwards --json",
|
||||
substrs=['''[{"id":0,"loadAddress":"0x400511"},{"id":1,"loadAddress":"0x400518"},{"id":2,"loadAddress":"0x40051f"},{"id":3,"loadAddress":"0x400529"},{"id":4,"loadAddress":"0x40052d"}]'''])
|
||||
|
||||
self.expect("thread trace dump instructions --raw --count 5 --forwards --pretty-json",
|
||||
substrs=['''[
|
||||
{
|
||||
"id": 0,
|
||||
"loadAddress": "0x400511"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"loadAddress": "0x400518"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"loadAddress": "0x40051f"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"loadAddress": "0x400529"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"loadAddress": "0x40052d"
|
||||
}
|
||||
]'''])
|
||||
|
||||
def testRawDumpInstructionsInJSONToFile(self):
|
||||
self.expect("trace load -v " +
|
||||
os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
|
||||
substrs=["intel-pt"])
|
||||
|
||||
outfile = os.path.join(self.getBuildDir(), "output.json")
|
||||
|
||||
self.expect("thread trace dump instructions --raw --count 5 --forwards --pretty-json --file " + outfile)
|
||||
|
||||
with open(outfile, "r") as out:
|
||||
self.assertEqual(out.read(), '''[
|
||||
{
|
||||
"id": 0,
|
||||
"loadAddress": "0x400511"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"loadAddress": "0x400518"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"loadAddress": "0x40051f"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"loadAddress": "0x400529"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"loadAddress": "0x40052d"
|
||||
}
|
||||
]''')
|
||||
|
||||
def testRawDumpInstructions(self):
|
||||
self.expect("trace load -v " +
|
||||
os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"),
|
||||
|
@ -162,6 +227,7 @@ class TestTraceDumpInstructions(TraceIntelPTTestCaseBase):
|
|||
os.path.join(self.getSourceDir(), "intelpt-trace", "trace_bad_image.json"))
|
||||
self.expect("thread trace dump instructions --forwards",
|
||||
substrs=['''thread #1: tid = 3842849
|
||||
...missing instructions
|
||||
0: 0x0000000000400511 error: no memory mapped at this address
|
||||
1: 0x0000000000400518 error: no memory mapped at this address'''])
|
||||
|
||||
|
@ -170,8 +236,76 @@ class TestTraceDumpInstructions(TraceIntelPTTestCaseBase):
|
|||
os.path.join(self.getSourceDir(), "intelpt-trace", "trace_wrong_cpu.json"))
|
||||
self.expect("thread trace dump instructions --forwards",
|
||||
substrs=['''thread #1: tid = 3842849
|
||||
...missing instructions
|
||||
0: error: unknown cpu'''])
|
||||
|
||||
def testMultiFileTraceWithMissingModuleInJSON(self):
|
||||
self.expect("trace load " +
|
||||
os.path.join(self.getSourceDir(), "intelpt-trace-multi-file", "multi-file-no-ld.json"))
|
||||
|
||||
self.expect("thread trace dump instructions --count 3 --id 4 --forwards --pretty-json",
|
||||
substrs=['''[
|
||||
{
|
||||
"id": 4,
|
||||
"loadAddress": "0x400510",
|
||||
"module": "a.out",
|
||||
"symbol": null,
|
||||
"mnemonic": "pushq"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"loadAddress": "0x400516",
|
||||
"module": "a.out",
|
||||
"symbol": null,
|
||||
"mnemonic": "jmpq"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"error": "0x00007ffff7df1950 error: no memory mapped at this address"
|
||||
}
|
||||
]'''])
|
||||
|
||||
self.expect("thread trace dump instructions --count 4 --id 20 --forwards --pretty-json",
|
||||
substrs=['''[
|
||||
{
|
||||
"id": 20,
|
||||
"loadAddress": "0x400540",
|
||||
"module": "a.out",
|
||||
"symbol": "foo()",
|
||||
"mnemonic": "jmpq"
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"loadAddress": "0x7ffff7bd96e0",
|
||||
"module": "libfoo.so",
|
||||
"symbol": "foo()",
|
||||
"mnemonic": "pushq",
|
||||
"source": "/home/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.cpp",
|
||||
"line": 3,
|
||||
"column": 0
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"loadAddress": "0x7ffff7bd96e1",
|
||||
"module": "libfoo.so",
|
||||
"symbol": "foo()",
|
||||
"mnemonic": "movq",
|
||||
"source": "/home/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.cpp",
|
||||
"line": 3,
|
||||
"column": 0
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"loadAddress": "0x7ffff7bd96e4",
|
||||
"module": "libfoo.so",
|
||||
"symbol": "foo()",
|
||||
"mnemonic": "subq",
|
||||
"source": "/home/wallace/llvm-sand/external/llvm-project/lldb/test/API/commands/trace/intelpt-trace-multi-file/foo.cpp",
|
||||
"line": 4,
|
||||
"column": 0
|
||||
}
|
||||
]'''])
|
||||
|
||||
def testMultiFileTraceWithMissingModule(self):
|
||||
self.expect("trace load " +
|
||||
os.path.join(self.getSourceDir(), "intelpt-trace-multi-file", "multi-file-no-ld.json"))
|
||||
|
@ -201,8 +335,8 @@ class TestTraceDumpInstructions(TraceIntelPTTestCaseBase):
|
|||
a.out`(none)
|
||||
4: 0x0000000000400510 pushq 0x200af2(%rip) ; _GLOBAL_OFFSET_TABLE_ + 8
|
||||
5: 0x0000000000400516 jmpq *0x200af4(%rip) ; _GLOBAL_OFFSET_TABLE_ + 16
|
||||
6: 0x00007ffff7df1950 error: no memory mapped at this address
|
||||
...missing instructions
|
||||
6: 0x00007ffff7df1950 error: no memory mapped at this address
|
||||
a.out`main + 20 at main.cpp:10
|
||||
7: 0x0000000000400674 movl %eax, -0xc(%rbp)
|
||||
a.out`main + 23 at main.cpp:12
|
||||
|
@ -341,3 +475,15 @@ class TestTraceDumpInstructions(TraceIntelPTTestCaseBase):
|
|||
|
||||
self.expect("", substrs=['''thread #1: tid = 815455
|
||||
no more data'''])
|
||||
|
||||
|
||||
self.expect("thread trace dump instructions --raw --all --forwards",
|
||||
substrs=['''thread #1: tid = 815455
|
||||
0: 0x000000000040066f
|
||||
1: 0x0000000000400540''', '''5: 0x0000000000400516
|
||||
...missing instructions
|
||||
6: 0x00007ffff7df1950 error: no memory mapped at this address
|
||||
7: 0x0000000000400674''', '''43: 0x00000000004006a4
|
||||
44: 0x00000000004006a7
|
||||
45: 0x00000000004006a9
|
||||
no more data'''])
|
||||
|
|
|
@ -13,11 +13,11 @@ class TestTraceLoad(TraceIntelPTTestCaseBase):
|
|||
trace_description_file_path = os.path.join(src_dir, "intelpt-multi-core-trace", "trace.json")
|
||||
self.traceLoad(traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"])
|
||||
self.expect("thread trace dump instructions 2 -t",
|
||||
substrs=["19521: [tsc=0x008fb5211c143fd8] error: expected tracing enabled event",
|
||||
substrs=["19521: [tsc=40450075479261144] error: expected tracing enabled event",
|
||||
"m.out`foo() + 65 at multi_thread.cpp:12:21",
|
||||
"19520: [tsc=0x008fb5211bfbc69e] 0x0000000000400ba7 jg 0x400bb3"])
|
||||
"19520: [tsc=40450075477657246] 0x0000000000400ba7 jg 0x400bb3"])
|
||||
self.expect("thread trace dump instructions 3 -t",
|
||||
substrs=["67910: [tsc=0x008fb5211bfdf270] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)",
|
||||
substrs=["67910: [tsc=40450075477799536] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)",
|
||||
"m.out`bar() + 26 at multi_thread.cpp:20:6"])
|
||||
|
||||
@testSBAPIAndCommands
|
||||
|
@ -26,11 +26,11 @@ class TestTraceLoad(TraceIntelPTTestCaseBase):
|
|||
trace_description_file_path = os.path.join(src_dir, "intelpt-multi-core-trace", "trace_with_string_numbers.json")
|
||||
self.traceLoad(traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"])
|
||||
self.expect("thread trace dump instructions 2 -t",
|
||||
substrs=["19521: [tsc=0x008fb5211c143fd8] error: expected tracing enabled event",
|
||||
substrs=["19521: [tsc=40450075479261144] error: expected tracing enabled event",
|
||||
"m.out`foo() + 65 at multi_thread.cpp:12:21",
|
||||
"19520: [tsc=0x008fb5211bfbc69e] 0x0000000000400ba7 jg 0x400bb3"])
|
||||
"19520: [tsc=40450075477657246] 0x0000000000400ba7 jg 0x400bb3"])
|
||||
self.expect("thread trace dump instructions 3 -t",
|
||||
substrs=["67910: [tsc=0x008fb5211bfdf270] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)",
|
||||
substrs=["67910: [tsc=40450075477799536] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)",
|
||||
"m.out`bar() + 26 at multi_thread.cpp:20:6"])
|
||||
|
||||
@testSBAPIAndCommands
|
||||
|
@ -39,11 +39,11 @@ class TestTraceLoad(TraceIntelPTTestCaseBase):
|
|||
trace_description_file_path = os.path.join(src_dir, "intelpt-multi-core-trace", "trace_missing_threads.json")
|
||||
self.traceLoad(traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"])
|
||||
self.expect("thread trace dump instructions 3 -t",
|
||||
substrs=["19521: [tsc=0x008fb5211c143fd8] error: expected tracing enabled event",
|
||||
substrs=["19521: [tsc=40450075479261144] error: expected tracing enabled event",
|
||||
"m.out`foo() + 65 at multi_thread.cpp:12:21",
|
||||
"19520: [tsc=0x008fb5211bfbc69e] 0x0000000000400ba7 jg 0x400bb3"])
|
||||
"19520: [tsc=40450075477657246] 0x0000000000400ba7 jg 0x400bb3"])
|
||||
self.expect("thread trace dump instructions 2 -t",
|
||||
substrs=["67910: [tsc=0x008fb5211bfdf270] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)",
|
||||
substrs=["67910: [tsc=40450075477799536] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)",
|
||||
"m.out`bar() + 26 at multi_thread.cpp:20:6"])
|
||||
|
||||
@testSBAPIAndCommands
|
||||
|
|
|
@ -17,7 +17,7 @@ class TestTraceTimestampCounters(TraceIntelPTTestCaseBase):
|
|||
|
||||
self.expect("n")
|
||||
self.expect("thread trace dump instructions --tsc -c 1",
|
||||
patterns=["0: \[tsc=0x[0-9a-fA-F]+\] 0x0000000000400511 movl"])
|
||||
patterns=["0: \[tsc=\d+\] 0x0000000000400511 movl"])
|
||||
|
||||
@testSBAPIAndCommands
|
||||
@skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64']))
|
||||
|
@ -58,7 +58,10 @@ class TestTraceTimestampCounters(TraceIntelPTTestCaseBase):
|
|||
|
||||
self.expect("n")
|
||||
self.expect("thread trace dump instructions --tsc -c 1",
|
||||
patterns=["0: \[tsc=0x[0-9a-fA-F]+\] 0x0000000000400511 movl"])
|
||||
patterns=["0: \[tsc=\d+\] 0x0000000000400511 movl"])
|
||||
|
||||
self.expect("thread trace dump instructions --tsc -c 1 --pretty-json",
|
||||
patterns=['''"tsc": "\d+"'''])
|
||||
|
||||
@testSBAPIAndCommands
|
||||
@skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64']))
|
||||
|
@ -73,6 +76,9 @@ class TestTraceTimestampCounters(TraceIntelPTTestCaseBase):
|
|||
self.expect("thread trace dump instructions --tsc -c 1",
|
||||
patterns=["0: \[tsc=unavailable\] 0x0000000000400511 movl"])
|
||||
|
||||
self.expect("thread trace dump instructions --tsc -c 1 --json",
|
||||
patterns=['''"tsc":null'''])
|
||||
|
||||
@testSBAPIAndCommands
|
||||
@skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64']))
|
||||
def testPSBPeriod(self):
|
||||
|
|
Loading…
Reference in New Issue