注册 登录
主题 : [翻译]gdb基本命令和技巧
级别: 新手上路
UID: 36414
精华: 0
发帖: 7
可可豆: 75 CB
威望: 75 点
在线时间: 42(时)
注册时间: 2010-11-02
最后登录: 2012-01-18
0 楼:  发表于: 2011-07-13 19:15    发自: Web Page
来源于 一般讨论 分类

[翻译]gdb基本命令和技巧   

/**********************************************\ +2S#3m?1  
4m3pF0k  
原作地址:http://www.mikeash.com/pyblog/friday-qa-2011-06-17-gdb-tips-and-tricks.html B[nkE+s  
(YH/#n1"{  
作者:mikeash `<^1Ik[g  
g tV*`g  
这是作者Friday Q&A 中的一篇。觉得很好于是翻译了一下。 Z* L{;  
za oC  
\*********************************************/ ;IYH5sG{  
|=W=H6h*  
nH % 1lD?:  
zS+_6s  
关于GDB a5xmIp@6  
$(U}#[Vie  
对于大多数Cocoa程序员来说,最常用的debugger莫过于Xcode自带的调试工具了。而实际上,它正是gdb的一个图形化包装。相对于gdb,图形化带来了很多便利,但同时也缺少了一些重要功能。而且在某些情况下,gdb反而更加方便。因此,学习gdb,了解一下幕后的实质,也是有必要的。 S|?Ht61k  
#-Ad0/  
gdb可以通过终端运行,也可以在Xcode的控制台调用命令。本文将通过终端讲述一些gdb的基本命令和技巧。 fK{[=xMr@  
CiSl  0  
首先,我们来看一个例子: <m3or  
'{(/C ?T  
    #import <Foundation/Foundation.h> yL>wCD,L  
]h6mJ{ k  
    int main(int argc, char **argv) }./_fFN@  
    {  p3YF  
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 1_z6O!rx  
        NSLog(@"Hello, world!"); SS/t8Y4W  
        [pool release]; .A2u7*h&  
U\\nSU  
        return 0; 3!V$fl0  
    } ~1yMw.04V  
U DC>iHt  
我们把文件命名为test.m,然后编译: w(xRL#%  
^,M&PP6  
    gcc -g -framework Foundation test.m e;/C}sK:  
>)IXc<"wq  
准备工作已经完成。现在我们可以开始调试了。只要把要调试的文件名作为参数,启动gdb: :9Vd=M6,  
g}]EIv{  
    gdb a.out \O7Vo<B&D  
FTg4i\Wp  
gdb启动后会输出很多法律声明之类的信息。无视它们,最后我们看到一个提示: fx783  
M"t=0[0DM:  
    (gdb)  !&Z,ev  
Dxwv\+7]  
成功!现在debugger和刚才编译好的程序都被装载了。不过,现在程序还没有开始运行。因为gdb在程序开始前把它暂停了,好让我们有机会设置调试参数。这次我们不需要做特别设置,所以马上开始运行吧: X0.-q%5  
*KXg;777  
    (gdb) run  E(&GZ QE  
;5.o;|w?!  
    Starting program: /Users/mikeash/shell/a.out @}e'(ju%R  
    Reading symbols for shared libraries .++++....................... done gAY%VFBP0  
    2011-06-16 20:28:53.658 a.out[2946:a0f] Hello, world! s;0eD5b>x  
0kD8wj%  
    Program exited normally. hR$lX8  
    (gdb) 5w#*JK   
