美文网首页
Block循环引用调试方案

Block循环引用调试方案

作者: 哦小小树 | 来源:发表于2020-04-25 21:10 被阅读0次

当我们调试Block时,有时会招致不知道这个Block是在哪里被调用,找起来相当麻烦,但是通过LLDB可能方便很多。

0x01 准备条件

self.myBlock = ^{
        // 此处会造成循环引用,导致此对象无法被释放
        self.name = @"测试";      
        NSLog(@"测试block");
};

0x02. 调试视图

a. 我们在Debug Memory Graph可以看到以下视图
block查询泄露.png
b. 操作以下区域
block引用图.jpg
c. 查询调用block的函数信息
找到block地址信息.jpg

步骤解释:

  1. 找到Block的调用地址addr后,找到block对象内部函数指针的地址:addr+16
  2. 反汇编函数指针地址信息,得到调用block的函数位置

0x03 问题分析

LLDB命令解释:

# dis s
dis -s <address-expression> ( --start-address <address-expression> )
            Address at which to start disassembling.
开始反汇编的起始地址
0x01 为什么block入口地址要加16才是函数指针地址
  • 写一段简单代码
void (^myBlock)(void) = ^{
        NSLog(@"this is a test file");
};
        
myBlock();
  • 将其转换为汇编后为:
使用命令:clang -rewrite-objc main.m -o main.mm
 /*
 __main_block_impl_0: block类型
 __main_block_func_0: 函数指针
 __main_block_desc_0_DATA: 参数
 */
 void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
 
 
/*
调用:通过block找到函数指针,再入参调用
(myBlock->FuncPtr)(myBlock);
*/
  ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

block类型摘要:

struct __main_block_impl_0 {            // 首地址假设为addr
  // struct __block_impl impl;  这个类型就是下面的结构体,可以直接替换展开出来
  struct __block_impl {
  void *isa;        // 对象的标识   首地址:addr
  int Flags;        // 标志信息         首地址:addr + 8
  int Reserved; // 保留字段         首地址:addr + 8 + 4
  void *FuncPtr;// 函数指针         首地址:addr + 8 + 4 + 4
};
....
};

由上面示例代码可以看到block首地址偏移16字节后就是函数指针首地址

0x02 反汇编函数指针内容的两种方式
  1. 找到内容直接反汇编
# x/g addr + 16
(lldb) x/g 0x6000017feca0+16
0x6000017fecb0: 0x000000010e4d80b0

(lldb) dis -s 0x000000010e4d80b0
CrashCollectDemo`__32-[TmpViewController viewDidLoad]_block_invoke:
    0x10e4d80b0 <+0>:  pushq  %rbp
    0x10e4d80b1 <+1>:  movq   %rsp, %rbp
    0x10e4d80b4 <+4>:  subq   $0x10, %rsp
    0x10e4d80b8 <+8>:  leaq   0x4159(%rip), %rax        ; @
    0x10e4d80bf <+15>: movq   %rdi, -0x8(%rbp)
    0x10e4d80c3 <+19>: movq   %rdi, %rcx
    0x10e4d80c6 <+22>: movq   %rcx, -0x10(%rbp)
    0x10e4d80ca <+26>: movq   0x20(%rdi), %rcx

  1. 直接强转获取指针内容
dis -s *(void**)(addr+16)

# 由于addr+16 是函数指针的地址,而我们需要的是函数指针中的内容
# 通过*(void**)(addr+16) 可以获取到指针中的内容, 注意void**没有空格

相关文章

网友评论

      本文标题:Block循环引用调试方案

      本文链接:https://www.haomeiwen.com/subject/jcllwhtx.html