Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to pass thread ID to thread select command #73596

Merged
merged 7 commits into from
Dec 14, 2023

Conversation

mdko
Copy link
Contributor

@mdko mdko commented Nov 28, 2023

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 #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 <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)

@mdko mdko requested a review from JDevlieghere as a code owner November 28, 2023 01:03
@llvmbot llvmbot added the lldb label Nov 28, 2023
@mdko
Copy link
Contributor Author

mdko commented Nov 28, 2023

CC @jeffreytan81 @clayborg

@llvmbot
Copy link
Member

llvmbot commented Nov 28, 2023

@llvm/pr-subscribers-lldb

Author: Michael Christensen (mdko)

Changes

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 &lt;thread-index&gt; 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 &lt; 5; i++) {
   16       pthread_create(&amp;thread_ids[i], NULL, foo, NULL);
   17     }
-&gt; 18     for (int i = 0; i &lt; 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:
-&gt;  0x7ffff68f9918 &lt;+72&gt;: cmpq   $-0x1000, %rax ; imm = 0xF000
    0x7ffff68f991e &lt;+78&gt;: ja     0x7ffff68f9952 ; &lt;+130&gt;
    0x7ffff68f9920 &lt;+80&gt;: movl   %edx, %edi
    0x7ffff68f9922 &lt;+82&gt;: 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 &lt; 5; i++) {
   16       pthread_create(&amp;thread_ids[i], NULL, foo, NULL);
   17     }
-&gt; 18     for (int i = 0; i &lt; 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:
-&gt;  0x7ffff68f9918 &lt;+72&gt;: cmpq   $-0x1000, %rax ; imm = 0xF000
    0x7ffff68f991e &lt;+78&gt;: ja     0x7ffff68f9952 ; &lt;+130&gt;
    0x7ffff68f9920 &lt;+80&gt;: movl   %edx, %edi
    0x7ffff68f9922 &lt;+82&gt;: 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:
-&gt;  0x7ffff68f9918 &lt;+72&gt;: cmpq   $-0x1000, %rax ; imm = 0xF000
    0x7ffff68f991e &lt;+78&gt;: ja     0x7ffff68f9952 ; &lt;+130&gt;
    0x7ffff68f9920 &lt;+80&gt;: movl   %edx, %edi
    0x7ffff68f9922 &lt;+82&gt;: 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:
-&gt;  0x7ffff68f9918 &lt;+72&gt;: cmpq   $-0x1000, %rax ; imm = 0xF000
    0x7ffff68f991e &lt;+78&gt;: ja     0x7ffff68f9952 ; &lt;+130&gt;
    0x7ffff68f9920 &lt;+80&gt;: movl   %edx, %edi
    0x7ffff68f9922 &lt;+82&gt;: 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:
-&gt;  0x7ffff68f9918 &lt;+72&gt;: cmpq   $-0x1000, %rax ; imm = 0xF000
    0x7ffff68f991e &lt;+78&gt;: ja     0x7ffff68f9952 ; &lt;+130&gt;
    0x7ffff68f9920 &lt;+80&gt;: movl   %edx, %edi
    0x7ffff68f9922 &lt;+82&gt;: movl   %eax, 0xc(%rsp)
(lldb) help thread select
Change the currently selected thread.

Syntax: thread select &lt;cmd-options&gt; &lt;thread-index&gt;

Command Options Usage:
  thread select [-t] &lt;thread-index&gt;

       -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)

Full diff: https://github.com/llvm/llvm-project/pull/73596.diff

3 Files Affected:

  • (modified) lldb/source/Commands/CommandObjectThread.cpp (+53-6)
  • (modified) lldb/source/Commands/Options.td (+5)
  • (modified) lldb/test/API/commands/thread/select/TestThreadSelect.py (+20-3)
diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp
index a9f5a4f8a4fbd71c..9384df319cc221dd 100644
--- a/lldb/source/Commands/CommandObjectThread.cpp
+++ b/lldb/source/Commands/CommandObjectThread.cpp
@@ -1129,8 +1129,44 @@ class CommandObjectThreadUntil : public CommandObjectParsed {
 
 // CommandObjectThreadSelect
 
+#define LLDB_OPTIONS_thread_select
+#include "CommandOptions.inc"
+
 class CommandObjectThreadSelect : public CommandObjectParsed {
 public:
+  class CommandOptions : public Options {
+  public:
+    CommandOptions() { OptionParsingStarting(nullptr); }
+
+    ~CommandOptions() override = default;
+
+    void OptionParsingStarting(ExecutionContext *execution_context) override {
+      m_thread_id = false;
+    }
+
+    Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+                          ExecutionContext *execution_context) override {
+      const int short_option = m_getopt_table[option_idx].val;
+      switch (short_option) {
+      case 't': {
+        m_thread_id = true;
+        break;
+      }
+
+      default:
+        llvm_unreachable("Unimplemented option");
+      }
+
+      return {};
+    }
+
+    llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
+      return llvm::ArrayRef(g_thread_select_options);
+    }
+
+    bool m_thread_id;
+  };
+
   CommandObjectThreadSelect(CommandInterpreter &interpreter)
       : CommandObjectParsed(interpreter, "thread select",
                             "Change the currently selected thread.", nullptr,
@@ -1165,6 +1201,8 @@ class CommandObjectThreadSelect : public CommandObjectParsed {
         nullptr);
   }
 
+  Options *GetOptions() override { return &m_options; }
+
 protected:
   void DoExecute(Args &command, CommandReturnObject &result) override {
     Process *process = m_exe_ctx.GetProcessPtr();
@@ -1173,22 +1211,29 @@ class CommandObjectThreadSelect : public CommandObjectParsed {
       return;
     } else if (command.GetArgumentCount() != 1) {
       result.AppendErrorWithFormat(
-          "'%s' takes exactly one thread index argument:\nUsage: %s\n",
-          m_cmd_name.c_str(), m_cmd_syntax.c_str());
+          "'%s' takes exactly one thread %s argument:\nUsage: %s\n",
+          m_cmd_name.c_str(), m_options.m_thread_id ? "ID" : "index",
+          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'",
+      result.AppendErrorWithFormat("Invalid thread %s '%s'",
+                                   m_options.m_thread_id ? "ID" : "index",
                                    command.GetArgumentAtIndex(0));
       return;
     }
 
-    Thread *new_thread =
-        process->GetThreadList().FindThreadByIndexID(index_id).get();
+    Thread *new_thread = nullptr;
+    if (m_options.m_thread_id) {
+        new_thread = process->GetThreadList().FindThreadByID(index_id).get();
+    } else {
+        new_thread = process->GetThreadList().FindThreadByIndexID(index_id).get();
+    }
     if (new_thread == nullptr) {
-      result.AppendErrorWithFormat("invalid thread #%s.\n",
+      result.AppendErrorWithFormat("invalid thread %s%s.\n",
+                                   m_options.m_thread_id ? "ID " : "#",
                                    command.GetArgumentAtIndex(0));
       return;
     }
@@ -1196,6 +1241,8 @@ class CommandObjectThreadSelect : public CommandObjectParsed {
     process->GetThreadList().SetSelectedThreadByID(new_thread->GetID(), true);
     result.SetStatus(eReturnStatusSuccessFinishNoResult);
   }
+
+  CommandOptions m_options;
 };
 
 // CommandObjectThreadList
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 542c78be5a12dada..23886046df8f673e 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1117,6 +1117,11 @@ let Command = "thread plan list" in {
     Desc<"Display thread plans for unreported threads">;
 }
 
+let Command = "thread select" in {
+  def thread_thread_id : Option<"thread_id", "t">,
+    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 91f8909471bf2bbe..4d01b82d9d947e5b 100644
--- a/lldb/test/API/commands/thread/select/TestThreadSelect.py
+++ b/lldb/test/API/commands/thread/select/TestThreadSelect.py
@@ -12,17 +12,34 @@ 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 '0x1ffffffff'",
+        )
         # Parses but not a valid thread id.
         self.expect(
             "thread select 0xffffffff",
             error=True,
             startstr="error: invalid thread #0xffffffff.",
         )
+        self.expect(
+            "thread select -t 0xffffffff",
+            error=True,
+            startstr="error: invalid thread ID 0xffffffff.",
+        )
+
+    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(),
+        )

Copy link