;^,2 QsM  
糟糕,程序竟然exited normally了(==|||)。这可不行,我们得让他崩溃才行。所以我们给这个小程序添加一个bug: |;-,(509  
7Pc0|Z/  
    int x = 42; Z-{!Z;T)z  
    NSLog("Hello, world! x = %@", x); Bn"r;pqWiT  
nice。这样一来程序就会漂亮地崩溃了: i~IQlyGr.  
YORFq9a{R  
    (gdb) run  LxGE<xj|V%  
E00zf3Jgv'  
    Starting program: /Users/mikeash/shell/a.out hao0_9q+  
    Reading symbols for shared libraries .++++....................... done B=;pyhc  
lbES9o5  
    Program received signal EXC_BAD_ACCESS, Could not access memory. 5 g- apod  
    Reason: 13 at address: 0x0000000000000000 ,qRSB>5c  
    0x00007fff84f1011c in objc_msgSend () sQA{[l!aj  
    (gdb) k:4?3zJI  
[hg|bpEG  
如果我们是在shell中直接运行的程序,在崩溃后就会回到shell。不过现在我们是通过gdb运行的,所以现在并没有跳出。gdb暂停了我们的程序,但依然使之驻留在内存中,让我们有机会做调试。 }eX_p6bBw  
W7a s =+;X  
首先,我们想知道具体是哪里导致了程序崩溃。gdb已经通过刚才的输出告知了我们: 函数objc_msgSend就是祸之根源。但是这个信息并不足够,因为这个objc_msgSend是objc运行时库中的函数。我们并不知道它是怎么调用的。我们关注的是我们自己的代码。 6Upg\(  
要知道这一点,我们需要得到当前进程的函数调用栈的情况,以此回溯找到我们自己的方法。这时我们需要用到backtrace命令,一般简写为bt: L-d8bA  
\ooqa<_  
    (gdb) bt |zu>G9m  
    #0 0x00007fff84f1011c in objc_msgSend () vtFA#})~  
    #1 0x00007fff864ff30b in _CFStringAppendFormatAndArgumentsAux () 0<L@f=i  
    #2 0x00007fff864ff27d in _CFStringCreateWithFormatAndArgumentsAux () xQ7n$.?y@  
    #3 0x00007fff8657e9ef in _CFLogvEx () LoG@(g&)  
    #4 0x00007fff87beab3e in NSLogv () 13Z6dhZu  
    #5 0x00007fff87beaad6 in NSLog () 33kI#45s  
    #6 0x0000000100000ed7 in main () at test.m:10 qD\%8l.]Z  
fBH&AO$Q  
现在我们可以看到,程序在test.m的第10行,调用NSLog方法时崩溃了。接下来我们想看一下这次调用的详细信息。这时我们要用到up命令。up命令可以在栈的各层之间跳转。本例中,我们的代码main是#6: Et'C4od s  
N, ik&NIWy  
    (gdb) up 6 L\og`L)5\  
    #6 0x0000000100000ed7 in main () at test.m:10 1#qCD["8  
    9       NSLog("Hello, world! x = %@", x); hkgPC-  
这回不仅是函数名,连出错的那行代码也打印出来了。但是,我们还可以使用list(简写为l)命令,打印出更多信息: v%< _Mh  
ps: 如果需要回到栈列表。可以使用down命令。 ) WIlj  
x=B+F IJ  
    (gdb) l SEchF"KJQF  
    5   p8"(z@T  
    6   int main(int argc, char **argv) Wsz='@XvB  
    7   { U>;itHW/  
    8       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; VP4t~$"  
    9       int x = 42; UKK}$B  
    10      NSLog("Hello, world! x = %@", x); gSj-~k P  
    11      [pool release]; i`] M2Q   
    12       zF i+ 6I$  
    13      return 0; %Y Rg1UKY  
    14  } ,P"R.A  
>KY\Bx  
啊,整个代码都被列出来了。虽然我们用编辑器打开test.m文件然后找到第10行也可以打到同样效果,但显然没有上面的方法更有效率。(当然没有Xcode自带的那个快就是了) Ux icqkX  
\N#)e1.0P  
好了,现在我们再来看看这个bug(虽然是我们自己弄出来的)。很明显,在格式化字符串前少加了一个@。我们改正它,并重新运行一遍程序: )+t5G>yKK  
0HD1Ob^@  
    (gdb) run xdo{4XY^*W  
    Starting program: /Users/mikeash/shell/a.out H5RHA^p|  
    Reading symbols for shared libraries .++++....................... done Ql*/{#$  
xYu~}kMu  
    Program received signal EXC_BAD_ACCESS, Could not access memory. !CBx$1z  
    Reason: KERN_INVALID_ADDRESS at address: 0x000000000000002a P'9aZd  
    0x00007fff84f102b3 in objc_msgSend_fixup () C6"!'6 W  
    (gdb) bt %y w*!A1  
    #0 0x00007fff84f102b3 in objc_msgSend_fixup () w78Ius,  
    #1 0x0000000000000000 in ?? () uW nS<O  
?XOeMI  
啊咧,程序还是崩溃了。更杯具的是,栈信息没有显示出这个objc_msgSend_fixup方法是从哪里调用的。这样我们就没法用上面的方法找到目标代码了。这时,我们只好请出一个debugger最常用的功能:断点。 L7Oytdc<  
A}y1v;FB  
在gdb中,设置断点通过break命令实现。它可以简写为b。有两种方法可以确定断点的位置:传入一个已定义的符号,或是直接地通过一个file:line对设置位置。 Bh' vr3|  
现在让我们在main函数的开始处设置一个断点: a3c4 #'c|D  
ex!^&7Q(  
    (gdb) b test.m:8 g[M@  
    Breakpoint 1 at 0x100000e8f: file test.m, line 8. $PS5xD~@  
bOz\-=au  
debugger给了我们一个回应,告诉我们断点设置成功了,而且这个断点的标号是1。断点的标号很有用,可以用来给断点排序&停用&启用&删除等。不过我们现在不需要理会,我们只是接着运行程序: ` `;$Kr  
T (2,iG8  
    (gdb) run <Z8] W1)  
    The program being debugged has been started already. Zc4h jg  
    Start it from the beginning? (y or n) y .hJ8K #r  
    Starting program: /Users/mikeash/shell/a.out |C&eH$?~=R  
W=EO=}l#  
    Breakpoint 1, main (argc=1, argv=0x7fff5fbff628) at test.m:8 )q l?}  
    8       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; k13/yiv  
