简介

QQLeakChecker是OOMDetector中用于检测内存走漏的库,现在只可检测真机运行时的内存走漏,模拟器暂不支撑

为了避免内存拜访冲突,扫描进程需要挂起一切线程,整个进程会卡住程序1-2秒。因为扫描进程较为耗时,占用的内存较大,建议在App的测验阶段使用,与自动化测验结合可快速高效的发现走漏问题

APM - iOS 内存泄漏监控 QQLeakChecker代码解析

在IOS系统中,或许包括指针变量的内存区域有堆内存、栈内存、大局数据区和寄存器,OOMDetector 经过对这些区域遍历扫描即可找到一切或许的“指针变量”,整个扫描流程完毕后都没有“指针变量”指向的内存块即是走漏内存块。

OOMDetector能够记录到每一个目标的分配堆栈信息,要从这些目标中找出 “走漏目标”,我们需要知道在程序可拜访的进程内存空间中,是否有“指针变量”指向对应的内存块。那些在整个进程内存空间都没有指针指向的内存块,便是我们要找的走漏内存块。

作业流程

UML类图

APM - iOS 内存泄漏监控 QQLeakChecker代码解析

调用

- (QQLeakChecker *)currentLeakChecker
{
    return _leakChecker;
}
- (void)executeLeakCheck:(QQLeakCheckCallback)callback
{
    [[self currentLeakChecker] executeLeakCheck:callback];
}

初始化

  • 创建了CStackChecker
  • 创建了CSegmentChecker
  • 创建了CHeapChecker
  • 创建了CRegisterChecker
-(id)init{
    if(self = [super init]){
        _stackChecker = new CStackChecker(global_leakChecker);
        _segmentChecker = new CSegmentChecker(global_leakChecker);
        _heapChecker = new CHeapChecker(global_leakChecker);
        _registerChecker = new CRegisterChecker(global_leakChecker);
        _max_stack_depth = 10;
        _needSysStack = YES;
    }
    return self;
}

发动

-(void)executeLeakCheck:(QQLeakCheckCallback)callback{
    if(!_isChecking && _isStackLogging){
        _segmentChecker->initAllSegments();
        global_leakChecker->leakCheckingWillStart();
        if(_stackChecker->suspendAllChildThreads()){
            global_leakChecker->unlockHashmap();
            _registerChecker->startPtrCheck();
            _stackChecker->startPtrCheck(2);
            _segmentChecker->startPtrcheck();
            _heapChecker->startPtrCheck();
            size_t total_size = 0;
            NSString *stackData = global_leakChecker->get_all_leak_stack(&total_size);
            _stackChecker->resumeAllChildThreads();
            _segmentChecker->removeAllSegments();
            global_leakChecker->leakCheckingWillFinish();
            callback(stackData,total_size);
        }
    }
}

获取对应的Segments

  • 获取image_header

  • 遍历load commands,获取__DATA段的segment

  • 获取section,判别section的称号

  • 获取__data段,__common段,__bss段的segment存起来

Data段结构

#define SEG_DATA        "__DATA"         /* the tradition UNIX data segment */
#define SECT_DATA        "__data"        /* the real initialized data section */
                                         /* no padding, no bss overlap */
#define SECT_BSS        "__bss"          /* the real uninitialized data section*/
                                         /* no padding */
#define SECT_COMMON        "__common"    /* the section common symbols are */
                                         /* allocated in by the link editor */
void CSegmentChecker::initAllSegments()
{
    uint32_t count = _dyld_image_count();
    for (uint32_t i = 0; i < count; i++) {
        const mach_header_t* header = (const mach_header_t*)_dyld_get_image_header(i);
        const char* image_name = _dyld_get_image_name(i);
        const char* tmp = strrchr(image_name, '/');
        vm_address_t slide = _dyld_get_image_vmaddr_slide(i);
        if (tmp) {
            image_name = tmp + 1;
        }
        long offset = (long)header + sizeof(mach_header_t);
        for (unsigned int i = 0; i < header->ncmds; i++)
        {
            const segment_command_t* segment = (const segment_command_t*)offset;
            if (segment->cmd == MY_SEGMENT_CMD_TYPE && strncmp(segment->segname,"__DATA",6) == 0) {
                section_t *section = (section_t *)((char*)segment + sizeof(segment_command_t));
                for(uint32_t j = 0; j < segment->nsects;j++){
                    if((strncmp(section->sectname,"__data",6) == 0) || (strncmp(section->sectname,"__common",8) == 0) || (strncmp(section->sectname,"__bss",5) == 0)){
                        vm_address_t begin = (vm_address_t)section->addr + slide;
                        vm_size_t size = (vm_size_t)section->size;
                        dataSegment seg = {image_name,section->sectname,begin,size};
                        segments.push_back(seg);
                    }
                    section = (section_t *)((char *)section + sizeof(section_t));
                }
            }
            offset += segment->cmdsize;
        }
    }
}

