mac os x 调试魔法

Mac OS X包含很多由苹果的工程小组加入的调试工具,用于帮助开发和调试特定的子系统。其中有很多工具仍保留在发布的系统中,可以用来调试您的代码。本文将介绍其中的一些使用较为广泛的工具。

如果调试工具的说明文档在其它地方,则本文会对该工具进行简短的概述,并提供一个指向其说明文档的链接。

本文并不是一个调试工具的完全列表:文中没有也不打算对所有的调试工具都进行说明。

警告信息: 我们并不对本文描述的调试工具提供支持。我们保留在Mac OS X发展过程中根据需要变更或者去除这些工具的权利;这种情况在过去出现过,未来也完全有可能发生。这些工具仅用于调试,您发售的产品不应依赖于本文描述的工具及其功能。

重要信息: 本文是基于Mac OS X 10.4 (安装有Xcode 2.0)的,其内容在这样的系统上是精确的。本文涵盖的很多细节在各个发行版本间是有变化的;您可能会在较老或较新的系统上遇到一些细微的改变。

请注意: 本文的前一个版本基于Mac OS X 10.3.5 (安装有Xcode 1.5)。在文档更新的过程中,我们重新测试了文中描述的所有调试工具。多数工具没有发生什么值得注意的变化;因此也就没有对相应的描述进行修改。这意味 着其中一些示例实际上是基于10.3.5的,其结果可能与10.4有轻微的不同。但是,示例要说明的总体内容仍然是准确的。

如果调试工具在10.4上发生显著的变化,我们就会对相关的描述进行更新,以便反映其在10.4上的行为。该工具在10.3.x中的行为也会顺便被提及。

本文讨论的是高级的调试技巧。如果你刚开始接触Mac OS X开发,应该参考下面的材料:

  • GDB是Mac OS X上的基本调试器。GDB的完全用法参见用GDB进行调试。

  • Xcode是苹果公司的集成开发环境(IDE)。它包括一个成熟的、对GDB进行封装的图形化调试器。关于Xcode的更多内容,请参见苹果开发人员参考库中的Xcode部分。


基础

本文后面的部分将详细介绍Mac OS X系统上的调试工具。这些工具大多使用相似的技巧来启用、禁用、以及查看输出。本部分将这些通用的技巧进行介绍。

启用调试工具

一些调试工具缺省处于启用状态。然而,大多数工具必须用下列的某种方式来启用。

环境变量

在很多情况下,您可以通过设置特定的环境变量来启用调试工具。最容易的方式是在终端(Terminal)窗口中启动您的应用程序,并在命令行中指定相应的环境变量。清单1展示了如何在de>shde>外壳(shell)中设置环境变量;清单2则展示如何在de>cshde>中完成相同的任务。

清单1: 在兼容sh的外壳中设置环境变量

$ MallocStackLogging=1 /Applications/TextEdit.app/Contents/MacOS/TextEdit malloc[1002]: recording stacks using standard recorder […] 

清单2: 在兼容csh的外壳中设置环境变量

% setenv MallocStackLogging 1 % /Applications/TextEdit.app/Contents/MacOS/TextEdit malloc[1004]: recording stacks using standard recorder […] 

请注意: 在Mac OS X 10.3及以上版本中,默认外壳为de>bashde>,是一个兼容de>shde>的外壳。在Mac OS X 10.2.x及之前的版本中,默认外壳为de>tcshde>,是一个兼容de>cshde>的外壳。本文的后续部分假定您用的是de>bashde>。

重要信息: 如果您使用的是兼容de>cshde>的外壳,那么在完成调试后一定要记得重置 (用de>unsetenvde>命令) 所有调试用的环境变量,否则这些设置可能对以后的命令造成不良影响。

或者您可以选择打开两个终端窗口,然后将第一个专门用于调试,将第二个用于执行所有其它命令。

另外,可以在GDB中设置环境变量,如清单3所示。

清单3: 在GDB中设置环境变量

$ gdb /Applications/TextEdit.app GNU gdb 5.3-20030128 (Apple version gdb-330.1) […] (gdb) set env MallocStackLogging 1 (gdb) r Starting program: /Applications/TextEdit.app/Contents/MacOS/TextEdit malloc[1062]: recording stacks using standard recorder […] 

如果您用Xcode来连编和调试应用程序,则可以用该可执行文件的查看器来设置环境变量。图1展示了这样的一个例子。

图 1: 在Xcode中设置环境变量

最后,Mac OS X提供了一种为特定用户启动的所有进程设置环境变量的机制。详细内容参见技术问答QA1067,即‘为用户进程设置环境变量’。

返回页首 

预设值

de>defaultsde>命令行工具用于设置预设值,您可以通过这个命令来启用其它的调试工具。清单4展示了具体的用法。该清单为TextEdit程序(其包标识为de>com.apple.TextEditde>)将de>NSTraceEventsde>预设值设置为YES。

清单4: 用defaults设置预设值

$ defaults write com.apple.TextEdit NSTraceEvents YES $ /Applications/TextEdit.app/Contents/MacOS/TextEdit 2004-08-30 16:47:55.851 TextEdit[1135] timeout = 62998416724.149353 seco… 2004-08-30 16:47:55.894 TextEdit[1135] got apple event of class 61657674… […] 

在调试完成后应该删除该预设值,同样使用de>defaultsde>,如清单5所示。

清单5: 用defaults删除预设值

$ defaults delete com.apple.TextEdit NSTraceEvents 

关于de>defaultsde>的更多内容,请参考其帮助手册。

Cocoa应用程序可用于在命令行上设置临时预设值。例如,清单6展示了如何在不修改永久预设值的前提下获得与清单4相当的结果。

清单6: 临时设置预设值

$ /Applications/TextEdit.app/Contents/MacOS/TextEdit -NSTraceEvents YES 2004-10-25 17:28:41.143 TextEdit[5774] timeout = 62993575878.857864 seco… 2004-10-25 17:28:41.179 TextEdit[5774] got apple event of class 61657674… […] 

返回页首 

文件

某些调试工具可以通过在文件系统中创建特定的文件来启用。清单7展示了这样的一个例子:创建文件de>/var/log/do_dnserver_logde>,使CFNotificationCenter服务器 (de>distnotedde>) 将所有通告信息记录到de>/var/log/dnserver.logde>文件中。

清单7: 创建特定文件来启用调试工具

$ sudo touch /var/log/do_dnserver_log  [… now restart …]  $ cat /var/log/dnserver.log ------------------------------------------------- […] create_client "KernelEventAgent" BFF7FA80 (1203) […] received_message_from "KernelEventAgent" register "DISCONNECT" […] […] 

关于这个具体示例的更多内容,请参见CFNotificationCenter。

返回页首 

可调用的例程

很多系统框架包含将调试信息输出到de>stderrde>的例程。这些例程经过了特别设计,可在GDB中调用。清单8展示了这样一个例子,即调用de>GDBComponentListde>例程来打印一列组件及其实例 (相当于传统Mac OS上的MacsBug命令de>thingde>)。

清单8: 在GDB中调用调试例程

(gdb) call (void) GDBComponentList() Cnt tRef# (address) Type/SubT/Manu Flags    EntryPnt File Parnt ThingName   0 10008  0180d540 adec/.mp3/appl 10000000 00000000   -3 00000 (Not loade… […]   2 1012c  005c3be0 clok/micr/appl 10000003 8b1586a8    1 00000 (Not loade…         Inst:0x850003; Err=0; Storage:0x1fe6f0         Inst:0x890004; Err=0; Storage:0x1fe6f8   0 10065  0180fd08 clok/soun/appl 00000005 00000000   -3 00000 (Not loade… […] There are 9 component manager files:         0: refs 2, path [/System/Library/Components/VCH263Codec.component]…         1: refs 94, path [/System/Library/QuickTime/QuickTimeComponents.co…         2: refs 5, path [/System/Library/Components/IOQTComponents.compone…         3: refs 2, path [/System/Library/QuickTime/QuickTimeVR.component],…         4: refs 7, path [/System/Library/QuickTime/QuickTimeFirewireDV.com…         5: refs 1, path [/System/Library/QuickTime/QuickTimeMPEG4.componen…         6: refs 1, path [/System/Library/Components/PDFImporter.component]…         7: refs 1, path [/System/Library/QuickTime/ApplePixletVideo.compon…         8: refs 1, path [/System/Library/QuickTime/QuickTimeStreaming.comp… 

如果您没有看到该例程的输出,那么可能需要看一下控制台日志,参见下一节的描述。

重要信息: 如果您将这个技巧用到自己的代码上,要当心它并不是对声明为de>staticde>的例程都起作用。编译器在不同的例程之间进行的优化可能导致de>staticde>例程偏离标准的调用约定。在这种情况下,GDB不能可靠地调用这些例程。

在实践中,上述的问题只对Intel代码有影响。

返回页首 

查看调试输出

程序通常通过以下三种不同的机制来产生调试输出:

  • 输出到de>stderrde>

  • 系统日志

  • 内核跟踪工具

输出到de>stderrde>是最常用的输出机制。鉴于该主题的重要性,我们将在下一节进行深入探讨。

另外两种机制要简单得多。您可以用控制台程序 (在de>/Applications/Utilitiesde>目录下) 来查看系统日志,更多信息请参见syslog的帮助手册 (syslog、syslog.conf和syslogd) 。

内核跟踪工具是一个高度专业化的、低延迟而又高效能的日志记录工具。多数情况下,通过内核跟踪工具记录日志信息的程序也会提供一种查看日志的方法 (例如,可以用kdump来打印由ktrace生成的跟踪文件)。

控制台输出

很多程序,甚至还有很多系统框架,都将调试信息输出到de>stderrde>。这种输出的目的地归根结底由程序自己来控制:它可以将de>stderrde>重定向到自己选择的任何目标。然而,多数情况下程序不会重定向de>stderrde>,因此缺省的输出目的地是由程序从启动环境继承而来的。典型的情况有如下几种:

  • 对于以一般的方式启动的GUI应用程序 (比如,在Finder中双击启动),系统会将其de>stderrde>连接到控制台设备 (de>/dev/consolede>),您可以通过控制台程序 (在de>/Applications/Utilitiesde>目录下) 查看它的输出。此外,这些输出也会被记录到de>/Library/Logs/Console/<用户名>/console.logde>文件中。

  • 对于从终端程序窗口运行起来的程序 (GUI或非GUI程序),其de>stderrde>会被连接到终端窗口;程序输出的所有内容都会呈现在该窗口中。这同样适用于通过终端窗口中的GDB运行起来的程序。

  • 对于通过Xcode运行起来的程序,您可以在Xcode的运行日志(Run Log)或者控制台日志(Console Log)窗口中看到该程序的de>stderrde>输出 (选择调试菜单中相应的命令即可查看该窗口)。

附着到正在运行的程序 (用GDB中的de>attachde>命令) 不会自动将该程序的de>stderrde>连接到GDB窗口。您可以用技术文档 TN2030,‘GDB之MacsBug高手篇’中的“在附着后查看stdout和stderr”部分介绍的技巧来实现这种连接。

返回页首 

架构的考虑

本文所演示的实例是在一台基于PowerPC的Macintosh计算机上实现的。不过,这些实例在基于Intel的计算机上也可以很好地运行。唯一的重要区别与参数的传递有关。PowerPC架构用寄存器传递参数,而Intel架构用堆栈传递参数。

在PowerPC上,一个很好的经验规则是第一个参数存放在GPR3 (通用寄存器3) 中,第二个参数存放在GPR4中,以此类推。在GDB语法上表示为de>$r3de>、de>$r4de>等。返回时,所有函数的返回结果都存放在GPR3 (de>$r3de>) 中。

