此文下半部分为转载:但是这里有一些我自己使用技巧,结合下面的文章,我们会有更多的收获,在此感谢原创者。
   
---------------------

关于调试异常崩溃:

一般崩溃是由内存使用错误导致的,要么多了,要么少了。

用xcode的调试提示可以知道是什么原因导致的崩溃。

在xcode中product àedit scheme à diagnostics 将enable Zombie objects 和 Malloc Stack 选中, 如果是内存释放错误,则gdb会提示release dealloc object。

然后可以用断点缩小错误范围, 在可能出现错误的地方用单步调试, 当执行到有错误代码时, gdb会再次提示release dealloc object。

其实XCODE内嵌GDB,那个 lldb就是gdb!
-----------------------
 

关于GDB

对于大多数Cocoa程序员来说,最常用的debugger莫过于Xcode自带的调试工具了。而实际上,它正是gdb的一个图形化包装。相对于gdb,图形化带来了很多便利,但同时也缺少了一些重要功能。而且在某些情况下,gdb反而更加方便。因此,学习gdb,了解一下幕后的实质,也是有必要的。

gdb可以通过终端运行,也可以在Xcode的控制台调用命令。本文将通过终端讲述一些gdb的基本命令和技巧。

首先,我们来看一个例子:

#import

int main(int argc, char **argv)
 {
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 NSLog(@"Hello, world!");
 [pool release];

return 0;
 }

糟糕,程序竟然exited normally了(==|||)。这可不行,我们得让他崩溃才行。所以我们给这个小程序添加一个bug:

int x = 42;
 NSLog("Hello, world! x = %@", x);
nice。这样一来程序就会漂亮地崩溃了:

(gdb) run

Starting program: /Users/mikeash/shell/a.out
 Reading symbols for shared libraries .++++....................... done

Program received signal EXC_BAD_ACCESS, Could not access memory.
 Reason: 13 at address: 0x0000000000000000
 0x00007fff84f1011c in objc_msgSend ()
 (gdb)

如果我们是在shell中直接运行的程序,在崩溃后就会回到shell。不过现在我们是通过gdb运行的,所以现在并没有跳出。gdb暂停了我们的程序,但依然使之驻留在内存中,让我们有机会做调试。

首先,我们想知道具体是哪里导致了程序崩溃。gdb已经通过刚才的输出告知了我们: 函数objc_msgSend就是祸之根源。但是这个信息并不足够,因为这个objc_msgSend是objc运行时库中的函数。我们并不知道它是怎么调用的。我们关注的是我们自己的代码。
要知道这一点,我们需要得到当前进程的函数调用栈的情况,以此回溯找到我们自己的方法。这时我们需要用到backtrace命令,一般简写为bt:

(gdb) bt
 #0 0x00007fff84f1011c in objc_msgSend ()
 #1 0x00007fff864ff30b in _CFStringAppendFormatAndArgumentsAux ()
 #2 0x00007fff864ff27d in _CFStringCreateWithFormatAndArgumentsAux ()
 #3 0x00007fff8657e9ef in _CFLogvEx ()
 #4 0x00007fff87beab3e in NSLogv ()
 #5 0x00007fff87beaad6 in NSLog ()
 #6 0x0000000100000ed7 in main () at test.m:10

现在我们可以看到,程序在test.m的第10行,调用NSLog方法时崩溃了。接下来我们想看一下这次调用的详细信息。这时我们要用到up命令。up命令可以在栈的各层之间跳转。本例中,我们的代码main是#6:

(gdb) up 6
 #6 0x0000000100000ed7 in main () at test.m:10
 9NSLog("Hello, world! x = %@", x);
这回不仅是函数名,连出错的那行代码也打印出来了。但是,我们还可以使用list(简写为l)命令,打印出更多信息:
ps: 如果需要回到栈列表。可以使用down命令。

