I have a question about LLDB Python scripting.
I'm running a sample script from the book "Advanced Apple Debugging & Reverse Engineering":
https://github.com/kodecocodes/dbg-materials/blob/editions/4.0/22-script-bridging-classes-and-hierarchy/projects/final/BreakAfterRegex.py
This script defines a command called bar
, which does the following:
However, when I load this script in LLDB (lldb-1500.0.22.8 on macOS Sonoma 14.5) and execute the command, step 4 doesn't occur. The execution doesn't continue unless I manually enter the c command in LLDB. If I remove the thread.StepOver() call, step 4 works correctly. How should I modify the script to ensure that step 4 (continuing execution) works properly?
Steps to reproduced are as follows.
$ lldb --version
lldb-1500.0.22.8
Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
$ cat test.m
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *filePath = @"/tmp/hoge.txt";
NSString *content = @"Hello World";
NSError *error;
BOOL success = [content writeToFile:filePath
atomically:YES
encoding:NSUTF8StringEncoding
error:&error];
if (success) {
NSLog(@"Success");
} else {
NSLog(@"Error");
}
}
return 0;
}
$ clang -framework Foundation test.m -o test
$ lldb ./test
(lldb) target create "./test"
Current executable set to '/Users/user/test' (x86_64).
(lldb) command script import ./lldb/BreakAfterRegex.py
(lldb) bar NSObject.init\] SBBreakpoint: id = 1, regex = 'NSObject.init\]', locations = 2
(lldb) r
Process 2754 launched: '/Users/user/test' (x86_64)
(lldb) ********************************************************************************
breakpoint: -[NSObject init]
object: <_NSThreadData: 0x600002fe8000>
stopped:-[_NSThreadData init]
Process 2754 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step out
frame #0: 0x00007ff8130211c2 Foundation`-[_NSThreadData init] + 55
Foundation`-[_NSThreadData init]:
-> 0x7ff8130211c2 <+55>: movq 0x411c5737(%rip), %rcx
0x7ff8130211c9 <+62>: movq (%rcx), %rcx
0x7ff8130211cc <+65>: cmpq -0x8(%rbp), %rcx
0x7ff8130211d0 <+69>: jne 0x7ff8130211d8 ; <+77>
Target 0: (test) stopped.
(lldb) c
Process 2754 resuming
(lldb) ********************************************************************************
breakpoint: -[NSObject init]
object: <NSProcessInfo: 0x6000010e0000>
stopped:__28+[NSProcessInfo processInfo]_block_invoke
Process 2754 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step out
frame #0: 0x00007ff81284b59b Foundation`__28+[NSProcessInfo processInfo]_block_invoke + 32
Foundation`:
-> 0x7ff81284b59b <+32>: movq %rax, 0x4080027e(%rip) ; processInfoCache
0x7ff81284b5a2 <+39>: popq %rbp
0x7ff81284b5a3 <+40>: retq
Foundation`-[NSProcessInfo arguments]:
0x7ff81284b5a4 <+0>: pushq %rbp
Target 0: (test) stopped.
(lldb) c
Process 2754 resuming
2024-08-12 16:43:30.871560+0900 test[2754:88642] Success
Process 2754 exited with status = 0 (0x00000000)
After running the r
command, breakpoints are hit several times, but it should not occur because the breakpointHandler function returns False.
I looked at this a bit further. The problem is that the callback installed by the bar
lldb command runs the StepOut
command and then runs return False
. But because running code in a breakpoint command can recursively re-enter the breakpoint commands (and lldb's command interpreter doesn't handle that), a breakpoint callback stops running when it calls any code that runs the target, which means that it never runs the return False
line.
This example was written before lldb had "scripted thread plans" which are the real way to have lldb run the process a bit for you and then react to wherever that stops next. For instance, the FinishPrintAndContinue thread plan from:
https://github.com/llvm/llvm-project/blob/main/lldb/examples/python/scripted_step.py
shows an example of how to do this. So the "correct" way to write the breakpoint callback is to make a scripted step class that does the reporting the way the example did, then use the SBThread::StepUsingScriptedThreadPlan - passing False for resume_immediately
, then return False from the breakpoint command. That will cause the process to resume, and then your scripted thread plan will take over, and do the step-out and recording in the normal lldb event loop.