Skip to content

Commit

Permalink
Add option to pass thread ID to thread select command (llvm#73596)
Browse files Browse the repository at this point in the history
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 rust-lang#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 rust-lang#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 rust-lang#2: tid = 216047, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'
  thread rust-lang#3: tid = 216048, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'
  thread rust-lang#4: tid = 216049, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'
  thread rust-lang#5: tid = 216050, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'
  thread rust-lang#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 rust-lang#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 rust-lang#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 rust-lang#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 rust-lang#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)
```
  • Loading branch information
mdko authored Dec 14, 2023
1 parent a590387 commit 4051942
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 52 deletions.
3 changes: 3 additions & 0 deletions lldb/include/lldb/Interpreter/CommandCompletions.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ class CommandCompletions {
CompletionRequest &request,
SearchFilter *searcher);

static void ThreadIDs(CommandInterpreter &interpreter,
CompletionRequest &request, SearchFilter *searcher);

/// This completer works for commands whose only arguments are a command path.
/// It isn't tied to an argument type because it completes not on a single
/// argument but on the sequence of arguments, so you have to invoke it by
Expand Down
1 change: 1 addition & 0 deletions lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ static constexpr OptionEnumValueElement g_completion_type[] = {
{lldb::eTypeCategoryNameCompletion, "type-category-name",
"Completes to a type category name."},
{lldb::eCustomCompletion, "custom", "Custom completion."},
{lldb::eThreadIDCompletion, "thread-id", "Completes to a thread ID."},
};

llvm::StringRef RegisterNameHelpTextCallback();
Expand Down
62 changes: 32 additions & 30 deletions lldb/include/lldb/lldb-enumerations.h
Original file line number Diff line number Diff line change
Expand Up @@ -1270,36 +1270,38 @@ enum WatchpointValueKind {
};

enum CompletionType {
eNoCompletion = 0u,
eSourceFileCompletion = (1u << 0),
eDiskFileCompletion = (1u << 1),
eDiskDirectoryCompletion = (1u << 2),
eSymbolCompletion = (1u << 3),
eModuleCompletion = (1u << 4),
eSettingsNameCompletion = (1u << 5),
ePlatformPluginCompletion = (1u << 6),
eArchitectureCompletion = (1u << 7),
eVariablePathCompletion = (1u << 8),
eRegisterCompletion = (1u << 9),
eBreakpointCompletion = (1u << 10),
eProcessPluginCompletion = (1u << 11),
eDisassemblyFlavorCompletion = (1u << 12),
eTypeLanguageCompletion = (1u << 13),
eFrameIndexCompletion = (1u << 14),
eModuleUUIDCompletion = (1u << 15),
eStopHookIDCompletion = (1u << 16),
eThreadIndexCompletion = (1u << 17),
eWatchpointIDCompletion = (1u << 18),
eBreakpointNameCompletion = (1u << 19),
eProcessIDCompletion = (1u << 20),
eProcessNameCompletion = (1u << 21),
eRemoteDiskFileCompletion = (1u << 22),
eRemoteDiskDirectoryCompletion = (1u << 23),
eTypeCategoryNameCompletion = (1u << 24),
// This item serves two purposes. It is the last element in the enum, so
// you can add custom enums starting from here in your Option class. Also
// if you & in this bit the base code will not process the option.
eCustomCompletion = (1u << 25)
eNoCompletion = 0ul,
eSourceFileCompletion = (1ul << 0),
eDiskFileCompletion = (1ul << 1),
eDiskDirectoryCompletion = (1ul << 2),
eSymbolCompletion = (1ul << 3),
eModuleCompletion = (1ul << 4),
eSettingsNameCompletion = (1ul << 5),
ePlatformPluginCompletion = (1ul << 6),
eArchitectureCompletion = (1ul << 7),
eVariablePathCompletion = (1ul << 8),
eRegisterCompletion = (1ul << 9),
eBreakpointCompletion = (1ul << 10),
eProcessPluginCompletion = (1ul << 11),
eDisassemblyFlavorCompletion = (1ul << 12),
eTypeLanguageCompletion = (1ul << 13),
eFrameIndexCompletion = (1ul << 14),
eModuleUUIDCompletion = (1ul << 15),
eStopHookIDCompletion = (1ul << 16),
eThreadIndexCompletion = (1ul << 17),
eWatchpointIDCompletion = (1ul << 18),
eBreakpointNameCompletion = (1ul << 19),
eProcessIDCompletion = (1ul << 20),
eProcessNameCompletion = (1ul << 21),
eRemoteDiskFileCompletion = (1ul << 22),
eRemoteDiskDirectoryCompletion = (1ul << 23),
eTypeCategoryNameCompletion = (1ul << 24),
eCustomCompletion = (1ul << 25),
eThreadIDCompletion = (1ul << 26),
// This last enum element is just for input validation.
// Add new completions before this element,
// and then increment eTerminatorCompletion's shift value
eTerminatorCompletion = (1ul << 27)
};

} // namespace lldb
Expand Down
25 changes: 22 additions & 3 deletions lldb/source/Commands/CommandCompletions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ typedef void (*CompletionCallback)(CommandInterpreter &interpreter,
lldb_private::SearchFilter *searcher);

struct CommonCompletionElement {
uint32_t type;
uint64_t type;
CompletionCallback callback;
};

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

const CommonCompletionElement common_completions[] = {
{lldb::eNoCompletion, nullptr},
{lldb::eSourceFileCompletion, CommandCompletions::SourceFiles},
{lldb::eDiskFileCompletion, CommandCompletions::DiskFiles},
{lldb::eDiskDirectoryCompletion, CommandCompletions::DiskDirectories},
Expand Down Expand Up @@ -83,12 +84,13 @@ bool CommandCompletions::InvokeCommonCompletionCallbacks(
CommandCompletions::RemoteDiskDirectories},
{lldb::eTypeCategoryNameCompletion,
CommandCompletions::TypeCategoryNames},
{lldb::CompletionType::eNoCompletion,
{lldb::eThreadIDCompletion, CommandCompletions::ThreadIDs},
{lldb::eTerminatorCompletion,
nullptr} // This one has to be last in the list.
};

for (int i = 0;; i++) {
if (common_completions[i].type == lldb::eNoCompletion)
if (common_completions[i].type == lldb::eTerminatorCompletion)
break;
else if ((common_completions[i].type & completion_mask) ==
common_completions[i].type &&
Expand Down Expand Up @@ -807,6 +809,23 @@ void CommandCompletions::TypeCategoryNames(CommandInterpreter &interpreter,
});
}

void CommandCompletions::ThreadIDs(CommandInterpreter &interpreter,
CompletionRequest &request,
SearchFilter *searcher) {
const ExecutionContext &exe_ctx = interpreter.GetExecutionContext();
if (!exe_ctx.HasProcessScope())
return;

ThreadList &threads = exe_ctx.GetProcessPtr()->GetThreadList();
lldb::ThreadSP thread_sp;
for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) {
StreamString strm;
thread_sp->GetStatus(strm, 0, 1, 1, true);
request.TryCompleteCurrentArg(std::to_string(thread_sp->GetID()),
strm.GetString());
}
}

void CommandCompletions::CompleteModifiableCmdPathArgs(
CommandInterpreter &interpreter, CompletionRequest &request,
OptionElementVector &opt_element_vector) {
Expand Down
96 changes: 81 additions & 15 deletions lldb/source/Commands/CommandObjectThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1129,11 +1129,51 @@ class CommandObjectThreadUntil : public CommandObjectParsed {

// CommandObjectThreadSelect

#define LLDB_OPTIONS_thread_select
#include "CommandOptions.inc"

class CommandObjectThreadSelect : public CommandObjectParsed {
public:
class OptionGroupThreadSelect : public OptionGroup {
public:
OptionGroupThreadSelect() { OptionParsingStarting(nullptr); }

~OptionGroupThreadSelect() override = default;

void OptionParsingStarting(ExecutionContext *execution_context) override {
m_thread_id = LLDB_INVALID_THREAD_ID;
}

Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
ExecutionContext *execution_context) override {
const int short_option = g_thread_select_options[option_idx].short_option;
switch (short_option) {
case 't': {
if (option_arg.getAsInteger(0, m_thread_id)) {
m_thread_id = LLDB_INVALID_THREAD_ID;
return Status("Invalid thread ID: '%s'.", option_arg.str().c_str());
}
break;
}

default:
llvm_unreachable("Unimplemented option");
}

return {};
}

llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
return llvm::ArrayRef(g_thread_select_options);
}

lldb::tid_t m_thread_id;
};

CommandObjectThreadSelect(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "thread select",
"Change the currently selected thread.", nullptr,
"Change the currently selected thread.",
"thread select <thread-index> (or -t <thread-id>)",
eCommandRequiresProcess | eCommandTryTargetAPILock |
eCommandProcessMustBeLaunched |
eCommandProcessMustBePaused) {
Expand All @@ -1143,13 +1183,17 @@ class CommandObjectThreadSelect : public CommandObjectParsed {
// Define the first (and only) variant of this arg.
thread_idx_arg.arg_type = eArgTypeThreadIndex;
thread_idx_arg.arg_repetition = eArgRepeatPlain;
thread_idx_arg.arg_opt_set_association = LLDB_OPT_SET_1;

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

// Push the data for the first argument into the m_arguments vector.
m_arguments.push_back(arg);

m_option_group.Append(&m_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_2);
m_option_group.Finalize();
}

~CommandObjectThreadSelect() override = default;
Expand All @@ -1165,37 +1209,59 @@ class CommandObjectThreadSelect : public CommandObjectParsed {
nullptr);
}

Options *GetOptions() override { return &m_option_group; }

protected:
void DoExecute(Args &command, CommandReturnObject &result) override {
Process *process = m_exe_ctx.GetProcessPtr();
if (process == nullptr) {
result.AppendError("no process");
return;
} else if (command.GetArgumentCount() != 1) {
} else if (m_options.m_thread_id == LLDB_INVALID_THREAD_ID &&
command.GetArgumentCount() != 1) {
result.AppendErrorWithFormat(
"'%s' takes exactly one thread index argument:\nUsage: %s\n",
"'%s' takes exactly one thread index argument, or a thread ID "
"option:\nUsage: %s\n",
m_cmd_name.c_str(), m_cmd_syntax.c_str());
return;
}

uint32_t index_id;
if (!llvm::to_integer(command.GetArgumentAtIndex(0), index_id)) {
result.AppendErrorWithFormat("Invalid thread index '%s'",
command.GetArgumentAtIndex(0));
} else if (m_options.m_thread_id != LLDB_INVALID_THREAD_ID &&
command.GetArgumentCount() != 0) {
result.AppendErrorWithFormat("'%s' cannot take both a thread ID option "
"and a thread index argument:\nUsage: %s\n",
m_cmd_name.c_str(), m_cmd_syntax.c_str());
return;
}

Thread *new_thread =
process->GetThreadList().FindThreadByIndexID(index_id).get();
if (new_thread == nullptr) {
result.AppendErrorWithFormat("invalid thread #%s.\n",
command.GetArgumentAtIndex(0));
return;
Thread *new_thread = nullptr;
if (command.GetArgumentCount() == 1) {
uint32_t index_id;
if (!llvm::to_integer(command.GetArgumentAtIndex(0), index_id)) {
result.AppendErrorWithFormat("Invalid thread index '%s'",
command.GetArgumentAtIndex(0));
return;
}
new_thread = process->GetThreadList().FindThreadByIndexID(index_id).get();
if (new_thread == nullptr) {
result.AppendErrorWithFormat("Invalid thread index #%s.\n",
command.GetArgumentAtIndex(0));
return;
}
} else {
new_thread =
process->GetThreadList().FindThreadByID(m_options.m_thread_id).get();
if (new_thread == nullptr) {
result.AppendErrorWithFormat("Invalid thread ID %" PRIu64 ".\n",
m_options.m_thread_id);
return;
}
}

process->GetThreadList().SetSelectedThreadByID(new_thread->GetID(), true);
result.SetStatus(eReturnStatusSuccessFinishNoResult);
}

OptionGroupThreadSelect m_options;
OptionGroupOptions m_option_group;
};

// CommandObjectThreadList
Expand Down
6 changes: 6 additions & 0 deletions lldb/source/Commands/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,12 @@ let Command = "thread plan list" in {
Desc<"Display thread plans for unreported threads">;
}

let Command = "thread select" in {
def thread_select_thread_id : Option<"thread-id", "t">, Group<2>,
Arg<"ThreadID">, Completion<"ThreadID">,
Desc<"Provide a thread ID instead of a thread index.">;
}

let Command = "thread trace dump function calls" in {
def thread_trace_dump_function_calls_file : Option<"file", "F">, Group<1>,
Arg<"Filename">,
Expand Down
35 changes: 31 additions & 4 deletions lldb/test/API/commands/thread/select/TestThreadSelect.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,44 @@ def test_invalid_arg(self):
self, "// break here", lldb.SBFileSpec("main.cpp")
)

self.expect(
"thread select -1", error=True, startstr="error: Invalid thread index '-1'"
)
self.expect(
"thread select 0x1ffffffff",
error=True,
startstr="error: Invalid thread index '0x1ffffffff'",
)
self.expect(
"thread select -t 0x1ffffffff",
error=True,
startstr="error: Invalid thread ID",
)
self.expect(
"thread select 1 2 3",
error=True,
startstr="error: 'thread select' takes exactly one thread index argument, or a thread ID option:",
)
self.expect(
"thread select -t 1234 1",
error=True,
startstr="error: 'thread select' cannot take both a thread ID option and a thread index argument:",
)
# Parses but not a valid thread id.
self.expect(
"thread select 0xffffffff",
error=True,
startstr="error: invalid thread #0xffffffff.",
startstr="error: Invalid thread index #0xffffffff.",
)
self.expect(
"thread select -t 0xffffffff",
error=True,
startstr="error: Invalid thread ID",
)

def test_thread_select_tid(self):
self.build()

lldbutil.run_to_source_breakpoint(
self, "// break here", lldb.SBFileSpec("main.cpp")
)
self.runCmd(
"thread select -t %d" % self.thread().GetThreadID(),
)

0 comments on commit 4051942

Please sign in to comment.