[关闭]
@qidiandasheng 2021-07-21T09:42:53.000000Z 字数 8749 阅读 5361

深入了解GDB和LLDB

iOS调试 简书


什么是GDB和LLDB

我们在开发iOS程序的时候常常会用到调试跟踪,如何正确的使用调试器来debug十分重要。xcode里有内置的Debugger,老版使用的是GDB,xcode自4.3之后默认使用的就是LLDB了。

GDB:
UNIX及UNIX-like下的调试工具。

LLDB:
LLDB是个开源的内置于XCode的具有REPL(read-eval-print-loop)特征的Debugger,其可以安装C++或者Python插件。

所以他们两个都是调试用的Debugger,只是LLDB是比较高级的版本,或者在调试开发iOS应用时比较好用,不然人家苹果为什么换成了LLDB了呢!

lldb与gdb命令名的对照表:http://lldb.llvm.org/lldb-gdb.html

LLDB的使用

在程序里你需要的地方设置断点。当断点断住的时候你就能看到我们进入LLDB调试器了。

这时我们就可以使用一些LLDB命令来进行一些调试了。

调试快捷键:(Xcode常用快捷键)

command+shift+Y 打开调试窗口
command+Y 调试运行程序
command+option+P 继续
command+shift+O 跳过
command+shift+I 进入
command+shift+T 跳出

设置断点触发条件

LLDB命令

help命令

help会列出所有命令列表,用户加载的插件一般来说列在最后。
执行help 可以打印指定command的帮助信息。
比如:help print会打印内建命令print的使用帮助。

print命令

print命令的简化方式有prin pri p,唯独pr不能用来作为检查,因为会和process混淆,幸运的是p被lldb实现为特指print。

实际上你会发现,lldb对于命令的简称,是头部匹配方式,只要不混淆,你可以随意简称某个命令。

例如:

最前面的(int)是类型。使0可以进行print $0 + 7这样打印出17。

输出view 下 subview 的数量。

由于 subview 的数量是一个 int 类型的值,所以我们使用命令p:
(lldb)p (int)[[[self view] subviews] count]

直接调用方法改变背景颜色之类

其实使用p,po,call都可以调用方法,只是p和po都是用于输出的有返回值的。call一般只在不需要显示输出,或是方法无返回值时使用。
(lldb)p [self.view setBackgroundColor:[UIColor redColor]]
(lldb)p (void)[CATransaction flush]
上述的p一般使用call比较好,因为方法是没有返回值的。

po命令

命令po跟p很像。p输出的是基本类型,po输出的Objective-C对象。调试器会输出这个 object 的 description。

例如:

expression命令

expression的简写就是e。可以用expression来声明新的变量,也可以改变已有变量的值。我们看到e声明的都是$开头的变量。我们在使用时也需要加上$符号。

例如:

创建新的变量

注意:如果上面这里输入以下命令,会发生错误。说明lldb无法判定某一步的计算结果是什么数据类型,这时需要强制类型转换来告诉lldb。

(lldb) p [[$array objectAtIndex:0] characterAtIndex:0]
error: no known method '-characterAtIndex:'; cast the message send to the method's return type
error: 1 errors parsing expression

(lldb) p (char)[[$array objectAtIndex:0] characterAtIndex:0]
'o'

修改已有变量

image命令

image 命令可用于寻址,有多个组合命令。比较实用的用法是用于寻找栈地址对应的代码位置。 下面我写了一段代码

NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil];
NSLog(@"%@",arr[2]);

这段代码有明显的错误,程序运行这段代码后会抛出下面的异常:

 *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
