llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
29.35k stars 12.14k forks source link

lldb: Calling `SBBreakpoint.SetScriptCallbackFunction` does not work when using Python scripts #112186

Open chenhuimao opened 1 month ago

chenhuimao commented 1 month ago

When I use lldb python script, calling SBBreakpoint.SetScriptCallbackFunction does not work in some cases. This is my script:

# MyScript.py

import lldb

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand(f'command script add -f MyScript.custom_command custom_command -h "Demo for SetScriptCallbackFunction API"')

def custom_command(debugger, command, exe_ctx, result, internal_dict):
    address_int = int(command, 16)
    bp = exe_ctx.GetTarget().BreakpointCreateByAddress(address_int)
    bp.SetScriptCallbackFunction("MyScript.breakpoint_function_wrapper")

def breakpoint_function_wrapper(frame, bp_loc, extra_args, internal_dict):
    print("hit breakpoint")
    return True

I made sure that the address entered is valid, but the breakpoint_function_wrapper function is not executed after hitting the breakpoint. In addition, when I create a breakpoint using another interface BreakpointCreateByName, SetScriptCallbackFunction worked:

bp = exe_ctx.GetTarget().BreakpointCreateByName("objc_msgSend", "libobjc.A.dylib")
bp.SetScriptCallbackFunction("MyScript.breakpoint_function_wrapper")

Environment: [sys version] macOS 15.0 Python 3.9.6 (default, Aug 9 2024, 14:24:13) [Clang 16.0.0 (clang-1600.0.26.3)] [LLDB version] lldb-1600.0.36.3 Apple Swift version 6.0 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) [Target triple] arm64-apple-ios- [Xcode version] 1600 (16A242d) [Model identifier] iPhone13,2 [iOS version] iOS 18.0.1

llvmbot commented 1 month ago

@llvm/issue-subscribers-lldb

Author: mao2020 (chenhuimao)

When I use lldb python script, calling `SBBreakpoint.SetScriptCallbackFunction` does not work in some cases. This is my script: ``` # MyScript.py import lldb def __lldb_init_module(debugger, internal_dict): debugger.HandleCommand(f'command script add -f MyScript.custom_command custom_command -h "Demo for SetScriptCallbackFunction API"') def custom_command(debugger, command, exe_ctx, result, internal_dict): address_int = int(command, 16) bp = exe_ctx.GetTarget().BreakpointCreateByAddress(address_int) bp.SetScriptCallbackFunction("MyScript.breakpoint_function_wrapper") def breakpoint_function_wrapper(frame, bp_loc, extra_args, internal_dict): print("hit breakpoint") return True ``` I made sure that the address entered is valid, but the `breakpoint_function_wrapper` function is not executed after hitting the breakpoint. In addition, when I create a breakpoint using another interface `BreakpointCreateByName`, `SetScriptCallbackFunction` worked: ``` bp = exe_ctx.GetTarget().BreakpointCreateByName("objc_msgSend", "libobjc.A.dylib") bp.SetScriptCallbackFunction("MyScript.breakpoint_function_wrapper") ``` Environment: [sys version] macOS 15.0 Python 3.9.6 (default, Aug 9 2024, 14:24:13) [Clang 16.0.0 (clang-1600.0.26.3)] [LLDB version] lldb-1600.0.36.3 Apple Swift version 6.0 (swiftlang-6.0.0.9.10 clang-1600.0.26.2) [Target triple] arm64-apple-ios- [Xcode version] 1600 (16A242d) [Model identifier] iPhone13,2 [iOS version] iOS 18.0.1
jimingham commented 1 month ago

Make sure that your address location is actually listed as "resolved" in the cases where you aren't seeing your callback called.

(lldb) break list
...
  1.1: address = 0x000000018deb2000, unresolved, hit count = 0 

That's an address breakpoint that we did NOT set. If you set an address breakpoint before running that didn't resolve to a section in a binary plus an offset, either because there was no binary at that address pre-run or there were more than one, then on run lldb won't set it for you. You have to re-enable it for it to be set.

Writing a trap into the executable is a dangerous thing to do. For instance, if lldb gets the address wrong and write the trap into some data in the program, it will have caused a subtle and hard to track down debugger-caused bug. So given we know libraries can slide around when you re-run, we don't automatically reset "by address" breakpoints.

The fact that by name breakpoints don't have this problem leads me to think the breakpoint trap just might not be getting inserted when this fails for you. We know that when we find a symbol, it will point to the beginning of an instruction, so it is safe, but that's not true for bare addresses.

jimingham commented 1 month ago

If you know you are setting a pre-run breakpoint at some address in some shared library, you can tell lldb that by passing the --shlib argument to break set. On most systems, pre-run all the libraries overlap; that's the most common reason why we can't turn your address into a "offset from section A in binary B" form, and telling us which shared library to use resolves that ambiguity.

chenhuimao commented 1 month ago