在Intel上,有两条经验规则:

  • 如果程序指针停在例程的第一条指令处,那么第一个参数就位于堆栈指针上方的4字节处,第二个参数位于堆栈指针上方的8字节处,以此类推。GDB语法为de>*(int *)($esp+4)de>、de>*(int *)($esp+8)de>等。

  • 如果程序指针停在例程中的其它位置上(在堆栈帧被创建之后),则第一个参数位于帧指针之上的8字节处,第二个参数位于帧指针之上的12字节处,以此类推。GDB语法为de>*(int *)($ebp+8)de>、de>*(int *)($ebp+12)de>等。

在Intel架构的两种情形中,所有函数的返回结果都存放在EAX (de>$eaxde>) 寄存器中。

如果该例程是一个C++成员函数,那么会有一个隐含的首参数de>thisde>。如果该例程是一个Objective-C的方法,那么会有两个隐含的首参数 (详细信息请参见Cocoa部分) 。

清单9和清单10展示了一些与上述经验规则有关的实例。

清单9: PowerPC上的参数

$ gdb /Applications/TextEdit.app GNU gdb 6.1-20040303 (Apple version gdb-434) […] (gdb) fb CFStringCreateWithFormat Function "CFStringCreateWithFormat" not defined. Breakpoint 1 (CFStringCreateWithFormat) pending. (gdb) r Starting program: /Applications/TextEdit.app/Contents/MacOS/TextEdit Reading symbols for shared libraries […] done Breakpoint 1 at 0x90741e80 Pending breakpoint 1 - "CFStringCreateWithFormat" resolved  Breakpoint 1, 0x90741e80 in CFStringCreateWithFormat () (gdb) # first param is "alloc" (gdb) p/a $r3 $1 = 0x0 (gdb) # second param is "formatOptions" (gdb) p/a $r4 $2 = 0x0 (gdb) # third param is "format" (gdb) p/a $r5 $3 = 0xa0742454  (gdb) call (void) CFShow($r5) %@%c (gdb) finish Run till exit from #0  0x90741e80 in CFStringCreateWithFormat () 0x9073eaf8 in CFURLCreateWithFileSystemPathRelativeToBase () (gdb) # function result (gdb) p/a $r3 $4 = 0x306a40 (gdb) call (void) CFShow($r3) /Applications/TextEdit.app/Contents/MacOS/ 

清单10: Intel上的参数

$ gdb /Applications/TextEdit.app GNU gdb 6.1-20040303 (Apple version gdb-425) […] (gdb) fb CFStringCreateWithFormat Function "CFStringCreateWithFormat" not defined. Breakpoint 1 (CFStringCreateWithFormat) pending. (gdb) r Starting program: /Applications/TextEdit.app/Contents/MacOS/TextEdit Reading symbols for shared libraries […] done Breakpoint 1 at 0x9078f111 Pending breakpoint 1 - "CFStringCreateWithFormat" resolved  Breakpoint 1, 0x9078f111 in CFStringCreateWithFormat () (gdb) # first param is "alloc" (gdb) p/a *(int *)($ebp+8) $1 = 0x0 (gdb) # second param is "formatOptions" (gdb) p/a *(int *)($ebp+12) $2 = 0x0 (gdb) # third param is "format" (gdb) p/a *(int *)($ebp+16) $3 = 0xa078bd88  (gdb) call (void) CFShow($3) %@%c (gdb) finish Run till exit from #0  0x9078f111 in CFStringCreateWithFormat () 0x9078b6c1 in CFURLCreateWithFileSystemPathRelativeToBase () (gdb) # function result (gdb) p/a $eax $4 = 0x306ce0 (gdb) call (void) CFShow($eax) /Applications/TextEdit.app/Contents/MacOS/ (gdb) # Now clear the breakpoint and reset it on (gdb) # the first instruction of CFStringCreateWithFormat. (gdb) delete 1 (gdb) # Note the "*" syntax in the following command, which sets the (gdb) # breakpoint at the first instruction of the routine. (gdb) b *CFStringCreateWithFormat Breakpoint 2 at 0x9078f10b (gdb) c Continuing.  Breakpoint 2, 0x9078f10b in CFStringCreateWithFormat () (gdb) # Now we're stopped at the first instruction of CFStringCreateWithFormat. (gdb) # The stack frame hasn't been created yet. (gdb) # Print each parameter, this time relative to the stack pointer. (gdb) p/a *(int *)($esp+4) $5 = 0x0 (gdb) p/a *(int *)($esp+8) $7 = 0x0 (gdb) p/a *(int *)($esp+12) $8 = 0xa078bd88  (gdb) call (void) CFShow($8) %@%c 

重要信息: 这些只是经验规则。只要例程有任何非标准的参数或者非标准的函数结果,这些经验规则将不再适用。在这种情况下,您应该从相关的文档中了解详细信息。

在这里的上下文中,标准参数是指整型 (刚好可放在单个寄存器中)、枚举型和指针 (包括数组指针和函数指针)。非标准参数是指浮点型、向量、结构以及超出寄存器大小的整型。

如果您需要适合所有Mac OS X架构的调用约定的详细介绍,请参见Mac OS X ABI函数调用指南。

最后,如果您需要查找与特定指令有关的信息,请注意,Shark (包含在Xcode开发工具) 的帮助菜单中有关于PowerPC和Intel指令的参考资料。

返回页首 

CrashReporter

CrashReporter是一个极有价值的、记录所有程序崩溃信息的调试工具。在技术文档 TN2123,‘CrashReporter’中有详细介绍。CrashReporter始终是被启用的;您需要做的就是查看其输出。

返回页首 

BSD

BSD子系统实现了进程、内存、文件和网络基础结构,因此对于Mac OS X上的所有应用程序都很重要。BSD实现了很多精巧的调试工具,您可以利用这些工具来进行调试。

核心转储

作为一款初级的调试工具,核心转储并未得到应有的认可。事实上,在调试棘手的问题,尤其是调试无法在本地重现的问题时,它可能是非常有用的。

通过在de>/etc/launchd.confde>文件中添加内容为de>limit core unlimitedde>的文本行、并重新启动系统,可以在系统范围内启用核心转储(如果该文件不存在,可以自行创建)。更多的内容请参见launchd.conf的帮助手册。

请注意: 在Mac OS X 10.4之前的系统中,启动系统级别的核心转储需要将de>/etc/hostconfigde>文件中的"COREDUMPS=-NO-"改为"COREDUMPS=-YES-",然后重新启动。

如果您通过终端运行程序,则还可以有另一个选择,即在运行之前简单地增加核心转储在外壳中的尺寸限制。清单11展示了这样的一个例子。

清单11: 尺寸不受限制的核心转储

$ ulimit -c unlimited $ /Applications/TextEdit.app/Contents/MacOS/TextEdit […] 

如果您要测试一下核心转储工具,可以通过de>killde>命令给程序发送de>SIGABRTde>信号,如清单12所示。

清单12: 通过发送SIGABRT测试核心转储

$ ps | grep TextEdit   374  p1  S+     0:00.58 /Applications/TextEdit.app/Contents/MacOS/TextEdit   379 std  S+     0:00.01 grep TextEdit $ kill -ABRT 374 

命令执行后,您的应用程序将退出,同时产生“Abort trap (core dumped)”信息。您可以在de>/coresde>路径下找到相应的核心转储;可以用de>otoolde>命令来区分核心转储和哪个程序相对应;最后,您还可以用带de>-cde>参数的GDB来调试核心转储。清单13展示了一个这样的过程。

清单13: 使用核心转储