*** First throw call stack:
(
 0   CoreFoundation                      0x0000000101951495 __exceptionPreprocess + 165
 1   libobjc.A.dylib                     0x00000001016b099e objc_exception_throw + 43
2   CoreFoundation                      0x0000000101909e3f -[__NSArrayI objectAtIndex:] + 175
3   ControlStyleDemo                    0x0000000100004af8 -[RootViewController viewDidLoad] + 312
4   UIKit                               0x000000010035359e -[UIViewController loadViewIfRequired] + 562
5   UIKit                               0x0000000100353777 -[UIViewController view] + 29
6   UIKit                               0x000000010029396b -[UIWindow addRootViewControllerViewIfPossible] + 58
7   UIKit                               0x0000000100293c70 -[UIWindow _setHidden:forced:] + 282
8   UIKit                               0x000000010029cffa -[UIWindow makeKeyAndVisible] + 51
9   ControlStyleDemo                    0x00000001000045e0 -[AppDelegate application:didFinishLaunchingWithOptions:] + 672
10  UIKit                               0x00000001002583d9 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 264
11  UIKit                               0x0000000100258be1 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1605
12  UIKit                               0x000000010025ca0c -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 660
13  UIKit                               0x000000010026dd4c -[UIApplication handleEvent:withNewEvent:] + 3189
14  UIKit                               0x000000010026e216 -[UIApplication sendEvent:] + 79
15  UIKit                               0x000000010025e086 _UIApplicationHandleEvent + 578
16  GraphicsServices                    0x0000000103aca71a _PurpleEventCallback + 762
17  GraphicsServices                    0x0000000103aca1e1 PurpleEventCallback + 35
18  CoreFoundation                      0x00000001018d3679 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 41
19  CoreFoundation                      0x00000001018d344e __CFRunLoopDoSource1 + 478
20  CoreFoundation                      0x00000001018fc903 __CFRunLoopRun + 1939
21  CoreFoundation                      0x00000001018fbd83 CFRunLoopRunSpecific + 467
22  UIKit                               0x000000010025c2e1 -[UIApplication _run] + 609
23  UIKit                               0x000000010025de33 UIApplicationMain + 1010
24  ControlStyleDemo                    0x0000000100006b73 main + 115
25  libdyld.dylib                       0x0000000101fe95fd start + 1
26  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

现在,我们怀疑出错的地址是0x0000000100004af8(可以根据执行文件名判断,或者最小的栈地址)。为了进一步精确定位,我们可以输入以下的命令:

(lldb)image lookup --address 0x0000000100004af8
(lldb)im loo -a 0x0000000100004af8

命令执行后返回:

Address: ControlStyleDemo[0x0000000100004af8] (ControlStyleDemo.__TEXT.__text + 13288)
Summary: ControlStyleDemo`-[RootViewController viewDidLoad] + 312 at RootViewController.m:53

我们可以看到,出错的位置是RootViewController.m的第53行。

查看可调试的平台

  1. (lldb) platform list
  2. Available platforms:
  3. host: Local Mac OS X user platform plug-in.
  4. remote-freebsd: Remote FreeBSD user platform plug-in.
  5. remote-linux: Remote Linux user platform plug-in.
  6. remote-netbsd: Remote NetBSD user platform plug-in.
  7. remote-openbsd: Remote OpenBSD user platform plug-in.
  8. remote-windows: Remote Windows user platform plug-in.
  9. remote-android: Remote Android user platform plug-in.
  10. remote-ios: Remote iOS platform plug-in.
  11. remote-macosx: Remote Mac OS X user platform plug-in.
  12. ios-simulator: iOS simulator platform plug-in.
  13. darwin-kernel: Darwin Kernel platform plug-in.
  14. tvos-simulator: Apple TV simulator platform plug-in.
  15. watchos-simulator: Apple Watch simulator platform plug-in.
  16. remote-tvos: Remote Apple TV platform plug-in.
  17. remote-watchos: Remote Apple Watch platform plug-in.
  18. remote-bridgeos: Remote BridgeOS platform plug-in.
  19. remote-gdb-server: A platform that uses the GDB remote protocol as the communication transport.

Chisel-LLDB命令插件

github地址:Chisel
chisel的使用

安装

  1. brew update
  2. brew install chisel

创建~/.lldbinit文件,写入一下内容。

  1. # ~/.lldbinit
  2. command script import /path/to/fblldb.py

其中/path/to/fblldb.py为自己的路径,我安装Chisel后路径为/usr/local/opt/chisel/libexec/fblldb.py


编译生成C程序

  1. #include<stdio.h>
  2. int main(){
  3. int a=4;
  4. int b=6;
  5. int array[3];
  6. array[0]=1;
  7. array[1]=10;
  8. array[2]=100;
  9. int *p;
  10. p=&a;
  11. int i=0;
  12. while(i<6){
  13. printf("*p=%d\n",*p);
  14. p++;
  15. i++;
  16. }
  17. return 0;
  18. }

编译生成可执行文件:

  1. gcc main.c -g -o main

告诉 LLDB 要调试哪个程序:

  1. lldb main

运行main

run

执行lldb main后LLDB 已经在监听 main 程序了,最基础的命令是 run,这条指令会开始运行 test 程序。终端会输出:

  1. (lldb) run
  2. Process 58419 launched: '/Users/dasheng/Desktop/lldb/main' (x86_64)
  3. *p=4
  4. *p=0
  5. *p=1
  6. *p=10
  7. *p=100
  8. *p=-19464151
  9. Process 58419 exited with status = 0 (0x00000000)

第一行标示了进程的 ID,后面的是 main 程序的输出。最后的是程序的返回值,在这里 0 则为正常结束。

看代码

看其他文件的代码:

如果你的这个程序编译的时候是由很多文件组成的,那么就可以使用list 文件名看其他文件的代码, 以后再执行list 3的时候,看的就是你前面设置的文件名的第三行(l 3就是从第3行开始往下看10行)

  1. (lldb) list main.c
  2. 1 #include<stdio.h>
  3. 2 int main(){
  4. 3 int a=4;
  5. 4 int b=6;
  6. 5 int array[3];
  7. 6 array[0]=1;
  8. 7 array[1]=10;
  9. 8 array[2]=100;
  10. 9 int *p;
  11. 10 p=&a;
  12. 11 int i=0;
  13. (lldb) l 3
  14. 3 int a=4;
  15. 4 int b=6;
  16. 5 int array[3];
  17. 6 array[0]=1;
  18. 7 array[1]=10;
  19. 8 array[2]=100;
  20. 9 int *p;
  21. 10 p=&a;
  22. 11 int i=0;
  23. 12 while(i<6){

看某个函数的代码

直接输入函数名字,下面就会输出函数所在的文件和函数

  1. (lldb) list main
  2. File: /Users/dasheng/Desktop/lldb/main.c
  3. 1 #include<stdio.h>
  4. 2 int main(){
  5. 3 int a=4;
  6. 4 int b=6;
  7. 5 int array[3];
  8. 6 array[0]=1;
  9. 7 array[1]=10;
  10. 8 array[2]=100;
  11. 9 int *p;
  12. 10 p=&a;
  13. 11 int i=0;

下断点

根据文件名和行号下断点

  1. (lldb) breakpoint set --file main.c --line 3
  2. Breakpoint 2: where = main`main + 29 at main.c:3:9, address = 0x0000000100003eed

根据函数名下断点

  1. # C函数
  2. (lldb) breakpoint set --name main
  3. # C++类方法
  4. (lldb) breakpoint set --method foo
  5. # Objective-C选择器
  6. (lldb) breakpoint set --selector alignLeftEdges:
  7. # swift
  8. (lldb) breakpoint set --func-regex applicationWillEnterForeground

查看断点列表、启用/禁用断点、删除断点

  1. //查看断点列表
  2. (lldb) breakpoint list
  3. # 禁用断点
  4. (lldb) breakpoint disable 4
  5. # 启用断点
  6. (lldb) breakpoint enable 4
  7. # 删除断点
  8. (lldb) breakpoint delete 4

运行环境操作

启动

前面已经下好断点了,现在就要启动这个程序了!

  1. # run命令就是启动程序
  2. (lldb) run
  3. Process 59843 launched: '/Users/dasheng/Desktop/lldb/main' (x86_64)
  4. Process 59843 stopped
  5. * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 2.1 3.1
  6. frame #0: 0x0000000100003eed main`main at main.c:3:9
  7. 1 #include<stdio.h>
  8. 2 int main(){
  9. -> 3 int a=4;
  10. 4 int b=6;
  11. 5 int array[3];
  12. 6 array[0]=1;
  13. 7 array[1]=10;
  14. Target 0: (main) stopped.

下一步、步入、步出、继续执行

  1. # 下一步 (next 或 n)
  2. (lldb) next
  3. Process 59843 stopped
  4. * thread #1, queue = 'com.apple.main-thread', stop reason = step over
  5. frame #0: 0x0000000100003ef4 main`main at main.c:4:9
  6. 1 #include<stdio.h>
  7. 2 int main(){
  8. 3 int a=4;
  9. -> 4 int b=6;
  10. 5 int array[3];
  11. 6 array[0]=1;
  12. 7 array[1]=10;
  13. Target 0: (main) stopped.
  14. #----------------------------------------------------------------------
  15. # 步入(step 或 s)
  16. (lldb) step
  17. # 步出(finish)
  18. (lldb) finish
  19. # 继续执行到下一个断点停, 后面没有断点的话就跑完了(continue 或 c)
  20. (lldb) continue
  21. Process 59843 resuming
  22. *p=4
  23. *p=0
  24. *p=1
  25. *p=10
  26. *p=100
  27. *p=-1922105239
  28. Process 59843 exited with status = 0 (0x00000000)

查看变量、跳帧查看变量

  1. # 使用po或p,po一般用来输出指针指向的那个对象,p一般用来输出基础变量。普通数组两者都可用
  2. Process 60184 stopped
  3. * thread #1, queue = 'com.apple.main-thread', stop reason = step over
  4. frame #0: 0x0000000100003ef4 main`main at main.c:4:9
  5. 1 #include<stdio.h>
  6. 2 int main(){
  7. 3 int a=4;
  8. -> 4 int b=6;
  9. 5 int array[3];
  10. 6 array[0]=1;
  11. 7 array[1]=10;
  12. Target 0: (main) stopped.
  13. (lldb) p a
  14. (int) $1 = 4
  15. #----------------------------------------------------------------------
  16. # 查看所有帧(bt)
  17. (lldb) bt
  18. * thread #1, queue = 'com.apple.main-thread', stop reason = step over
  19. * frame #0: 0x0000000100003ef4 main`main at main.c:4:9
  20. frame #1: 0x00007fff2036f621 libdyld.dylib`start + 1
  21. #----------------------------------------------------------------------
  22. # 查看当前帧中所有变量的值(frame variable)
  23. (lldb) frame variable
  24. (int) a = 4
  25. (int) b = 6
  26. (int [3]) array = ([0] = 0, [1] = -272634592, [2] = 32766)
  27. (int *) p = 0x0000000000000000
  28. (int) i = 0

常见问题

参考

LLDB使用篇
LLDB调试命令初探
LLDB 十分钟快速教程
使用LLDB调试程序

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注