(gdb) l
 5
 6int main(int argc, char **argv)
 7{
 8NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 9int x = 42;
 10NSLog("Hello, world! x = %@", x);
 11[pool release];
 12
 13return 0;
 14}

啊,整个代码都被列出来了。虽然我们用编辑器打开test.m文件然后找到第10行也可以打到同样效果,但显然没有上面的方法更有效率。(当然没有Xcode自带的那个快就是了)

好了,现在我们再来看看这个bug(虽然是我们自己弄出来的)。很明显,在格式化字符串前少加了一个@。我们改正它,并重新运行一遍程序:

(gdb) run
 Starting program: /Users/mikeash/shell/a.out
 Reading symbols for shared libraries .++++....................... done

Program received signal EXC_BAD_ACCESS, Could not access memory.
 Reason: KERN_INVALID_ADDRESS at address: 0x000000000000002a
 0x00007fff84f102b3 in objc_msgSend_fixup ()
 (gdb) bt
 #0 0x00007fff84f102b3 in objc_msgSend_fixup ()
 #1 0x0000000000000000 in ?? ()

啊咧,程序还是崩溃了。更杯具的是,栈信息没有显示出这个objc_msgSend_fixup方法是从哪里调用的。这样我们就没法用上面的方法找到目标代码了。这时,我们只好请出一个debugger最常用的功能:断点。

在gdb中,设置断点通过break命令实现。它可以简写为b。有两种方法可以确定断点的位置:传入一个已定义的符号,或是直接地通过一个file:line对设置位置。
现在让我们在main函数的开始处设置一个断点:

(gdb) b test.m:8
 Breakpoint 1 at 0x100000e8f: file test.m, line 8.

debugger给了我们一个回应,告诉我们断点设置成功了,而且这个断点的标号是1。断点的标号很有用,可以用来给断点排序&停用&启用&删除等。不过我们现在不需要理会,我们只是接着运行程序:

(gdb) run
 The program being debugged has been started already.
 Start it from the beginning? (y or n) y
 Starting program: /Users/mikeash/shell/a.out

Breakpoint 1, main (argc=1, argv=0x7fff5fbff628) at test.m:8
 8NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
debugger在在我们期望的地方停下了。现在我们使用next(简写n)命令单步调试程序,看看它到底是在哪一行崩溃的:

(gdb) n
 9int x = 42;
 (gdb)
 10NSLog(@"Hello, world! x = %@", x);
 (gdb)

Program received signal EXC_BAD_ACCESS, Could not access memory.
 Reason: KERN_INVALID_ADDRESS at address: 0x000000000000002a
 0x00007fff84f102b3 in objc_msgSend_fixup ()
值得注意的是,我只键入了一次n命令,随后直接敲了2次回车。这样做的原因是gdb把任何空输入当作最近一次输入命令的重复。所以这里相当于输入了3次n。

现在我们可以看到,崩溃之处依然是NSLog。原因嘛,当然是在格式化输出的地方用%@表示int型变量x了。我们仔细看一下输出信息:崩溃原因是错误地访问了0x000000000000002a这个地址。而2a的十进制表示正是42--我们为x赋的值。编译器把它当作地址了。

输出数值

一个很重要的调试方法是输出表达式和变量的值。在gdb中,这是通过print命令完成的。

(gdb) p x
 $1 = 42

在print命令后追加/format可以格式化输出。/format是一个gdb的格式化字符串,比较有用的格式化字符有 x:十进制数; c:字符; a:地址等。

(gdb) p/x x
 $2 = 0x2a

print-object方法(简写为po)用来输出obj-c中的对象。它的工作原理是,向被调用的对象发送名为debugDescription的消息。它和常见的description消息很像。

举例来说,让我们输出一下autorelease pool:

(gdb) po pool

这个命令不仅仅可以输出显式定义的对象,也可以输出表达式的结果。这次我们测试一下nsobject中debugDescription的方法签名:

