Skip to content

Commit 05b4bf2

Browse files
author
Walter Erquinigo
committed
[trace][intelpt] Introduce instruction Ids
In order to support quick arbitrary access to instructions in the trace, we need each instruction to have an id. It could be an index or any other value that the trace plugin defines. This will be useful for reverse debugging or for creating callstacks, as each frame will need an instruction id associated with them. I've updated the `thread trace dump instructions` command accordingly. It now prints the instruction id instead of relative offset. I've also added a new --id argument that allows starting the dump from an arbitrary position. Differential Revision: https://reviews.llvm.org/D122254
1 parent 0d237d1 commit 05b4bf2

File tree

13 files changed

+481
-326
lines changed

13 files changed

+481
-326
lines changed

lldb/include/lldb/Target/TraceCursor.h

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@ class TraceCursor {
7474
public:
7575
/// Helper enum to indicate the reference point when invoking
7676
/// \a TraceCursor::Seek().
77+
/// The following values are inspired by \a std::istream::seekg.
7778
enum class SeekType {
7879
/// The beginning of the trace, i.e the oldest item.
79-
Set = 0,
80+
Beginning = 0,
8081
/// The current position in the trace.
8182
Current,
8283
/// The end of the trace, i.e the most recent item.
@@ -133,6 +134,51 @@ class TraceCursor {
133134
/// \b true if the cursor effectively moved, \b false otherwise.
134135
virtual bool Next() = 0;
135136

137+
/// Instruction identifiers:
138+
///
139+
/// When building complex higher level tools, fast random accesses in the
140+
/// trace might be needed, for which each instruction requires a unique
141+
/// identifier within its thread trace. For example, a tool might want to
142+
/// repeatedly inspect random consecutive portions of a trace. This means that
143+
/// it will need to first move quickly to the beginning of each section and
144+
/// then start its iteration. Given that the number of instructions can be in
145+
/// the order of hundreds of millions, fast random access is necessary.
146+
///
147+
/// An example of such a tool could be an inspector of the call graph of a
148+
/// trace, where each call is represented with its start and end instructions.
149+
/// Inspecting all the instructions of a call requires moving to its first
150+
/// instruction and then iterating until the last instruction, which following
151+
/// the pattern explained above.
152+
///
153+
/// Instead of using 0-based indices as identifiers, each Trace plug-in can
154+
/// decide the nature of these identifiers and thus no assumptions can be made
155+
/// regarding their ordering and sequentiality. The reason is that an
156+
/// instruction might be encoded by the plug-in in a way that hides its actual
157+
/// 0-based index in the trace, but it's still possible to efficiently find
158+
/// it.
159+
///
160+
/// Requirements:
161+
/// - For a given thread, no two instructions have the same id.
162+
/// - In terms of efficiency, moving the cursor to a given id should be as
163+
/// fast as possible, but not necessarily O(1). That's why the recommended
164+
/// way to traverse sequential instructions is to use the \a
165+
/// TraceCursor::Next() method and only use \a TraceCursor::GoToId(id)
166+
/// sparingly.
167+
168+
/// Make the cursor point to the item whose identifier is \p id.
169+
///
170+
/// \return
171+
/// \b true if the given identifier exists and the cursor effectively
172+
/// moved. Otherwise, \b false is returned and the cursor doesn't change
173+
/// its position.
174+
virtual bool GoToId(lldb::user_id_t id) = 0;
175+
176+
/// \return
177+
/// A unique identifier for the instruction or error this cursor is
178+
/// pointing to.
179+
virtual lldb::user_id_t GetId() const = 0;
180+
/// \}
181+
136182
/// Make the cursor point to an item in the trace based on an origin point and
137183
/// an offset. This API doesn't distinguishes instruction types nor errors in
138184
/// the trace, unlike the \a TraceCursor::Next() method.
@@ -152,7 +198,7 @@ class TraceCursor {
152198
///
153199
/// \return
154200
/// The number of trace items moved from the origin.
155-
virtual size_t Seek(ssize_t offset, SeekType origin) = 0;
201+
virtual uint64_t Seek(int64_t offset, SeekType origin) = 0;
156202

157203
/// \return
158204
/// The \a ExecutionContextRef of the backing thread from the creation time

lldb/include/lldb/Target/TraceInstructionDumper.h

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,26 @@
1313

1414
namespace lldb_private {
1515

16+
/// Class that holds the configuration used by \a TraceInstructionDumper for
17+
/// traversing and dumping instructions.
18+
struct TraceInstructionDumperOptions {
19+
/// If \b true, the cursor will be iterated forwards starting from the
20+
/// oldest instruction. Otherwise, the iteration starts from the most
21+
/// recent instruction.
22+
bool forwards = false;
23+
/// Dump only instruction addresses without disassembly nor symbol
24+
/// information.
25+
bool raw = false;
26+
/// For each instruction, print the corresponding timestamp counter if
27+
/// available.
28+
bool show_tsc = false;
29+
/// Optional custom id to start traversing from.
30+
llvm::Optional<uint64_t> id = llvm::None;
31+
/// Optional number of instructions to skip from the starting position
32+
/// of the cursor.
33+
llvm::Optional<size_t> skip = llvm::None;
34+
};
35+
1636
/// Class used to dump the instructions of a \a TraceCursor using its current
1737
/// state and granularity.
1838
class TraceInstructionDumper {
@@ -22,52 +42,46 @@ class TraceInstructionDumper {
2242
/// \param[in] cursor
2343
/// The cursor whose instructions will be dumped.
2444
///
25-
/// \param[in] initial_index
26-
/// Presentation index to use for referring to the current instruction
27-
/// of the cursor. If the direction is forwards, the index will increase,
28-
/// and if the direction is backwards, the index will decrease.
29-
///
30-
/// \param[in] raw
31-
/// Dump only instruction addresses without disassembly nor symbol
32-
/// information.
45+
/// \param[in] s
46+
/// The stream where to dump the instructions to.
3347
///
34-
/// \param[in] show_tsc
35-
/// For each instruction, print the corresponding timestamp counter if
36-
/// available.
37-
TraceInstructionDumper(lldb::TraceCursorUP &&cursor_up, int initial_index = 0,
38-
bool raw = false, bool show_tsc = false);
48+
/// \param[in] options
49+
/// Additional options for configuring the dumping.
50+
TraceInstructionDumper(lldb::TraceCursorUP &&cursor_up, Stream &s,
51+
const TraceInstructionDumperOptions &options);
3952

4053
/// Dump \a count instructions of the thread trace starting at the current
4154
/// cursor position.
4255
///
4356
/// This effectively moves the cursor to the next unvisited position, so that
4457
/// a subsequent call to this method continues where it left off.
4558
///
46-
/// \param[in] s
47-
/// The stream object where the instructions are printed.
48-
///
4959
/// \param[in] count
5060
/// The number of instructions to print.
51-
void DumpInstructions(Stream &s, size_t count);
52-
53-
/// Indicate the dumper that no more data is available in the trace.
54-
void SetNoMoreData();
61+
///
62+
/// \return
63+
/// The instruction id of the last traversed instruction, or \b llvm::None
64+
/// if no instructions were visited.
65+
llvm::Optional<lldb::user_id_t> DumpInstructions(size_t count);
5566

5667
/// \return
5768
/// \b true if there's still more data to traverse in the trace.
5869
bool HasMoreData();
5970

6071
private:
72+
/// Indicate to the dumper that no more data is available in the trace.
73+
/// This will prevent further iterations.
74+
void SetNoMoreData();
75+
6176
/// Move the cursor one step.
6277
///
6378
/// \return
6479
/// \b true if the cursor moved.
6580
bool TryMoveOneStep();
6681

6782
lldb::TraceCursorUP m_cursor_up;
68-
int m_index;
69-
bool m_raw;
70-
bool m_show_tsc;
83+
TraceInstructionDumperOptions m_options;
84+
Stream &m_s;
7185
/// If \b true, all the instructions have been traversed.
7286
bool m_no_more_data = false;
7387
};

lldb/source/Commands/CommandObjectThread.cpp

Lines changed: 64 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2099,8 +2099,7 @@ class CommandObjectTraceStop : public CommandObjectMultipleThreads {
20992099
#define LLDB_OPTIONS_thread_trace_dump_instructions
21002100
#include "CommandOptions.inc"
21012101

2102-
class CommandObjectTraceDumpInstructions
2103-
: public CommandObjectIterateOverThreads {
2102+
class CommandObjectTraceDumpInstructions : public CommandObjectParsed {
21042103
public:
21052104
class CommandOptions : public Options {
21062105
public:
@@ -2132,19 +2131,29 @@ class CommandObjectTraceDumpInstructions
21322131
"invalid integer value for option '%s'",
21332132
option_arg.str().c_str());
21342133
else
2135-
m_skip = skip;
2134+
m_dumper_options.skip = skip;
2135+
break;
2136+
}
2137+
case 'i': {
2138+
uint64_t id;
2139+
if (option_arg.empty() || option_arg.getAsInteger(0, id))
2140+
error.SetErrorStringWithFormat(
2141+
"invalid integer value for option '%s'",
2142+
option_arg.str().c_str());
2143+
else
2144+
m_dumper_options.id = id;
21362145
break;
21372146
}
21382147
case 'r': {
2139-
m_raw = true;
2148+
m_dumper_options.raw = true;
21402149
break;
21412150
}
21422151
case 'f': {
2143-
m_forwards = true;
2152+
m_dumper_options.forwards = true;
21442153
break;
21452154
}
21462155
case 't': {
2147-
m_show_tsc = true;
2156+
m_dumper_options.show_tsc = true;
21482157
break;
21492158
}
21502159
case 'C': {
@@ -2159,11 +2168,8 @@ class CommandObjectTraceDumpInstructions
21592168

21602169
void OptionParsingStarting(ExecutionContext *execution_context) override {
21612170
m_count = kDefaultCount;
2162-
m_skip = 0;
2163-
m_raw = false;
2164-
m_forwards = false;
2165-
m_show_tsc = false;
21662171
m_continue = false;
2172+
m_dumper_options = {};
21672173
}
21682174

21692175
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
@@ -2174,23 +2180,19 @@ class CommandObjectTraceDumpInstructions
21742180

21752181
// Instance variables to hold the values for command options.
21762182
size_t m_count;
2177-
size_t m_skip;
2178-
bool m_raw;
2179-
bool m_forwards;
2180-
bool m_show_tsc;
2181-
bool m_continue;
2183+
size_t m_continue;
2184+
TraceInstructionDumperOptions m_dumper_options;
21822185
};
21832186

21842187
CommandObjectTraceDumpInstructions(CommandInterpreter &interpreter)
2185-
: CommandObjectIterateOverThreads(
2188+
: CommandObjectParsed(
21862189
interpreter, "thread trace dump instructions",
2187-
"Dump the traced instructions for one or more threads. If no "
2188-
"threads are specified, show the current thread. Use the "
2189-
"thread-index \"all\" to see all threads.",
2190+
"Dump the traced instructions for one thread. If no "
2191+
"thread is specified, show the current thread.",
21902192
nullptr,
2191-
eCommandRequiresProcess | eCommandTryTargetAPILock |
2192-
eCommandProcessMustBeLaunched | eCommandProcessMustBePaused |
2193-
eCommandProcessMustBeTraced) {}
2193+
eCommandRequiresProcess | eCommandRequiresThread |
2194+
eCommandTryTargetAPILock | eCommandProcessMustBeLaunched |
2195+
eCommandProcessMustBePaused | eCommandProcessMustBeTraced) {}
21942196

21952197
~CommandObjectTraceDumpInstructions() override = default;
21962198

@@ -2200,57 +2202,63 @@ class CommandObjectTraceDumpInstructions
22002202
uint32_t index) override {
22012203
std::string cmd;
22022204
current_command_args.GetCommandString(cmd);
2203-
if (cmd.find("--continue") == std::string::npos)
2205+
if (cmd.find(" --continue") == std::string::npos)
22042206
cmd += " --continue";
22052207
return cmd;
22062208
}
22072209

22082210
protected:
2209-
bool DoExecute(Args &args, CommandReturnObject &result) override {
2210-
if (!m_options.m_continue)
2211-
m_dumpers.clear();
2211+
ThreadSP GetThread(Args &args, CommandReturnObject &result) {
2212+
if (args.GetArgumentCount() == 0)
2213+
return m_exe_ctx.GetThreadSP();
22122214

2213-
return CommandObjectIterateOverThreads::DoExecute(args, result);
2214-
}
2215-
2216-
bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override {
2217-
Stream &s = result.GetOutputStream();
2215+
const char *arg = args.GetArgumentAtIndex(0);
2216+
uint32_t thread_idx;
22182217

2219-
const TraceSP &trace_sp = m_exe_ctx.GetTargetSP()->GetTrace();
2218+
if (!llvm::to_integer(arg, thread_idx)) {
2219+
result.AppendErrorWithFormat("invalid thread specification: \"%s\"\n",
2220+
arg);
2221+
return nullptr;
2222+
}
22202223
ThreadSP thread_sp =
2221-
m_exe_ctx.GetProcessPtr()->GetThreadList().FindThreadByID(tid);
2222-
2223-
if (!m_dumpers.count(thread_sp->GetID())) {
2224-
lldb::TraceCursorUP cursor_up = trace_sp->GetCursor(*thread_sp);
2225-
// Set up the cursor and return the presentation index of the first
2226-
// instruction to dump after skipping instructions.
2227-
auto setUpCursor = [&]() {
2228-
cursor_up->SetForwards(m_options.m_forwards);
2229-
if (m_options.m_forwards)
2230-
return cursor_up->Seek(m_options.m_skip, TraceCursor::SeekType::Set);
2231-
return -cursor_up->Seek(-m_options.m_skip, TraceCursor::SeekType::End);
2232-
};
2233-
2234-
int initial_index = setUpCursor();
2224+
m_exe_ctx.GetProcessRef().GetThreadList().FindThreadByIndexID(
2225+
thread_idx);
2226+
if (!thread_sp)
2227+
result.AppendErrorWithFormat("no thread with index: \"%s\"\n", arg);
2228+
return thread_sp;
2229+
}
22352230

2236-
auto dumper = std::make_unique<TraceInstructionDumper>(
2237-
std::move(cursor_up), initial_index, m_options.m_raw,
2238-
m_options.m_show_tsc);
2231+
bool DoExecute(Args &args, CommandReturnObject &result) override {
2232+
ThreadSP thread_sp = GetThread(args, result);
2233+
if (!thread_sp)
2234+
return false;
22392235

2240-
// This happens when the seek value was more than the number of available
2241-
// instructions.
2242-
if (std::abs(initial_index) < (int)m_options.m_skip)
2243-
dumper->SetNoMoreData();
2236+
Stream &s = result.GetOutputStream();
2237+
s.Printf("thread #%u: tid = %" PRIu64 "\n", thread_sp->GetIndexID(),
2238+
thread_sp->GetID());
22442239

2245-
m_dumpers[thread_sp->GetID()] = std::move(dumper);
2240+
if (m_options.m_continue) {
2241+
if (!m_last_id) {
2242+
result.AppendMessage(" no more data\n");
2243+
return true;
2244+
}
2245+
// We set up the options to continue one instruction past where
2246+
// the previous iteration stopped.
2247+
m_options.m_dumper_options.skip = 1;
2248+
m_options.m_dumper_options.id = m_last_id;
22462249
}
22472250

2248-
m_dumpers[thread_sp->GetID()]->DumpInstructions(s, m_options.m_count);
2251+
const TraceSP &trace_sp = m_exe_ctx.GetTargetSP()->GetTrace();
2252+
TraceInstructionDumper dumper(trace_sp->GetCursor(*thread_sp), s,
2253+
m_options.m_dumper_options);
2254+
m_last_id = dumper.DumpInstructions(m_options.m_count);
22492255
return true;
22502256
}
22512257

22522258
CommandOptions m_options;
2253-
std::map<lldb::tid_t, std::unique_ptr<TraceInstructionDumper>> m_dumpers;
2259+
// Last traversed id used to continue a repeat command. None means
2260+
// that all the trace has been consumed.
2261+
llvm::Optional<lldb::user_id_t> m_last_id;
22542262
};
22552263

22562264
// CommandObjectTraceDumpInfo

lldb/source/Commands/Options.td

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,25 +1097,30 @@ let Command = "thread plan list" in {
10971097
}
10981098

10991099
let Command = "thread trace dump instructions" in {
1100-
def thread_trace_dump_instructions_forwards: Option<"forwards", "f">, Group<1>,
1100+
def thread_trace_dump_instructions_forwards: Option<"forwards", "f">,
1101+
Group<1>,
11011102
Desc<"If specified, the trace is traversed forwards chronologically "
11021103
"starting at the oldest instruction. Otherwise, it starts at the most "
11031104
"recent one and the traversal is backwards.">;
11041105
def thread_trace_dump_instructions_count : Option<"count", "c">, Group<1>,
11051106
Arg<"Count">,
11061107
Desc<"The number of instructions to display starting at the most recent "
11071108
"instruction, or the oldest if --forwards is provided.">;
1108-
def thread_trace_dump_instructions_skip: Option<"skip", "s">,
1109-
Group<1>,
1109+
def thread_trace_dump_instructions_id: Option<"id", "i">, Group<1>,
11101110
Arg<"Index">,
1111-
Desc<"How many instruction to skip from the end of the trace to start "
1112-
"dumping instructions, or from the beginning if --forwards is provided">;
1111+
Desc<"Custom starting instruction id from where to start traversing. This "
1112+
"id can be provided in decimal or hexadecimal representation.">;
1113+
def thread_trace_dump_instructions_skip: Option<"skip", "s">, Group<1>,
1114+
Arg<"Index">,
1115+
Desc<"How many instruction to skip from the starting position of the trace "
1116+
"before starting the traversal.">;
11131117
def thread_trace_dump_instructions_raw : Option<"raw", "r">,
11141118
Group<1>,
1115-
Desc<"Dump only instruction address without disassembly nor symbol information.">;
1116-
def thread_trace_dump_instructions_show_tsc : Option<"tsc", "t">,
1117-
Group<1>,
1118-
Desc<"For each instruction, print the corresponding timestamp counter if available.">;
1119+
Desc<"Dump only instruction address without disassembly nor symbol "
1120+
"information.">;
1121+
def thread_trace_dump_instructions_show_tsc : Option<"tsc", "t">, Group<1>,
1122+
Desc<"For each instruction, print the corresponding timestamp counter if "
1123+
"available.">;
11191124
def thread_trace_dump_instructions_continue: Option<"continue", "C">,
11201125
Group<1>,
11211126
Desc<"Continue dumping instructions right where the previous invocation of this "

0 commit comments

Comments
 (0)