开端检测

避免内存拜访冲突,首先挂起一切线程,之后开端检测

  • 获取task的一切线程
  • 遍历线程,排除掉当时操作线程
  • 调用thread_suspend
BOOL CStackChecker::suspendAllChildThreads(){
    kern_return_t ret = task_threads(mach_task_self(), &thread_list, &thread_count);
    if (ret != KERN_SUCCESS) {
        return false;
    }
    for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
        thread_t thread = thread_list[i];
        if (thread == mach_thread_self()) {
            continue;
        }
        if (KERN_SUCCESS != thread_suspend(thread)) {
            for (mach_msg_type_number_t j = 0; j < i; j++){
                thread_t pre_thread = thread_list[j];
                if (pre_thread == mach_thread_self()) {
                    continue;
                }
                thread_resume(pre_thread);
            }
            for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
                mach_port_deallocate(mach_task_self(), thread_list[i]);
            }
            vm_deallocate(mach_task_self(), (vm_address_t)thread_list, thread_count * sizeof(thread_t));
            return false;
        }
    }
    return YES;
}

寄存器

_registerChecker->startPtrCheck();

  • 获取一切线程
  • 遍历非本线程的一切线程
  • 获取MY_EXCEPITON_STATE(不同设备不同,宏包裹),排除掉因为软中断,硬中断导致的反常
  • 获取MY_THREAD_STATE
  • 遍历通用寄存器,arm64有29个,arm有13个
bool CRegisterChecker::startPtrCheck(){
#if !TARGET_IPHONE_SIMULATOR
    thread_act_array_t thread_list;
    mach_msg_type_number_t thread_count;
    kern_return_t ret = task_threads(mach_task_self(), &thread_list, &thread_count);
    if (ret != KERN_SUCCESS) {
        return false;
    }
    for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
        thread_t thread = thread_list[i];
        if(thread != mach_thread_self()){
            _STRUCT_MCONTEXT _mcontext;
            mach_msg_type_number_t stateCount = MY_EXCEPTION_STATE_COUNT;
            kern_return_t ret = thread_get_state(thread, MY_EXCEPITON_STATE, (thread_state_t)&_mcontext.__es, &stateCount);
            if (ret != KERN_SUCCESS) {
                return false;
            }
            if (_mcontext.__es.__exception != 0) {
                return false;
            }
            stateCount = MY_THREAD_STATE_COUTE;
            ret = thread_get_state(thread, MY_THREAD_STATE, (thread_state_t)&_mcontext.__ss, &stateCount);
            if (ret != KERN_SUCCESS) {
                return false;
            }
#ifdef __LP64__
            vm_address_t x_regs[29];
            vm_size_t len = sizeof(x_regs);
            ret = vm_read_overwrite(mach_task_self(), (vm_address_t)(_mcontext.__ss.__x),len, (vm_address_t)x_regs, &len);
            for(int i = 0;i < 29;i++){
               leakChecker->findPtrInMemoryRegion((vm_address_t)x_regs[i]);
            }
#else
            vm_address_t r_regs[13];
            vm_size_t len = sizeof(r_regs);
            ret = vm_read_overwrite(mach_task_self(), (vm_address_t)(_mcontext.__ss.__r),len, (vm_address_t)r_regs, &len);
            for(int i = 0;i < 13;i++){
                leakChecker->findPtrInMemoryRegion((vm_address_t)r_regs[i]);
            }
#endif
        }
    }
    for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
        mach_port_deallocate(mach_task_self(), thread_list[i]);
    }
    vm_deallocate(mach_task_self(), (vm_address_t)thread_list, thread_count * sizeof(thread_t));
    return true;
#else
    return true;
#endif
}

在内存区域中寻找指针

