Search code examples
debuggingllvmremote-debugginglldb

lldb hangs when trying to execute command with "-o"


Trying to execute following command

lldb -o 'process connect connect://[fd42:af35:2043::1]:50773'

on a MacOS zsh terminal leads to an 'hanging' state in lldb. I cannot cancel it in any way. I have to kill zsh instance.

However, the command process connect connect://[fd42:af35:2043::1]:50773 works without any problems when entering it directly in lldb console:

$ lldb
(lldb) process connect connect://[fd42:af35:2043::1]:50773
(lldb) process launch

Question

Any idea why lldb hangs when trying to give the executed command from the 'outside'? Any other possibilities to inject commands into lldb?

Other commands like script lldb.target.module[0].SetPlatformFileSpec(lldb.SBFileSpec('/private/var/containers/Bundle/Application/3DFF7807-330C-4A5E-8952-D93034FEC2AF/Test.app')) are working with '-o' option.


Solution

  • This is a bug, but a rather subtle one...

    lldb's command-line can run in two modes, "synchronous" and "asynchronous". In sync mode, any command that runs (or produces) the process lldb is debugging won't return until the process stops. That mode is convenient for scripts - including the startup script you build out of the -o flags to lldb.

    For instance, if you do:

    $ lldb abinary -o "break set -n main" -o run -o bt
    

    you want the run to have stopped at the breakpoint at main before the bt command is run.

    In asynchronous mode all commands return right away, and then the lldb driver waits asynchronously for the stop and informs you. That's a more convenient mode for using the console, so lldb will run that way if it can. It's not possible to do this if lldb and the process being debugged share a terminal, but otherwise that's how lldb will run.

    The second factor in the bug is that there's also an ambiguity in process connect. The stub on the other end might or might not already have a process it is controlling. On Darwin, this is the difference between running:

    $ debugserver localhost:12345
    

    which is a debugserver that you can connect to and instruct to launch any process, and:

    $ debugserver localhost:12345 process_name
    

    where debugserver starts up a process with the binary process_name and waits for a client to attach to it. You can add further arguments to set the arguments & environment passed to the new process.

    The bug is that in synchronous mode, lldb assumes that process connect is going to produce a process, and it is synchronously waiting for that. In async mode, if it successfully connects and then detects that the stub isn't managing a process, it exits the command at that point, returning control to the user so they can then do process launch.

    If you had connected to a debugserver run with a process, then the process connect would have completed, and you would have been left at the command-line with the new process in a stopped state.

    That fact gives you one workaround, if you can run your stub with a process instead of having lldb "launch" it. Note, debugserver (and lldb-server) stop the new process before it gets a chance to execute any code, so you won't miss any of your program's code. The main advantage of launching from lldb is the ease of setting arguments and environment variables.

    Another workaround is to switch to async mode for the process connect command, for example:

    $ lldb -o "script old_debug = lldb.debugger.GetAsync()" -o "script lldb.debugger.SetAsync(True)" -o "gdb-remote localhost:12345" -o "script lldb.debugger.SetAsync(old_debug)"
    

    The latter would get pretty tedious to type, but you can also put this in a command-file somewhere and use -s instead of -o.

    If you feel like filing a bug about this, the lldb issues tracker is here:

    https://github.com/llvm/llvm-project/labels/lldb