Skip to content

Commit 4051942

Browse files
authored
Add option to pass thread ID to thread select command (llvm#73596)
We'd like a way to select the current thread by its thread ID (rather than its internal LLDB thread index). This PR adds a `-t` option (`--thread_id` long option) that tells the `thread select` command to interpret the `<thread-index>` argument as a thread ID. Here's an example of it working: ``` michristensen@devbig356 llvm/llvm-project (thread-select-tid) » ../Debug/bin/lldb ~/scratch/cpp/threading/a.out (lldb) target create "/home/michristensen/scratch/cpp/threading/a.out" Current executable set to '/home/michristensen/scratch/cpp/threading/a.out' (x86_64). (lldb) b 18 Breakpoint 1: where = a.out`main + 80 at main.cpp:18:12, address = 0x0000000000000850 (lldb) run Process 215715 launched: '/home/michristensen/scratch/cpp/threading/a.out' (x86_64) This is a thread, i=1 This is a thread, i=2 This is a thread, i=3 This is a thread, i=4 This is a thread, i=5 Process 215715 stopped * thread #1, name = 'a.out', stop reason = breakpoint 1.1 frame #0: 0x0000555555400850 a.out`main at main.cpp:18:12 15 for (int i = 0; i < 5; i++) { 16 pthread_create(&thread_ids[i], NULL, foo, NULL); 17 } -> 18 for (int i = 0; i < 5; i++) { 19 pthread_join(thread_ids[i], NULL); 20 } 21 return 0; (lldb) thread select 2 * thread #2, name = 'a.out' frame #0: 0x00007ffff68f9918 libc.so.6`__nanosleep + 72 libc.so.6`__nanosleep: -> 0x7ffff68f9918 <+72>: cmpq $-0x1000, %rax ; imm = 0xF000 0x7ffff68f991e <+78>: ja 0x7ffff68f9952 ; <+130> 0x7ffff68f9920 <+80>: movl %edx, %edi 0x7ffff68f9922 <+82>: movl %eax, 0xc(%rsp) (lldb) thread info thread #2: tid = 216047, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out' (lldb) thread list Process 215715 stopped thread #1: tid = 215715, 0x0000555555400850 a.out`main at main.cpp:18:12, name = 'a.out', stop reason = breakpoint 1.1 * thread #2: tid = 216047, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out' thread #3: tid = 216048, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out' thread llvm#4: tid = 216049, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out' thread llvm#5: tid = 216050, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out' thread llvm#6: tid = 216051, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out' (lldb) thread select 215715 error: invalid thread #215715. (lldb) thread select -t 215715 * thread #1, name = 'a.out', stop reason = breakpoint 1.1 frame #0: 0x0000555555400850 a.out`main at main.cpp:18:12 15 for (int i = 0; i < 5; i++) { 16 pthread_create(&thread_ids[i], NULL, foo, NULL); 17 } -> 18 for (int i = 0; i < 5; i++) { 19 pthread_join(thread_ids[i], NULL); 20 } 21 return 0; (lldb) thread select -t 216051 * thread llvm#6, name = 'a.out' frame #0: 0x00007ffff68f9918 libc.so.6`__nanosleep + 72 libc.so.6`__nanosleep: -> 0x7ffff68f9918 <+72>: cmpq $-0x1000, %rax ; imm = 0xF000 0x7ffff68f991e <+78>: ja 0x7ffff68f9952 ; <+130> 0x7ffff68f9920 <+80>: movl %edx, %edi 0x7ffff68f9922 <+82>: movl %eax, 0xc(%rsp) (lldb) thread select 3 * thread #3, name = 'a.out' frame #0: 0x00007ffff68f9918 libc.so.6`__nanosleep + 72 libc.so.6`__nanosleep: -> 0x7ffff68f9918 <+72>: cmpq $-0x1000, %rax ; imm = 0xF000 0x7ffff68f991e <+78>: ja 0x7ffff68f9952 ; <+130> 0x7ffff68f9920 <+80>: movl %edx, %edi 0x7ffff68f9922 <+82>: movl %eax, 0xc(%rsp) (lldb) thread select -t 216048 * thread #3, name = 'a.out' frame #0: 0x00007ffff68f9918 libc.so.6`__nanosleep + 72 libc.so.6`__nanosleep: -> 0x7ffff68f9918 <+72>: cmpq $-0x1000, %rax ; imm = 0xF000 0x7ffff68f991e <+78>: ja 0x7ffff68f9952 ; <+130> 0x7ffff68f9920 <+80>: movl %edx, %edi 0x7ffff68f9922 <+82>: movl %eax, 0xc(%rsp) (lldb) thread select --thread_id 216048 * thread #3, name = 'a.out' frame #0: 0x00007ffff68f9918 libc.so.6`__nanosleep + 72 libc.so.6`__nanosleep: -> 0x7ffff68f9918 <+72>: cmpq $-0x1000, %rax ; imm = 0xF000 0x7ffff68f991e <+78>: ja 0x7ffff68f9952 ; <+130> 0x7ffff68f9920 <+80>: movl %edx, %edi 0x7ffff68f9922 <+82>: movl %eax, 0xc(%rsp) (lldb) help thread select Change the currently selected thread. Syntax: thread select <cmd-options> <thread-index> Command Options Usage: thread select [-t] <thread-index> -t ( --thread_id ) Provide a thread ID instead of a thread index. This command takes options and free-form arguments. If your arguments resemble option specifiers (i.e., they start with a - or --), you must use ' -- ' between the end of the command options and the beginning of the arguments. (lldb) c Process 215715 resuming Process 215715 exited with status = 0 (0x00000000) ```
1 parent a590387 commit 4051942

File tree

7 files changed

+176
-52
lines changed

7 files changed

+176
-52
lines changed

lldb/include/lldb/Interpreter/CommandCompletions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ class CommandCompletions {
120120
CompletionRequest &request,
121121
SearchFilter *searcher);
122122

123+
static void ThreadIDs(CommandInterpreter &interpreter,
124+
CompletionRequest &request, SearchFilter *searcher);
125+
123126
/// This completer works for commands whose only arguments are a command path.
124127
/// It isn't tied to an argument type because it completes not on a single
125128
/// argument but on the sequence of arguments, so you have to invoke it by

lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ static constexpr OptionEnumValueElement g_completion_type[] = {
192192
{lldb::eTypeCategoryNameCompletion, "type-category-name",
193193
"Completes to a type category name."},
194194
{lldb::eCustomCompletion, "custom", "Custom completion."},
195+
{lldb::eThreadIDCompletion, "thread-id", "Completes to a thread ID."},
195196
};
196197

197198
llvm::StringRef RegisterNameHelpTextCallback();

lldb/include/lldb/lldb-enumerations.h

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,36 +1270,38 @@ enum WatchpointValueKind {
12701270
};
12711271

12721272
enum CompletionType {
1273-
eNoCompletion = 0u,
1274-
eSourceFileCompletion = (1u << 0),
1275-
eDiskFileCompletion = (1u << 1),
1276-
eDiskDirectoryCompletion = (1u << 2),
1277-
eSymbolCompletion = (1u << 3),
1278-
eModuleCompletion = (1u << 4),
1279-
eSettingsNameCompletion = (1u << 5),
1280-
ePlatformPluginCompletion = (1u << 6),
1281-
eArchitectureCompletion = (1u << 7),
1282-
eVariablePathCompletion = (1u << 8),
1283-
eRegisterCompletion = (1u << 9),
1284-
eBreakpointCompletion = (1u << 10),
1285-
eProcessPluginCompletion = (1u << 11),
1286-
eDisassemblyFlavorCompletion = (1u << 12),
1287-
eTypeLanguageCompletion = (1u << 13),
1288-
eFrameIndexCompletion = (1u << 14),
1289-
eModuleUUIDCompletion = (1u << 15),
1290-
eStopHookIDCompletion = (1u << 16),
1291-
eThreadIndexCompletion = (1u << 17),
1292-
eWatchpointIDCompletion = (1u << 18),
1293-
eBreakpointNameCompletion = (1u << 19),
1294-
eProcessIDCompletion = (1u << 20),
1295-
eProcessNameCompletion = (1u << 21),
1296-
eRemoteDiskFileCompletion = (1u << 22),
1297-
eRemoteDiskDirectoryCompletion = (1u << 23),
1298-
eTypeCategoryNameCompletion = (1u << 24),
1299-
// This item serves two purposes. It is the last element in the enum, so
1300-
// you can add custom enums starting from here in your Option class. Also
1301-
// if you & in this bit the base code will not process the option.
1302-
eCustomCompletion = (1u << 25)
1273+
eNoCompletion = 0ul,
1274+
eSourceFileCompletion = (1ul << 0),
1275+
eDiskFileCompletion = (1ul << 1),
1276+
eDiskDirectoryCompletion = (1ul << 2),
1277+
eSymbolCompletion = (1ul << 3),
1278+
eModuleCompletion = (1ul << 4),
1279+
eSettingsNameCompletion = (1ul << 5),
1280+
ePlatformPluginCompletion = (1ul << 6),
1281+
eArchitectureCompletion = (1ul << 7),
1282+
eVariablePathCompletion = (1ul << 8),
1283+
eRegisterCompletion = (1ul << 9),
1284+
eBreakpointCompletion = (1ul << 10),
1285+
eProcessPluginCompletion = (1ul << 11),
1286+
eDisassemblyFlavorCompletion = (1ul << 12),
1287+
eTypeLanguageCompletion = (1ul << 13),
1288+
eFrameIndexCompletion = (1ul << 14),
1289+
eModuleUUIDCompletion = (1ul << 15),
1290+
eStopHookIDCompletion = (1ul << 16),
1291+
eThreadIndexCompletion = (1ul << 17),
1292+
eWatchpointIDCompletion = (1ul << 18),
1293+
eBreakpointNameCompletion = (1ul << 19),
1294+
eProcessIDCompletion = (1ul << 20),
1295+
eProcessNameCompletion = (1ul << 21),
1296+
eRemoteDiskFileCompletion = (1ul << 22),
1297+
eRemoteDiskDirectoryCompletion = (1ul << 23),
1298+
eTypeCategoryNameCompletion = (1ul << 24),
1299+
eCustomCompletion = (1ul << 25),
1300+
eThreadIDCompletion = (1ul << 26),
1301+
// This last enum element is just for input validation.
1302+
// Add new completions before this element,
1303+
// and then increment eTerminatorCompletion's shift value
1304+
eTerminatorCompletion = (1ul << 27)
13031305
};
13041306

13051307
} // namespace lldb

