第3章:使用LLDB进行连接

现在你已经了解了两个最重要的命令,help和apropos,现在是时候研究LLDB如何将自己附加到进程中。你将学习所有不同的方法,你可以使用各种选项将LLDB附加到进程,以及附加到进程时在幕后发生了什么。

LLDB "附加 "的说法实际上有点误导。一个名为debugserver的程序(在Xcode.app/Contents/SharedFrameworks/LDB.framework/Resources/)负责附加到一个目标进程。

如果它是一个远程进程,例如在远程设备上运行的iOS、watchOS或tvOS应用程序,则在该远程设备上启动一个远程debugserver。LLDB的工作是启动、连接并与调试服务器协调,以处理调试应用程序的所有互动。

附加到一个现有的进程

正如你在第1章中已经看到的,你可以像这样附加到一个进程中。

lldb -n Xcode

然而,还有其他方法可以做同样的事情。你可以通过提供一个正在运行的程序的进程标识符(PID)来附加到Xcode。

注意:提醒一下,如果你没有在你的macOS电脑上禁用SIP,你将不能将LLDB附加到苹果应用程序上。从Mojave的10.14.1版本开始,如果应用程序中没有反调试技术,附加到第三方应用程序(甚至来自App Store!)仍是可能的。

打开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进程并强制其重启。 当Finder被杀死时,macOS会自动重新启动Finder。切换回你的第一个终端标签,你会发现LLDB现在已经附着在新创建的Finder进程上。

另一种附加到进程的方法是指定可执行文件的路径,并在你方便时手动启动该进程。

lldb -f /System/Library/CoreServices/Finder.app/Contents/MacOS/Finder

这将把Finder设置为要启动的可执行文件。一旦你准备好开始调试会话,只需在LLDB会话中键入以下内容。

(lldb) process launch

注意:一个有趣的副作用是,当手动启动一个进程时,stderr输出(即Swift的print、ObjectiveC的NSlog、C的printf和company)会自动发送到终端窗口。其他的LLDB附加构架不会自动这样做。

启动时的选项

进程启动命令带有一系列的选项,值得进一步探索。如果你很好奇,想看看进程启动的全部可用选项列表,只需输入help process launch。

关闭以前的LLDB会话,打开一个新的终端窗口,输入以下内容。

lldb -f /bin/ls

这告诉LLDB使用/bin/ls(文件列表命令)作为目标可执行文件。

注意:如果你省略了-f选项,LLDB将自动推断第一个参数为要启动和调试的可执行文件。在调试终端可执行文件时,我经常会输入ldb $(which ls)(或类似的),然后被翻译成ldb /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进程将在你启动的目录下启动。你可以用-w选项告诉LLDB在哪里启动,从而改变当前的工作目录。输入以下内容。

(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来扩展参数中的tilde。试试这个。

(lldb) process launch -X true -- ~/Desktop

-X选项会展开你提供的任何shell参数,比如tilde。在LLDB中有一个快捷键:简单地输入run。要了解更多关于创建你自己的命令快捷方式的信息,请查看第9章,"持久化和自定义命令"。

键入以下内容,查看run的文档。

(lldb) help run

你会看到下面的内容。

看到了吗?这是你刚刚运行的命令的缩写!你可以通过输入下面的命令来试试。通过键入以下内容,让该命令运行一下。

(lldb) run ~/Desktop

环境变量

对于终端程序,环境变量可以和程序的参数一样重要。如果你查阅man 1 ls,你会看到ls命令可以用彩色显示输出,只要彩色环境变量被启用(CSICOLOR),并且你有 "color pallete "环境变量LSCOLORS来告诉如何显示某些类型的文件。

有了LLDB中的目标,你可以用任何环境变量的组合来启动和设置一个程序。

例如,要显示ls命令启动时的所有环境变量,在LLDB中运行以下命令。

(lldb) env

这将显示目标的所有环境变量。值得注意的是,LLDB不会显示环境变量,直到该目标至少运行一次。如果你没有看到任何输出,只要在执行env命令之前给ldb一个简单的运行。

你可以在启动前使用设置set|show|replace|clear|list target.env-vars命令来检查和增加这些环境变量,但你也可以

但你也可以在启动时用进程启动命令中的-v选项来指定它们。

是时候用可怕的红色显示/Applications目录了!

(lldb) process launch -v LSCOLORS=Ab -v CLICOLOR=1  -- /Applications/

哇! 这不就是烧眼睛吗?用下面的方法试试不同的颜色。

(ldb) process launch -v LSCOLORS=Af -v CLICOLOR=1 --/Applications/

这相当于你在没有LLDB的情况下在终端执行以下内容。

LSCOLORS=Af CLICOLOR=1 LS /Applications/

很多终端命令都包含环境变量,它们的描述在命令的手册页中。一定要确保阅读关于你期望一个环境变量如何增强程序的内容。

此外,许多命令(和苹果框架!)有 "私人 "环境变量,没有在任何文档或手册页中讨论。在本书后面,你会看到如何从可执行文件中提取这些信息。

stdin, stderr, 和stout

把标准流改到不同的位置呢?在第1章中,你已经尝试过用-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会话,输入以下内容。

(ldb) process launch -i /tmp/wc_input.txt

你会看到下面的输出。

这在功能上等同于以下内容。

$ wc < /tmp/wc_input.txt

有时你不希望有stdin(标准输入)。这对GUI程序(如Xcode)很有用,但对终端命令(如ls和wc)没有什么帮助。为了说明这一点,运行没有参数的wc目标,像这样。

(lldb) run

该程序将只是坐在那里并挂起,因为它期待着从stdin中读取什么。

给它一些输入,输入hello world,按Return键,然后按Control + D键,这就是传输结束的字符。你会看到与之前使用文件作为输入时相同的输出。

现在,像这样启动这个进程。

(lldb) process launch -n

你会看到wc立即退出,输出结果如下。

Process 28849 launched: '/usr/bin/wc' (x86_64) 
Process 28849 exited with status = 0 (0x00000000)

-n选项告诉LLDB不要创建stdin;因此wc没有数据可以使用并立即退出。

从这里开始该怎么做?

还有一些更有趣的选项可以玩(你可以通过帮助命令找到),但那是你自己的时间去探索。

现在,你可以尝试连接到GUI和非GUI程序上。没有源代码,你似乎无法理解什么,但在接下来的章节中,你会发现你对这些程序有多少信息和控制。


上一章 目录 下一章