debugger在在我们期望的地方停下了。现在我们使用next(简写n)命令单步调试程序,看看它到底是在哪一行崩溃的: Jj6kZK  
UdI>x 4bI  
    (gdb) n (Z"Xp{u  
    9       int x = 42; .sG,TLE[<  
    (gdb) @J<B^_+Se  
    10      NSLog(@"Hello, world! x = %@", x); l.)N  
    (gdb) <dYk|5AdLF  
wO!hVm,T a  
    Program received signal EXC_BAD_ACCESS, Could not access memory. >Au<y,Tw  
    Reason: KERN_INVALID_ADDRESS at address: 0x000000000000002a NUnP'X=J,  
    0x00007fff84f102b3 in objc_msgSend_fixup () (x^|  
值得注意的是,我只键入了一次n命令,随后直接敲了2次回车。这样做的原因是gdb把任何空输入当作最近一次输入命令的重复。所以这里相当于输入了3次n。 E+1j3Q;  
G>RYQ{O  
现在我们可以看到,崩溃之处依然是NSLog。原因嘛,当然是在格式化输出的地方用%@表示int型变量x了。我们仔细看一下输出信息:崩溃原因是错误地访问了0x000000000000002a这个地址。而2a的十进制表示正是42--我们为x赋的值。编译器把它当作地址了。 CLkVe  
\7j)^  
输出数值 I(<G;ft<}  
Awh"SU Oh0  
一个很重要的调试方法是输出表达式和变量的值。在gdb中,这是通过print命令完成的。 PRz oLzr  
 <aHt6s'  
    (gdb) p x _@OYC<  
    $1 = 42 /3TorB~Y  
I@S<D"af  
在print命令后追加/format可以格式化输出。/format是一个gdb的格式化字符串,比较有用的格式化字符有 x:十进制数; c:字符; a:地址等。 CXQ ?P  
f<;9q?0VF  
    (gdb) p/x x #wjBMR%  
    $2 = 0x2a S-/ #3  
E7h}0DX  
print-object方法(简写为po)用来输出obj-c中的对象。它的工作原理是,向被调用的对象发送名为debugDescription的消息。它和常见的description消息很像。 :9h8q"T  
w%_BX3GTO  
举例来说,让我们输出一下autorelease pool: |Gf{}  
0 j.Sb2  
    (gdb) po pool | fI%L9  
    <NSAutoreleasePool: 0x10010e820> Q~@8t"P  
wwAT@=X*}  
这个命令不仅仅可以输出显式定义的对象,也可以输出表达式的结果。这次我们测试一下nsobject中debugDescription的方法签名: " }ZD)7K  
ibuI/VDF  
    (gdb) po [NSObject instanceMethodSignatureForSelector: @selector(debugDescription)] *tO<wp&  
    <NSMethodSignature: 0x10010f320> }1W$9\%  
        number of arguments = 2 7}x-( {bqy  
        frame size = 224 ]Cz16e&=2  
        is special struct return? NO UA8!?r-cR  
        return value: -------- -------- -------- -------- Ur-^X(nL  
            type encoding (@) '@' ww,c)$  
            flags {isObject} 2>!ykUw^O  
            modifiers {} y^A $bTQq  
            frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0} QLUe{@ivc  
            memory {offset = 0, size = 8} sR 9F:  
        argument 0: -------- -------- -------- -------- OWjZ)f/  
            type encoding (@) '@' ~+np7  
            flags {isObject} $"FdS,*qKl  
            modifiers {} "QF083$  
            frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0} O+8`.  
            memory {offset = 0, size = 8} &rd(q'Vi  
        argument 1: -------- -------- -------- -------- y%,BDyK  
            type encoding (:) ':' h[8y$.YsC  
            flags {} #CS>A# Lk  
            modifiers {} Qm.kXlsDI  
            frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0} LX fiSM{o  
            memory {offset = 0, size = 8} |d3agfS[n  
z )pV $  
是不是很方便。但是要注意,gdb也许会不能识别NSObject这样的类名。这时我们就要使用一些小技巧,比如说用NSClassFromString来获得类名: IID(mmy6 L  
E nvs[YZe  
    (gdb) po [NSClassFromString(@"NSObject") instanceMethodSignatureForSelector: @selector(debugDescription)] +n,BD C;  
0/ Ht;(  
返回值是对象的表达式可以用po命令输出结果,那么返回值是基本类型的方法又怎样呢?显然,它们是可以用p命令输出的。但是要小心,因为gdb并不能自动识别出返回值的类型。所以我们在输出前要显式地转换一下: |i`@!NrFL  
9vbh5xX   
    (gdb) p [NSObject instancesRespondToSelector: @selector(doesNotExist)] biG9?  
    Unable to call function "objc_msgSend" at 0x7fff84f100f4: no return type information available. .;:xx~G_Q  
    To call this function anyway, you can cast the return type explicitly (e.g. 'print (float) fabs (3.0)') ,![=_d  
    (gdb) p (char)[NSObject instancesRespondToSelector: @selector(doesNotExist)] *YtNt5u  
    $5 = 0 '00' uf^:3{1  
~/U0S.C  
你也许发现了,doesNotExist方法的返回值是BOOL,而我们做的转换却是char。这是因为gdb也不能识别那些用typedef定义的类型。不仅仅是你定义的,即使是Cocoa框架里定义的也不行。 %yMzgk[u  
~tLR  
你也许已经注意到,在用p进行输出的时侯,输出值前面会有一个类似"$1="的前缀。它们是gdb变量。它们可以在后面的表达式中使用,来指代它后面的值。在下面的例子里,我们开辟了一块内存,将其置零,然后释放。在这个过程中,我们使用了gdb变量,这样就不用一遍遍地复制粘贴地址了。 \$C 4H  
LbR-uc?x  
    (gdb) p (int *)malloc(4) ^8m+*t  
    $6 = (int *) 0x100105ab0 f$>orVm%.  
    (gdb) p (void)bzero($6, 4) *mQit/ k.  
    $7 = void eFio,  
    (gdb) p *$6 >&&xJ5  
    $8 = 0 1pb;A;F,A  
    (gdb) p (void)free($6) yx2.7h3  
    $9 = void g,:N zb  
<n\.S  
我们也想把这个技巧用到对象上,但不幸的是po命令并不会把它的返回值存储到变量里。所以我们在得到一个新的对象时必须先使用p命令: AVr!e   
2jW>uk 4/i  
    (gdb) p (void *)[[NSObject alloc] init] DOerSh_0W  
    $10 = (void *) 0x100105950 `+.I  
    (gdb) po $10 I5L7BTe  
    <NSObject: 0x100105950> #I?iR 3u  
    (gdb) p (long)[$10 retainCount] [j? <9  
    $11 = 1 i-?zwVmn  
    (gdb) p (void)[$10 release] 1,j9(m2  
    $12 = void _o.Z`]  
%)9]dOdOk  
检查内存 $P(nh'\  
%-l:_A  
有些时候,仅仅输出一个数值还不能帮助我们查找出错误。我们需要一次性地打印出一整块内存来窥视全局。这时候我们就需要使用x命令。 hQm4R]a  
S |x)7NC  
x命令的格式是x/format address。其中address很简单,它通常是指向一块内存的表达式。但是format的语法就有点复杂了。它由三个部分组成: dNQ Sbp  
?Qig$  
第一个是要显示的块的数量;第二个是显示格式(如x代表16进制,d代表十进制,c代表字符);第三个是每个块的大小。值得注意的是第三部分,即块大小是用字符对应的。用b, h, w,  g 分别表示1, 2, 4, 8 bytes。举例来说,用十六进制方式,打印从ptr开始的4个4-byte块应该这样写: .NT&>X~.V  
X6.O ;  
    (gdb) x/4xw ptr I{zE73  
OQ7c| O  
接下来举一个比较实际的例子。我们看一下NSObject类的内容: .e"Qv*[^  
(g m^o{  
    (gdb) x/4xg (void *)[NSObject class] k^pu1g=6I  
    0x7fff70adb468 <OBJC_CLASS_$_NSObject>: 0x00007fff70adb440  0x0000000000000000 #'x?) AS  
    0x7fff70adb478 <OBJC_CLASS_$_NSObject+16>:  0x0000000100105ac0  0x0000000100104ac0 hzLGmWN2j8  
A E&n^vdQW  
接下来再看看一个NSObject实例的内容: $t$f1?  
6Qb)Uq3}]  
    (gdb) x/1xg (void *)[NSObject new] 2]9<%-=S  
    0x100105ba0:    0x00007fff70adb468 y^=oY L  