github-actions bot commented Nov 28, 2023

✅ With the latest revision this PR passed the C/C++ code formatter.

@jimingham
Copy link
Collaborator

I would prefer you make the -t an option with a value, rather than a flag saying what the argument means. It makes the completer much easier to hook up since the option value can complete against the native thread ID's, and the argument complete against thread indexes. You CAN write completers that look at the whole line, check what options are provided and complete based on that. break set does this for break set -n Foo -s libmylib.dylib if you want to have a look at that. But otherwise, making the option have a value will be more straightforward.

@mdko
Copy link
Contributor Author

mdko commented Nov 28, 2023

@jimingham So it seems that when -t <tid> is supplied, the user shouldn't be able to specify a thread index as well, e.g. the following is disallowed:

thread select -t 216051 1

Would it make sense then for thread select to take two possible mutually exclusive options (-t <tid>) and (-i <index>), with the -i option being the default (i.e. doesn't need to be specified)? e.g.

thread select -t 216051

and

thread select -i 1
thread select 1

(where the -i doesn't need to be supplied by default)?

It makes the completer much easier to hook up since the option value can complete against the native thread ID's

Sounds good, I'll work on adding the complete for the native thread ID option with the above change.

@jimingham
Copy link
Collaborator

jimingham commented Nov 28, 2023 via email

Copy link
Collaborator

@clayborg clayborg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename long option and change it so the option requires a value, and make the code use the option value instead of command argument overloading.

lldb/source/Commands/Options.td Outdated Show resolved Hide resolved
@clayborg clayborg requested a review from jimingham November 28, 2023 21:48
@mdko
Copy link
Contributor Author

mdko commented Nov 29, 2023

Latest changes implement reviewer suggestions:

  • thread_id option is now thread-id
  • This option takes a value rather than causing original argument to be interpreted differently
  • This option is now in a separate grouping, so we can show the two separate ways of calling this command: thread select <thread-index> and thread select [-t <thread-id>]. This is similar to what shows up with memory write ...

Example usage:

michristensen@devbig356 llvm/Debug » ./bin/lldb ~/scratch/cpp/virtual/a.out
error: module importing failed: This script interpreter does not support importing modules.
NAME         PASS     STOP     NOTIFY
===========  =======  =======  =======
SIGPIPE      true     false    false
(lldb) target create "/home/michristensen/scratch/cpp/virtual/a.out"
Current executable set to '/home/michristensen/scratch/cpp/virtual/a.out' (x86_64).
(lldb) b 18
Breakpoint 1: where = a.out`main + 13 at t2.cpp:20:14, address = 0x0000000000000c2d
(lldb) ^D
michristensen@devbig356 llvm/Debug »
michristensen@devbig356 llvm/Debug » ./bin/lldb ~/scratch/cpp/threading/a.out
error: module importing failed: This script interpreter does not support importing modules.
NAME         PASS     STOP     NOTIFY
===========  =======  =======  =======
SIGPIPE      true     false    false
(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 3517265 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 3517265 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 = 3517533, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'

(lldb) thread list
Process 3517265 stopped
  thread #1: tid = 3517265, 0x0000555555400850 a.out`main at main.cpp:18:12, name = 'a.out', stop reason = breakpoint 1.1
* thread #2: tid = 3517533, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'
  thread #3: tid = 3517534, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'
  thread #4: tid = 3517535, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'
  thread #5: tid = 3517536, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'
  thread #6: tid = 3517537, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'
(lldb) thread select 3517265
error: Invalid thread #3517265.
(lldb) thread select -t 3517265
* 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 3517534
* 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 6
* 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 info
thread #6: tid = 3517537, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'

(lldb) thread select -t 3517536
* thread #5, 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 #5: tid = 3517536, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'

(lldb) help thread select
Change the currently selected thread.

Syntax: thread select <cmd-options> <thread-index>

Command Options Usage:
  thread select <thread-index>
  thread select [-t <thread-id>]

       -t <thread-id> ( --thread-id <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) thread select 0
error: Invalid thread #0.
(lldb) thread select -t 0
error: 'thread select' takes exactly one thread index argument, or a thread ID option:
Usage: thread select <cmd-options> <thread-index>
(lldb) thread select -t 1
error: Invalid thread ID 1.
(lldb) thread select -t 3517536 1
error: 'thread select' cannot take both a thread ID option and a thread index argument:
Usage: thread select <cmd-options> <thread-index>

Copy link
Collaborator

@clayborg clayborg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great changes, just a few inline comments.

lldb/source/Commands/CommandObjectThread.cpp Outdated Show resolved Hide resolved
lldb/source/Commands/Options.td Outdated Show resolved Hide resolved
lldb/source/Commands/CommandObjectThread.cpp Outdated Show resolved Hide resolved
@mdko
Copy link
Contributor Author

mdko commented Nov 29, 2023

Latest changes update help and usage text, format specifier, add a general thread ID completion mechanism, and use this thread ID completion in the thread select -t argument. (If the thread ID completer is satisfactory, I can add it to other ThreadID args in Options.td in a later PR.)

I'm not sure why the buildkite/github-pull-requests CI check is failing though. It says it's failing to checkout the commit (https://buildkite.com/llvm-project/github-pull-requests/builds/18426#018c1a42-0b4f-4fa6-8208-f821616e5579).

Example of changes, including completion.

michristensen@devbig356 llvm/Debug » ./bin/lldb ~/scratch/cpp/threading/a.out
NAME         PASS     STOP     NOTIFY
===========  =======  =======  =======
SIGPIPE      true     false    false
(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 2749371 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 2749371 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 list
Process 2749371 stopped
* thread #1: tid = 2749371, 0x0000555555400850 a.out`main at main.cpp:18:12, name = 'a.out', stop reason = breakpoint 1.1
  thread #2: tid = 2749646, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'
  thread #3: tid = 2749647, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'
  thread #4: tid = 2749648, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'
  thread #5: tid = 2749649, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'
  thread #6: tid = 2749650, 0x00007ffff68f9918 libc.so.6`__nanosleep + 72, name = 'a.out'
(lldb) thread select -t 2749
Available completions:
        2749371 -- * 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;

        2749646 --   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)

        2749647 --   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)

        2749648 --   thread #4, 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)

        2749649 --   thread #5, 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)

        2749650 --   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 -t 2749371
* 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
Available completions:
        1 -- * 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;

        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)

        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)

        4 --   thread #4, 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)

        5 --   thread #5, 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)

        6 --   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 6