lldb/source/Commands/CommandCompletions.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ typedef void (*CompletionCallback)(CommandInterpreter &interpreter,
4444
lldb_private::SearchFilter *searcher);
4545

4646
struct CommonCompletionElement {
47-
uint32_t type;
47+
uint64_t type;
4848
CompletionCallback callback;
4949
};
5050

@@ -54,6 +54,7 @@ bool CommandCompletions::InvokeCommonCompletionCallbacks(
5454
bool handled = false;
5555

5656
const CommonCompletionElement common_completions[] = {
57+
{lldb::eNoCompletion, nullptr},
5758
{lldb::eSourceFileCompletion, CommandCompletions::SourceFiles},
5859
{lldb::eDiskFileCompletion, CommandCompletions::DiskFiles},
5960
{lldb::eDiskDirectoryCompletion, CommandCompletions::DiskDirectories},
@@ -83,12 +84,13 @@ bool CommandCompletions::InvokeCommonCompletionCallbacks(
8384
CommandCompletions::RemoteDiskDirectories},
8485
{lldb::eTypeCategoryNameCompletion,
8586
CommandCompletions::TypeCategoryNames},
86-
{lldb::CompletionType::eNoCompletion,
87+
{lldb::eThreadIDCompletion, CommandCompletions::ThreadIDs},
88+
{lldb::eTerminatorCompletion,
8789
nullptr} // This one has to be last in the list.
8890
};
8991

9092
for (int i = 0;; i++) {
91-
if (common_completions[i].type == lldb::eNoCompletion)
93+
if (common_completions[i].type == lldb::eTerminatorCompletion)
9294
break;
9395
else if ((common_completions[i].type & completion_mask) ==
9496
common_completions[i].type &&
@@ -807,6 +809,23 @@ void CommandCompletions::TypeCategoryNames(CommandInterpreter &interpreter,
807809
});
808810
}
809811

812+
void CommandCompletions::ThreadIDs(CommandInterpreter &interpreter,
813+
CompletionRequest &request,
814+
SearchFilter *searcher) {
815+
const ExecutionContext &exe_ctx = interpreter.GetExecutionContext();
816+
if (!exe_ctx.HasProcessScope())
817+
return;
818+
819+
ThreadList &threads = exe_ctx.GetProcessPtr()->GetThreadList();
820+
lldb::ThreadSP thread_sp;
821+
for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) {
822+
StreamString strm;
823+
thread_sp->GetStatus(strm, 0, 1, 1, true);
824+
request.TryCompleteCurrentArg(std::to_string(thread_sp->GetID()),
825+
strm.GetString());
826+
}
827+
}
828+
810829
void CommandCompletions::CompleteModifiableCmdPathArgs(
811830
CommandInterpreter &interpreter, CompletionRequest &request,
812831
OptionElementVector &opt_element_vector) {

lldb/source/Commands/CommandObjectThread.cpp

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,11 +1129,51 @@ class CommandObjectThreadUntil : public CommandObjectParsed {
11291129

11301130
// CommandObjectThreadSelect
11311131

1132+
#define LLDB_OPTIONS_thread_select
1133+
#include "CommandOptions.inc"
1134+
11321135
class CommandObjectThreadSelect : public CommandObjectParsed {
11331136
public:
1137+
class OptionGroupThreadSelect : public OptionGroup {
1138+
public:
1139+
OptionGroupThreadSelect() { OptionParsingStarting(nullptr); }
1140+
1141+
~OptionGroupThreadSelect() override = default;
1142+
1143+
void OptionParsingStarting(ExecutionContext *execution_context) override {
1144+
m_thread_id = LLDB_INVALID_THREAD_ID;
1145+
}
1146+
1147+
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
1148+
ExecutionContext *execution_context) override {
1149+
const int short_option = g_thread_select_options[option_idx].short_option;
1150+
switch (short_option) {
1151+
case 't': {
1152+
if (option_arg.getAsInteger(0, m_thread_id)) {
1153+
m_thread_id = LLDB_INVALID_THREAD_ID;
1154+
return Status("Invalid thread ID: '%s'.", option_arg.str().c_str());
1155+
}
1156+
break;
1157+
}
1158+
1159+
default:
1160+
llvm_unreachable("Unimplemented option");
1161+
}
1162+
1163+
return {};
1164+
}
1165+
1166+
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
1167+
return llvm::ArrayRef(g_thread_select_options);
1168+
}
1169+
1170+
lldb::tid_t m_thread_id;
1171+
};
1172+
11341173
CommandObjectThreadSelect(CommandInterpreter &interpreter)
11351174
: CommandObjectParsed(interpreter, "thread select",
1136-
"Change the currently selected thread.", nullptr,
1175+
"Change the currently selected thread.",
1176+
"thread select <thread-index> (or -t <thread-id>)",
11371177
eCommandRequiresProcess | eCommandTryTargetAPILock |
11381178
eCommandProcessMustBeLaunched |
11391179
eCommandProcessMustBePaused) {
@@ -1143,13 +1183,17 @@ class CommandObjectThreadSelect : public CommandObjectParsed {
11431183
// Define the first (and only) variant of this arg.
11441184
thread_idx_arg.arg_type = eArgTypeThreadIndex;
11451185
thread_idx_arg.arg_repetition = eArgRepeatPlain;
1186+
thread_idx_arg.arg_opt_set_association = LLDB_OPT_SET_1;
11461187

11471188
// There is only one variant this argument could be; put it into the
11481189
// argument entry.
11491190
arg.push_back(thread_idx_arg);
11501191

11511192
// Push the data for the first argument into the m_arguments vector.
11521193
m_arguments.push_back(arg);
1194+
1195+
m_option_group.Append(&m_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_2);
1196+
m_option_group.Finalize();
11531197
}
11541198

11551199
~CommandObjectThreadSelect() override = default;
@@ -1165,37 +1209,59 @@ class CommandObjectThreadSelect : public CommandObjectParsed {
11651209
nullptr);
11661210
}
11671211

1212+
Options *GetOptions() override { return &m_option_group; }
1213+
11681214
protected:
11691215
void DoExecute(Args &command, CommandReturnObject &result) override {
11701216
Process *process = m_exe_ctx.GetProcessPtr();
11711217
if (process == nullptr) {
11721218
result.AppendError("no process");
11731219
return;
1174-
} else if (command.GetArgumentCount() != 1) {
1220+
} else if (m_options.m_thread_id == LLDB_INVALID_THREAD_ID &&
1221+
command.GetArgumentCount() != 1) {
11751222
result.AppendErrorWithFormat(
1176-
"'%s' takes exactly one thread index argument:\nUsage: %s\n",
1223+
"'%s' takes exactly one thread index argument, or a thread ID "
1224+
"option:\nUsage: %s\n",
11771225
m_cmd_name.c_str(), m_cmd_syntax.c_str());
11781226
return;
1179-
}
1180-
1181-
uint32_t index_id;
1182-
if (!llvm::to_integer(command.GetArgumentAtIndex(0), index_id)) {
1183-
result.AppendErrorWithFormat("Invalid thread index '%s'",
1184-
command.GetArgumentAtIndex(0));
1227+
} else if (m_options.m_thread_id != LLDB_INVALID_THREAD_ID &&
1228+
command.GetArgumentCount() != 0) {
1229+
result.AppendErrorWithFormat("'%s' cannot take both a thread ID option "
1230+
"and a thread index argument:\nUsage: %s\n",
1231+
m_cmd_name.c_str(), m_cmd_syntax.c_str());
11851232
return;
11861233
}
11871234

1188-
Thread *new_thread =
1189-
process->GetThreadList().FindThreadByIndexID(index_id).get();
1190-
if (new_thread == nullptr) {
1191-
result.AppendErrorWithFormat("invalid thread #%s.\n",
1192-
command.GetArgumentAtIndex(0));
1193-
return;
1235+
Thread *new_thread = nullptr;
1236+
if (command.GetArgumentCount() == 1) {
1237+
uint32_t index_id;
1238+
if (!llvm::to_integer(command.GetArgumentAtIndex(0), index_id)) {
1239+
result.AppendErrorWithFormat("Invalid thread index '%s'",
1240+
command.GetArgumentAtIndex(0));
1241+
return;
1242+
}
1243+
new_thread = process->GetThreadList().FindThreadByIndexID(index_id).get();
1244+
if (new_thread == nullptr) {
1245+
result.AppendErrorWithFormat("Invalid thread index #%s.\n",
1246+
command.GetArgumentAtIndex(0));
1247+
return;
1248+
}
1249+
} else {
1250+
new_thread =
1251+
process->GetThreadList().FindThreadByID(m_options.m_thread_id).get();
1252+
if (new_thread == nullptr) {
1253+
result.AppendErrorWithFormat("Invalid thread ID %" PRIu64 ".\n",
1254+
m_options.m_thread_id);
1255+
return;
1256+
}
11941257
}
11951258

11961259
process->GetThreadList().SetSelectedThreadByID(new_thread->GetID(), true);
11971260
result.SetStatus(eReturnStatusSuccessFinishNoResult);
11981261
}
1262+
1263+
OptionGroupThreadSelect m_options;
1264+
OptionGroupOptions m_option_group;
11991265
};
12001266

12011267
// CommandObjectThreadList

lldb/source/Commands/Options.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,12 @@ let Command = "thread plan list" in {
11171117
Desc<"Display thread plans for unreported threads">;
11181118
}
11191119

1120+
let Command = "thread select" in {
1121+
def thread_select_thread_id : Option<"thread-id", "t">, Group<2>,
1122+
Arg<"ThreadID">, Completion<"ThreadID">,
1123+
Desc<"Provide a thread ID instead of a thread index.">;
1124+
}
1125+
11201126
let Command = "thread trace dump function calls" in {
11211127
def thread_trace_dump_function_calls_file : Option<"file", "F">, Group<1>,
11221128
Arg<"Filename">,

lldb/test/API/commands/thread/select/TestThreadSelect.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,44 @@ def test_invalid_arg(self):
1212
self, "// break here", lldb.SBFileSpec("main.cpp")
1313
)
1414

15-
self.expect(
16-
"thread select -1", error=True, startstr="error: Invalid thread index '-1'"
17-
)
1815
self.expect(
1916
"thread select 0x1ffffffff",
2017
error=True,
2118
startstr="error: Invalid thread index '0x1ffffffff'",
2219
)
20+
self.expect(
21+
"thread select -t 0x1ffffffff",
22+
error=True,
23+
startstr="error: Invalid thread ID",
24+
)
25+
self.expect(
26+
"thread select 1 2 3",
27+
error=True,
28+
startstr="error: 'thread select' takes exactly one thread index argument, or a thread ID option:",
29+
)
30+
self.expect(
31+
"thread select -t 1234 1",
32+
error=True,
33+
startstr="error: 'thread select' cannot take both a thread ID option and a thread index argument:",
34+
)
2335
# Parses but not a valid thread id.
2436
self.expect(
2537
"thread select 0xffffffff",
2638
error=True,
27-
startstr="error: invalid thread #0xffffffff.",
39+
startstr="error: Invalid thread index #0xffffffff.",
40+
)
41+
self.expect(
42+
"thread select -t 0xffffffff",
43+
error=True,
44+
startstr="error: Invalid thread ID",
45+
)
46+
47+
def test_thread_select_tid(self):
48+
self.build()
49+
50+
lldbutil.run_to_source_breakpoint(
51+
self, "// break here", lldb.SBFileSpec("main.cpp")
52+
)
53+
self.runCmd(
54+
"thread select -t %d" % self.thread().GetThreadID(),
2855
)

0 commit comments

Comments
 (0)