返回值是对象的表达式可以用po命令输出结果,那么返回值是基本类型的方法又怎样呢?显然,它们是可以用p命令输出的。但是要小心,因为gdb并不能自动识别出返回值的类型。所以我们在输出前要显式地转换一下:

(gdb) p [NSObject instancesRespondToSelector: @selector(doesNotExist)]
 Unable to call function "objc_msgSend" at 0x7fff84f100f4: no return type information available.
 To call this function anyway, you can cast the return type explicitly (e.g. 'print (float) fabs (3.0)')
 (gdb) p (char)[NSObject instancesRespondToSelector: @selector(doesNotExist)]
 $5 = 0 '00'

你也许发现了,doesNotExist方法的返回值是BOOL,而我们做的转换却是char。这是因为gdb也不能识别那些用typedef定义的类型。不仅仅是你定义的,即使是Cocoa框架里定义的也不行。

你也许已经注意到,在用p进行输出的时侯,输出值前面会有一个类似"$1="的前缀。它们是gdb变量。它们可以在后面的表达式中使用,来指代它后面的值。在下面的例子里,我们开辟了一块内存,将其置零,然后释放。在这个过程中,我们使用了gdb变量,这样就不用一遍遍地复制粘贴地址了。

(gdb) p (int *)malloc(4)
 $6 = (int *) 0x100105ab0
 (gdb) p (void)bzero($6, 4)
 $7 = void
 (gdb) p *$6
 $8 = 0
 (gdb) p (void)free($6)
 $9 = void

我们也想把这个技巧用到对象上,但不幸的是po命令并不会把它的返回值存储到变量里。所以我们在得到一个新的对象时必须先使用p命令:

(gdb) p (void *)[[NSObject alloc] init]
 $10 = (void *) 0x100105950
 (gdb) po $10
 
 (gdb) p (long)[$10 retainCount]
 $11 = 1
 (gdb) p (void)[$10 release]
 $12 = void

检查内存

有些时候,仅仅输出一个数值还不能帮助我们查找出错误。我们需要一次性地打印出一整块内存来窥视全局。这时候我们就需要使用x命令。

x命令的格式是x/format address。其中address很简单,它通常是指向一块内存的表达式。但是format的语法就有点复杂了。它由三个部分组成:

第一个是要显示的块的数量;第二个是显示格式(如x代表16进制,d代表十进制,c代表字符);第三个是每个块的大小。值得注意的是第三部分,即块大小是用字符对应的。用b, h, w,g 分别表示1, 2, 4, 8 bytes。举例来说,用十六进制方式,打印从ptr开始的4个4-byte块应该这样写:

(gdb) x/4xw ptr

接下来举一个比较实际的例子。我们看一下NSObject类的内容:

(gdb) x/4xg (void *)[NSObject class]
 0x7fff70adb468 : 0x00007fff70adb4400x0000000000000000
 0x7fff70adb478 :0x0000000100105ac00x0000000100104ac0

接下来再看看一个NSObject实例的内容:

(gdb) x/1xg (void *)[NSObject new]
 0x100105ba0:0x00007fff70adb468

现在我们看到,在实例开头引用了类的地址。

设置变量

有时,查看数值程度的能力还是稍弱了一点,我们还想能够修改变量。这也很简单,只需要使用set命令:

(gdb) set x = 43

我们可以用任意表达式给一个变量赋值。比如说新创建一个对象然后赋值:

(gdb) set obj = (void *)[[NSObject alloc] init]

断点

我们可以在程序的某个位置设置断点,这样当程序运行到那里的时候就会暂停,而把控制权转移给调试器。就像之前提到的,我们用break命令来设置断点。下面详细地列出了如何设置断点的目标:

SymbolName: 为断点指定一个函数名。这样断点就会设置在该函数上。
file.c:1234: 把断点设置在指定文件的一行。
-[ClassName method:name:]: 把断点设置在objc的方法上。用+代表类方法。
*0xdeadbeef: 在内存的指定位置设置断点。这不是很常用,一般在没有源码的调试时使用。

