Hello,
I’m trying to write a plugin for LLDB for my use. I’m having a problem understanding how am I supposed to use SBProcess::GetSelectedThread()
. When writing a plugin and using it from the command-line, everything works as expected: GetSelectedThread()
returns the currently selected thread. But when LLDB runs my command from e.g. breakpoint’s callback declared with the -C option, the GetSelectedThread()
seems to always return the first thread instead of the selected thread. But, at the same time, LLDB’s built-in commands work fine (they return the selected thread instead of the first thread).
I have prepared a simple proof of concept that shows what I mean in more details. This behaves the same way on Linux (lldb 13.0.0) and macOS (lldb-1316.0.9.41). It doesn’t matter if I try to write a native plugin in C++, or use Python wrappers.
First I’ve prepared a small threadtool
app that creates another thread and runs some function inside this new thread:
#include <thread>
#include <cstdio>
int main() {
std::thread t([] () {
fopen("", "");
});
t.join();
return 0;
}
The point of this small threadtool
app is that I want to create a process which spawns a new thread, and I want to force LLDB to break into a non-primary thread inside a process by setting a breakpoint to “fopen” later. I’ve compiled this tool it using:
$ g++ threadtool -o threadtool
Then, my Python plugin uses a simple command that just prints current thread ID. The code is (‘plugin.py’):
import sys
import lldb
sys.path.append("/home/antek/dev/haxe/lldbscripts/bin")
import main
import argparse
def cmd_db(ctx, cmd, result, state):
tgt = ctx.GetSelectedTarget()
proc = tgt.GetProcess()
thread = proc.GetSelectedThread()
print("Return of GetSelectedThread() is: {}".format(thread.GetThreadID()))
Then I’m using this command line to run LLDB:
$ lldb -O "command script import plugin.py" -O "command script add -f stubs.cmd_db db" threadtool -o 'break set -n fopen -C "register read rip" -C "db" -C "thread info"' -o 'r' -o 'thread list' -o 'register read rip' -o 'db' -o 'q'
To make it clear, this command will:
- Load the ‘plugin.py’ to LLDB,
- Define a new “db” that will run the code from inside the Python script,
- Load the “threadtool” application into LLDB
- Set a new breakpoint using function name trigger “fopen”, and declare on-trigger callbacks:
a) First, run “register read rip” command,
b) Then, run the “db” command (defined in Python.py),
c) Then, run the “thread info” command. - Then issue the “r” command, so this will run the “threadtool” app.
- LLDB will break of the fopen() function inside the second thread, and will run all callbacks defined in step 4.
- Then, it will run “thread list”
- It will run “register read rip”
- It will run “db”
A. And last, it will quit.
This is the log of an example session:
1 (lldb) command script import src/stubs.py
2 (lldb) command script add -f stubs.cmd_db db
3 (lldb) target create "threadtool"
4 Current executable set to '[...]/threadtool' (x86_64).
5 (lldb) break set -n fopen -C "register read rip" -C "db" -C "thread info"
6 Breakpoint 1: no locations (pending).
7 WARNING: Unable to resolve breakpoint to any actual locations.
8 (lldb) r
9 1 location added to breakpoint 1
10 (lldb) register read rip
11 rip = 0x00007ffff7bc26e0 libc.so.6`_IO_new_fopen at iofopen.c:85:1
12 Return of GetSelectedThread() is: 14753
13 (lldb) db
14 (lldb) thread info
15 thread #2: tid = 14788, 0x00007ffff7bc26e0 libc.so.6`_IO_new_fopen(filename="", mode="") at iofopen.c:85:1, name = 'threadtool', stop reason = breakpoint 1.1
16 Process 14753 stopped
17 * thread #2, name = 'threadtool', stop reason = breakpoint 1.1
18 frame #0: 0x00007ffff7bc26e0 libc.so.6`_IO_new_fopen(filename="", mode="") at iofopen.c:85:1
19 Process 14753 launched: '[...]/threadtool' (x86_64)
20 (lldb) thread list
21 Process 14753 stopped
22 thread #1: tid = 14753, 0x00007ffff7bd4019 libc.so.6`__GI___futex_abstimed_wait_cancelable64 at futex-internal.c:57:12, name = 'threadtool'
23 * thread #2: tid = 14788, 0x00007ffff7bc26e0 libc.so.6`_IO_new_fopen(filename="", mode="") at iofopen.c:85:1, name = 'threadtool', stop reason = breakpoint 1.1
24 (lldb) register read rip
25 rip = 0x00007ffff7bc26e0 libc.so.6`_IO_new_fopen at iofopen.c:85:1
26 (lldb) db
27 Return of GetSelectedThread() is: 14788
28 (lldb) q
As you can see, when calling my db
Python command from inside the breakpoint’s callback function, the GetSelectedThread()
returns TID 14753 (line 12). But when calling the db
command from normal REPL context, it returns the proper value of 14788 (line 27).
I’m calling the second TID “proper”, because built-in LLDB functions, as can be seen in lines 11 and 15, actually already use TID 14788. So the built-in functions behave the same way when they’re called from the breakpoint callback context, and from REPL context. But, from some reason GetSelectedThread()
from the public API returns the wrong thread when called from the breakpoint context.
Is this expected and am I using the API in a wrong way, or is it a bug?
If I’m using the API in a wrong way, what would be the preferred way of getting the proper thread ID in this situation? One idea I’m having would be to simply enumerate all threads, and find the first thread with the stop reason different than lldb::StopReason::eStopReasonNone
(it seems to work after a quick test). Would that be a good approach?