既然你已经了解了两个最重要的命令,help 和 apropos,现在是时候研究 LLDB 如何连接进程。 你将学习使用各种选项将 LLDB 连接到进程的所有方式,以及连接到进程后发生的结果。
LLDB “连接”这个词实际上有点误导。 名为 debugserver 的程序(在 Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/ 中找到)负责连接到目标进程。
如果它是一个远程进程,例如运行在远程设备上的 iOS,watchOS 或 tvOS 应用程序,远程的 debugserver 将在该远程设备上启动。 LLDB 的工作是启动,连接和协调 debugserver,以处理调试应用程序中的所有交互。
连接到已有进程
正如你在第一章中已经看到的那样,你可以像这样连接一个进程:
lldb -n Xcode
然而,还有其他方法可以做同样的事情。 你可以通过提供正在运行的程序的进程标识符或 PID 来连接到 Xcode。
打开 Xcode,然后打开一个新的终端会话,最后运行以下内容:
pgrep -x Xcode
这会输出 Xcode 进程的 PID。
接下来,运行以下命令,用上面的命令替换 89944 的数字输出:
lldb -p 89944
这让 LLDB 使用给定的 PID 连接到进程。 目前,连接的是你正在运行的 Xcode 进程。
连接到未来进程
之前的命令只能获取正在运行的进程地址。 如果 Xcode 没有运行,或者已经连接到调试器,那么之前的命令将会失败。 如果你还不知道 PID,你怎么能得到一个即将启动的进程呢?
你可以用 -w 参数来做到这一点,这会导致 LLDB 等待一个进程启动,其 PID 或可执行进程名称与使用 -p 或 -n 参数提供的条件匹配。
例如,通过在终端窗口中按 Ctrl + D 来终止现有的 LLDB 会话,然后输入以下内容:
lldb -n Finder -w
这会让 LLDB 在下次启动时连接名为 Finder 的进程。 接下来,打开一个新的终端选项卡,并输入以下内容:
pkill Finder
这将终止 Finder 进程并强制重启。 终止后,macOS 会自动重新启动 Finder。 切换回你的第一个终端选项卡,你会注意到 LLDB 已经把自己连接到新创建的 Finder 进程中。
另一种连接到进程的方法是指定可执行进程的路径,并在方便时手动启动进程:
lldb -f /System/Library/CoreServices/Finder.app/Contents/MacOS/Finder
这将设置 Finder 作为可执行进程启动。 一旦准备好开始调试会话,只需在 LLDB 会话中输入以下内容:
(lldb) process launch
注意:一个有趣的副作用是,手动启动进程时,stderr 输出(即 NSLog 和公司)会自动发送到终端窗口。 其他 LLDB 连接配置不会自动执行此操作。
启动时的选项
process launch 命令带有一套值得进一步探索的选项。 如果你好奇,想要查看 process launch 的可用选项的完整列表,只需输入 help process launch。
关闭之前的 LLDB 会话,打开一个新的终端窗口并输入以下内容:
lldb -f /bin/ls
这告诉 LLDB 使用 /bin/ls(文件列表命令)作为目标可执行进程。
注意:如果你省略了-f选项,LLDB 会自动推断第一个参数作为可执行进程来启动和调试。 在调试终端可执行进程时,我经常输入 lldb $(which ls)(或其他等价内容),然后转换成 lldb /bin/ls。
你会看到下面的输出:
(lldb) target create "/bin/ls"
Current executable set to '/bin/ls' (x86_64).
由于 ls 是一个快速的程序(它启动,完成工作,然后退出),你将用不同的参数多次运行这个程序来探索每一个的作用。
不带参数从 LLDB 启动 ls。 输入以下内容:
(lldb) process launch
你会看到下面的输出:
Process 7681 launched: '/bin/ls' (x86_64)
... # Omitted directory listing output
Process 7681 exited with status = 0 (0x00000000)
一个 ls 进程将在你最初的目录中启动。您可以通过告诉 LLDB 在哪里启动 -w 选项来更改当前的工作目录。 输入以下内容:
(lldb) process launch -w /Applications
这将从 /Applications 目录中启动 ls。 这等价于以下内容:
$ cd /Applications
$ ls
还有另一种方式来做到这一点。 不要让 LLDB 改变目录然后运行程序,你可以直接将参数传递给程序。
(lldb) process launch -- /Applications
这和前面的命令有相同的效果,但是这一次是这样做的:
$ ls /Applications
再说一次,这列出了所有的 macOS 程序,但是你指定了一个参数而不是改变起始目录。 将桌面目录指定为启动参数怎么样? 尝试运行这个:
(lldb) process launch -- ~/Desktop
你会看到下面的输出:
Process 8103 launched: '/bin/ls' (x86_64)
ls: ~/Desktop: No such file or directory
Process 8103 exited with status = 1 (0x00000001)
噢,没有生效。 你需要 shell 来扩展参数中的波浪号。 试试这个:
(lldb) process launch -X true -- ~/Desktop
-X 选项扩展你提供的任何 shell 参数,例如波浪号。 在 LLDB 中有一个快捷方式:只需输入 run 即可。 要了解有关创建自定义命令快捷键的更多信息,请查看第八章“持久化和自定义命令”。
输入以下内容以查看 run 的文档:
(lldb) help run
你会看到下面的输出:
...
Command Options Usage:
run [<run-args>]
'run' is an abbreviation for 'process launch -X true --'
看到了吗? 这是你刚才跑的命令的缩写! 通过输入以下内容来执行命令:
(lldb) run ~/Desktop
将控制台输出更改到不同的位置会怎么样? 你已经在第一章中尝试使用 -e 标志将 stderr 更改为其他终端选项卡,但 stdout 会怎么样呢?
输入以下内容:
(lldb) process launch -o /tmp/ls_output.txt -- /Applications
-o 选项告诉 LLDB 将 stdout 传到给定的文件。
你会看到下面的输出:
Process 15194 launched: '/bin/ls' (x86_64)
Process 15194 exited with status = 0 (0x00000000)
注意,没有直接从 ls 输出。
打开另一个终端选项卡,然后运行以下命令:
cat /tmp/ls_output.txt
你的应用程序目录应该再次输出,如预期结果一样!
stdin 也有一个选项 -i。 首先,输入以下内容:
(lldb) target delete
这将消除 ls 作为目标。 接下来,输入这个:
(lldb) target create /usr/bin/wc
这将 /usr/ bin/wc 设置为新目标。 wc 可以用来计算给 stdin 输入的字符,单词或者行数。
你已经把你的 LLDB 会话的目标可执行进程从 ls 换成了 wc。 现在你需要提供一些数据给 wc。 打开一个新的终端选项卡并输入以下内容:
echo "hello world" > /tmp/wc_input.txt
你会用这个文件给 wc 一些输入。
切换回 LLDB 会话并输入以下内容:
(lldb) process launch -i /tmp/wc_input.txt
你会看到下面的输出:
Process 24511 launched: '/usr/bin/wc' (x86_64)
1 2 12
Process 24511 exited with status = 0 (0x00000000)
这在功能上等同于以下内容:
$ wc < /tmp/wc_input.txt
有时候你不需要 stdin(标准输入)。 这对于 Xcode 等 GUI 程序非常有用,但对于终端命令(如 ls 和 wc)没什么帮助。
为了举例说明,不带任何参数运行 wc 目标,就像这样:
(lldb) run
程序只是等待着,因为它期望从 stdin 读取一些东西。
给它输入 hello world,按回车键,然后按 Control + D,这是传输字符的结尾。 wc 将解析输入并退出。 你将看到与作为输入的使用文件相同的输出。
现在,像这样启动过程:
(lldb) process launch -n
你会看到 wc 立即退出,输出如下:
Process 28849 launched: '/usr/bin/wc' (x86_64)
Process 28849 exited with status = 0 (0x00000000)
-n 选项告诉 LLDB 不要创建 stdin,wc 没有数据可以处理,因此退出。
接下来?
还有一些有趣的选项可以使用(你可以通过 help 命令找到),你需要自己花时间去探索。
现在,尝试连接到 GUI 和非 GUI 程序。 如果没有源代码,你可能理解不了太多的东西,但是在即将到来的章节中你会发现在这些程序中你能得到多少信息和控制。