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展示了如何在
清单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及以上版本中,默认外壳为
重要信息: 如果您使用的是兼容
或者您可以选择打开两个终端窗口,然后将第一个专门用于调试,将第二个用于执行所有其它命令。
另外,可以在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,即‘为用户进程设置环境变量’。
预设值
清单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… […]
在调试完成后应该删除该预设值,同样使用
清单5: 用defaults删除预设值
$ defaults delete com.apple.TextEdit NSTraceEvents
关于
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展示了这样的一个例子:创建文件
清单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。
可调用的例程
很多系统框架包含将调试信息输出到
清单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…
如果您没有看到该例程的输出,那么可能需要看一下控制台日志,参见下一节的描述。
重要信息: 如果您将这个技巧用到自己的代码上,要当心它并不是对声明为
在实践中,上述的问题只对Intel代码有影响。
查看调试输出
程序通常通过以下三种不同的机制来产生调试输出:
-
输出到
de>stderr de> -
系统日志
-
内核跟踪工具
输出到
另外两种机制要简单得多。您可以用控制台程序 (在
内核跟踪工具是一个高度专业化的、低延迟而又高效能的日志记录工具。多数情况下,通过内核跟踪工具记录日志信息的程序也会提供一种查看日志的方法 (例如,可以用kdump来打印由ktrace生成的跟踪文件)。
控制台输出
很多程序,甚至还有很多系统框架,都将调试信息输出到
-
对于以一般的方式启动的GUI应用程序 (比如,在Finder中双击启动),系统会将其
de>stderr de>连接到控制台设备 ( de>/dev/console de>),您可以通过控制台程序 (在 de>/Applications/Utilities de>目录下) 查看它的输出。此外,这些输出也会被记录到 de>/Library/Logs/Console/<用户名>/console.log de>文件中。 -
对于从终端程序窗口运行起来的程序 (GUI或非GUI程序),其
de>stderr de>会被连接到终端窗口;程序输出的所有内容都会呈现在该窗口中。这同样适用于通过终端窗口中的GDB运行起来的程序。 -
对于通过Xcode运行起来的程序,您可以在Xcode的运行日志(Run Log)或者控制台日志(Console Log)窗口中看到该程序的
de>stderr de>输出 (选择调试菜单中相应的命令即可查看该窗口)。
附着到正在运行的程序 (用GDB中的
架构的考虑
本文所演示的实例是在一台基于PowerPC的Macintosh计算机上实现的。不过,这些实例在基于Intel的计算机上也可以很好地运行。唯一的重要区别与参数的传递有关。PowerPC架构用寄存器传递参数,而Intel架构用堆栈传递参数。
在PowerPC上,一个很好的经验规则是第一个参数存放在GPR3 (通用寄存器3) 中,第二个参数存放在GPR4中,以此类推。在GDB语法上表示为
在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 (
如果该例程是一个C++成员函数,那么会有一个隐含的首参数
清单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 <kCFURLLocalhost+340> (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 <dyld_func_lookup_pointer+16100> (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 <dyld_func_lookup_pointer+16100> (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实现了很多精巧的调试工具,您可以利用这些工具来进行调试。
核心转储
作为一款初级的调试工具,核心转储并未得到应有的认可。事实上,在调试棘手的问题,尤其是调试无法在本地重现的问题时,它可能是非常有用的。
通过在
请注意: 在Mac OS X 10.4之前的系统中,启动系统级别的核心转储需要将
如果您通过终端运行程序,则还可以有另一个选择,即在运行之前简单地增加核心转储在外壳中的尺寸限制。清单11展示了这样的一个例子。
清单11: 尺寸不受限制的核心转储
$ ulimit -c unlimited $ /Applications/TextEdit.app/Contents/MacOS/TextEdit […]
如果您要测试一下核心转储工具,可以通过
清单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)”信息。您可以在
清单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所示,核心转储不包含调试器符号。如果手边有符号文件,可以通过
清单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。如果您将核心转储设置为必然启用,则请务必定期清理
内存分配器
默认的内存分配器包含了很多可以通过环境变量启用的调试工具。在帮助手册中有对这些环境变量的完整介绍。表1列出了其中最有用的一些环境变量。
表1: 一些比较有用的内存分配器环境变量
变量 | 概要说明 |
---|---|
用0x55填充解除分配的内存 | |
用0xAA填充新分配的内存 | |
在进行大的内存分配时,在内存块的前后分别添加防护页 | |
为每个内存块记录堆栈日志,以便为内存调试工具提供支持 | |
为所有操作记录堆栈日志,以便为内存调试工具提供支持 |
重要信息: 这些环境变量不要求使用特殊的内存库(比如MallocDebug或者防护内存分配程序)。事实上,它们在默认的、不用于调试目的的内存分配器上也得到支持,因此总是可用的。
默认的内存分配器在检测到某些常见的编程问题时也会进行记录。例如,如果对一块内存进行两次释放,或者对未分配的内存进行释放,则
清单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中运行您的程序、并在输出这些信息的
MallocDebug 和 ObjectAlloc
Mac OS X包含两个用于内存分配调试的GUI应用程序,即MallocDebug和ObjectAlloc。有关这些工具的更多内容,请参见内存性能文档。
防卫内存分配器
Mac OS X系统中包含一个防卫内存分配器,即
清单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 […]
更多有关
请注意:在Mac OS X 10.3.x 中,
关于在Mac OS X 10.3.x上如何使用
标准 C++库
标准C++库支持很多调试特性。
-
设置名为
de>_GLIBCXX_DEBUG de>的编译时变量,启用标准C++库的调试模式。详细信息请参见标准C++库的文档。 -
在GCC 4.0以前的版本中,将
de>GLIBCPP_FORCE_NEW de>环境变量设置为1可以禁用标准C++库的内存缓存功能。这使您可以使用其它的内存调试工具 (比如防卫内存分配器) 来调试C++内存分配。详细信息请参见标准C++库文档。 在GCC 4.0及以上版本中,这是默认的行为。
命令行工具
Mac OS X包含很多很酷的命令行调试工具。表2列出了我最喜爱的工具。
表2: 命令行工具精选
工具 | 文档 | 概要说明 |
---|---|---|
帮助手册, 用GDB进行调试 | 命令行调试器 | |
帮助手册 | 文件系统跟踪工具 | |
帮助手册 | 系统调用跟踪工具 | |
帮助手册 | 调度时延调试工具 | |
帮助手册 | 内核跟踪 | |
帮助手册 | 堆转储 | |
帮助手册 | 地址空间转储 | |
帮助手册 | 内存分配历史 | |
帮助手册 | 泄漏检测 | |
帮助手册, 技术问答 QA1176,‘进行包跟踪’ | 网络信息包跟踪 | |
帮助手册 | 网络统计; | |
帮助手册 | 列出打开的文件 | |
介绍PPC助记符 |
动态链接器 (dyld)
Mac OS X动态链接器 (dyld) 支持很多可通过环境变量启用的调试工具,完整的说明请参见它的帮助手册。表3列出了其中一些最有用的变量。
表3: 动态链接器的环境变量
变量 | 概要说明 |
---|---|
优先查找带有这个后缀的库 | |
记录库的加载 | |
同上,但仅在 | |
输出预绑定的诊断信息 | |
输出启动时的命令行参数 | |
输出启动时的环境变量 | |
禁用性能测试的预绑定 | |
记录对dyld API的调用(例如,dlopen) | |
记录符号绑定信息 | |
记录映像初始化调用 | |
记录段映射 | |
打印启动性能的统计信息 |
在这些工具中,
如果在动态加载代码时出现代码加载失败,可以用名为
此外,如果您使用dlopen加载代码,则可以用 dlerror获得类似的信息。
调试库
很多Mac OS X框架同时包含产品版本和调试版本。调试版本带有“_debug”后缀。例如,Core Foundation框架的产品版本为
清单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”版本。然而,当您将
幸运的是,这个问题有一个简单的答案,虽然在某种程度上有点儿并不精巧:只需对调试版本进行拷贝,使其覆盖非调试版本就可以了。清单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。然后对该分区禁用软件更新。这就保证了产品库和调试库开始时是同步的,并始终保持同步。
Pro file 库
很多库还包含带有“_pro
脱离窗口服务器
在某些情况下,脱离窗口服务器可能是有帮助的。举例来说,假定您需要修改在
-
在系统偏好设置(System Preference)的账户(Account)面板中,点击登录选项(Login Options)。如果文本是灰色的,那么必须先点击锁的图标,以解锁该面板。
-
将名为“将登录窗口显示为”的设置改为“名称和密码”。
-
注销。
-
在登录窗口中,输入“>console”作为用户名;
de>loginwindow de>将退出,然后您将直接面对显示“Login”提示符的TTY界面。 -
用标准用户名称和密码登录。
当您在这个级别上完成操作后,只需从shell中退出 (用
重要信息: 该环境不是单用户模式。大多数系统后台程序仍然在运行;只是系统的GUI组件被关掉了。
后台程序
大多数系统后台程序都包括某种调试工具。很多情况下,后台程序的调试功能在其手册中都有介绍。本部分将介绍其中一些特别令人感兴趣的特性。
launchd
launchd是由内核 (Mac OS X 10.4及以上版本) 运行的第一个进程;它负责启动系统中所有其它进程。
launchctl
您可以用launchctl命令修改
表4: 有用的launchctl命令
命令 | 概要说明 |
---|---|
修改 | |
设置 | |
设置 | |
设置 |
请注意: 对
您可以将上述的某个命令作为参数运行
重要信息: 要影响
您可以在/etc/launchd.conf中添加命令,以进行长久的修改。
launchd 日志记录
清单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`
清单21: 获取更多的launchd日志记录
$ sudo launchctl log level debug Password: ********
调试特定任务
最后,
表5: 对调试有用的属性
属性 | 概要说明 |
---|---|
在 | |
设置该任务的标准输出目标 | |
设置该任务的标准错误目标 | |
为该任务设置环境变量 | |
为该任务设置资源限制;在只为一项任务启用核心转储时最有用 |
lookupd
如果在
-
在本地机器上名为
de>/config/lookupd de>的NetInfo目录中创建 de>Debug de>和 de>Trace de>属性。 -
更改syslog配置,使NetInfo调试信息 (
de>netinfo.debug de>) 发送给NetInfo日志文件 ( de>/var/log/netinfo.log de>)。 -
向
de>syslogd de>进程发送 de>SIGHUP de>信号,使其接受之前所做的更改。 -
向
de>lookupd de>进程发送 de>SIGHUP de>信号,使其接受从步骤1之后的更改。
清单22展示了这样的一个例子。在完成这些步骤后,可以在
清单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`
有关
打印 (CUPS)
Mac OS X 10.2及以上的版本采用CUPS (Common UNIX Printing System,通用UNIX打印系统) 作为其核心打印架构。CUPS有内置的调试日志记录特性,由CUPS配置文件 (
重要信息: 在更改该文件后,必须向CUPS后台程序发送
清单24: 重新启动CUPS后台程序
sudo /System/Library/StartupItems/PrintingServices/PrintingServices restart
如果您正在编写CUPS驱动或者过滤器,则可以向
清单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包含很多例程 (比如,
图3: 在Xcode中设置USERBREAK环境变量
代码片断管理器 (CFM)
在Mac OS X上的CFM兼容环境支持两个有用的环境变量:
Core Foundation
Core Foundation (CF) 框架的所有变体都支持
清单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) <CFRunLoop 0x116290 [0xa01900e0]>{ locked = false, wait port = 0xf03, stopped = false, current mode = (none), common modes = <CFSet 0x1162c0 [0xa01900e0]>{ count = 1, capacity = 4, values = ( 1 : <CFString 0xa0195b38 [0xa01900e0]>{ contents = "kCFRunLoopDefaultMode" } ) }, common mode items = (null), modes = <CFSet 0x116310 [0xa01900e0]>{ count = 1, capacity = 17, values = ( 20 : <CFRunLoopMode 0x1163a0 [0xa01900e0]>{ name = kCFRunLoopDefaultMode, locked = false, port set = 0x1003, sources = (null), observers == (null), timers = (null) }, ) } }
重要信息: 如果您没有看到
请注意:在清单26中,我们已经对
您可能会发现,从GDB调用一些其它的CF例程是很有用的,包括
Core Foundation框架还有一个调试版本,可以提供很多的调试帮助。例如,Core Foundation的非调试版本不检查传入参数的合法性,而调试版本则包含了完全的参数检查。这有助于跟踪代码中存在的很多与Core Foundation相关的错误。
Core Foundation调试库支持一个叫做
表6: CFZombieLevel环境变量的位定义
位 | 操作 |
---|---|
0 | 涂写解除分配了的CF内存 |
1 | 在涂写已经解除分配的CF内存时,不要涂写对象头 ( |
4 | 从不释放用于存放CF对象的内存 |
7 | 如果这个位被设置,则用位8..15中的内容涂写解除分配的内存,否则则用0xFC进行涂写 |
8..15 | 如果位7被设置,用该值涂写解除分配的内存 |
16 | 涂写已分配的CF内存 |
23 | 如果这个位被设置,则用位24..31中的内容涂写分配的内存,否则则用0xCF进行涂写 |
24..31 | 如果位16被设置,用该值涂写已分配的内存 |
警告:
CFNotificationCenter
您可以通过创建
您可以在本文的文件部分看到这样的一个例子。
请注意: 在Mac OS X 10.3.x上,对应的文件为
Mac OS X 10.3.x还支持客户端日志。如果您创建了
组件管理器
组件管理器导出一个名为
另外,如果将
最后,如果组件加载失败,您可以使用动态链接器工具来进行调试。详细信息请参见动态链接器 (dyld)部分。
文件管理器
Core Services文件管理器 (通常叫做Carbon文件管理器,或就叫文件管理器) 与
如果您将
如果您将
文件管理器有很多可以在GDB中调用的有用例程。最有用的例程是
清单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"’)。
文件ID树由
清单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](<unused entries>):<address> [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](<unused entries>):<address> [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树由每个进程独立维护。因此可以直接调用
文件夹管理器
文件夹管理器支持一个名为
Gestalt
Core Services导出三个例程——
清单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线程和线程管理器) 支持环境变量
Web Services
Web Services支持两个有用的环境变量,
磁盘和光盘
磁盘仲裁
如果在
光盘刻录
如果将
重要信息: 由于软件错误 (r. 4413303)的原因,该工具在Mac OS X 10.4.x上不起作用。
磁盘工具
如果将
ApplicationServices
苹果事件
苹果事件管理器有很多的内置调试支持。了解该支持的最好的方式是用GDB调用
清单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 da ta 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>/var/tmp/AEDebug文件 de>相当于设置 de>AEDebug de>、 de>AEDebugSends de>、 de>AEDebugReceives de>、 de>AEDebugReceives de>及 de>AEDebugOSL de>环境变量。 -
创建
de>/var/tmp/AEDebug.out de>文件相当于将 de>AEDebugFile de>设置为“/var/tmp/AEDebug.out”。 -
创建
de>/var/tmp/AEDebugLogs de>文件将使苹果事件管理器将其所有输出发送到一个名为 de>/var/tmp/AELog-<progname> de>的文件中。
重要信息: 最后两点只影响调试输出的目标。为了产生调试输出,必须使用上述的某个环境变量,或者通过创建
如果将
远程的苹果事件
如果您在远程的苹果事件上遇到了问题,可能会发现启用苹果事件服务器进程的日志记录很有用。通过编辑
清单31: 启用远程的苹果事件调试
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" \ "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Disabled</key> <true/> <key>Label</key> <string>com.apple.AEServer</string> <key>ProgramArguments</key> <array> <string>/System/Library/Frameworks/ApplicationServices.\ framework/Frameworks/AE.framework/Versions/A/Support/AEServer</string> <string>--debug</string> </array> <key>inetdCompatibility</key> <dict> <key>Wait</key> <false/> </dict> <key>Sockets</key> <dict> <key>Listeners</key> <dict> <key>SockServiceName</key> <string>eppc</string> <key>SockType</key> <string>stream</string> <key>Bonjour</key> <true/> </dict> </dict> </dict> </plist>
您必须重新启动远程苹果事件服务才能使更改生效,在系统偏好设置(System Preference)的共享(Sharing)面板中可以完成此项操作。日志信息将出现在系统日志中。
重要信息: 因为
请注意:在Mac OS X 10.3.x上,苹果事件服务器由xinetd运行。配置文件为
进程管理器
在某些情况下,您可能想要调试一个进程,但又不想从GDB中启动它。举例来说,如果您为了调试一个GUI应用程序,已经以SSH方式登 录一台远程计算机,这时您不应该直接从GDB启动该程序,因为那样的话该应用程序将在错误的Mach引导命名空间中,不能连接到象pasteboard服 务器这样重要的服务上。正常情况下,这并不是一个问题:只要简单地请求远程用户启动该应用程序,然后用GDB的
进程管理器针对该问题提出了一个很好的解决方案。如果将
清单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的状态。最重要的三个例程是
清单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展示了如何调用这些例程;遗憾的是在本文档中无法看到执行的结果,因此你只能自己试用。这里假定
清单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 <Apple> 2 0x0042EC00 128 QuickTime Player 3 0x0043C4B0 129 File 4 0x00445B70 130 Edit […] <hierarchical menus> 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: <none> Cmd Key: <none> Icon: <none> 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 : <none> 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: <none> Refcount: 1 Previous group: <none> Next group: <none> 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 <appl/sbar> ( "" ), ID ''/0, (172,301,226,317), Vis,… Control 0x4c6080 <appl/sbar> ( "" ), ID ''/0, (75,301,142,317), Vis, … Control 0x4c49c0 <appl/push> ( "Delete" ), ID ''/0, (241,220,261,290)… Control 0x4c4790 <appl/push> ( "Edit?" ), ID ''/0, (241,135,261,205),… Control 0x4c17c0 <appl/push> ( "Add?" ), ID ''/0, (241,50,261,120), V… Control 0x4be1d0 <appl/popb> ( "" ), ID ''/0, (12,176,28,316), Vis, A… Control 0x4ba1f0 <appl/popb> ( "" ), 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
最后,您可以使用
使 HIToolbox 区域闪烁
清单39所示的例程可以用来使区域闪烁,从而方便在屏幕上查看。遗憾的是,在本文档中无法看到闪烁的结果,你必须自己试用。
清单39: 使HIToolbox区域发生闪烁的例程
(gdb) call (void) DebugFlashWindowVisRgn($window) (gdb) call (void) DebugFlashWindowUpdateRgn($window)
HIToolbox 事件的调试
随着Carbon事件的出现,开发者通常很难理解工具箱中的事件传递过程。HIToolbox提供了两个调试工具,有助于解决这个问题。
EventDebug 环境变量
将
清单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 […]
事件跟踪
这个问题的一个解决方案是按事件进行跟踪。您可以通过从GDB中调用
清单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输出事件的统计信息。将
其它 HIToolbox 调试工具
如果将
重要信息: 从Mac OS X 10.4起,你必须使用HIToolbox框架的pro
将
将
Cocoa
所有Cocoa对象 (即派生自
清单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 <NSCFDictionary 0x32d6d0>{ copyright = ; author = ; OpenPanelFollowsMainWindow = 0; UseTransitionalDocType = 0; UseInlineCSS = 0; […] }
请注意:
Objective-C
如果对
表7: 有用的Objective-C运行环境的调试环境变量
变量 | 概要说明 |
---|---|
记录由运行时加载的映像 | |
记录 | |
如果一个类的实例变量覆盖了其超类的实例变量,则发出警告 |
如果将
在汇编级别上调试Cocoa代码时,请记住下面这些Objective-C运行环境的特性:
-
Objective-C编译器给每个方法都添加两个隐含参数,第一个是一个指针,指向接收消息的对象 (
de>self de>) 。在PowerPC上,这个参数保存在寄存器 de>r3 de>中。在Intel上则在位于 de>esp de>+4的内存中,这里假定程序停在该方法实现的第一条指令处 (关于在基于Intel的计算机上访问参数的详细信息,请参见架构考量)。 -
第二个隐含参数是方法选择器。在Objective-C中,它是
de>SEL de>类型的参数;在GDB中可以作为一个C字符串来打印。在PowerPC上,该参数保存在寄存器 de>r4 de>中。在Intel上,该参数位于内存的 de>esp de>+8处。 -
Objective-C运行环境通过一个名为
de>objc_msgSend de>的C函数进行方法的派发。 -
任何Objective-C对象的第一个字 (
de>isa de>字段) 是一个指向该对象类的指针。
清单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 <NSAutoreleasePool: 0x10cc10> (gdb) # Print the 'isa' pointer. (gdb) po 0xa0a04e18 NSAutoreleasePool
在进行无符号调试时,可以使用Objective-C运行环境中的函数来辅助调试。表8所示的例程是非常有用的。
表8: 有用的Objective-C运行环境函数
函数 | 概要说明 |
---|---|
获取给定类的Objective-C | |
根据给定方法名称获取其Objective-C | |
获取给定类中给定方法的Objective-C |
一旦有了
清单44展示了一个对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 co de (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 co de (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 <Controller: 0x328b50> (gdb) x/s $r4 0x909f36a0 <_errNewVars+220620>: "applicationShouldTerminate:" (gdb) po $r5 <NSApplication: 0x3198e0>
通过浏览
警告信息: 和本文所讲的很多内容一样,Objective-C运行环境的内部工作方式是不公开的:它们在过去发生过变更,在以后也还会发生变更。您可以将这些信息用于调试,但不要在交付给客户的代码中依赖这些信息。
Foundation
Foundation组件中包含许多通过环境变量来启用的调试工具。表9摘录了其中一些比较引人关注的部分;这些工具在
表9: 'NSDebug.h'中的环境变量
名称 | 默认值 | 操作 |
---|---|---|
NO | 如果设置为YES,已经解除分配的对象会被‘zombified’(死而复生,表示它的内存并不真正释放);这使您可以快速调试向已释放对象发送消息时产生的问题,详细内容见下。 | |
NO | 如果设置为YES,‘zombified’对象占用的内存会真正被释放。 | |
NO | 如果设置为YES,则当一个未被捕获的例外产生时,进程将挂起而不是退出。 | |
YES | 如果设置为NO,则当自autorelease池被释放时,不释放其中的对象。 | |
NO | 如果设置为YES,则当autorelease池试图释放一个已经被释放掉的对象时,会打印一条信息。 | |
0 | 如果设置为X,则当autorelease池中的对象个数多于X时,会输出一条信息。 | |
0 | 如果设置为Y,则对池中超过高水位 (X)的每Y个对象都会打印一条信息。 (X) |
重要信息: 要启用或禁用一个Foundation调试工具,相应的环境变量值应该设置为“YES”或者“NO”,而不是像其它系统组件一样使用1或者0。
基于Cocoa编程时最常见的一种错误是由过度释放对象引起的。这通常会导致应用程序崩溃,但崩溃发生在最后的引用计数被释放 (并且你试图给freed对象发送消息)的时候,这通常已经远离开始出错的地方。
清单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在
您还可以用
清单46: 启用NSScriptingDebugLogLevel
$ /Applications/TextEdit.app/Contents/MacOS/TextEdit -NSScriptingDebugLogLevel 1 […] Suite NSCoreSuite, apple event code 0x3f3f3f3f […] Suite NSTextSuite, apple event co de 0x3f3f3f3f […] Suite TextEdit, apple event co de 0x74786474 […] Command: NSCoreSuite.Get Direct Parameter: <NSPropertySpecifier: version> Receivers: <NSPropertySpecifier: version> Arguments: {} […] Property Value: 1.4 […] Result: <NSAppleEventDescriptor: 'utxt'($0031002E0034$)
将
将
将
将
AppKit
如果将
AppKit 的事件
如果将
清单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 的视图
如果将
图4:启用了NSShowAllViews的TextEdit
如果将
图5:NSShowAllDrawing的效果
您可以将
其它 AppKit 调试工具
清单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 <CF… 2004-09-07 16:26:42.034 TextEdit[4523] type NeXT plain ascii pasteboard … 2004-09-07 16:26:42.036 TextEdit[4523] type CorePasteboardFlavorType 0x7… 2004-09-07 16:26:42.036 TextEdit[4523] type CorePasteboardFlavorType 0x7… 2004-09-07 16:26:42.049 TextEdit[4523] type CorePasteboardFlavorType 0x5… 2004-09-07 16:26:42.050 TextEdit[4523] type CorePasteboardFlavorType 0x7… […]
清单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): <NSTextView: 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 […]