From 405194257506685ca11848fbaff79c4333c18c3b Mon Sep 17 00:00:00 2001 From: Michael Christensen Date: Thu, 14 Dec 2023 15:19:38 -0800 Subject: [PATCH] Add option to pass thread ID to thread select command (#73596) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 `` 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 #4: tid = 216049, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out' thread #5: tid = 216050, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out' thread #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 #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 Command Options Usage: thread select [-t] -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) ``` --- .../lldb/Interpreter/CommandCompletions.h | 3 + .../Interpreter/CommandOptionArgumentTable.h | 1 + lldb/include/lldb/lldb-enumerations.h | 62 ++++++------ lldb/source/Commands/CommandCompletions.cpp | 25 ++++- lldb/source/Commands/CommandObjectThread.cpp | 96 ++++++++++++++++--- lldb/source/Commands/Options.td | 6 ++ .../thread/select/TestThreadSelect.py | 35 ++++++- 7 files changed, 176 insertions(+), 52 deletions(-) diff --git a/lldb/include/lldb/Interpreter/CommandCompletions.h b/lldb/include/lldb/Interpreter/CommandCompletions.h index a89a5be95b801c..c7292b3b1471a8 100644 --- a/lldb/include/lldb/Interpreter/CommandCompletions.h +++ b/lldb/include/lldb/Interpreter/CommandCompletions.h @@ -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 diff --git a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h index 4bf71393bb07dc..d0cf54c31ca73f 100644 --- a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h +++ b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h @@ -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(); diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index 633a3ee696c208..ed1dec85d48406 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -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 diff --git a/lldb/source/Commands/CommandCompletions.cpp b/lldb/source/Commands/CommandCompletions.cpp index 0b69ce098195b1..e766a6c8c10bcc 100644 --- a/lldb/source/Commands/CommandCompletions.cpp +++ b/lldb/source/Commands/CommandCompletions.cpp @@ -44,7 +44,7 @@ typedef void (*CompletionCallback)(CommandInterpreter &interpreter, lldb_private::SearchFilter *searcher); struct CommonCompletionElement { - uint32_t type; + uint64_t type; CompletionCallback callback; }; @@ -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}, @@ -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 && @@ -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) { diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp index a9f5a4f8a4fbd7..dd9fab4bbddabc 100644 --- a/lldb/source/Commands/CommandObjectThread.cpp +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -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 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 (or -t )", eCommandRequiresProcess | eCommandTryTargetAPILock | eCommandProcessMustBeLaunched | eCommandProcessMustBePaused) { @@ -1143,6 +1183,7 @@ 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. @@ -1150,6 +1191,9 @@ class CommandObjectThreadSelect : public CommandObjectParsed { // 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; @@ -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 diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index 542c78be5a12da..ed3167727bcd32 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -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">, diff --git a/lldb/test/API/commands/thread/select/TestThreadSelect.py b/lldb/test/API/commands/thread/select/TestThreadSelect.py index 91f8909471bf2b..2a2cef4a905c92 100644 --- a/lldb/test/API/commands/thread/select/TestThreadSelect.py +++ b/lldb/test/API/commands/thread/select/TestThreadSelect.py @@ -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(), )