bool CLeakChecker::findPtrInMemoryRegion(vm_address_t address){
    ptr_log_t *ptr_log = qleak_ptrs_hashmap->lookupPtr(address);
    if(ptr_log != NULL){
        ptr_log->size++;
        return true;
    }
    return false;
}
ptr_log_t *CPtrsHashmap::lookupPtr(vm_address_t addr)
{
    size_t offset = addr%(entry_num - 1);
    base_entry_t *entry = hashmap_entry + offset;
    ptr_log_t *parent = (ptr_log_t *)entry->root;
    if(parent != NULL){
        if(parent->address == addr){
            return parent;
        }
        ptr_log_t *current = parent->next;
        while(current != NULL){
            if(current->address == addr){
                return current;
            }
            parent = current;
            current = current->next;
        }
    }
    return NULL;
}

栈内存

_stackChecker->startPtrCheck(2);

  • 获取一切线程
  • 遍历线程,pthread需要从mach_thread转换过来
  • 如果是当时线程,找对应的frame pointer
  • 如果是其他线程,找对应的stack pointer
  • 计算得到对应的vmrange
void CStackChecker::startPtrCheck(size_t bt){
    thread_act_array_t thread_list;
    mach_msg_type_number_t thread_count;
    kern_return_t ret = task_threads(mach_task_self(), &thread_list, &thread_count);
    if (ret != KERN_SUCCESS) {
        return;
    }
    for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
        thread_t thread = thread_list[i];
        pthread_t pthread = pthread_from_mach_thread_np(thread);
        vm_size_t stacksize = pthread_get_stacksize_np(pthread);
        if(stacksize > 0){
            void *stack = pthread_get_stackaddr_np(pthread);
            if(stack != NULL){
                vm_address_t stack_ptr;
                if(thread == mach_thread_self()){
                    find_thread_fp(thread, &stack_ptr, bt);
                }
                else{
                    find_thread_sp(thread, &stack_ptr);
                }
                vm_size_t depth = (vm_address_t)stack - stack_ptr + 1;
                if(depth > 0 && depth <= stacksize){
                    vm_range_t range = {stack_ptr,depth};
                    check_ptr_in_vmrange(range, STACK_TYPE);
                }
            }
        }
    }
    for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
        mach_port_deallocate(mach_task_self(), thread_list[i]);
    }
    vm_deallocate(mach_task_self(), (vm_address_t)thread_list, thread_count * sizeof(thread_t));
}

check_ptr_in_vmrange

void CMemoryChecker::check_ptr_in_vmrange(vm_range_t range,memory_type type)
{
    const uint32_t align_size = sizeof(void *);
    vm_address_t vm_addr = range.address;
    vm_size_t vm_size = range.size;
    vm_size_t end_addr = vm_addr + vm_size;
    if (align_size <= vm_size)
    {
        uint8_t *ptr_addr = (uint8_t *)vm_addr;
        for (uint64_t addr = vm_addr;
             addr < end_addr && ((end_addr - addr) >= align_size);
             addr += align_size, ptr_addr += align_size)
        {
            vm_address_t *dest_ptr = (vm_address_t *)ptr_addr;
            leakChecker->findPtrInMemoryRegion(*dest_ptr);
        }
    }
}

在内存区域中寻找指针

findPtrInMemoryRegion

大局内存区

_segmentChecker->startPtrcheck();

void CSegmentChecker::startPtrcheck()
{
    for(auto it = segments.begin();it != segments.end();it++){
        dataSegment seg = *it;
        vm_range_t range = {seg.beginAddr,seg.size};
        check_ptr_in_vmrange(range,SEGMENT_TYPE);
    }
}

check_ptr_in_vmrange

栈内存和大局内存的遍历,最后都是经过vmrange来检测

堆内存

_heapChecker->startPtrCheck();

  • 遍历MemoryZone
  • 遍历Zone中的指针
void CHeapChecker::startPtrCheck(){
    vm_address_t *zones = NULL;
    unsigned int zone_num;
    kern_return_t err = malloc_get_all_zones (mach_task_self(), memory_reader, &zones, &zone_num);
    if (KERN_SUCCESS == err)
    {
        for (int i = 0; i < zone_num; ++i)
        {
            if(zones[i] == (vm_address_t)(leakChecker->getMemoryZone())){
                continue;
            }
            enumerate_ptr_in_zone(this,(const malloc_zone_t *)zones[i],CHeapChecker::check_ptr_in_heap);
        }
    }
}

遍历一切使用中的malloc指针