@(c<av?  
现在我们看到,在实例开头引用了类的地址。 pYVy(]1I(3  
l,u{:JC  
设置变量 $/TA5h  
FA9e(Ha   
有时,查看数值程度的能力还是稍弱了一点,我们还想能够修改变量。这也很简单,只需要使用set命令: ;n!X% S<z*  
Yd;r8rN  
    (gdb) set x = 43 ^,K.)s  
8uxFXQ  
我们可以用任意表达式给一个变量赋值。比如说新创建一个对象然后赋值: M:M>@|)  
Z5Ihc%J^  
    (gdb) set obj = (void *)[[NSObject alloc] init] d5y2Y/QO  
RuNH (>Eb  
断点 6B/"M-YME  
#`4^zU)  
我们可以在程序的某个位置设置断点,这样当程序运行到那里的时候就会暂停,而把控制权转移给调试器。就像之前提到的,我们用break命令来设置断点。下面详细地列出了如何设置断点的目标: {,FeNf46  
~{YgM/c|dt  
SymbolName: 为断点指定一个函数名。这样断点就会设置在该函数上。 0NtsFPO  
file.c:1234: 把断点设置在指定文件的一行。 5"am> $rh  
-[ClassName method:name:]: 把断点设置在objc的方法上。用+代表类方法。 g u =fq\`  
*0xdeadbeef: 在内存的指定位置设置断点。这不是很常用,一般在没有源码的调试时使用。 [\9(@Bx  
_CJr6Evs  
断点可以用enable命令和disable命令来切换到使用和停用状态,也可以通过delete命令彻底删除。想要查看现有断点的话,使用info breakpoints命令(可以简写成info b,或是i b)。 Ro]IE|Fv  
q "D L6 >j  
另外,我们也可以用if命令,把断点升级成条件断点。顾名思义,条件断点只会在设定的条件成真时起作用。举例来说,下面的语句为MyMethod添加了一个条件断点,它只在参数等于5的时候有效: K=x1m M+RK  
IKDjatn  
    (gdb) b -[Class myMethod:] if parameter == 5 gVU\^KN]  
"IG$VjgcB  
最后,在断点上可以附加gdb命令。这样,当断点中断时,附带的命令会自动执行。附加命令使用commands breakpointnumber。这时gdb就会进入断点指令输入状态。 )95yV;n   
[7 `Dgnmq  
断点指令就是一个以end结尾的标准gdb指令序列。举个例子,我们想在每次NSLog被调用时输出栈信息: 3c^=<i %  
(eG]Cp@  
    (gdb) b NSLog '1'1T5x~  
    Breakpoint 4 at 0x7fff87beaa62 THgzT\_zq  
    (gdb) commands m]d6@"Z.  
    Type commands for when breakpoint 4 is hit, one per line. M3@fc,Ch  
    End with a line saying just "end". 0Eu$-)  
    >bt o:D BOpS  
    >end W4*BR_H&*  
!7`=rT&  
这很好理解,只有一点需要提一下:如果commands命令是作用在刚设置的断点上的话,那么就可以省略断点序号。 %]0U60  
hav?mnVJ  
有些时候,我们希望调试器输出一些信息,但是并不想中断程序运行。这实际上也可以通过追加指令实现。我们只需要在指令的最后增加continue指令就行了。在下面的例子里,我们在断点中断后打印栈信息和参数信息,随后继续运行: ~|{)h^]@  
+N$7=oGC  
    (gdb) b -[Class myMethod:] u7zB9iQ&  
    Breakpoint 5 at 0x7fff864f1404 J+ZdZa}Ob  
    (gdb) commands "!Oh#Vf  
    Type commands for when breakpoint 5 is hit, one per line. l;}7A,u  
    End with a line saying just "end". k*3_) S -  
    >bt 'eDgeWt/CQ  
    >p parameter |C6(0fgWd  
    >continue D 2:a  
    >end ,XG|oo -  
/B@% p q  
最后一个奇特的运用是return命令。它和c中的同名命令一样,都用来跳出当前函数。如果设置了参数,这参数会作为函数的返回值。 $t# ,'M  
SE9u2Jk  
比如说,我们可以用这个技巧屏蔽掉NSLog函数: }0*ra37z>  
jjwMvf.R  
    (gdb) commands B"8JFf}"q  
    Type commands for when breakpoint 6 is hit, one per line. 11<@++,i  
    End with a line saying just "end". Z*(! `,.bB  
    >return 5rA !VES T  
    >continue 0 ,Qj:  
    >end g~hk-nXL.  
H.8CwsfP  
有一点需要提醒:虽然上述的技巧很有用,但同时它会带来副作用。例如上面屏蔽NSLog的技巧会严重拖慢程序的运行速度。因为每次断点中断,都会使控制权转移到debugger一边,然后运行命令。这些跨进程的操作很耗时间。 +.#S[G  
b/,!J] W  
有时候也许看不出来,但当执行的断点变多,或是你在诸如objc_msgSend这样的方法上添加了条件断点,那么也许你的程序会一直运行到天荒地老。 FR,#s^kF  
R^M (fC  
无源码时的参数 y8*@dRrq  
h"1"h.  
有时我们需要在没有代码的地方调试。比如说,我们在用xcode调试时,经常会发现程序在Cocoa框架里的某个地方崩溃了。我们需要找到到底是在哪里出错了。这种时候,一个可行的方法就是查看崩溃处的参数,看看到底发生了什么。 5'c+313 lm  
qVD!/;l  
这是一篇很好的文章,它讲解了在不同的体系结构下,参数是如何存储的。不过它并没有讲到ARM(= =)。所幸ARM的存储很简单,参数只是按顺序被存储在$r0, $r1, $r2, $r3寄存器里。记住,在所有通过寄存器传递参数的体系结构里(i386不是),只有在函数开头的一小段里,寄存器里存的才是参数。因为在程序进行的过程中,它们随时都可能被其他变量替换掉。 ^ $+f3Z'  
a^\- }4yR  
举例来说,我们可以打印出传给NSLog的参数: [{p?BTs  
- )a_ub  
    Breakpoint 2, 0x00007fff87beaa62 in NSLog () _Kl{50}]  
    (gdb) po $rdi Ed&,[rC  
    Hello, world! ]7S7CVDk4  
_HHJw""j  
这里有个很常见的技巧:如果我们想给NSLog添加断点来巡查崩溃,就可以根据输出内容设置一下判断,让debugger不至于在每次NSLog时都中断: FHNuMdFn  
.V 3X#t  
    (gdb) break NSLog if (char)[$rdi hasPrefix: @"crashing"] lDPRn~[#\  
BdB`  
记住,方法的前两个参数是self和_cmd。所以我们的参数应该从$rdx(x86_64)或$rd2(ARM)开始计算。 z?uQlm*We  
2@m(XT (  
异常 h[je_^5  
)-?uX.E{  
异常会被运行时方法objc_exception_throw抛出。在这个方法里设置断点是很重要的。原因有两点: D0y,TF  
&Wv`AoV  
1. 抛出异常,通常是程序出现严重错误的信号。 <aSL m=  
2. 被抛出的异常通常会被对应的代码捕获。如果你不在这里设置断点的话,就只能获得异常被捕获之后的信息,而不知道它到底是在哪里被抛出的。 fFZ` rPb  
wdcryejCkr  
如果你设置了断点,程序就会在异常被抛出的时候停止。这样你就有机会查看栈信息,知道具体是哪里抛出了异常。 h/0-Mrk;e  
OZB}aow  
为异常设置断点的方法也很简单,因为要抛出的异常是objc_exception_throw方法的唯一一个参数,所以我们可以用上一小节提到的方法来完成它。 95?5=T F  
iWN.3|r  
线程 b!5tFX;J  
_ U8OIXN  
现在,多线程代码随处可见。知道如何调试多线程程序也越来越重要。以下一段代码启动了几个后台运行的线程: aEFe!_QY  
Z_V&IQo-7  
    dispatch_apply(3, dispatch_get_global_queue(0, 0), ^(size_t x){ v>y8s&/  
        sleep(100); U??f<  
    }); :Bv&)RK  
_ 2gT1B  
运行debugger,在程序睡眠的时候用Control-C杀掉它: ^ ,Y~M_=  
9HNh*Gc=  
    (gdb) run K jn&  
    Starting program: /Users/mikeash/shell/a.out GkGC4*n  
    Reading symbols for shared libraries .+++........................ done )' x/q  
    ^C FM"GK '  
    Program received signal SIGINT, Interrupt. (m3I#L  
    0x00007fff88c6ff8a in __semwait_signal () %YvSHh;c  
    (gdb) bt *Nf4bH%MN  
    #0 0x00007fff88c6ff8a in __semwait_signal () A.$VM#  
    #1 0x00007fff88c6fe19 in nanosleep () 8A8xY446)  
    #2 0x00007fff88cbcdf0 in sleep () iVpA @p   
    #3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=0) at test.m:12 -4y)qGb*?  
    #4 0x00007fff88cbbbc8 in _dispatch_apply2 () d% _78nOh"  
    #5 0x00007fff88cb31e5 in dispatch_apply_f () x)#<.DX  
    #6 0x0000000100000e6a in main (argc=1, argv=0x7fff5fbff628) at test.m:11 $~G0#JL  
]IHD:!Z-=  
和我们想的一样,我们输出了一个线程的信息。但是,另外两个后台运行的线程在哪里?我们可以用info threads命令获取所有线程的列表: }OP%p/eY  
34m']n  
    (gdb) info threads }lC64;yo  
      3 "com.apple.root.default-priorit" 0x00007fff88c6ff8a in __semwait_signal () cfC;eRgq~  
      2 "com.apple.root.default-priorit" 0x00007fff88c6ff8a in __semwait_signal () !skb=B#  
    * 1 "com.apple.root.default-priorit" 0x00007fff88c6ff8a in __semwait_signal () dW{o+9nw  
cGpN4|*rQ  
线程1前面有个星号,这表示它是现在活动中的线程。现在我们切换到线程2: _]=TFz2O  
qD$GKN .  
    (gdb) thread 2 (J^Lqh_  
    [Switching to thread 2 (process 4794), "com.apple.root.default-priority"] :L [YmZ  
    0x00007fff88c6ff8a in __semwait_signal () Yn$>QS 4  
    (gdb) bt )Vg{Y [!  
    #0 0x00007fff88c6ff8a in __semwait_signal () Mdltzy=)L  
    #1 0x00007fff88c6fe19 in nanosleep () rq'##`H  
    #2 0x00007fff88cbcdf0 in sleep () >d27[%  
    #3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=1) at test.m:12 ,O g[[0g  
    #4 0x00007fff88cbbbc8 in _dispatch_apply2 () N}}PlGp$  
    #5 0x00007fff88c4f7f1 in _dispatch_worker_thread2 () 9#s95R O  
    #6 0x00007fff88c4f128 in _pthread_wqthread () $ gr6  
    #7 0x00007fff88c4efc5 in start_wqthread () / UaNYv/  
1fO2)$Y  
现在我们输出了线程2的信息。然后时线程3……是不是觉得这种方法效率太低了?我们只有3个线程,但如果有300个呢?幸好,gdb提供了thread apply all backtrace命令(简写为t a a bt),用来列出所有线程的详细信息。 ek(kY6x:  
`Dz]z_  
Thread 3 (process 4794): 9&XV}I,~?|  
#0 0x00007fff88c6ff8a in __semwait_signal () [TbG55  
#1 0x00007fff88c6fe19 in nanosleep () 7SA-OFM  
#2 0x00007fff88cbcdf0 in sleep () ]U#[\ Z  
#3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=2) at test.m:12 %7C%`)T]  
#4 0x00007fff88cbbbc8 in _dispatch_apply2 () RP`GG+K  
#5 0x00007fff88c4f7f1 in _dispatch_worker_thread2 ()  J:~[ j  
#6 0x00007fff88c4f128 in _pthread_wqthread () A4#3O5kij  
#7 0x00007fff88c4efc5 in start_wqthread () <(#xOe  
J/]o WC`u  
Thread 2 (process 4794): liugaRO8J  
#0 0x00007fff88c6ff8a in __semwait_signal () y?Hj %,  
#1 0x00007fff88c6fe19 in nanosleep () c >u>Pi;Z  
#2 0x00007fff88cbcdf0 in sleep () k}I5x1>&  
#3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=1) at test.m:12 j h1bn  
#4 0x00007fff88cbbbc8 in _dispatch_apply2 () 7}#*3*]  
#5 0x00007fff88c4f7f1 in _dispatch_worker_thread2 () L.IoGUxD  
#6 0x00007fff88c4f128 in _pthread_wqthread () Z/NGv  
#7 0x00007fff88c4efc5 in start_wqthread () _>jrlIfc  
i)@U.-*5m  
Thread 1 (process 4794): ~ F?G5cN5  
#0 0x00007fff88c6ff8a in __semwait_signal () f74%YY  
#1 0x00007fff88c6fe19 in nanosleep () KD%xo/Z.  
#2 0x00007fff88cbcdf0 in sleep () AT%* ~tr  
#3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=0) at test.m:12 (^tr}?C  
#4 0x00007fff88cbbbc8 in _dispatch_apply2 () \'s$ZN$k  
#5 0x00007fff88cb31e5 in dispatch_apply_f () oRT  
#6 0x0000000100000e6a in main (argc=1, argv=0x7fff5fbff628) at test.m:11 @Hspg^  
=qV4Sje|q  
现在我们可以方便地查看整个程序中的线程了。如果想要更彻底地观察某个线程,只需要用thread命令切换到该线程,然后使用各种已经学过的gdb命令。 8u:v:>D.'  
.y %pGi  
控制台参数和环境变量 VW{aUgajO  
u5cVz_S  
在用gdb调试带参数的程序时会遇到一个疑惑,即程序的参数究竟怎么输入: W2F +^  
I8<Il ^  
    $ gdb /bin/echo hello world  fW5" 4,  
    Excess command line arguments ignored. (world) WIEx ' {  
    [...] >YJ8u{Z{o  
    /Users/mikeash/shell/hello: No such file or directory L)kb (TH  
C}(<PNT  
如上,把参数直接缀在后面显然是不对的。因为这样它们会被解释成gdb的参数,而不是要调试程序的参数。运行结果也证明了这一点,gdb把hello和world都解释成了要运行的程序名。 Rm,[D)D^0N  
vDK :v$g  
解决方法也很简单,即,在gdb启动之后,执行run命令的同时输入参数: hapB! ~M?  
r6F{  
    (gdb) run hello world |n|U;|'^  
    Starting program: /bin/echo hello world S.Wh4kMUe  
    Reading symbols for shared libraries +. done Pp1zW3+Q  
    hello world PmPyb>HK=P  
%jbJ6c  
环境变量可以在启动gdb之前预先在shell中载入,通常情况下这么做也没有问题。但是,如果你操纵的环境变量会对每个程序都造成严重影响的话,这就不是一个好主意了。在这种情况下,我们用set env命令,做针对于目标程序的修改: *2qh3  
zqHpT^B?  
    (gdb) set env DYLD_INSERT_LIBRARIES /gdb/crashes/if/this/is/inserted.dylib #L@} .Giz  

清空我的评分动态本帖最近评分记录: 共1条评分记录
yoyokko 威望 +5 2011-08-07 优秀文章
隐藏评分记录
级别: 禁止发言
UID: 30630
精华: 0
发帖: 566
可可豆: 5201 CB
威望: 5201 点
在线时间: 1955(时)
注册时间: 2010-09-14
最后登录: 2017-12-08
1 楼:  发表于: 2011-07-13 23:35    发自: Web Page
用户被禁言,该主题自动屏蔽!
级别: 精灵王

状态: 连续签到 - [1天]
UID: 5181
精华: 1
发帖: 160
可可豆: 24462 CB
威望: 24462 点
在线时间: 2764(时)
注册时间: 2009-04-23
最后登录: 2017-02-07
2 楼:  发表于: 2011-07-14 00:06    发自: Web Page
很不错。但以后是不是都用LLVM了
级别: 精灵王
UID: 22435
精华: 0
发帖: 847
可可豆: 7948 CB
威望: 7895 点
在线时间: 563(时)
注册时间: 2010-06-17
最后登录: 2018-06-07
3 楼:  发表于: 2011-07-14 08:38    发自: Web Page
很不错,偶要慢慢带入实践中。。。
级别: 侠客
UID: 8795
精华: 0
发帖: 57
可可豆: 465 CB
威望: 466 点
在线时间: 148(时)
注册时间: 2009-09-15
最后登录: 2016-06-12
4 楼:  发表于: 2011-07-14 08:51    发自: iPhone
苹果建议用LLVM
级别: 骑士
UID: 23921
精华: 0
发帖: 161
可可豆: 1439 CB
威望: 1439 点
在线时间: 198(时)
注册时间: 2010-07-05
最后登录: 2014-03-12
5 楼:  发表于: 2011-07-14 09:10    发自: Web Page
楼主好人,鉴定完毕
级别: 新手上路
UID: 36414
精华: 0
发帖: 7
可可豆: 75 CB
威望: 75 点
在线时间: 42(时)
注册时间: 2010-11-02
最后登录: 2012-01-18
6 楼:  发表于: 2011-07-14 09:20    发自: Web Page
引用
引用第2楼vince于2011-07-14 00:06发表的  : \(fq8AL?  
很不错。但以后是不是都用LLVM了 @VK6JjIq  
+  xp*]a  
_B[WY  
llvm确实很nb,llib发展也很快,但个人感觉llib现在还是不如gdb好用^.^ .,M;huRg  
'E-FO_N  
ps: cocoachina的发帖能不能直接贴html啊。。感觉排版好乱
级别: 精灵王
UID: 6938
精华: 0
发帖: 169
可可豆: 8548 CB
威望: 8528 点
在线时间: 1426(时)
注册时间: 2009-07-11
最后登录: 2019-02-26
7 楼:  发表于: 2011-07-14 09:30    发自: Web Page
mark^^^^^^^^^^^^ #\ #3r  

级别: 精灵王

状态: 连续签到 - [14天]
UID: 27083
精华: 0
发帖: 1234
可可豆: 7130 CB
威望: 9357 点
在线时间: 1427(时)
注册时间: 2010-08-09
最后登录: 2018-05-21
8 楼:  发表于: 2011-07-14 10:02    发自: Web Page



http://blog.sina.com.cn/definefeeling
级别: 骑士
UID: 24093
精华: 0
发帖: 145
可可豆: 1324 CB
威望: 1324 点
在线时间: 544(时)
注册时间: 2010-07-07
最后登录: 2015-08-06
9 楼:  发表于: 2011-08-04 15:19    发自: Web Page
这么好的东西居然沉了

CocoaChina社区转载内容已尽可能注明出处,如未能核实来源或转发内容图片有权利瑕疵的,请及时联系社区进行修改或删除【联系方式QQ : 3442093904 邮箱:support@cocoachina.com】文章内容为作者独立观点,不代表CocoaChina社区立场。版权归原作者所有,如申请授权请联系作者,因文章侵权CocoaChina社区不承担任何法律及连带责任。

描述
快速回复

关注本帖(如果有新回复会站内信通知您)

发帖、回帖都会得到可观的积分奖励。查看论坛积分规则

按"Ctrl+Enter"直接提交
    顶部