断点可以用enable命令和disable命令来切换到使用和停用状态,也可以通过delete命令彻底删除。想要查看现有断点的话,使用info breakpoints命令(可以简写成info b,或是i b)。

另外,我们也可以用if命令,把断点升级成条件断点。顾名思义,条件断点只会在设定的条件成真时起作用。举例来说,下面的语句为MyMethod添加了一个条件断点,它只在参数等于5的时候有效:

(gdb) b -[Class myMethod:] if parameter == 5

最后,在断点上可以附加gdb命令。这样,当断点中断时,附带的命令会自动执行。附加命令使用commands breakpointnumber。这时gdb就会进入断点指令输入状态。

断点指令就是一个以end结尾的标准gdb指令序列。举个例子,我们想在每次NSLog被调用时输出栈信息:

(gdb) b NSLog
 Breakpoint 4 at 0x7fff87beaa62
 (gdb) commands
 Type commands for when breakpoint 4 is hit, one per line.
 End with a line saying just "end".
 >bt
 >end

 
  1. 命 令                        解释
  2. break NUM               在指定的行上设置断点。
  3. bt                      显 示所有的调用栈帧。该命令可用来显示函数的调用顺序。
  4. clear                   删 除设置在特定源文件、特定行上的断点。其用法为:clear FILENAME:NUM。
  5. continue                继续执行正在调试的程序。该命令用在程序 由于处理信号或断点而
  6. 导致停止运行 时。
  7. display EXPR            每次程序停止后显示表达式的值。表达式由程序定 义的变量组成。
  8. file FILE               装载指定的可执行文件进行调试。
  9. help NAME               显 示指定命令的帮助信息。
  10. info break              显 示当前断点清单,包括到达断点处的次数等。
  11. info files              显 示被调试文件的详细信息。
  12. info func               显示所有的函数名称。
  13. info local              显 示当函数中的局部变量信息。
  14. info prog               显示被调试程序的执行状 态。
  15. info var                显示所有的全局和静态变量名称。
  16. kill                    终 止正被调试的程序。
  17. list                    显示源代码段。
  18. make                    在 不退出 gdb 的情况下运行 make 工具。
  19. next                    在 不单步执行进入其他函数的情况下,向前执行一行源代码。
  20. print EXPR              显 示表达式 EXPR 的值。
  21. print- object            打印一个对象
  22. print (int) name      打印一个类型
  23. print- object [artist description]   调用一个函数
  24. set artist = @"test"    设置变量值
  25. whatis                      查 看变理的数据类型
 