Abort trap (core dumped) $ ls -lh /cores total 296856 -r--------  1 quinn  admin       144M 29 Oct 10:23 core.374 $ otool -c /cores/core.374 /cores/core.374: Argument strings on the stack at: 0xc0000000         /Applications/TextEdit.app/Contents/MacOS/TextEdit          /Applications/TextEdit.app/Contents/MacOS/TextEdit         TERM_PROGRAM=Apple_Terminal         TERM=xterm-color         SHELL=/bin/bash         TERM_PROGRAM_VERSION=100         USER=quinn         __CF_USER_TEXT_ENCODING=0x1F5:0:15         PATH=/bin:/sbin:/usr/bin:/usr/sbin         PWD=/Users/quinn         SHLVL=1         HOME=/Users/quinn         LOGNAME=quinn         SECURITYSESSIONID=20f550         _=/Applications/TextEdit.app/Contents/MacOS/TextEdit $ gdb -c /cores/core.374 GNU gdb 5.3-20030128 (Apple version gdb-292) (Sat Sep 20 03:22:27 GMT 2003) Copyright 2003 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB.  Type "show warranty" for details. This GDB was configured as "powerpc-apple-darwin". Core was generated by `/Applications/TextEdit.app/Contents/MacOS/TextEdit'. #0  0x900075c8 in ?? () (gdb) bt #0  0x900075c8 in ?? () #1  0x90007118 in ?? () #2  0x901960bc in ?? () #3  0x927d5ecc in ?? () #4  0x927dc640 in ?? () #5  0x927fe6d0 in ?? () #6  0x92dd2a80 in ?? () #7  0x92de93fc in ?? () #8  0x92dfd730 in ?? () #9  0x92eb9a1c in ?? () #10 0x00007d98 in ?? () #11 0x00007c0c in ?? () 

如清单13所示,核心转储不包含调试器符号。如果手边有符号文件,可以通过de>add-symbol-filede>命令来让GDB使用该文件。清单14展示了如何为TextEdit的回溯信息中用到的系统框架加入符号文件。

清单14: 添加符号

(gdb) add-symbol-file /System/Library/Frameworks/AppKit.framework/AppKit […] (gdb) add-symbol-file /System/Library/Frameworks/CoreFoundation.framework\ /CoreFoundation […] (gdb) add-symbol-file /System/Library/Frameworks/System.framework/System […] #12 0x00007c0c in ?? () (gdb) add-symbol-file /System/Library/Frameworks/Carbon.framework/\ Frameworks/HIToolbox.framework/HIToolbox […] (gdb) bt #0  0x900075c8 in mach_msg_trap () #1  0x90007118 in mach_msg () #2  0x90191930 in __CFRunLoopRun () #3  0x901960bc in CFRunLoopRunSpecific () #4  0x927d5ecc in RunCurrentEventLoopInMode () #5  0x927dc640 in ReceiveNextEventCommon () #6  0x927fe6d0 in BlockUntilNextEventMatchingListInMode () #7  0x92dd2a80 in _DPSNextEvent () #8  0x92de93fc in -[NSApplication nextEventMatchingMask:untilDate:inMode:… #9  0x92dfd730 in -[NSApplication run] () #10 0x92eb9a1c in NSApplicationMain () #11 0x00007d98 in ?? () #12 0x00007c0c in ?? () 

重要信息: 核心转储非常大。在清单13的示例中,TextEdit的核心转储为144MB。如果您将核心转储设置为必然启用,则请务必定期清理de>/cores目录,de>以防止启动磁盘的空间被填满。

返回页首 

内存分配器

默认的内存分配器包含了很多可以通过环境变量启用的调试工具。在帮助手册中有对这些环境变量的完整介绍。表1列出了其中最有用的一些环境变量。

表1: 一些比较有用的内存分配器环境变量

变量 概要说明
de>MallocScribblede> 用0x55填充解除分配的内存
de>MallocPreScribble(10.4及以上版本)de> 用0xAA填充新分配的内存
de>MallocGuardEdgesde> 在进行大的内存分配时,在内存块的前后分别添加防护页
de>MallocStackLoggingde> 为每个内存块记录堆栈日志,以便为内存调试工具提供支持
de>MallocStackLoggingNoCompactde> 为所有操作记录堆栈日志,以便为内存调试工具提供支持

重要信息: 这些环境变量要求使用特殊的内存库(比如MallocDebug或者防护内存分配程序)。事实上,它们在默认的、不用于调试目的的内存分配器上也得到支持,因此总是可用的。

默认的内存分配器在检测到某些常见的编程问题时也会进行记录。例如,如果对一块内存进行两次释放,或者对未分配的内存进行释放,则de>freede>函数可能会输出清单15中所示的信息。方括号中的数字是指进程ID。

清单15: free函数输出的常见信息

*** malloc[4691]: Deallocation of a pointer not malloced: 0x1000;  \ This could be a double free(), or free() called with the middle of \ an allocated block; Try setting environment variable MallocHelp to \ see tools to help debug 

在GDB中运行您的程序、并在输出这些信息的de>malloc_printfde>例程上设置断点,就可以调试此类问题。只要程序指针到达该断点,就可以用GDB的de>backtracede>命令来确定上一级的调用例程。

返回页首 

MallocDebug 和 ObjectAlloc

Mac OS X包含两个用于内存分配调试的GUI应用程序,即MallocDebug和ObjectAlloc。有关这些工具的更多内容,请参见内存性能文档。

返回页首 

防卫内存分配器

Mac OS X系统中包含一个防卫内存分配器,即de>libgmallocde>,在调试过程中可以用于捕获常见的内存问题,比如缓冲区溢出和使用已释放的内存。清单16展示了一个关于如何启用这个内存分配器的例子。

清单16: 启用libgmalloc

$ gdb /Applications/TextEdit.app GNU gdb 6.1-20040303 (Apple version gdb-434) […] (gdb) set env DYLD_INSERT_LIBRARIES /usr/lib/libgmalloc.dylib (gdb) r Starting program: /Applications/TextEdit.app/Contents/MacOS/TextEdit Allocations will be placed on word (4 byte) boundaries.  - Small buffer overruns may not be noticed.  - Applications using AltiVec instructions may fail. GuardMalloc-11 Reading symbols for shared libraries […] 

更多有关de>libgmallocde>的信息请参见其帮助手册。

请注意:在Mac OS X 10.3.x 中,de>libgmallocde>要求强制使用平坦的命名空间 (通过设置de>DYLD_FORCE_FLAT_NAMESPACEde>环境变量)。现在这已经不再是必需的了。

关于在Mac OS X 10.3.x上如何使用de>libgmallocde>的信息,请参见相应系统上的帮助手册。

返回页首 

标准 C++库

标准C++库支持很多调试特性。

  • 设置名为de>_GLIBCXX_DEBUGde>的编译时变量,启用标准C++库的调试模式。详细信息请参见标准C++库的文档。

  • 在GCC 4.0以前的版本中,将de>GLIBCPP_FORCE_NEWde>环境变量设置为1可以禁用标准C++库的内存缓存功能。这使您可以使用其它的内存调试工具 (比如防卫内存分配器) 来调试C++内存分配。详细信息请参见标准C++库文档。

    在GCC 4.0及以上版本中,这是默认的行为。

返回页首 

命令行工具

Mac OS X包含很多很酷的命令行调试工具。表2列出了我最喜爱的工具。

表2: 命令行工具精选

工具 文档 概要说明
de>gdbde> 帮助手册, 用GDB进行调试 命令行调试器
de>fs_usagede> 帮助手册 文件系统跟踪工具
de>sc_usagede> 帮助手册 系统调用跟踪工具
de>latencyde> 帮助手册 调度时延调试工具
de>ktracede>, de>kdumpde> 帮助手册 内核跟踪
de>heapde> 帮助手册 堆转储
de>vmmapde> 帮助手册 地址空间转储
de>malloc_historyde> 帮助手册 内存分配历史
de>leaksde> 帮助手册 泄漏检测
de>tcpdumpde> 帮助手册, 技术问答 QA1176,‘进行包跟踪’ 网络信息包跟踪
de>netstatde> 帮助手册 网络统计;de>netstat -ande>显示所有网络连接
de>lsofde> 帮助手册 列出打开的文件
de>PPCExplainde>   介绍PPC助记符

返回页首 

动态链接器 (dyld)

Mac OS X动态链接器 (dyld) 支持很多可通过环境变量启用的调试工具,完整的说明请参见它的帮助手册。表3列出了其中一些最有用的变量。

表3: 动态链接器的环境变量

变量 概要说明
de>DYLD_IMAGE_SUFFIXde> 优先查找带有这个后缀的库
de>DYLD_PRINT_LIBRARIESde> 记录库的加载
de>DYLD_PRINT_LIBRARIES_POST_LAUNCHde> 同上,但仅在de>mainde>函数运行之后
de>DYLD_PREBIND_DEBUGde> 输出预绑定的诊断信息
de>DYLD_PRINT_OPTSde> (10.4 及以上版本) 输出启动时的命令行参数
de>DYLD_PRINT_ENVde> (10.4 及以上版本) 输出启动时的环境变量
de>DYLD_IGNORE_PREBINDINGde> (10.4 及以上版本) 禁用性能测试的预绑定
de>DYLD_PRINT_APISde> (10.4 及以上版本) 记录对dyld API的调用(例如,dlopen)
de>DYLD_PRINT_BINDINGSde> (10.4 及以上版本) 记录符号绑定信息
de>DYLD_PRINT_INITIALIZERSde> (10.4 及以上版本) 记录映像初始化调用
de>DYLD_PRINT_SEGMENTSde> (10.4 及以上版本) 记录段映射
de>DYLD_PRINT_STATISTICSde> (10.4 及以上版本) 打印启动性能的统计信息

在这些工具中,de>DYLD_IMAGE_SUFFIXde>是最有用的,您可以用它来启用系统的调试库,如调试库部分所述。

如果在动态加载代码时出现代码加载失败,可以用名为de>NSLinkEditErrorde>的动态连接器例程来获取与出错代码有关的信息,更多信息请参见NSModule文档。即便使用像CFBundle这样的高级API来加载代码,de>NSLinkEditErrorde>也仍然可以使用。

此外,如果您使用dlopen加载代码,则可以用 dlerror获得类似的信息。

调试库

很多Mac OS X框架同时包含产品版本和调试版本。调试版本带有“_debug”后缀。例如,Core Foundation框架的产品版本为de>/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundationde>,而其调试版本则为de>/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation_debugde>。您可以将de>DYLD_IMAGE_SUFFIXde>环境变量设置为“_debug”,强制使dyld加载连接库的调试版本 (如果该连接库有这样的变体的话)。清单17的例子展示了如何在终端程序中完成这样的操作。

清单17: 使用_debug库

$ DYLD_IMAGE_SUFFIX=_debug /Applications/TextEdit.app/Contents/MacOS/TextEdit 2004-08-30 18:32:06.051 TextEdit[1393] CFLog (0): Assertions enabled […] 

如果您不喜欢终端方式,也可以用Xcode中可执行文件的查看器来完成同样的设定。在图2中,您可以看到“Use … suffix when loading frameworks”弹出菜单被设置为 “debug”。

图2: 在Xcode中启用调试库

不同框架的调试库的精确行为是各不相同的。大多数调试库包含如下行为:

  • 完全的调试符号——这个行为特别有用,尤其是当框架的源代码包含在Darwin中的时候。

  • 额外的断言——这有助于定位您的代码中的编程错误。

  • 额外的调试工具——本文档后面介绍的很多调试工具只能在调试库中使用。

我们强烈推荐您将这些调试库用于日常的调试过程。

请注意:除非特别声明,本文介绍的调试工具不要求使用调试库。

返回页首 

只启用一个调试库

在有些情况下,您可能希望只启用一个调试库。举例来说,假定你正在调试一个苹果事件的问题,你希望只启用AE框架的“_debug”版本。然而,当您将de>DYLD_IMAGE_SUFFIXde>设置为“_debug”时,却发现由于使用所有的调试库,应用程序的其它地方出现了问题。而在这个时候,您只想集中调试苹果事件的问题,那该怎么办呢?

幸运的是,这个问题有一个简单的答案,虽然在某种程度上有点儿并不精巧:只需对调试版本进行拷贝,使其覆盖非调试版本就可以了。清单18展示了一个这样的例子。

清单18: 只激活AE调试库

$ cd /System/Library/Frameworks/ApplicationServices.framework/\ Frameworks/AE.framework/Versions/A/ $ sudo cp -n AE AE_original Password: ******** $ sudo cp AE_debug AE 

在完成该操作后应该重新启动系统,以使更改生效。

重要信息: 这个技巧对调试是有用的,但有很多负面效果。例如,调试库与非调试库的预绑定地址不 同,因此预绑定的程序将不能进行预绑定,因此启动过程会变得比较慢。所以,我们建议只在专用于测试的计算机上这样做;或者,如果这么做不实用,那就在用测 试系统启动的主计算机上使用。而且,在调试完成后应该马上恢复到原来的库。

将原来的库拷贝回来,就可以恢复使用非调试库了,如清单19所示。

清单19: 使AE调试库实效

$ sudo cp AE_original AE Password: ******** 

请注意:推荐对库进行拷贝而不是重命名的原因是,这样不容易意外覆盖库的最新副本。

返回页首 

调试库版本

调试库由Xcode安装器(而不是系统安装器)安装。在更新系统软件时,调试库不会被更新。这使调试库和产品库常常会变得不同步 (通常调试库的版本更老一些)。因此可能导致一些无法预料的不兼容问题。

举例来说,如果您安装了Mac OS X 10.4,接着安装Xcode 2.0,然后把系统更新到Mac OS X 10.4.3,您将发现在调试库启用时,应用程序不能被启动。这是因为,在Mac OS X 10.4.3中,苹果公司给CoreServices框架添加了一个新例程,并且更新了DesktopServicesPriv框架,以使用该例程。然 而,这些框架 (CoreServices) 有一个有调试版本,而其它的 (DesktopServicesPriv) 则没有。这样,如果您启用调试库,就会得到CoreServices (由Xcode 2.0安装,因此相当于10.4版本) 的调试版本和DesktopServicesPriv (通过软件更新升级到10.4.3) 的产品变体,而这种组合无法成功加载。

这个问题在Mac OS X上一直存在,苹果公司正在考虑如何解决 (r. 4379270)。 与此同时,使用调试库的最可靠方式是建立专门的调试分区。在该分区上首先全新安装您希望使用的操作系统的主要版本,再安装与该版本相关联的开发工具。例 如,先全新安装Mac OS X 10.4,再安装Xcode 2.0。然后对该分区禁用软件更新。这就保证了产品库和调试库开始时是同步的,并始终保持同步。

返回页首 

Profile 库

很多库还包含带有“_profile”后缀的profiling版本。Profile库和调试库一样,有很多相同的限制。

返回页首 

脱离窗口服务器

在某些情况下,脱离窗口服务器可能是有帮助的。举例来说,假定您需要修改在de>/etc/ttysde>中指定的de>loginwindowde>参数,那么最好可以在de>loginwindowde>未处于运行状态时进行修改。Mac OS X提供了一个简便的方法,可以完成这个操作:

  1. 在系统偏好设置(System Preference)的账户(Account)面板中,点击登录选项(Login Options)。如果文本是灰色的,那么必须先点击锁的图标,以解锁该面板。

  2. 将名为“将登录窗口显示为”的设置改为“名称和密码”。

  3. 注销。

  4. 在登录窗口中,输入“>console”作为用户名;de>loginwindowde>将退出,然后您将直接面对显示“Login”提示符的TTY界面。

  5. 用标准用户名称和密码登录。

当您在这个级别上完成操作后,只需从shell中退出 (用de>exitde>命令),就会返回到标准登录窗口。

重要信息: 该环境不是单用户模式。大多数系统后台程序仍然在运行;只是系统的GUI组件被关掉了。

返回页首 

后台程序

大多数系统后台程序都包括某种调试工具。很多情况下,后台程序的调试功能在其手册中都有介绍。本部分将介绍其中一些特别令人感兴趣的特性。

launchd

launchd是由内核 (Mac OS X 10.4及以上版本) 运行的第一个进程;它负责启动系统中所有其它进程。de>launchdde>有很多有用的调试工具,我们将在随后的部分中分别介绍。

launchctl

您可以用launchctl命令修改de>launchdde>的状态。de>launchctlde>支持很多子命令;表4列出了其中一些与调试最为密切相关的命令。

表4: 有用的launchctl命令

命令 概要说明
de>logde> 修改de>launchdde>自身的日志记录级别;该命令的语法稍稍需要点儿技巧;最有用的命令是de>log level debugde>,用于启用所有的日志记录;有关如何查看命令输出的信息,请参见launchd日志记录部分。
de>stdoutde> 设置de>launchdde>的标准输出;由de>launchdde>执行的进程将继承这个值。
de>stderrde> 设置de>launchdde>的标准错误;由de>launchdde>执行的进程继承这个值。
de>limitde> 设置de>launchdde>的资源限制;由de>launchdde>执行的进程将继承这个限制;最常见的用法是de>limit core unlimitedde>,用于启用核心转储。

请注意: 对de>stdoutde>和de>stderrde>进行控制的能力并不像想象的那样有用。大多数程序或者重载de>stdoutde>和de>stderrde> (比如,在GUI程序中会指向de>/dev/consolede>),或者不向它们写任何内容 (比如,大多数后台程序用syslog记录日志)。

您可以将上述的某个命令作为参数运行de>launchctlde>,进行临时的更改。这会影响de>launchdde>自身以及更改之后由de>launchdde>执行的所有进程。

重要信息: 要影响de>launchdde>全局实例的状态,必须以root (通常用de>sudode>) 身份运行de>launchctlde>。如果您没有使用de>sudode>运行de>launchctlde>,那么所做的更改将只影响当前用户的de>launchdde>实例。

您可以在/etc/launchd.conf中添加命令,以进行长久的修改。de>launchdde>在引导进程初期应用这些设置,在重新启动后,则会影响系统中的每个进程。

返回页首 

launchd 日志记录

de>launchdde>将其操作记录到名为“launchd”的系统日志中。如果您要将所有的de>launchdde>日志输出发送到一个文件中,可在de>/etc/syslog.confde>中添加一行内容为“launchd.* /var/log/launchd.log”的文本。清单20展示了一种添加方法。

清单20: 将launchd操作记录到一个文件中

$ sudo cp /etc/syslog.conf /etc/syslog.conf-orig Password: ******** $ ( cat /etc/syslog.conf-orig ; echo "launchd.* /var/log/launchd.log" ) | \ sudo cp /dev/stdin /etc/syslog.conf $ sudo kill -HUP `cat /var/run/syslog.pid` 

de>/var/log/launchd.logde>文件通常为空,因为在默认情况下,de>launchdde>不会生成大量syslog输出。您可以用de>launchctlde>命令来增加日志记录的总量,如清单21所示。

清单21: 获取更多的launchd日志记录

$ sudo launchctl log level debug Password: ******** 

返回页首 

调试特定任务

最后,de>launchdde>允许您通过修改在其属性列表文件中的属性来调试特定任务。表5列出了对调试最有用的属性。

表5: 对调试有用的属性

属性 概要说明
de>Debugde> 在de>launchdde>处理任务的同时启用其中的日志记录
de>StandardOutPathde> 设置该任务的标准输出目标
de>StandardErrorPathde> 设置该任务的标准错误目标
de>EnvironmentVariablesde> 为该任务设置环境变量
de>SoftResourceLimitsde>, de>HardResourceLimitsde> 为该任务设置资源限制;在只为一项任务启用核心转储时最有用

返回页首 

lookupd

如果在de>lookupdde>上遇到了问题 (比如,DNS请求要花很长时间),可以用下列步骤来启用de>lookupd的de>调试特性。

  1. 在本地机器上名为de>/config/lookupdde>的NetInfo目录中创建de>Debugde>和de>Tracede>属性。

  2. 更改syslog配置,使NetInfo调试信息 (de>netinfo.debugde>) 发送给NetInfo日志文件 (de>/var/log/netinfo.logde>)。

  3. 向de>syslogdde>进程发送de>SIGHUPde>信号,使其接受之前所做的更改。

  4. 向de>lookupdde>进程发送de>SIGHUPde>信号,使其接受从步骤1之后的更改。

清单22展示了这样的一个例子。在完成这些步骤后,可以在de>/var/log/netinfo.logde>中查找调试输出。您还可以通过执行de>lookupd -statisticsde>命令来获得de>lookupdde>的统计信息。

清单22: 启用lookupd的调试特性

$ sudo dscl . create /dsRecTypeStandard:Config/lookupd Debug YES Password: ******** $ sudo dscl . create /dsRecTypeStandard:Config/lookupd Trace YES $ sudo cp /etc/syslog.conf /etc/syslog.conf-orig $ sed 's/netinfo.err/netinfo.debug/' /etc/syslog.conf-orig | \ sudo cp /dev/stdin /etc/syslog.conf $ sudo kill -HUP `cat /var/run/syslog.pid` $ sudo kill -HUP `cat /var/run/lookupd.pid` 

清单23展示如何撤销这些改动。

清单23: 禁用lookupd的调试特性

$ sudo dscl . delete /dsRecTypeStandard:Config/lookupd $ sudo mv /etc/syslog.conf-orig /etc/syslog.conf $ sudo kill -HUP `cat /var/run/syslog.pid` $ sudo kill -HUP `cat /var/run/lookupd.pid` 

有关de>lookupdde>的更多内容,请参见其帮助手册。

返回页首 

打印 (CUPS)

Mac OS X 10.2及以上的版本采用CUPS (Common UNIX Printing System,通用UNIX打印系统) 作为其核心打印架构。CUPS有内置的调试日志记录特性,由CUPS配置文件 (de>/etc/cups/cupsd.confde>) 控制。在该配置文件中,de>LogLevelde>指令控制日志记录的级别,而de>ErrorLogde>指令则控制日志的目标 (默认值为 de>/var/log/cups/error_logde>)。关于该文件的更多内容,参见帮助手册。

重要信息: 在更改该文件后,必须向CUPS后台程序发送de>SIGHUPde>信号,以使改动生效。清单24展示了一种实现方式。

清单24: 重新启动CUPS后台程序

sudo /System/Library/StartupItems/PrintingServices/PrintingServices restart 

如果您正在编写CUPS驱动或者过滤器,则可以向de>stderrde>打印信息,以增加日志的内容。您的每行输出必须以该信息的记录级别作为开头,如清单25所示。

清单25: 记录CUPS日志

// Debug message  fprintf(stderr, "DEBUG: page_width = %.0f\n", page_width);  // Warning message  fprintf(stderr, "WARNING: Printer not responding\n");  // Error message  fprintf(stderr, "ERROR: Lost connection with printer\n"); 

返回页首 

Core Services

Core Services包含很多例程 (比如,de>Debuggerde>、de>DebugStrde>以及de>SysBreakde>),用于进入调试器,以及在进入的同时产生一条消息。如果您将de>USERBREAKde>环境变量设置为1,这些例程就会向当前进程发送de>SIGINTde>信号,使程序进入GDB的调试状态。Xcode中有用于启用这个工具的图形用户界面 (即可执行文件查看器的“调试”面板中的“Break on Debugger() and DebugStr()”复选框, 如图3所示)。

图3: 在Xcode中设置USERBREAK环境变量

代码片断管理器 (CFM)

在Mac OS X上的CFM兼容环境支持两个有用的环境变量:de>CFMDebugBasicde>和de>CFMDebugFullde>,将其设置为1分别可以获得有限的和详细的调试日志记录。该工具对跟踪CFM终止和片段初始化例程失败的问题特别有用。

返回页首 

Core Foundation

Core Foundation (CF) 框架的所有变体都支持de>CFShowde>例程。该例程可以将所有的CF对象描述输出到de>stderrde>。您可以在自己的代码中对其进行调用,不过在GDB中调用更加有用。清单26展示了这样的一个例子。

清单26: 在GDB中调用CFShow

$ gdb /Applications/TextEdit.app GNU gdb 5.3-20030128 (Apple version gdb-330.1) […] (gdb) fb CFRunLoopAddSource No symbol table is loaded.  Use the "file" command。 Breakpoint 1 at 0x0 (gdb) r […] Breakpoint 1, 0x901b5764 in CFRunLoopAddSource () (gdb) call (void) CFShow($r3) {     locked = false,     wait port = 0xf03,     stopped = false,     current mode = (none),     common modes = {         count = 1,         capacity = 4,         values = (             1 : {                 contents = "kCFRunLoopDefaultMode"             }         )     },     common mode items = (null),     modes = {         count = 1,         capacity = 17,         values = (             20 : {                 name = kCFRunLoopDefaultMode,                 locked = false,                 port set = 0x1003,                 sources = (null),                 observers == (null),                 timers = (null)             },         )     } } 

重要信息: 如果您没有看到de>CFShow的de>输出,那么可能是被发送到控制台了。有关如何查看这种输出的信息请参见查看调试输出部分。

请注意:在清单26中,我们已经对de>CFShow的de>输出重新进行了格式化,使其更便于阅读。

您可能会发现,从GDB调用一些其它的CF例程是很有用的,包括de>CFGetRetainCountde>、de>CFBundleGetMainBundlede>以及de>CFRunLoopGetCurrentde>。

Core Foundation框架还有一个调试版本,可以提供很多的调试帮助。例如,Core Foundation的非调试版本不检查传入参数的合法性,而调试版本则包含了完全的参数检查。这有助于跟踪代码中存在的很多与Core Foundation相关的错误。

Core Foundation调试库支持一个叫做de>CFZombieLevelde>的环境变量。它将这个变量解析为一个包含一系列标志位的整数。表6介绍了目前定义的位。这些有助于追踪各种CF内存管理问题。

表6: CFZombieLevel环境变量的位定义

操作
0 涂写解除分配了的CF内存
1 在涂写已经解除分配的CF内存时,不要涂写对象头 (de>CFRuntimeBasede>)
4 从不释放用于存放CF对象的内存
7 如果这个位被设置,则用位8..15中的内容涂写解除分配的内存,否则则用0xFC进行涂写
8..15 如果位7被设置,用该值涂写解除分配的内存
16 涂写已分配的CF内存
23 如果这个位被设置,则用位24..31中的内容涂写分配的内存,否则则用0xCF进行涂写
24..31 如果位16被设置,用该值涂写已分配的内存

警告: de>CFZombieLevelde>目前并不兼容自定义的分配程序 (通过de>CFAllocatorCreatede>函数创建的分配器)(r. 4158401)。如果您试图使用这个组合,则在第一次使用自定义解除分配器对一个对象进行解除分配时,程序将会崩溃。

CFNotificationCenter

您可以通过创建de>/var/log/do_dnserver_logde>文件来启用CFNotificationCenter日志记录。如果该文件存在并由root用户所有,CFNotificationCenter的后台程序 (distnoted) 会将其活动记录到de>/var/log/dnserver.logde>。这个变更需要重新启动de>distnotedde>才能生效,重新启动系统是完成这个操作的最安全方式。

您可以在本文的文件部分看到这样的一个例子。

请注意: 在Mac OS X 10.3.x上,对应的文件为de>/var/tmp/do_dnserver_logde>和de>/var/tmp/dnserver.logde>。

Mac OS X 10.3.x还支持客户端日志。如果您创建了de>/var/tmp/do_xnc_logde>文件,那么CFNotificationCenter会在每个客户端的活动记录到它自己的文件 (de>/var/tmp/xnc_logs/de>) 中。这个设置需要重新启动程序才能生效。客户端日志记录在Mac OS X 10.4及以上版本中不可用。

返回页首 

组件管理器

组件管理器导出一个名为de>GDBComponentListde>的例程,在GDB中可以调用这个例程来输出已注册组件及其实例的数据库。清单8给出了一个用法示例。

另外,如果将de>ComponentDebugde>环境变量设置为1,组件管理器就会输出各种调试信息。当组件的某个部分出现问题,特别是加载失败的时候,这些可能是有帮助的。

最后,如果组件加载失败,您可以使用动态链接器工具来进行调试。详细信息请参见动态链接器 (dyld)部分。

返回页首 

文件管理器

Core Services文件管理器 (通常叫做Carbon文件管理器,或就叫文件管理器) 与de>fs_usagede>命令行工具集成得非常好。如果启用了Core Services调试库,那么de>fs_usagede>将不仅会显示基本的BSD文件系统调用,而且还会提示它们的文件管理器调用。更多的细节请参见de>fs_usage的de>帮助手册。

如果您将de>FilesASDDebugde>环境变量设置为1,文件管理器会将其对AppleDouble文件的处理记录在对资源分支和Macintosh元数据没有内建支持的文件系统上。

如果您将de>VNDebugde>环境变量设置为1,则文件管理器会记录其与磁盘仲裁组件的交互。

文件管理器有很多可以在GDB中调用的有用例程。最有用的例程是de>PrintVolumeInfode>,用于输出卷的列表。它接受一个参数,用于指定是使用文件管理器API来列举卷(1),还是通过遍历一个内部数据结构(0)来获得卷列表。前者得到的结果是一个很好的概要,后者则可以获得更为详尽的信息。清单27分别展示这两种情况。

清单27: PrintVolumeInfo的实例

(gdb) call (void) PrintVolumeInfo 1:vol=-100      "X2" 2:vol=-101      "X1" 3:vol=-102      "Guy Smiley" 4:vol=-130      "UFS Victim" 5:vol=-105      "Network" 6:returned error -35 (gdb) call (void) PrintVolumeInfo(0) Volume Information:         "X2"    mountpoint: "/"                 vRef=-100  volID=-100 diskID=disk0s10         "X1"    mountpoint: "/Volumes/X1"                 vRef=-101  volID=-101 diskID=disk0s9         "Guy Smiley"    mountpoint: "/Volumes/Guy Smiley"                 vRef=-102  volID=-102 diskID=disk0s11         "UFS Victim"    mountpoint: "/Volumes/UFS Victim"                 vRef=-130  volID=-130 diskID=disk3s2         "Network"       mountpoint: "/Network"                 vRef=-105  volID=-105 diskID=/Network 

另外还有两个GDB可调用的例程,主要着眼于在Mac OS X上VFS插件的开发。这两个例程用于输出目录列表缓存以及文件ID树,两者都是由文件管理器为非volfs卷维护的兼容结构 (有关volfs的内容请参见技术问答QA1113,‘文件夹"/.vol"和"volfs"’)。

de>PrintEnumerationCachede>用于输出卷上的目录列表缓存。它接受单一参数,即您感兴趣的卷的de>vRefNumde>。

文件ID树由de>coreservicesdde>进程集中维护。在输出前,必须首先将GDB附加着de>coreservicesdde>进程上,然后将de>coreservicesdde>的de>stderrde>重定向到终端设备上 (如技术文档 TN2030,‘GDB之MacsBug高手篇’中所述),再用单一字符串“fsnode_all”作为参数,调用de>FileIDTreeStorageServerDumpde>例程,结果得到一个满是深度加密信息的de>CFStringde>字符串。清单28展示了这样的一个例子。

清单28: 打印文件ID树

$ sudo gdb Password:******** GNU gdb 6.1-20040303 (Apple version gdb-425) […] (gdb) call (int) close(2) $1 = 0 (gdb) shell tty /dev/ttyp2 (gdb) call (int) open("/dev/ttyp2", 2) $2 = 2 (gdb) call (void *) FileIDTreeStorageServerDump("fsnode_all") $3 = (void *) 0x316320 (gdb) call (void) CFShow($3) Shared universes:         501(3): mod seed = 2         Shared segments ([domainID][segmentID]():
[0][0](32/480):0x200e00 [1][0](496/16):0x300e00 [2][0](504/8):0x504b00 Entries scheduled for removal: 69000000(108)@1: FSObjectVolumeEntry: vRefNum: -105 0(5): mod seed = 2 Shared segments ([domainID][segmentID]():
[0][0](32/480):0x400c00 [1][0](496/16):0x500c00 [2][0](504/8):0x300800 Entries scheduled for removal: 69000000(108)@1: FSObjectVolumeEntry: vRefNum: -105 checked-in processes: 204(0/0x7000040): seed 2(0), idle 211(0/0x6000040): seed 2(0), idle 248(501/0x7000040): seed 2(0), idle 114(501/0x6000040): seed 1(0), idle 120(501/0x5000040): seed 2(0), idle 183(0/0x5000040): seed 1(0), idle No transactions in progress

请注意:在Mac OS X 10.3.x中,文件ID树由每个进程独立维护。因此可以直接调用de>PrintFileIDTreede>例程。它接受单一的de>vRefNumde>参数,输出当前进程在指定卷上从文件ID到文件路径的映射。

返回页首 

文件夹管理器

文件夹管理器支持一个名为de>FolderDebugde>的环境变量。如果该变量设置为1,文件夹管理器将输出很多额外的调试消息。该工具要求使用Core Services调试库。

返回页首 

Gestalt

Core Services导出三个例程——de>DebugDumpGestaltde>、de>DebugGestaltde>及de>DebugGestaltStrde>。您可以在GDB中调用这些例程来查看Gestalt注册表。清单29展示了这些例程的用法实例。

清单29: 使用Gestalt调试例程

(gdb) call (void) DebugDumpGestalt() DebugDumpGestalt 'a/ux': 0x2a65c6ac (proc) 'addr': 0x00000007 […] (gdb) call (void) DebugGestalt(0x766d2020) 'vm  ': 0x00000011 (gdb) call (void) DebugGestaltStr("vm  ") 'vm  ': 0x00000011 

返回页首 

线程

Core Services线程API (MP线程和线程管理器) 支持环境变量de>ThreadDebugde>。该变量用于启用少量的调试消息和一些内部的一致性检查。该工具要求使用Core Services调试库。

返回页首 

Web Services

Web Services支持两个有用的环境变量,de>WSDebugde>和de>WSDebugVerbosede>,将其设置为1可以分别获得有限的和详细的调试日志。这些变量在非调试库中是有效的,但在调试库中包含更多的日志记录。

返回页首 

磁盘和光盘

磁盘仲裁

如果在de>/etc/mach_init.d/diskarbitrationd.plistde>中的de>Commandde>属性后加“-d”,然后重新启动,磁盘仲裁组件会将其活动的详细内容记录到de>/var/log/diskarbitrationd.logde>文件中。

返回页首 

光盘刻录

如果将de>DRVerboseLoggingde>环境变量设置为1,光盘刻录组件就会将信息记录到控制台。

重要信息: 由于软件错误 (r. 4413303)的原因,该工具在Mac OS X 10.4.x上不起作用。

返回页首 

磁盘工具

如果将de>DUDebugMenuEnabledde>预设值设置为1,磁盘工具组件将显示一个带有很多有用命令的“调试”菜单。

返回页首 

ApplicationServices

苹果事件

苹果事件管理器有很多的内置调试支持。了解该支持的最好的方式是用GDB调用de>GDBPrintHelpDebuggingAppleEventsde>例程,如清单30所示。

清单30: 苹果事件管理器调试帮助

(gdb) call (void) GDBPrintHelpDebuggingAppleEvents() The AppleEvent Manager has been completely rewritten for this version of Mac OS X.  The internal structure of an AEDesc is now a pointer to a sparse tree.  If you're having problems it could be because you're accessing the dataHandle of an AEDesc directly.  Also of note is that AEGetDescData and AEGetDescDataSize only work with value descriptors created by AECreateDesc - you cannot get the data size of an AERecord or AEList, for example.  To print the contents of an AppleEvent from GDB, you can:   (gdb) call (void) GDBPrintAEDesc(descPtr)  To view all currently installed AppleEvent coercion handlers:   (gdb) call (void) GDBPrintAECoercionTables()  To view all contents install AppleEvent handlers:   (gdb) call (void) GDBPrintAEHandlerTables()  Additionally, to log information about AppleEvent manager calls, you can set environment variables that will produce debugging output to the console:    % setenv AEDebug         1            # general debug output   % setenv AEDebugSends    1            # print sent events   % setenv AEDebugReceives 1            # print received events and replies   % setenv AEDebugVerbose  1            # print result information on (most) \                                           calls (very verbose)   % setenv AEDebugOSL      1            # print result information from OSL   % setenv AEDebugFile     /tmp/logfile # send debug output to this file 

请注意: 在以上文本中,“this version of Mac OS X”指的是Mac OS X 10.2。

一些环境变量—尤其是de>AEDebugde>、de>AEDebugVerbosede>、de>AEDebugOSLde>及de>AEDebugFilede>—只在调试库支持。

如果您正在使用调试库,则也可以在de>/var/tmpde>目录下创建三个文件来控制苹果事件的调试。

  • 创建de>/var/tmp/AEDebug文件de>相当于设置de>AEDebugde>、de>AEDebugSendsde>、de>AEDebugReceivesde>、de>AEDebugReceivesde>及de>AEDebugOSLde>环境变量。

  • 创建de>/var/tmp/AEDebug.outde>文件相当于将de>AEDebugFilede>设置为“/var/tmp/AEDebug.out”。

  • 创建de>/var/tmp/AEDebugLogsde>文件将使苹果事件管理器将其所有输出发送到一个名为de>/var/tmp/AELog-de>的文件中。

重要信息: 最后两点只影响调试输出的目标。为了产生调试输出,必须使用上述的某个环境变量,或者通过创建de>/var/tmp/AEDebugde>文件来启用。

如果将de>AE_PRINT_XMLde>环境变量设置为1 (10.4及以上版本),de>GDBPrintAEDescde>将输出XML描述符 (如果可能的话)。

远程的苹果事件

如果您在远程的苹果事件上遇到了问题,可能会发现启用苹果事件服务器进程的日志记录很有用。通过编辑de>/System/Library/LaunchDaemons/eppc.plistde>文件、向de>ProgramArgumentsde>数组添加“--debug”项,就可以完成这一操作。最终结果应该是一个如清单31所示的文件。

清单31: 启用远程的苹果事件调试

            Disabled                  Label         com.apple.AEServer         ProgramArguments                          /System/Library/Frameworks/ApplicationServices.\ framework/Frameworks/AE.framework/Versions/A/Support/AEServer                 --debug                  inetdCompatibility                          Wait                                   Sockets                          Listeners                                          SockServiceName                         eppc                         SockType                         stream                         Bonjour                                                     

您必须重新启动远程苹果事件服务才能使更改生效,在系统偏好设置(System Preference)的共享(Sharing)面板中可以完成此项操作。日志信息将出现在系统日志中。

重要信息: 因为de>AEServerde>是由launchd按需启动的,因此除非向该机器实际发送苹果事件,否则不会看到任何日志项。如果发送事件后仍然看不到任何日志项,请重新启动服务器,然后再试。

请注意:在Mac OS X 10.3.x上,苹果事件服务器由xinetd运行。配置文件为de>/etc/xinetd.d/eppcde>。要启用调试,请取消“server_args”行的注释,并将该参数的值更改为“--debug”。

返回页首 

进程管理器

在某些情况下,您可能想要调试一个进程,但又不想从GDB中启动它。举例来说,如果您为了调试一个GUI应用程序,已经以SSH方式登 录一台远程计算机,这时您不应该直接从GDB启动该程序,因为那样的话该应用程序将在错误的Mach引导命名空间中,不能连接到象pasteboard服 务器这样重要的服务上。正常情况下,这并不是一个问题:只要简单地请求远程用户启动该应用程序,然后用GDB的de>attachde>命令将GDB附着到该运行着的应用程序就可以了。 然而,如果您想要调试该应用程序的启动过程,会发生什么呢?你最终会进入一场“附着比赛”,在远程用户启动应用程序的同时,你要飞快地用GDB连接,不好。

进程管理器针对该问题提出了一个很好的解决方案。如果将de>INIT_Processesde>环境变量设置为1,进程管理器将延迟该应用程序的启动15秒,您可以在这段时间内用GDB进行附着。进程管理器甚至会在系统日志中记录一条细致的消息 (参见清单32) ,以使你知道它在干什么。

清单32: 由被暂停的进程生成的系统日志信息

Sep  7 14:18:37 guy-smiley QuickTime Player: Blocking on INIT_Processes \ for 15 seconds; attach to pid 4344 if you want. 

返回页首 

Core Graphics

Quartz Debug有很多有用的调试特性,更多细节请参见技术问答 QA1236,‘用QuartzDebug调试图形程序’。

返回页首 

QuickDraw

QuickDraw导出很多可以在GDB中调用的例程,您可以通过这些例程获得QuickDraw的状态。最重要的三个例程是de>QDDebugPrintPortInfode>、de>QDDebugPrintCGSInfode>和de>QDDebugDumpRegionde>,用于将信息输出到de>stderrde>。清单33展示了这些例程的用法实例。

清单33: QuickDraw的打印例程

(gdb) set $window = (void *) FrontWindow() (gdb) set $port = (void *) GetWindowPort($window) (gdb) call (int) QDDebugPrintPortInfo($port) Dumping port 0x435670...     PixMap:              0x1FE72C         Base Address:    0xB0028000 [onscreen, buffered]         RowBytes:        0xFFFF9400         Bounds:          (0, 0, 106, 352)  (352w x 106h)         Depth:           0020     Port bounds:         (0, 0, 106, 352)  (352w x 106h)     Port shape:          0x1FE798 (0, 0, 106, 352)  (352w x 106h) …     Vis rgn:             0x1FE730 (0, 0, 106, 352)  (352w x 106h) …     Clip rgn:            0x1FE738 (-32000, -32000, 32000, 32000)  …     Fore Color:          0000 0000 0000     Back Color:          FFFF FFFF FFFF […] $21 = 0 (gdb) call (int) QDDebugPrintCGSInfo($port) CGS info for port 0x435670     CGSWindowID:           19798     Shape:                 0x59E734 (99, 785, 205, 1137)  (352w x …     Vis Region:            0x59E72C (0, 0, 0, 0)  (0w x 0h) [rect]     Dirty Region:          0x59E730 (0, 0, 0, 0)  (0w x 0h) [rect] $20 = 0 (gdb) # 0x1FE730 is "Vis rgn" from QDDebugPrintPortInfo (gdb) set $rgn=0x1FE730 (gdb) call (int)QDDebugDumpRegion($rgn) Size = 116  Bounds = (0, 0, 106, 352)  (352w x 106h)  NEW FORMAT 0: 2 350 1: 1 351 2: 0 352 104: 1 351 105: 2 350 106:  $21 = 0 

其它例程的设计目的是帮助您在屏幕上显示区域,实现的方式是使该区域的形状闪烁。清单34展示了如何调用这些例程;遗憾的是在本文档中无法看到执行的结果,因此你只能自己试用。这里假定de>$portde>和de>$rgnde>两个变量已经如清单33那样准备好了。

清单34: 使屏幕区域闪烁的QuickDraw例程

(gdb) call (int) QDDebugFlashRegion($port, $rgn) $23 = 0 (gdb) call (void) QDDebugFlashClipRgn($port) (gdb) call (void) QDDebugFlashPortShape($port) (gdb) call (void) QDDebugFlashVisRgn($port) (gdb) call (int) QDDebugFlashCGSWindowShape($port) $24 = 0 (gdb) call (int) QDDebugFlashCGSWindowOpaqueShape($port) $25 = 0 (gdb) call (int) QDDebugFlashCGSVisRgn($port) $26 = 0 (gdb) call (int) QDDebugFlashCGSDirtyRgn($port) $27 = 0 

返回页首 

Carbon (HIToolbox)

Carbon的HIToolbox包含很多方便调试的工具,包括:

  • 一个的HIToolbox库的调试版本,在出现错误时可输出调试信息。

  • 很多的可从GDB调用的HIToolbox例程,用于输出进程中HIToolbox对象的内容,包括事件、菜单、窗口、对话框和控件。

  • 一些可以使区域闪烁的例程,使您可以在屏幕上看到相应的区域。

  • 多种跟踪事件的工具,用于跟踪在工具箱流转的事件。

HIToolbox 对象打印例程

下面的清单展示了各种HIToolbox对象打印例程。

清单35: 打印HIToolbox事件

(gdb) call (int)GDBPrintEventQueue()  Printing event queue 0x7632536c... RunLoop: 0x40c560 Count: 4 Header: 0x1805010 Head: 0x49faf0 Tail: 0x489d10  EventRef Event Kind           Time       P Cnt Desc -------- -------------------- ---------- - --- --------------------   49FAF0 kEventMouseDown       219335.28 H 001 x=879, y=61, button 1   489530 kEventWindowActivate  219335.46 H 002 0x4350A0 "Untitled 1"   43A4E0 kEventAppActiveWindo 218971.143 S 002   489D10 kEventWindowUpdate   219335.473 L 002 0x4A3C10 "Untitled 1 Properties" $2 = 0 (gdb) # 0x489D10 is the kEventWindowUpdate event from last command (gdb) call (void) _DebugPrintEvent(0x489D10) Displaying event 489D10...    Class           wind    Kind            1    When                219335    Priority        Low    RetainCount     2    Queued          Yes    Info            kEventWindowUpdate, 0x4A3C10 "Untitled 1 Properties"    Parameters         param: ----            type: wind            size: 4            data: 004A3C10                   J<

清单36: 打印HIToolbox菜单

(gdb) call (void) DebugPrintMenuList() Index  MenuRef     ID   Title -----  ----------  ------  -----     1  0x0041F330  -21629       2  0x0042EC00     128  QuickTime Player     3  0x0043C4B0     129  File     4  0x00445B70     130  Edit […]             0x0042CF90     140  Open Recent (gdb) # 0x0042EC00 is the QuickTime Player menu (gdb) set $menu=0x0042EC00 (gdb) call (void) DebugPrintMenu($menu) MenuRef:          0x0042EC00     Title               : QuickTime Player     ID                  : 128     Width               : 0     Height              : 0     Enabled             : true     Attributes          : CondenseSeparators, ReceivedInit     Modal level         : 0     Refcount            : 3     Element             : 0x004435B0     Item Count          : 12     Item Icon  Cmd Key   Mark   CmdID E V Text     ---- ---- -------- -------- ----- - - ----     0001 0000 0x00 ' ' 0x00 ' '       Y Y About QuickTime Player     0002 0000 0x00 ' ' 0x00 ' '       N Y -     0003 0000 0x00 ' ' 0x00 ' '  pref Y Y Preferences […] HIObject     Ref count           : 3     Event Target        : 0x42f040     Event Handler       : 0x436f10 (gdb) call (void) DebugPrintMenuItem($menu, 1) Menu: 0x0042EC00 Item: 1 Info:  Text:                  About QuickTime Player  Mark:                    Cmd Key:                 Icon:                    Style Normal  Command ID:          0 (0x00000000)  Modifiers:            0x00 […] 

清单37: 打印HIToolbox的窗口和对话框

(gdb) call (void) DebugPrintWindowList()  Window      Class    WID  Vis Hil Level Title                 Group ----------  -------- ---- --- --- ----- --------------------- -----------… 0x004350A0  Document 4ED4  Y   Y      0 Untitled 1            0x76E47A89 … 0x004A3C10  Document 4EED  Y   N      0 Untitled 1 Properties 0x76E47A89 … (gdb) # 0x004350A0 is the "Untitled 1" window (gdb) set $window=0x004350A0 (gdb) # 0x004A3C10 is the "Untitled 1 Properties" dialog (gdb) set $dialogWindow=0x004A3C10 (gdb) call (void) DebugPrintWindow($window) Window 0x004350A0     Title               : Untitled 1     Class               : Document     Group               : 0x76E47A89 "com.apple.HIToolbox.windowgroups.document"     Scope               : all     Attributes          : Collapse Box, In WindowMenu     Visible             : Yes     Collapsed           : No     Latent visibility   :      Highlighted         : Yes     Structure region    : 1FE80C #0, #0, #106, #352 (#352w x #106h) [non-rect] […] (gdb) call (void) DebugPrintAllWindowGroups() Window group tree -------------------------------------------------------------------------…   1  level    0  group 0x76E0BFE9 "com.apple.hitoolbox.windowgroups.root"   2  level    0    group 0x76E47A89 "com.apple.HIToolbox.windowgroups.doc… (gdb) # 0x76E47A89 is the second window group (gdb) call (void) DebugPrintWindowGroup(0x76E47A89) WindowGroup 0x76E47A89 "com.apple.HIToolbox.windowgroups.document"  Attributes:             Refcount:             1  Previous group:         Next group:             Parent group:         0x76E0BFE9 "com.apple.hitoolbox.windowgroups.root" […] (gdb) set $dialog = (void *) GetDialogFromWindow($dialogWindow) (gdb) call (void) GDBShowDialogInfo($dialog) Dialog:                 0x76ED59A1  Window:                0x004A3C10 "Untitled 1 Properties"  TextHandle:            0x0059EC7C  Default Item:          1  Cancel Item:           0  Keyboard Focus Item:   0  RefCon:                0x06054AB5 (101010101) 

清单38: 打印HIToolbox控件

(gdb) call (void) GDBShowControlHierarchy($window) Dumping info for window 0x4A3C10 Window found. Dumping views... Root 0x4ba260 , ID ''/0, (-32768,-32768,32767,32767), Embedder, Vis, Act,…     Control 0x4c24d0  ( "" ), ID ''/0, (172,301,226,317), Vis,…     Control 0x4c6080  ( "" ), ID ''/0, (75,301,142,317), Vis, …     Control 0x4c49c0  ( "Delete" ), ID ''/0, (241,220,261,290)…     Control 0x4c4790  ( "Edit?" ), ID ''/0, (241,135,261,205),…     Control 0x4c17c0  ( "Add?" ), ID ''/0, (241,50,261,120), V…     Control 0x4be1d0  ( "" ), ID ''/0, (12,176,28,316), Vis, A…     Control 0x4ba1f0  ( "" ), ID ''/0, (12,24,28,164), Vis, Ac… (gdb) # 0x4c24d0 is first scrollbar control (gdb) call (void) GDBShowControlInfo(0x4c24d0) HIScrollBar     Size                : Auto     Live Tracking       : No Control 0x004C24D0 ""     Control Kind        : 'appl', 'sbar'     Control ID          : '', 0     Window              : 0x004A3C10 "Untitled 1 Properties"     Parent              : 0x004BA260     Minimum             : 0 (0x00000000)     Maximum             : 0 (0x00000000)     Value               : 0 (0x00000000) […] HIObject     Ref count           : 1     Event Target        : 0x4c39a0     Event Handler       : 0x4c3a10 

最后,您可以使用de>HIObjectPrintDebugInfode>来输出任何HIObject (窗口,菜单,控件,HIViews等) 的调试信息。

返回页首 

使 HIToolbox 区域闪烁

清单39所示的例程可以用来使区域闪烁,从而方便在屏幕上查看。遗憾的是,在本文档中无法看到闪烁的结果,你必须自己试用。

清单39: 使HIToolbox区域发生闪烁的例程

(gdb) call (void) DebugFlashWindowVisRgn($window) (gdb) call (void) DebugFlashWindowUpdateRgn($window) 

返回页首 

HIToolbox 事件的调试

随着Carbon事件的出现,开发者通常很难理解工具箱中的事件传递过程。HIToolbox提供了两个调试工具,有助于解决这个问题。

EventDebug 环境变量

将de>EventDebugde>环境变量设置为1可以使HIToolbox记录所有发生的事件。清单40展示了这样的一个例子。

清单40: EventDebug的输出

$ EventDebug=1 /Applications/QuickTime\ Player.app/Contents/MacOS/\ QuickTime\ Player Event Posted: Queue: 0x763059ae, Event: kEventAppleEvent, 221132.233, S … SendEventToEventTarget entered     Sending Event to 0x4128E0: hiob 2         Called handler 0x927F3200. Event was handled         Leaving target 0x4128E0 with result 0 SendEventToEventTarget entered     Sending Event to 0x41E6F0: hiob 2         SendEventToEventTarget entered             Sending Event to 0x41EC30: hiob 2                 SendEventToEventTarget entered                     Sending Event to 0x41EC30: hiob 2                         Called handler 0x927F3200. Event was handled                         Leaving target 0x41EC30 with result 0                 Called handler 0x927FBB50. Event was handled                 Leaving target 0x41EC30 with result 0         Called handler 0x927F3200. Event was handled         Leaving target 0x41E6F0 with result 0 […] 

返回页首 

事件跟踪

de>EventDebugde>环境变量会产生大量输出,这可能会有一些负面效果。特别是会使程序明显慢下来,而且有时难于从所有输出中找到需要的信息。

这个问题的一个解决方案是按事件进行跟踪。您可以通过从GDB中调用de>TraceEventByNamede>例程来进行这样的跟踪。清单41展示了这样的一个例子,即只启用de>kEventRawKeyDownde>事件的跟踪。

清单41: 事件跟踪

(gdb) call (void) TraceEventByName("kEventRawKeyDown") (gdb) c Continuing. Event Posted: Queue: 0x76309338, Event: kEventRawKeyDown, 221443.183, S SendEventToEventTarget entered     Sending Event to 0x415750: kEventRawKeyDown         SendEventToEventTarget entered             Sending Event to 0x413050: kEventRawKeyDown                 Called handler 0x928CD05C. Event was NOT handled                 Leaving target 0x413050 with result -9874         SendEventToEventTarget entered             Sending Event to 0x42DE30: kEventRawKeyDown                 Leaving target 0x42DE30 with result -9874             Sending Event to 0x4351F0: kEventRawKeyDown                 Leaving target 0x4351F0 with result -9874             Sending Event to 0x4126F0: kEventRawKeyDown                 Called handler 0x929597E0. Event was NOT handled                 Called handler 0x927F4F40. Event was NOT handled                 Leaving target 0x4126F0 with result -9874         Leaving target 0x415750 with result -9874 Event Removed: Queue: 0x76309338, Event: kEventRawKeyDown, 221443.183, S Event Pulled (C): kEventRawKeyDown, 221443.183, S 

返回页首 

HIToolbox 事件统计

有两个环境变量可以使HIToolbox输出事件的统计信息。将de>EventRatede>变量设置为1可以使HIToolbox每秒输出一个该秒时间中处理的事件的概要信息。对de>HLTBRecordEventStatsde>变量进行设置则可以使HIToolbox在应用程序退出时输出一批事件处理的统计信息;这个特性要求使用调试库。

返回页首 

其它 HIToolbox 调试工具

如果将de>NSQuitAfterLaunchde>环境变量设置为1,则应用程序就会在进入事件循环后退出。这在希望测量应用程序的启动时间或者查找内存泄漏时很有用。AppKit也支持这个环境变量。

重要信息: 从Mac OS X 10.4起,你必须使用HIToolbox框架的profile版本(de>HIToolbox_profilede>)才能使用这个环境变量。

将de>HLTBPrintKeyMatchingStatusde>环境变量设置为1可以使HIToolbox输出关于菜单项匹配详细信息。该特性要求使用该调试库。

将de>TSMEventTracingde>环境变量设置为1则可以使HIToolbox输出与文本服务管理器 (TSM) 事件处理有关的详细信息。

返回页首 

Cocoa

所有Cocoa对象 (即派生自de>NSObjectde>的对象) 都支持de>descriptionde>方法,该方法返回一个用于描述该对象的de>NSStringde>。访问这个对象描述的最好方式是通过GDB的de>print-objectde> (或者简写为de>pode>) 命令,如清单42所示。

清单42: 使用GDB的po命令

$ gdb /Applications/TextEdit.app GNU gdb 6.1-20040303 (Apple version gdb-434) […] (gdb) fb -[NSCFDictionary copyWithZone:] Function "-[NSCFDictionary copyWithZone:]" not defined. Breakpoint 1 (-[NSCFDictionary copyWithZone:]) pending. (gdb) r […] Breakpoint 1 at 0x928ea1d4 Pending breakpoint 1 - "-[NSCFDictionary copyWithZone:]" resolved  Breakpoint 1, 0x928ea1d4 in -[NSCFDictionary copyWithZone:] () (gdb) po $r3 Reading symbols for shared libraries . done { copyright = ; author = ; OpenPanelFollowsMainWindow = 0; UseTransitionalDocType = 0; UseInlineCSS = 0; […] } 

请注意:de>print-objectde>实际上是调用指定对象的de>debugDescriptionde>方法。de>NSObjectde>通过调用de>descriptionde>方法来实现这个方法。因此在缺省情况下,一个对象的调试描述跟它自身的描述是一样的。然而,如果您希望降低它们之间的耦合度,可以对de>debugDescriptionde>方法进行重载。很多Cocoa对象都是这样做的。

Objective-C

如果对de>OBJC_HELPde>环境变量 (10.4 及以上版本)进行设置,Objective-C运行时将输出一个含有运行环境支持的所有调试环境变量的列表。其中一些最有用的环境变量如表7所示。

表7: 有用的Objective-C运行环境的调试环境变量

变量 概要说明
de>OBJC_PRINT_IMAGESde> 记录由运行时加载的映像
de>OBJC_PRINT_LOAD_METHODSde> 记录de>+loadde>方法的执行
de>OBJC_DEBUG_FRAGILE_SUPERCLASSESde> 如果一个类的实例变量覆盖了其超类的实例变量,则发出警告

如果将de>NSObjCMessageLoggingEnabledde>环境变量设置为“YES”,Objective-C运行环境会把所有派发的Objective-C消息记录到一个名为de>/tmp/msgSends-de>的文件中。

在汇编级别上调试Cocoa代码时,请记住下面这些Objective-C运行环境的特性:

  • Objective-C编译器给每个方法都添加两个隐含参数,第一个是一个指针,指向接收消息的对象 (de>selfde>) 。在PowerPC上,这个参数保存在寄存器de>r3de>中。在Intel上则在位于de>espde>+4的内存中,这里假定程序停在该方法实现的第一条指令处 (关于在基于Intel的计算机上访问参数的详细信息,请参见架构考量)。

  • 第二个隐含参数是方法选择器。在Objective-C中,它是de>SELde>类型的参数;在GDB中可以作为一个C字符串来打印。在PowerPC上,该参数保存在寄存器de>r4de>中。在Intel上,该参数位于内存的de>espde>+8处。

  • Objective-C运行环境通过一个名为de>objc_msgSendde>的C函数进行方法的派发。

  • 任何Objective-C对象的第一个字 (de>isade>字段) 是一个指向该对象类的指针。

清单43展示了一个在GDB中使用这些信息的例子。

清单43: Objective-C运行环境的‘秘密’

$ gdb /Applications/TextEdit.app GNU gdb 5.3-20030128 (Apple version gdb-330.1) […] (gdb) r Starting program: /Applications/TextEdit.app/Contents/MacOS/TextEdit […] ^C Program received signal SIGINT, Interrupt. 0x900074c8 in mach_msg_trap () (gdb) # Set a breakpoint on the Objective-C method dispatcher (gdb) b objc_msgSend Breakpoint 1 at 0x908311f4 (gdb) # Continue execution... (gdb) c Continuing.  Breakpoint 1, 0x908311f4 in objc_msgSend () (gdb) # Hit the breakpoint; dump the first 4 words of the object (gdb) x/4x $r3 0x10cc10:       0xa0a04e18      0x00000001      0x00000000      0x00000000 (gdb) # Print the selector. (gdb) x/s $r4 0x9083ed94 <_errDoesntRecognize+884>:    "init" (gdb) # Want to 'po' object; must disable the breakpoint first (gdb) dis 1 (gdb) po $r3  (gdb) # Print the 'isa' pointer. (gdb) po 0xa0a04e18 NSAutoreleasePool 

在进行无符号调试时,可以使用Objective-C运行环境中的函数来辅助调试。表8所示的例程是非常有用的。

表8: 有用的Objective-C运行环境函数

函数 概要说明
de>id objc_getClass(const char *name);de> 获取给定类的Objective-C de>Classde>对象
de>SEL sel_getUid(const char *str);de> 根据给定方法名称获取其Objective-C de>SELde>
de>Method class_getInstanceMethod(Class, SEL);de> 获取给定类中给定方法的Objective-C de>IMPde>

一旦有了de>IMPde>,您就可以从第一个字得到一个指向方法名的指针 (在双重检查是否一切正常时有用) ,以及从第三个字中得到一个指向方法实现的指针。

清单44展示了一个对TextEdit的de>-[Controller applicationShouldTerminate:]de>方法进行调试的例子,尽管TextEdit程序中不包含符号。

清单44: 用Objective-C运行环境进行无符号调试

$ gdb /Applications/TextEdit.app GNU gdb 6.1-20040303 (Apple version gdb-413) […] (gdb) r Starting program: /Applications/TextEdit.app/Contents/MacOS/TextEdit […] ^C Program received signal SIGINT, Interrupt. 0x9000a778 in mach_msg_trap () (gdb) # Want to set a breakpoint on -[Controller applicationShouldTerminate:] (gdb) # but the application has had all of the symbols stripped, so we (gdb) # can't do it the easy way. (gdb) info func applicationShouldTerminate All functions matching regular expression "applicationShouldTerminate": (gdb) # Get the Class object for the Controller class (gdb) call (void *)objc_getClass("Controller") $1 = (void *) 0x1caa8 (gdb) # Get the SEL object for the "applicationShouldTerminate:" method (gdb) call (void *)sel_getUid("applicationShouldTerminate:") $2 = (void *) 0x909f36a0 (gdb) # Get the IMP for that method of that class (gdb) call (void *)class_getInstanceMethod($1, $2) $3 = (void *) 0x325bd8 (gdb) # Dump the IMP (gdb) x/3x $3 0x325bd8:       0x909f36a0      0x00018ea4      0x0000a7b0 (gdb) # Print the first word, just to make sure everything is copacetic (gdb) x/s 0x909f36a0 0x909f36a0 <_errNewVars+220620>:         "applicationShouldTerminate:" (gdb) # The third word is a pointer to code (gdb) x/8i 0x0000a7b0 0xa7b0: mflr    r0 0xa7b4: stmw    r23,-36(r1) 0xa7b8: lis     r4,2 0xa7bc: stw     r0,8(r1) 0xa7c0: mr      r3,r5 0xa7c4: li      r26,0 0xa7c8: stwu    r1,-112(r1) 0xa7cc: lwz     r4,-15200(r4) (gdb) # Set a breakpoint on the code (gdb) b *0x0000a7b0 Breakpoint 1 at 0xa7b0 (gdb) # Resume execution, and then quit the app (gdb) c Continuing. Reading symbols for shared libraries ............ done Reading symbols for shared libraries . done  Breakpoint 1, 0x0000a7b0 in ?? () (gdb) # We've hit our breakpoint; print the parameters, starting (gdb) # with the implicit "self" and "SEL" parameters that are common (gdb) # to all methods, followed by the method-specific "app" parameter (gdb) po $r3  (gdb) x/s $r4 0x909f36a0 <_errNewVars+220620>:         "applicationShouldTerminate:" (gdb) po $r5  

通过浏览de>/usr/include/objc/de>目录中的头文件可以学到很多关于Objective-C运行时函数和数据结构的内容。

警告信息: 和本文所讲的很多内容一样,Objective-C运行环境的内部工作方式是不公开的:它们在过去发生过变更,在以后也还会发生变更。您可以将这些信息用于调试,但不要在交付给客户的代码中依赖这些信息。

返回页首 

Foundation

Foundation组件中包含许多通过环境变量来启用的调试工具。表9摘录了其中一些比较引人关注的部分;这些工具在de>NSDebug.hde>中有进一步说明。

表9: 'NSDebug.h'中的环境变量

名称 默认值 操作
de>NSZombieEnabledde> NO 如果设置为YES,已经解除分配的对象会被‘zombified’(死而复生,表示它的内存并不真正释放);这使您可以快速调试向已释放对象发送消息时产生的问题,详细内容见下。
de>NSDeallocateZombiesde> NO 如果设置为YES,‘zombified’对象占用的内存会真正被释放。
de>NSHangOnUncaughtExceptionde> NO 如果设置为YES,则当一个未被捕获的例外产生时,进程将挂起而不是退出。
de>NSEnableAutoreleasePoolde> YES 如果设置为NO,则当自autorelease池被释放时,不释放其中的对象。
de>NSAutoreleaseFreedObjectCheckEnabledde> NO 如果设置为YES,则当autorelease池试图释放一个已经被释放掉的对象时,会打印一条信息。
de>NSAutoreleaseHighWaterMarkde> 0 如果设置为X,则当autorelease池中的对象个数多于X时,会输出一条信息。
de>NSAutoreleaseHighWaterResolutionde> 0 如果设置为Y,则对池中超过高水位 (X)的每Y个对象都会打印一条信息。 (X)

重要信息: 要启用或禁用一个Foundation调试工具,相应的环境变量值应该设置为“YES”或者“NO”,而不是像其它系统组件一样使用1或者0。

基于Cocoa编程时最常见的一种错误是由过度释放对象引起的。这通常会导致应用程序崩溃,但崩溃发生在最后的引用计数被释放 (并且你试图给freed对象发送消息)的时候,这通常已经远离开始出错的地方。de>NSZombieEnabledde>是调试该类问题最合适的工具,因为在对其进行设置后,任何试图和一个已释放的对象进行交互都将会生成一个例外。清单45展示了在上述情况下可以看到的信息。

清单45: NSZombie的作用

$ NSZombieEnabled=YES DragNDropOutlineView 2004-04-30 14:56:28.238 DragNDropOutlineView[803] *** *** Selector \ '_propagateDirtyRectsToOpaqueAncestors' sent to dealloced instance \ 0x530540 of class AnimatingOutlineView. 

您可以用GDB在de>-[_NSZombiemethodSignatureForSelector:]de>方法上设置断点,以进一步调试此类问题。

您还可以用de>NSScriptingDebugLogLevelde>预设值来启用对Foundation脚本支持的日志记录。清单46展示了这样的一个例子;下面的日志是运行内容为de>version of application "TextEdit"de>的AppleScript的结果。

清单46: 启用NSScriptingDebugLogLevel

$ /Applications/TextEdit.app/Contents/MacOS/TextEdit -NSScriptingDebugLogLevel 1 […] Suite NSCoreSuite, apple event code 0x3f3f3f3f […] Suite NSTextSuite, apple event code 0x3f3f3f3f […] Suite TextEdit, apple event code 0x74786474 […] Command: NSCoreSuite.Get         Direct Parameter:          Receivers:          Arguments: {} […] Property Value: 1.4 […] Result:  
  

将de>NSPrintDynamicClassLoadsde>环境变量设置为“YES”会使Foundation在动态加载类或范畴时 (也就是从程序包中加载时)记录一条信息。

将de>NSExceptionLoggingEnabledde>环境变量设置为“YES”会使Foundation将所有的例外活动 (de>NSExceptionde>) 记录到de>stderrde>中。

将de>NSUnbufferedIOde>环境变量设置为“YES”会使Foundation使用未缓冲I/O的de>stdoutde> (de>stderrde>默认不缓冲的)。

将de>NSDOLoggingEnabledde>环境变量设置为“YES”,则会使Foundation启用对分布式对象 (de>NSConnectionde>、de>NSInvocationde>、de>NSDistantObjectde>以及de>NSConcretePortCoderde>) 的日志记录。

返回页首 

AppKit

如果将de>NSQuitAfterLaunchde>环境变量设置为1,应用程序就会在刚刚进入事件循环的时候退出。这在希望度量应用程序启动时间或者查找内存泄漏时是很有用的。Carbon也支持这个环境变量。

AppKit 的事件

如果将de>NSTraceEventsde>预设值设置为YES,AppKit就会记录它所处理的所有事件的信息。清单47展示了这样的一个例子。

清单47: 使用NSTraceEvents

$ /Applications/TextEdit.app/Contents/MacOS/TextEdit -NSTraceEvents YES 2004-09-07 16:21:23.334 TextEdit[4520] timeout = 62997727116.666718 seco… 2004-09-07 16:21:23.341 TextEdit[4520] got apple event of class 61657674… 2004-09-07 16:21:23.454 TextEdit[4520] still in loop, timeout = 62997727… 2004-09-07 16:21:23.455 TextEdit[4520] timeout = 62997727116.546562 seco… 2004-09-07 16:21:27.793 TextEdit[4520] Received event: Kitdefined at: 0.… 2004-09-07 16:21:27.804 TextEdit[4520]     In Application: NSEvent: type… 2004-09-07 16:21:27.804 TextEdit[4520] timeout = 62997727112.196404 seco… 2004-09-07 16:21:27.805 TextEdit[4520] Received event: LMouseDown at: 37… 2004-09-07 16:21:27.805 TextEdit[4520]     In Application: NSEvent: type… 2004-09-07 16:21:27.805 TextEdit[4520]     In Window: NSEvent: type=LMou… 2004-09-07 16:21:27.809 TextEdit[4520]     In Application: NSEvent: type… […] 

返回页首 

AppKit 的视图

如果将de>NSShowAllViewsde>预设值设置为YES,AppKit就会绘制在窗口中每个视图周围的轮廓线。图4展示了一个启用了de>NSShowAllViewsde>预设值的标准TextEdit窗口。

图4:启用了NSShowAllViews的TextEdit

如果将de>NSShowAllDrawingde>预设值设置为YES,AppKit会刷新它绘制的每个矩形,非常象Quartz Debug的行为。图5展示了一个具有期望效果的实例。

图5:NSShowAllDrawing的效果

您可以将de>NSShowAllDrawingde>设置为一个数值,来控制刷新的时间间隔。该值被解释为两次刷新间隔的毫秒数;默认值为100毫秒。您还可以将de>NSShowAllDrawingColorde>设置为一个字符串,以控制刷新的颜色。该字符串包含三个由空格分隔的浮点数,分别表示红色、绿色、蓝色的成分。每个值都应该是一个介于0.0和1.0之间、表示该部分亮度的值。例如,字符串“0.0 1.0 1.0”将产生纯青色。还有,如果将de>NSShowAllDrawingColorde>设置为“CYCLE”,AppKit将在纯红色、纯绿色和纯蓝色间循环选取。

返回页首 

其它 AppKit 调试工具

de>NSDragManagerLogLevelde>预设值是一个数值,用来控制AppKit在拖放操作中记录多少日志。值越大,记录的日志越多。目前6是最大的有效值。清单48展示了一个如何设置该值的例子,以及从TextEdit中拖拽被选定的纯文本时获得的输出。

清单48: 使用NSDragManagerLogLevel

$ /Applications/TextEdit.app/Contents/MacOS/TextEdit -NSDragManagerLogLevel 6 2004-09-07 16:26:42.031 TextEdit[4523] mouseDown location: {29, 389}, ba… 2004-09-07 16:26:42.033 TextEdit[4523] offset of image lower left relati… 2004-09-07 16:26:42.033 TextEdit[4523] type NeXT Rich Text Format v1.0 p… 2004-09-07 16:26:42.034 TextEdit[4523] type NSStringPboardType: data  
  

de>NSAccessibilityDebugLogLevelde>预设值也是一个数值,用来控制AppKit在辅助访问操作中记录多少日志。值越大,记录的日志就越多。目前3是最大的有效值。清单49展示了一个有关如何设置该值的例子,以及用UIElementInspector (参见'UIElementInspector'工程示例代码) 查看TextEdit时获得的输出。

清单49: 使用NSAccessibilityDebugLogLevel

$ /Applications/TextEdit.app/Contents/MacOS/TextEdit -NSAccessibilityDebugLogLevel 3 2004-09-07 16:36:34.990 TextEdit[4526] creating id<=element table 2004-09-07 16:36:34.990 TextEdit[4526] creating id=>element 2004-09-07 16:36:35.001 TextEdit[4526] Element<=>UniqueId Tables --- id<=element --- size 1 42 <- (0x1576e0):      Frame = {{0.00, 0.00}, {460.00, 395.00}}, Bounds = {{0.00, 0.00}, …     Horizontally resizable: NO, Vertically resizable: YES     MinSize = {460.00, 395.00}, MaxSize = {340282346638528859811704183… 2004-09-07 16:36:35.003 TextEdit[4526] Element<=>UniqueId Tables […] 



你可能感兴趣的:(mac os x 调试魔法)