void CHeapChecker::enumerate_ptr_in_zone (void *baton, const malloc_zone_t *zone,vm_range_recorder_t recorder)
{
    if (zone && zone->introspect && zone->introspect->enumerator)
        zone->introspect->enumerator (mach_task_self(),
                                      this,
                                      MALLOC_PTR_IN_USE_RANGE_TYPE,
                                      (vm_address_t)zone,
                                      memory_reader,
                                      recorder);
}

收拾上报

global_leakChecker->get_all_leak_stack(&total_size);

  • 遍历leaked_hashmap,获取hashmap_entry
  • 从entry->root得到ptr_log_t
  • 经过ptr_log_t中的digest,从qleak_stacks_hashmap中找到merge_stack
  • 在merge_stack中找到对应的image和adrress信息,记录下来
NSString* CLeakChecker::get_all_leak_stack(size_t *total_count)
{
    get_all_leak_ptrs();
    NSMutableString *stackData = [[[NSMutableString alloc] init] autorelease];
    size_t total = 0;
    for(size_t i = 0; i <leaked_hashmap->getEntryNum(); i++){
        base_entry_t *entry = leaked_hashmap->getHashmapEntry() + i;
        leaked_ptr_t *current = (leaked_ptr_t *)entry->root;
        while(current != NULL){
            merge_leaked_stack_t *merge_stack = qleak_stacks_hashmap->lookupStack(current->digest);
            if(merge_stack == NULL) {
                current = current->next;
                continue;
            }
            total += current->leak_count;
            [stackData appendString:@"********************************\n"];
            [stackData appendFormat:@"[**LeakCheck**] Leak addr:0x%lx name:%s leak num:%u, stack:\n",(long)current->address, merge_stack->extra.name, current->leak_count];
            for(size_t j = 0; j < merge_stack->depth; j++){
                vm_address_t addr = (vm_address_t)merge_stack->stack[j];
                segImageInfo segImage;
                if(stackHelper->getImageByAddr(addr, &segImage)){
                    [stackData appendFormat:@""%lu %s 0x%lx 0x%lx" ",j,(segImage.name != NULL) ? segImage.name : "unknown",segImage.loadAddr,(long)addr];
                }
            }
            [stackData appendString:@"\n"];
            current = current->next;
        }
    }
    [stackData insertString:[NSString stringWithFormat:@"QQLeakChecker find %lu leak object!!!\n",total] atIndex:0];
    *total_count = total;
    if(total > 0){
        uploadLeakData(stackData);
    }
    return stackData;
}

global_leakChecker->get_all_leak_ptrs();

  • 遍历qleak_ptrs_hashmap,获取hashmap_entry,得到ptr_log_t
  • 过ptr_log_t中的digest,从qleak_stacks_hashmap中找到merge_stack
  • 判别当时的ptr_log_t,添加在leaked_hashmap中,并从qleak_ptrs_hashmap中移除
void CLeakChecker::get_all_leak_ptrs()
{
    for(size_t i = 0; i < qleak_ptrs_hashmap->getEntryNum(); i++)
    {
        base_entry_t *entry = qleak_ptrs_hashmap->getHashmapEntry() + i;
        ptr_log_t *current = (ptr_log_t *)entry->root;
        while(current != NULL){
            merge_leaked_stack_t *merge_stack = qleak_stacks_hashmap->lookupStack(current->digest);
            if(merge_stack == NULL) {
                current = current->next;
                continue;
            }
            if(merge_stack->extra.name != NULL){
                if(current->size == 0){
                    leaked_hashmap->insertLeakPtrAndIncreaseCountIfExist(current->digest, current);
                    vm_address_t address = current->address;
                    qleak_ptrs_hashmap->removePtr(address,NULL,NULL);
                }
                current->size = 0;
            }
            else{
                vm_address_t address = current->address;
                const char* name = objcFilter->getObjectNameExceptBlack((void *)address);
                if(name != NULL){
                    if(current->size == 0){
                        merge_stack->extra.name = name;
                        leaked_hashmap->insertLeakPtrAndIncreaseCountIfExist(current->digest, current);
                        vm_address_t address = (vm_address_t)(0x100000000 | current->address);
                        qleak_ptrs_hashmap->removePtr(address,NULL,NULL);
                    }
                    current->size = 0;
                }
                else {
                    qleak_ptrs_hashmap->removePtr(current->address,NULL,NULL);
                }
            }
            current = current->next;
        }
    }
}

引用

【腾讯开源】iOS爆内存问题解决方案-OOMDetector组件