Xcode GDB 命令list的更多相关文章

  1. IOS开发--常用的基本GDB命令

    gdb不是万能的,可是没有gdb却是万万不能的.这里给大家简单介绍下iOS开发中最基本的gdb命令. po po是print-object的简写,可用来打印所有NSObject对象.使用举例如下: ( ...

  2. IOS开发之----常用的基本GDB命令【转】

    原文地址:http://blog.sina.com.cn/s/blog_71715bf801016d2y.html gdb不是万能的,可是没有gdb却是万万不能的.这里给大家简单介绍下iOS开发中最基 ...

  3. jLink(v8)GDB 命令总结

    /** ****************************************************************************** * @author    Maox ...

  4. Linux学习笔记15——GDB 命令详细解释【转】

    GDB 命令详细解释 Linux中包含有一个很有用的调试工具--gdb(GNU Debuger),它可以用来调试C和C++程序,功能不亚于Windows下的许多图形界面的调试工具. 和所有常用的调试工 ...

  5. GDB命令行最基本操作

    程序启动: A.冷启动 gdb program              e.g., gdb ./cs gdb –p pid                 e.g., gdb –p `pidof c ...

  6. gdb命令调试技巧

    gdb命令调试技巧 一.信息显示1.显示gdb版本 (gdb) show version2.显示gdb版权 (gdb) show version or show warranty3.启动时不显示提示信 ...

  7. 如何写gdb命令脚本

    作为UNIX/Linux下使用广泛的调试器,gdb不仅提供了丰富的命令,还引入了对脚本的支持:一种是对已存在的脚本语言支持,比如python,用户可以直接书写python脚本,由gdb调用python ...

  8. GDB 命令回顾

    0) 为使用 GDB, 编译时需要加入调试信息 -g 选项,例如, $ gcc -g test.c -o test 1) 使用 GDB 开始调试 $ gdb test 也可以, $ gdb $ fil ...

  9. [转]常用 GDB 命令中文速览

    目录 break -- 在指定的行或函数处设置断点,缩写为 b info breakpoints -- 打印未删除的所有断点,观察点和捕获点的列表,缩写为 i b disable -- 禁用断点,缩写 ...

随机推荐

  1. 文件和文件夹权限-Win7公共盘中出现大量临时文件

    公司中有一个文件服务器,给不同部门和员工设置了不同的权限,最近有员工(没有修改权限,有读取及执行,读取,写入)反映在公共盘上修改文件的时候会产生大量的临时文件,添加上修改权限之后就可以了,然后被同事问 ...

  2. sql日志损坏造成数据库置疑解决办法

    --如果确定是日志损坏造成,请用下面的方法恢复日志文件.--第一步--use mastergo sp_configure 'allow updates', 1reconfigure with over ...

  3. 相比于python2.6,python3.0的新特性。

    这篇文章主要介绍了相比于python2.6,python3.0的新特性.更详细的介绍请参见python3.0的文档. Common Stumbling Blocks 本段简单的列出容易使人出错的变动. ...

  4. 一致性哈希(Consistent Hashing)

    前言:对于一致性哈希已经不是罕见概念,在此只是对原有理论概念的一个整理和用自己的理解讲述,希望对新手有些许帮助,利人利己足矣. 1.概念 一致哈希是一种特殊的哈希算法.在使用一致哈希算法后,哈希表槽位 ...

  5. JVM学习之GC常用算法

    出处:博客园左潇龙的技术博客--http://www.cnblogs.com/zuoxiaolong,多谢分享 GC策略解决了哪些问题? 既然是要进行自动GC,那必然会有相应的策略,而这些策略解决了哪 ...

  6. centos之tomcat安装

    1.环境说明     系统:centos, 2.6.32-573.el6.x86_64; tomcat: apache-tomcat-7.0.68 2.下载文件并上传     下载apache-tom ...

  7. hdu 2828 Lamp 重复覆盖

    题目链接 给n个灯和m个开关, 每个灯可以由若干个开关控制, 每个开关也可以控制若干个灯, 问你能否找到一种开关的状态, 使得所有的灯都亮. 将灯作为列, 然后把每个开关拆成两行, 开是一行, 关是一 ...

  8. VPN服务器搭建好以后的安全防护

    之前讲过VPN的搭建过程,那么搭建完毕后,需要做哪些防护呢? 这里只说一下禁止VPN账户登录到服务器的设置,直接上图 找到权限分配后把VPN账号添加到拒绝本地登录的策略中,这样保障了VPN账户不能通过 ...

  9. delphi 关于命名

    请告别 TMyXXX 的命名方法吧... 程序名: Demo.exe 窗体:TFrmDemo ,窗体文件 uFrmDemo.Pas DataModule: TDMDemo, 窗体文件 uDMDemo. ...

  10. Delphi 对泛型TList的的改进(TSimpleList)

    TList 有一个比较麻烦的问题是,到底由谁来释放List中的对象或指针. 本例将释放任务教给 TSimpleList ,方便使用. 如果 TList 为于管理对象,还可以实现 AddNewOne 功 ...