zoukankan      html  css  js  c++  java
  • 研究怎么运用xcode处理常见的调试问题

    本文转载至 http://blog.csdn.net/zhuzhihai1988/article/details/7749022

    所谓磨刀不误砍柴工,这里菜鸟我在研究怎么运用xcode处理常见的调试问题,把今后遇到的问题慢慢整理总结下来,以便以后遇到问题能够快速的解决。

    调试前,先在xcode中添加环境变量,如下三个

    NSDebugEnabled
      NSZombieEnabled
      MallocStackLogging

    都先把它们设置为YES

    话说你还可以添加接下来这个环境变量

      MallocStackLoggingNoCompact

    同样置为YES

    ok,调试前准备的东东就这些,当然,gdb得晓得在哪里设置开启。Gdb会暂停我们的程序,但依然使之驻留在内存中,让我们有机会做调试

    第一个:

     2003-03-18 13:01:38.644 autoreleasebug[3939] *** *** Selector 'release'
      sent to dealloced instance 0xa4e10 of class NSConcreteData.
    大概指的是对象被释放了两次,对已经autorelease的对象进行release会让程序crash掉

    如果你处于gdb模式中(gdb即可在Console中打开,也可在terminal终端打开,在终端输入gdb即可进入gdb模式),可以在gdb中输入

    shell malloc_history 3939 0xa4e10

    回车查看堆分配状态

    message sent to deallocated instance :报错

    常常程式一長,哪邊就不小心多release了一次
    這時候編譯器就只會告訴你:BAD_ACCESS,然後程式就死了
    剛開始會google到去Argument加個NSZombieEnabled YES
    會多吐一點東西讓你把bug除掉
    今天遇到加了這個後error message變:
    [CALayer release]:message sent to deallocated instance 0x4dd650
    1.在Argument裡面加入這三個參數:
    NSZombieEnabled YES
    MallocStackLogging YES
    MallocStackLoggingNoCompact Yes
    第一項可監控deallocated的記憶體,給更多的錯誤訊息
    第二項可開啟MallocStack,就知道記憶體在程式運行中被配置的歷史
    第三項可以更清楚顯示指定的MallocStack狀況(一開始沒加看到快脫窗還是看不懂)
    2.跑程式(建議用模擬器),開console,這時候可以注意到一開始會出現類似下列訊息:
    myproject(11779) malloc: stack logs being written into /tmp/stack-logs.11779.myproject.81hXWV
    表示gdb開始有在紀錄
    3.讓程式跑到出錯
    如果有做步驟一,應該就會看到message sent to deallocated instance的錯誤訊息
    複製後面跟的位址
    4.在(gdb)後面下指令info malloc-history 0x4dd650(剛剛得到的位址)
    如果gdb說找不到指令,可改用shell 11779 malloc_history(11779為程式的pid)
    建議在模擬器跑的原因是因為程式跑在裝置的OS上,pid是裝置給予的,要存取好像會有點問題
    今天在這卡關卡了一陣
    5.如果上述步驟順利的話就會看到一串比較像程式碼的東西,應該也就看得出bug了

    第二个:此处不描述问题,gdb中常用指令

    gdb中,

    1.使用backtrace命令,简写bt,用来查看当前进程的函数调用栈情况,以此回溯到我们自己所写的方法,有时可以看到出错在哪一行;(真怀恋在vs中的编程,找问题哪须这么麻烦)。

    2.使用list命令,简写l,回到栈列表,会将当前栈里的程序代码罗列出来,方便问题查找;

    3.使用break命令,简写b,设置断点,格式:b filename:line   即在哪个文件的哪一行设置断点

    如:b test.m:10

    4.使用next命令,简写n,单步调试

    5.使用continue,简写c,跳出当前断点继续执行

    6.使用回车键,将继续按照上条指令执行

    7.使用print,简写p,可打印表达式和变量的值,在print命令后追加/format可以格式化输出。/format是一个gdb的格式化字符串,比较有用的格式化字符有 x:十进制数; c:字符; a:地址

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

    9.使用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

    10.使用set命令,设置变量的值,set x=0

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    断点

    我们可以在程序的某个位置设置断点,这样当程序运行到那里的时候就会暂停,而把控制权转移给调试器。就像之前提到的,我们用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

    这很好理解,只有一点需要提一下:如果commands命令是作用在刚设置的断点上的话,那么就可以省略断点序号。

    有些时候,我们希望调试器输出一些信息,但是并不想中断程序运行。这实际上也可以通过追加指令实现。我们只需要在指令的最后增加continue指令就行了。在下面的例子里,我们在断点中断后打印栈信息和参数信息,随后继续运行:

        (gdb) b -[Class myMethod:] 

        Breakpoint 5 at 0x7fff864f1404 

        (gdb) commands 

        Type commands for when breakpoint 5 is hit, one per line. 

        End with a line saying just "end". 

        >bt 

        >p parameter 

        >continue 

        >end

    最后一个奇特的运用是return命令。它和c中的同名命令一样,都用来跳出当前函数。如果设置了参数,这参数会作为函数的返回值。

    比如说,我们可以用这个技巧屏蔽掉NSLog函数:

        (gdb) commands 

        Type commands for when breakpoint 6 is hit, one per line. 

        End with a line saying just "end". 

        >return 

        >continue 

        >end

    有一点需要提醒:虽然上述的技巧很有用,但同时它会带来副作用。例如上面屏蔽NSLog的技巧会严重拖慢程序的运行速度。因为每次断点中断,都会使控制权转移到debugger一边,然后运行命令。这些跨进程的操作很耗时间。

    有时候也许看不出来,但当执行的断点变多,或是你在诸如objc_msgSend这样的方法上添加了条件断点,那么也许你的程序会一直运行到天荒地老。

    无源码时的参数

    有时我们需要在没有代码的地方调试。比如说,我们在用xcode调试时,经常会发现程序在Cocoa框架里的某个地方崩溃了。我们需要找到到底是在哪里出错了。这种时候,一个可行的方法就是查看崩溃处的参数,看看到底发生了什么。

    ARM的存储很简单,参数只是按顺序被存储在$r0, $r1, $r2, $r3寄存器里。记住,在所有通过寄存器传递参数的体系结构里(i386不是),只有在函数开头的一小段里,寄存器里存的才是参数。因为在程序进行的过程中,它们随时都可能被其他变量替换掉。

    举例来说,我们可以打印出传给NSLog的参数:

        Breakpoint 2, 0x00007fff87beaa62 in NSLog () 

        (gdb) po $rdi 

        Hello, world!

    这里有个很常见的技巧:如果我们想给NSLog添加断点来巡查崩溃,就可以根据输出内容设置一下判断,让debugger不至于在每次NSLog时都中断:

        (gdb) break NSLog if (char)[$rdi hasPrefix: @"crashing"]

    记住,方法的前两个参数是self和_cmd。所以我们的参数应该从$rdx(x86_64)或$rd2(ARM)开始计算。

    异常

    异常会被运行时方法objc_exception_throw抛出。在这个方法里设置断点是很重要的。原因有两点:

    1. 抛出异常,通常是程序出现严重错误的信号。

    2. 被抛出的异常通常会被对应的代码捕获。如果你不在这里设置断点的话,就只能获得异常被捕获之后的信息,而不知道它到底是在哪里被抛出的。

    如果你设置了断点,程序就会在异常被抛出的时候停止。这样你就有机会查看栈信息,知道具体是哪里抛出了异常。

    为异常设置断点的方法也很简单,因为要抛出的异常是objc_exception_throw方法的唯一一个参数,所以我们可以用上一小节提到的方法来完成它。

    第三个:线程

    现在,多线程代码随处可见。知道如何调试多线程程序也越来越重要。以下一段代码启动了几个后台运行的线程:

        dispatch_apply(3, dispatch_get_global_queue(0, 0), ^(size_t x){ 

            sleep(100); 

        });

    运行debugger,在程序睡眠的时候用Control-C杀掉它:

       (gdb) run 

        Starting program: /Users/mikeash/shell/a.out 

        Reading symbols for shared libraries .+++........................ done 

        ^C 

        Program received signal SIGINT, Interrupt. 

        0x00007fff88c6ff8a in __semwait_signal () 

        (gdb) bt 

        #0 0x00007fff88c6ff8a in __semwait_signal () 

        #1 0x00007fff88c6fe19 in nanosleep () 

        #2 0x00007fff88cbcdf0 in sleep () 

        #3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=0) at test.m:12 

        #4 0x00007fff88cbbbc8 in _dispatch_apply2 () 

        #5 0x00007fff88cb31e5 in dispatch_apply_f () 

        #6 0x0000000100000e6a in main (argc=1, argv=0x7fff5fbff628) at test.m:11

    和我们想的一样,我们输出了一个线程的信息。但是,另外两个后台运行的线程在哪里?我们可以用info threads命令获取所有线程的列表:

        (gdb) info threads 

          3 "com.apple.root.default-priorit" 0x00007fff88c6ff8a in __semwait_signal () 

          2 "com.apple.root.default-priorit" 0x00007fff88c6ff8a in __semwait_signal () 

        * 1 "com.apple.root.default-priorit" 0x00007fff88c6ff8a in __semwait_signal ()

    线程1前面有个星号,这表示它是现在活动中的线程。现在我们切换到线程2:

        (gdb) thread 2 

        [Switching to thread 2 (process 4794), "com.apple.root.default-priority"] 

        0x00007fff88c6ff8a in __semwait_signal () 

        (gdb) bt 

        #0 0x00007fff88c6ff8a in __semwait_signal () 

        #1 0x00007fff88c6fe19 in nanosleep () 

        #2 0x00007fff88cbcdf0 in sleep () 

        #3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=1) at test.m:12 

        #4 0x00007fff88cbbbc8 in _dispatch_apply2 () 

        #5 0x00007fff88c4f7f1 in _dispatch_worker_thread2 () 

        #6 0x00007fff88c4f128 in _pthread_wqthread () 

        #7 0x00007fff88c4efc5 in start_wqthread ()

    现在我们输出了线程2的信息。然后时线程3……是不是觉得这种方法效率太低了?我们只有3个线程,但如果有300个呢?幸好,gdb提供了thread apply all backtrace命令(简写为t a a bt),用来列出所有线程的详细信息。

    Thread 3 (process 4794):

    #0 0x00007fff88c6ff8a in __semwait_signal ()

    #1 0x00007fff88c6fe19 in nanosleep ()

    #2 0x00007fff88cbcdf0 in sleep ()

    #3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=2) at test.m:12

    #4 0x00007fff88cbbbc8 in _dispatch_apply2 ()

    #5 0x00007fff88c4f7f1 in _dispatch_worker_thread2 ()

    #6 0x00007fff88c4f128 in _pthread_wqthread ()

    #7 0x00007fff88c4efc5 in start_wqthread ()

    Thread 2 (process 4794):

    #0 0x00007fff88c6ff8a in __semwait_signal ()

    #1 0x00007fff88c6fe19 in nanosleep ()

    #2 0x00007fff88cbcdf0 in sleep ()

    #3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=1) at test.m:12

    #4 0x00007fff88cbbbc8 in _dispatch_apply2 ()

    #5 0x00007fff88c4f7f1 in _dispatch_worker_thread2 ()

    #6 0x00007fff88c4f128 in _pthread_wqthread ()

    #7 0x00007fff88c4efc5 in start_wqthread ()

    Thread 1 (process 4794):

    #0 0x00007fff88c6ff8a in __semwait_signal ()

    #1 0x00007fff88c6fe19 in nanosleep ()

    #2 0x00007fff88cbcdf0 in sleep ()

    #3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=0) at test.m:12

    #4 0x00007fff88cbbbc8 in _dispatch_apply2 ()

    #5 0x00007fff88cb31e5 in dispatch_apply_f ()

    #6 0x0000000100000e6a in main (argc=1, argv=0x7fff5fbff628) at test.m:11

    现在我们可以方便地查看整个程序中的线程了。如果想要更彻底地观察某个线程,只需要用thread命令切换到该线程,然后使用各种已经学过的gdb命令。

    控制台参数和环境变量

    在用gdb调试带参数的程序时会遇到一个疑惑,即程序的参数究竟怎么输入:

        $ gdb /bin/echo hello world 

        Excess command line arguments ignored. (world) 

        [...] 

        /Users/mikeash/shell/hello: No such file or directory

    如上,把参数直接缀在后面显然是不对的。因为这样它们会被解释成gdb的参数,而不是要调试程序的参数。运行结果也证明了这一点,gdb把hello和world都解释成了要运行的程序名。

    解决方法也很简单,即,在gdb启动之后,执行run命令的同时输入参数:

        (gdb) run hello world 

        Starting program: /bin/echo hello world 

        Reading symbols for shared libraries +. done 

        hello world

    环境变量可以在启动gdb之前预先在shell中载入,通常情况下这么做也没有问题。但是,如果你操纵的环境变量会对每个程序都造成严重影响的话,这就不是一个好主意了。在这种情况下,我们用set env命令,做针对于目标程序的修改:

        (gdb) set env DYLD_INSERT_LIBRARIES /gdb/crashes/if/this/is/inserted.dylib

  • 相关阅读:
    bzoj 1017 魔兽地图DotR
    poj 1322 chocolate
    bzoj 1045 糖果传递
    poj 3067 japan
    timus 1109 Conference(二分图匹配)
    URAL 1205 By the Underground or by Foot?(SPFA)
    URAL 1242 Werewolf(DFS)
    timus 1033 Labyrinth(BFS)
    URAL 1208 Legendary Teams Contest(DFS)
    URAL 1930 Ivan's Car(BFS)
  • 原文地址:https://www.cnblogs.com/Camier-myNiuer/p/3664074.html
Copyright © 2011-2022 走看看