@jimingham Thanks for your answer. But I executed the custom_command command after running the program, and the breakpoint was marked as "resolved". The x86_64 architecture also has this problem. The following is the process of reproducing it with Xcode 16.0.

// main.m
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
    }
    return 0;
}

First, use Xcode to create a main symbolic breakpoint, then run the program. After hitting the breakpoint, the disassembly is as follows:

test_lldb_script`main:
    0x100000f40 <+0>:  pushq  %rbp
    0x100000f41 <+1>:  movq   %rsp, %rbp
    0x100000f44 <+4>:  subq   $0x20, %rsp
    0x100000f48 <+8>:  movl   $0x0, -0x4(%rbp)
    0x100000f4f <+15>: movl   %edi, -0x8(%rbp)
    0x100000f52 <+18>: movq   %rsi, -0x10(%rbp)
->  0x100000f56 <+22>: callq  0x100000f8a               ; symbol stub for: objc_autoreleasePoolPush
    0x100000f5b <+27>: movq   %rax, -0x18(%rbp)
    0x100000f5f <+31>: leaq   0xb2(%rip), %rdi          ; @"Hello, World!"
    0x100000f66 <+38>: movb   $0x0, %al
    0x100000f68 <+40>: callq  0x100000f7e               ; symbol stub for: NSLog
    0x100000f6d <+45>: movq   -0x18(%rbp), %rdi
    0x100000f71 <+49>: callq  0x100000f84               ; symbol stub for: objc_autoreleasePoolPop
    0x100000f76 <+54>: xorl   %eax, %eax
    0x100000f78 <+56>: addq   $0x20, %rsp
    0x100000f7c <+60>: popq   %rbp
    0x100000f7d <+61>: retq   

The subsequent steps are indicated by the console message (I omitted the other 13 breakpoint locations from the system framework):

(lldb) register read $rip
     rip = 0x0000000100000f56  test_lldb_script`main + 22 at main.m:3:22

(lldb) breakpoint list
Current breakpoints:
1: name = 'main', locations = 14, resolved = 14, hit count = 1
  1.1: where = test_lldb_script`main + 22 at main.m:4:22, address = 0x0000000100000f56, resolved, hit count = 1 
  ......

(lldb) command script import /Users/fermichen/Desktop/My_Projects/HMLLDB/commands/MyScript.py

(lldb) custom_command 0x100000f6d

(lldb) breakpoint list
Current breakpoints:
1: name = 'main', locations = 14, resolved = 14, hit count = 1
  1.1: where = test_lldb_script`main + 22 at main.m:4:22, address = 0x0000000100000f56, resolved, hit count = 1 
  ......
2: address = test_lldb_script[0x0000000100000f6d], locations = 1, resolved = 1, hit count = 0
  2.1: where = test_lldb_script`main + 45 at main.m:5:9, address = 0x0000000100000f6d, resolved, hit count = 0 

(lldb) continue
Process 96934 resuming

Hello, World!

(lldb) register read $rip
     rip = 0x0000000100000f6d  test_lldb_script`main + 45 at main.m:5:9
(lldb) breakpoint list
Current breakpoints:
1: name = 'main', locations = 14, resolved = 14, hit count = 1
  1.1: where = test_lldb_script`main + 22 at main.m:4:22, address = 0x0000000100000f56, resolved, hit count = 1 
  ...... 
2: address = test_lldb_script[0x0000000100000f6d], locations = 1, resolved = 1, hit count = 1
  2.1: where = test_lldb_script`main + 45 at main.m:5:9, address = 0x0000000100000f6d, resolved, hit count = 1 

From the console message, we can see that after hitting breakpoint 2, there is no output of "hit breakpoint" and the callback function breakpoint_function_wrapper is not executed.

If I switch to Xcode 15.2, the console will output "hit breakpoint" and breakpoint list will display the callback function information:

(lldb) breakpoint list
......
2: address = test_lldb_script[0x0000000100003f6d], locations = 1, resolved = 1, hit count = 1
    Breakpoint commands (Python):
      MyScript.breakpoint_function_wrapper(frame, bp_loc, extra_args, internal_dict)
  2.1: where = test_lldb_script`main + 45 at main.m:5:9, address = 0x0000000100003f6d, resolved, hit count = 1
jimingham commented 1 month ago

Your break list output shows that in the failing case we don't add the script to the breakpoint at all. I tried this on an AS Mac with Xcode 16.0 and it worked correctly, the callback got registered, and was run when the breakpoint was hit.

I can't see how this could be target architecture specific, however.

chenhuimao commented 1 month ago

I tried to start it in the terminal and was able to successfully register the callback function. The lldb version is the same. Maybe there is some configuration in my Xcode that I don't know about yet. I have to spend some time to check it.

chenhuimao commented 1 month ago

I asked another developer to try it according to this issue, and he reproduced this problem. In addition, I found that when using Xcode16.0, using time.sleep(0.1) before calling SetScriptCallbackFunction can solve this problem 🤔