Xcode的动态调试

简述

  1. 什么叫动态调试?
    1. 􏰊􏰋􏰌􏰍􏰎􏰏􏰐􏰑􏰒􏰓􏰔􏰕􏰖􏰁􏰗􏰘􏰙􏰚􏰛􏰑􏰜􏰝􏰞􏰟􏰁􏰠􏰡􏰢􏰁􏰣􏰟􏰇􏰤􏰥􏰋􏰙􏰊􏰋􏰌􏰍􏰎􏰏􏰐􏰑􏰒􏰓􏰔􏰕􏰖􏰁􏰗􏰘􏰙􏰚􏰛􏰑􏰜􏰝􏰞􏰟􏰁􏰠􏰡􏰢􏰁􏰣􏰟􏰇􏰤􏰥􏰋􏰙􏰊􏰋􏰌􏰍􏰎􏰏􏰐􏰑􏰒􏰓􏰔􏰕􏰖􏰁􏰗􏰘􏰙􏰚􏰛􏰑􏰜􏰝􏰞􏰟􏰁􏰠􏰡􏰢􏰁􏰣􏰟􏰇􏰤􏰥􏰋􏰙将程序运行起来,通过下断点、打印等方式,查看参数、返回值、函数调用流程等。
  2. Xcode的动态调试原理
    1. 通常我们使用的打断点、调试窗口输入命令打印等这些功能都是LLDB这个调试器的功能。
    2. 如下关系图

      图1

      1. MAC方面
        1. MAC上装有Xcode,Xcode内部嵌入有LLDB调试器
      2. 设备方面
        1. 设备上自带的有debugserver,专门用于对应LLDB的调试
      3. 调试过程
        1. Xcode调试窗口通过输入LLDB指令,给设备的debugserver
        2. debugserver接受到这个指令后执行到相应的APP上
        3. APP执行调试指令然后将执行的反馈信息返回给debugserver
        4. debugserver将这个信息反馈给LLDB
        5. LLDB将这个返回的信息,打印出来
  3. GCC、LLVM、GDB、LLDB都是什么?
    1. GCC、LLVM都是编译器
      1. Xcode内置有编译器
      2. 打开项目-target->build setting ->搜索compiler(编译器)->能看见编译器就是LLVM
      3. 早期的Xcode(<4)内置的编译器是GCC,后来的就是LLVM
    2. GDB、LLDB都是调试器
      1. Xcode的调试器发展历程:GDB->LLDB
  4. debugserver
    1. 一开始存放在MAC的Xcode中
    2. /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/De viceSupport/9.1/DeveloperDiskImage.dmg/usr/bin/debugserver
    3. 当Xcode识别到手机设备时,Xcode会自动将debugserver安装到iPhone上,且仅仅在这个手机第一次调试时安装一次
      1. 位置: /Developer/usr/bin/debugserver
    4. Xcode调试的局限性
      1. 一般情况下,只能调试通过Xcode安装的APP
  5. 动态调试任意的APP(逆向破解学习
    1. 从上面我们知道Xcode调试必须是先运行真机设备,而且保持Xcode一直运行在真机,然后才能调试
    2. 但是如果我想调试任意的APP呢?
    3. 。。。。。

LLDB指令

  1. 指令格式:

     <command> [<subcommand> [<subcommand>...]] <action> [-options [option- value]] [argument [argument...]]
    
    1. 其中,[] 代表可有可无
    2. command: 命令
    3. subcommand: 子命令
    4. action:命令操作
    5. options: 命令选项
    6. argument : 命令参数
    7. 比如给test函数设置断点:

       breakpoint set -n test
      
  2. help
    1. 查看指令的用法
    2. 比如:help breakpoint􏰁help、 breakpoint set
  3. expression <cmd-options>--<expr>
    1. 执行一个表达式
    2. <cmd-options>:命令选项
    3. --: 命令选项结束符,表示所有的命令选项已设置完毕,如果没有命令选项,–可以省略
    4. <expr>:需要执行的表达式
    5. 举例:

       //在某个控制器代码处打个断点,然后在lldb输入一下命令,回车
       expression self.view.backgroundColor = [UIColor redColor]
       //然后继续点击箭头,放开断点,就会发现项目执行了这句代码。
      
    6. expression、expression--和指令print、p、call的效果一样
    7. expression-O--和指令po的效果一样。
    8. 因此,综上所述,通常用P/po就可以了
      1. P 通常跟表达式
      2. po 通常跟对象
  4. thread backtrace
    1. 打印线程的堆栈信息(其实就是函数调用栈)
    2. 和指令Bt效果一样
      1. 在某个函数中打个断点,然后输入改指令,回车
      2. 会出现一堆frame开头的东西
      3. 一个frame代表一个函数栈帧
      4. 可以看到,当前打断点的函数是由哪些函数一层一层调用过来的。
  5. thread return [返回的数据]
    1. 让函数直接返回某个值,不执行断点后面的代码
  6. frame variable [变量名]
    1. 打印当前栈帧的变量
    2. 不写变量名,打印出所有变量值
  7. 调试窗口上面的按钮对应的命令:
    1. thread continue、􏰁continue􏰁、c :程序继续运行
    2. thread step-over、􏰁next􏰁、n :单步运行,把子函数当做整体,一步执行。
    3. hread step-in􏰁、step􏰁、s:单步运行,遇到子函数会进入子函数。
    4. thread step-out􏰁、finish􏰲:􏲞􏲟􏱚􏰎􏲲􏰺􏲤􏰣􏰟􏰧􏱾􏲳􏲢􏲣􏰑􏰠􏰡􏰽􏱇􏰀􏱢􏰣􏰟直接执行完当前函数的所有代码,返回到上一个函数
  8. breakpoint set (只列出主要部分,其他的略)
    1. 设置断点
    2. breakpoint set -a 􏰣􏰟􏱬􏱭函数地址
    3. breakpoint set -n 􏰣􏰟􏱻函数名
      1. breakpoint set -n test
      2. breakpoint set -n touchesBegan:withEvent:
      3. breakpoint set -n "-[ViewController touchesBegan:withEvent:]"
    4. breakpoint set -r 正则表达式
    5. breakpoint set -s 动态库-n函数名
  9. 内存断点:
    1. 在内存数据发生改变的时候触发
    2. watchpoint set variable 变量
      1. watchpoint set variable self->age
    3. watchpoint set expression 地址
      1. watchpoint set expression &(self->_age)
    4. watchpoint list
    5. watchpoint disable 􏰀􏰁􏰂􏰃 断点编号
    6. watchpoint enable 􏰀􏰁􏰂􏰃 断点编号
    7. watchpoint delete 􏰀􏰁􏰂􏰃 断点编号
    8. watchpoint command add 􏰀􏰁􏰂􏰃 断点编号
    9. watchpoint command list 􏰀􏰁􏰂􏰃 断点编号
    10. watchpoint command delete 􏰀􏰁􏰂􏰃断点编号
  10. image lookup
    1. image:在iOS底层代表模块(Windows叫镜像)的意思
    2. Xcode代码中任意代码位置打一个断点,输入image list,就会打印出这个项目中加载的所有模块
    3. iOS程序如何跑起来的呢
      1. 通过一个叫dyld的程序,将Mach-O可执行文件加载到内存中
      2. 然后载入可执行程序所需要的动态库
      3. 这些所需要的动态库和当前的程序,我们称之为模块(image)
    4. image lookup -t 􏰬􏰭类型
      1. 比如:image lookup -t 􏰬􏰭NSInteger
      2. 查找某个类型的信息
      3. 上面的打印结果如下;

         Best match found in /Users/mac/Library/Developer/Xcode/DerivedData/Testt-bevkcsprdibrnydobcyztythopas/Build/Products/Debug-iphonesimulator/Testt.app/Testt:
         id = {0x00009026}, name = "NSInteger", byte-size = 8, decl = NSObjCRuntime.h:12, compiler_type = "typedef NSInteger"
              typedef 'NSInteger': id = {0x0000906e}, name = "long int", qualified = "long", byte-size = 8, compiler_type = "long"
        
      4. 从上面compiler_type = "long"可以看出,NSIngeter这个类型实际编译的时候是long类型
    5. image lookup -a 􏰪􏰫 地址
      1. 根据内存地址查找在模块中的位置
      2. 常用场合,在崩溃信息分析中比较常用
      3. 比如下面代码,造成的崩溃

         - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
         NSLog(@"==============");
         NSArray *array = @[@"1",@"3"];
         array[2];
         }
        
      4. 崩溃结果如下:

         2018-11-13 09:28:31.501097+0800 Testt[2649:46148] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndexedSubscript:]: index 2 beyond bounds [0 .. 1]'
         *** First throw call stack:
         (
         	0   CoreFoundation                      0x0000000103f251bb __exceptionPreprocess + 331
         	1   libobjc.A.dylib                     0x00000001034c3735 objc_exception_throw + 48
         	2   CoreFoundation                      0x0000000103e714ec _CFThrowFormattedException + 194
         	3   CoreFoundation                      0x0000000103fa7b00 +[__NSArrayI allocWithZone:] + 0
         	4   Testt                               0x0000000102ba6685 -[ViewController touchesBegan:withEvent:] + 213
         	5   UIKitCore                           0x0000000106e578e8 forwardTouchMethod + 353
         	6   UIKitCore                           0x0000000106e57776 -[UIResponder touchesBegan:withEvent:] + 49
         	7   UIKitCore                           0x0000000106e66dff -[UIWindow _sendTouchesForEvent:] + 2052
         	8   UIKitCore                           0x0000000106e687a0 -[UIWindow sendEvent:] + 4080
         	9   UIKitCore                           0x0000000106e46394 -[UIApplication sendEvent:] + 352
         	10  UIKitCore                           0x0000000106f1b5a9 __dispatchPreprocessedEventFromEventQueue + 3054
         	11  UIKitCore                           0x0000000106f1e1cb __handleEventQueueInternal + 5948
         	12  CoreFoundation                      0x0000000103e8a721 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
         	13  CoreFoundation                      0x0000000103e89f93 __CFRunLoopDoSources0 + 243
         	14  CoreFoundation                      0x0000000103e8463f __CFRunLoopRun + 1263
         	15  CoreFoundation                      0x0000000103e83e11 CFRunLoopRunSpecific + 625
         	16  GraphicsServices                    0x000000010c5bb1dd GSEventRunModal + 62
         	17  UIKitCore                           0x0000000106e2a81d UIApplicationMain + 140
         	18  Testt                               0x0000000102ba6760 main + 112
         	19  libdyld.dylib                       0x0000000105940575 start + 1
         	20  ???                                 0x0000000000000001 0x0 + 1
         )
        
      5. 从这个崩溃的函数调用栈,我们可以分析出,代码是崩溃在下面这一行

         4   Testt                               0x0000000102ba6685 -[ViewController touchesBegan:withEvent:] + 213
        
      6. 下面我跟就定位一下,在调试器中输入:image lookup -a 0x0000000102ba6685,回车,结果如下:

         Address: Testt[0x0000000100001685] (Testt.__TEXT.__text + 341)
         Summary: Testt`-[ViewController touchesBegan:withEvent:] + 213 at ViewController.m:38
        
      7. 从这个打印,我们可以看出,崩溃地方在ViewController.m的第38行
    6. image lookup -n 􏰷􏰃􏰸􏰹􏰺􏰣􏰻符号或者函数名
      1. 查找某个符号,或者函数的位置
      2. 比如:image lookup -n test
      3. 打印如下:

         1 match found in /Users/mac/Library/Developer/Xcode/DerivedData/Testt-bevkcsprdibrnydobcyztythopas/Build/Products/Debug-iphonesimulator/Testt.app/Testt:
         Address: Testt[0x0000000100001570] (Testt.__TEXT.__text + 64)
         Summary: Testt`test at ViewController.m:27
         .....这些是其他地方有这个函数的打印,省略......
        
        1. 从上面可以看出,在Testt模块中有一个test函数,在ViewController.m:27
  11. image list
    1. 列出所有加载的模块信息
    2. image list -o -f
      1. 打印出模块的偏移地址、全路径。
  12. 小技巧
    1. 点击enter,会自动执行上次的命令
    2. 绝大部分指令都可以使用缩写。
Table of Contents