• Xcode GDB 命令list


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

    关于调试异常崩溃:

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

    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.   
    22. print- object            打印一个对象  
    23. print (int) name      打印一个类型  
    24. print- object [artist description]   调用一个函数  
    25. set artist = @"test"    设置变量值  
    26. whatis                      查 看变理的数据类型  
     
  • 相关阅读:
    Dubbo
    支持微服务架构落地的Java框架
    thinkphp6的主要特性
    thinkphp5的主要特性
    RPC
    HTTP1.0 HTTP1.1 HTTP2.0 主要特性对比
    RabbitMQ 生产环境配置详解
    分布式AKF拆分原则
    通过Hystrix了解分布式接口级的高可用
    Python中使用grpc与consul
  • 原文地址:https://www.cnblogs.com/fgyqbs/p/4385296.html
Copyright © 2020-2023  润新知