* 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 -t 1 2
error: 'thread select' cannot take both a thread ID option and a thread index argument:
Usage: thread select <thread-index> (or -t <thread-id>)

Copy link
Collaborator

@jimingham jimingham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great. Thanks for adding the thread ID completer as well!


ThreadList &threads = exe_ctx.GetProcessPtr()->GetThreadList();
lldb::ThreadSP thread_sp;
for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nit, don't block the review on this, but why do you have ()s here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's to avoid this warning:

/home/michristensen/llvm/llvm-project/lldb/source/Commands/CommandCompletions.cpp:820:36: warning: using the result of an assignment as a condition without parentheses [-Wparentheses]
  for (uint32_t idx = 0; thread_sp = threads.GetThreadAtIndex(idx); ++idx) {
                         ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/michristensen/llvm/llvm-project/lldb/source/Commands/CommandCompletions.cpp:820:36: note: place parentheses around the assignment to silence this warning
  for (uint32_t idx = 0; thread_sp = threads.GetThreadAtIndex(idx); ++idx) {

It's also consistent to how it was done in the ThreadIndex completer code:

for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) {

Copy link
Contributor

@felipepiovezan felipepiovezan Nov 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wow, I had not noticed what we were doing here, we usually see that pattern in ifs, but not so much in fors

So that means that if one of the thread pointers in the middle of the list is null, we stop processing other threads? This seems counter intuitive, I would have expected the code to be:

for (auto idx : llvm::seq<uint32_t>(threads.GetSize()) {
   const auto thread_sp = threads.GetThreadAtIndex(idx);
   if (!thread_sp) continue;
   ...
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(please don't resolve conversations until the author has had a chance to look at the answer)

Copy link
Collaborator

@clayborg clayborg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't change the numbers of any public API enums. Fix that and this is ready to go!

@@ -1296,10 +1296,11 @@ enum CompletionType {
eRemoteDiskFileCompletion = (1u << 22),
eRemoteDiskDirectoryCompletion = (1u << 23),
eTypeCategoryNameCompletion = (1u << 24),
eThreadIDCompletion = (1u << 25),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Public enums are part of our public API, we can't add new values in the middle, we must always add them to the end. If someone compiled against an older LLDB, they would have 25 meaning eCustomCompletion, but if the re-link allowing the above change it would now mean eThreadIDCompletion

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clayborg Is there anything special about eCustomCompletion being last? The comment in 1300-1302 seems to allude to this, but I don't see anything in the code base requiring this nor any other custom completions.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clayborg Is there anything special about eCustomCompletion being last? The comment in 1300-1302 seems to allude to this, but I don't see anything in the code base requiring this nor any other custom completions.

Yes, this is a problem. If there ever is a magic bit it should be something like:

eCustomCompletion = (1u << 63) // Pick the last bit in a 64 bit value

But this is public API now which causes a problem for reasons I mentioned before. @jimingham any ideas on if we care about adding the thread ID before the eCustomCompletion from an API standpoint? lldb-rpc-server is the main thing I worry about here since it sends enums as integers, not as strings which are then converted back into integers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jimingham What are your thoughts on putting eThreadIDCompletion before eCustomCompletion? (See @clayborg 's comments above)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent of the API standpoint, Greg is right. This is ABI-breaking, something we try hard to avoid in LLDB. Even though programs linked against older versions of LLDB will still be able to run against newer versions of LLDB, the semantics of these values have changed and the program may do the wrong thing. Even though I don't think it's likely that this will affect many folks, you can never be too sure.

https://lldb.llvm.org/resources/sbapi.html

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though this was a public enum, there was never anything you could do with it from the SB API's. The only place you could set completions at all from the outside was in command script add and that didn't use the enum values, it used string equivalents.
So I think at this point we can change it as we need.
We also don't add completions that extend this table anywhere in lldb that I can see, we use custom completion functions that we use by hand in the HandleCompletion's instead. And anyway, all that is internal to lldb_private, that's not exposed.

So I think none of this is really designed or used at this point, and we should think of what we want to do with this.

I think eCustomCompletion should mean "this object implements a custom completion, use that instead of any of the built-in ones." So overloading it to be the enum terminator is wrong, since its value cannot float, it has to have a definite meaning.

The terminator is useful for input validation. The "parsed command" patch I'm working on WILL allow users to specify a completion for the arguments, and it would be useful to be able to say "that enum value you just passed me was out of the range of valid completions." But I think that's all it's needed for.

So I'd suggest leaving eCustomCompletion where it is, adding the Thread ID one after and then adding a terminator for input validation.

@mdko
Copy link
Contributor Author

mdko commented Dec 5, 2023

@clayborg Latest change:

  • Reverts eCustomCompletion to original value
  • Moves eThreadIDCompletion after eCustomCompletion
  • Adds eTerminatorCompletion as last value in enum
  • Sets all values to 64 bit to future-proof it

Copy link
Collaborator

@jimingham jimingham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine, except you need to fix the eTerminatorCompletion value.

eThreadIDCompletion = (1ul << 26),
// This last enum element is just for input validation.
// Add new completions before this element.
eTerminatorCompletion = (1ul << 63)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't right, eTerminatorCompletion should be (1ul << 27) here. I want to be able to write:

if (number_passed_in >= eTerminatorCompletion)
error("You passed an invalid completion")

The value of eTerminatorCompletion gets shifted up each time someone adds a new completion, that allows each lldb version to check the input value against the completions it knows.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jimingham Thanks for the quick feedback, makes sense. Latest changes update eTerminatorCompletion value based on your comments.

Copy link
Collaborator

@jimingham jimingham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@mdko
Copy link
Contributor Author

mdko commented Dec 14, 2023

@jimingham @clayborg IIUC I don't have merge permissions, otherwise I'd squash and merge this myself. Please let me know if there's anything I should do at this point. Thanks!

@clayborg clayborg merged commit 4051942 into llvm:main Dec 14, 2023
4 checks passed
@mdko mdko deleted the thread-select-tid branch December 15, 2023 17:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants