iOS10的nano_free nano_relocated crash总结

来源:互联网 发布:年度工作总结 知乎 编辑:程序博客网 时间:2024/06/07 09:47

             在SDWebImage版本替换时遇见了让人捉狂的crash,旧版本为2011年左右的版本,新版本为4.0版本。替换之后,批量下载图片出现了非常大概率的crash,并且该crash在iOS8,iOS9中没有复现,iOS10.0~iOS10.1.1中大概率复现,iOS10.2中概率下降,但是也是非常容易复现出该问题。

             iOS10.1.1中,crash时报错:“freeing unallocated pointer”。iOS10.2中crash报错为:“reallocated pointer was not allocated”。开始的时候不明确是否是SDWebImage库的问题,堆栈中有nanon_free字样。后查询资料,看到微信团队关于该crash的分析文章与解决方案,终于算找到方向了(链接:https://mp.weixin.qq.com/s/hnwj24xqrtOhcjEt_TaQ9w)。下面就沿着该方案解决问题的过程,总结一下吧,虽然最终也没有算解决掉这个问题。

       按照该方案(方案1)敲出代码进行测试(代码附在后面),用定时器去跑该文章的测试代码,发现仍然crash。

       分析该文章,认为helper分区分配的内存空间,地址处于了nano分区的地址区段,释放时由nano分区释放,导致nano_free方法中,检查认为该内存空间没有在nano分区中分配过,因此报错“指针过度释放”。因此,尝试方案2,方案二替换helper分区的内存分配代码,当内存分配的地址处于nano分区的区段时,则释放该内存,重新申请内存,直到地址区段正确。测试该方案发现,当helper分区分配的内存空间地址不正确后,重新申请的地址空间也大概率不正确,导致分配空间性能严重下降。不可行~

     鉴于第二个方案不可行,因此尝试第三个方案。方案3为,替换nano分区的内存释放函数,替换后,当nano分区释放内存时,检查该指针是否属于helper分区分配,如果是,则把该内存的释放转至helper zone。测试该方案,该方案在iOS10.1.1中跑图片下载功能,crash率下降,下降至和iOS10.2的情况比较类似。根据该方案的crash提示报错分析,应该是iOS10中,不止存在helper分区分配了nano分区地址段的内存,也存在nano分区分配了非nano分区地址区段的内存的情况(感觉iOS10.2就是部分解决了该问题,只是没考虑后一种情况),因此按照这个思路,helper zone释放空间时也进行检测,可能能完全解决这个问题。但是由于每次释放空间之前都检查内存是否由另一个分区分配,会导致性能下降,另外也有项目进度原因,我便没有进行进一步尝试。而是选择了减少内存操作的方法来解决了该功能引起的crash。 附上测试的代码,以供参考。

        代码中若有不对之处,请指正,多谢。

        哪位看到有更好的解决办法的,麻烦告知一下,多谢。


//方案1:微信团队方案,使用guard zone//效果,只要地址错误一次之后就经常申请错误,导致会反复的申请释放空间malloc_zone_t *NanoCrashGuardInitialize3();static malloc_zone_t *s_default_zone = NULL;static malloc_zone_t *s_guard_zone = NanoCrashGuardInitialize3();typedef void *(*GuardMalloc)(struct _malloc_zone_t *zone, size_t size);typedef void (*GuardFree)(struct _malloc_zone_t *zone, void *ptr);typedef size_t (*GuardSize)(struct _malloc_zone_t *zone, const void *ptr);typedef void * (*GuardCalloc)(struct _malloc_zone_t *zone, size_t num_items, size_t size);typedef void * (*GuardValloc)(struct _malloc_zone_t *zone, size_t size);typedef void *(*GuardRealloc)(struct _malloc_zone_t *zone, void *ptr, size_t size);typedef void (*GuardBatchFree)(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed);typedef void *(*GuardMemalign)(struct _malloc_zone_t *zone, size_t alignment, size_t size);typedef void (*GuardFreeDefiniteSize)(struct _malloc_zone_t *zone, void *ptr, size_t size);typedef size_t (*GuardPressureRelief)(struct _malloc_zone_t *zone, size_t goal);typedef unsigned (*GuardBatchMalloc)(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested);GuardMalloc s_default_zone_origin_malloc = NULL;GuardFree s_default_zone_origin_free = NULL;GuardSize s_default_zone_origin_size = NULL;GuardCalloc s_default_zone_origin_calloc = NULL;GuardValloc s_default_zone_origin_valloc = NULL;GuardRealloc s_default_zone_origin_realloc = NULL;GuardBatchMalloc s_default_zone_origin_batch_malloc = NULL;GuardBatchFree s_default_zone_origin_batch_free = NULL;GuardMemalign s_default_zone_origin_memalign = NULL;GuardFreeDefiniteSize s_default_zone_origin_free_definite_size = NULL;GuardPressureRelief s_default_zone_origin_pressure_relief = NULL;void *default_zone_malloc(struct _malloc_zone_t *zone, size_t size){    return s_guard_zone->malloc(s_guard_zone, size);}void default_zone_free(struct _malloc_zone_t *zone, void *ptr){    size_t s = s_guard_zone->size(s_guard_zone, ptr);    if (s) {        return s_guard_zone->free(s_guard_zone, ptr);    }    return s_default_zone_origin_free(zone, ptr);}size_t default_zone_size(struct _malloc_zone_t *zone, const void *ptr){    size_t s = s_guard_zone->size(s_guard_zone, ptr);    if (s) {        return s;    }    return s_default_zone_origin_size(zone, ptr);}// same as malloc, but block returned is set to zero //void *default_zone_calloc(struct _malloc_zone_t *zone, size_t num_items, size_t size){    return s_guard_zone->calloc(s_guard_zone, num_items, size);}// same as malloc, but block returned is set to zero and is guaranteed to be page alignedvoid *default_zone_valloc(struct _malloc_zone_t *zone, size_t size){    return s_guard_zone->valloc(s_guard_zone, size);}void *default_zone_realloc(struct _malloc_zone_t *zone, void *ptr, size_t size){    size_t s = s_guard_zone->size(s_guard_zone, ptr);    if (s) {        return s_guard_zone->realloc(s_guard_zone, ptr, size);    }else{        return s_default_zone_origin_realloc(zone, ptr, size);    }}// Optional batch callbacks; these may be NULL// given a size, returns pointers capable of holding that size; returns the number of pointers allocated (maybe 0 or less than num_requested)unsigned default_zone_batch_malloc(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested){    return s_guard_zone->batch_malloc(s_guard_zone, size, results, num_requested);}// frees all the pointers in to_be_freed; note that to_be_freed may be overwritten during the processvoid default_zone_batch_free(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed){    size_t s = s_guard_zone->size(s_guard_zone, to_be_freed);    if (s) {        return s_guard_zone->batch_free(s_guard_zone, to_be_freed, num_to_be_freed);    }    return s_default_zone_origin_batch_free(zone, to_be_freed, num_to_be_freed);}// aligned memory allocation. The callback may be NULL. Present in version >= 5.void *default_zone_memalign(struct _malloc_zone_t *zone, size_t alignment, size_t size){    //guard zone一起执行    s_default_zone_origin_memalign(zone, alignment, size);    s_guard_zone->memalign(s_guard_zone, alignment, size);  //??返回值是否换这个    return NULL;}// free a pointer known to be in zone and known to have the given size. The callback may be NULL. Present in version >= 6.//void default_zone_free_definite_size(struct _malloc_zone_t *zone, void *ptr, size_t size){    size_t s = s_guard_zone->size(s_guard_zone, ptr);    if (s) {        return s_guard_zone->free_definite_size(s_guard_zone, ptr, size);    }    return s_default_zone_origin_free_definite_size(zone, ptr, size);}// Empty out caches in the face of memory pressure. The callback may be NULL. Present in version >= 8.size_t default_zone_pressure_relief(struct _malloc_zone_t *zone, size_t goal){    //guard zone一起执行    return s_guard_zone->pressure_relief(s_guard_zone, goal) + s_default_zone_origin_pressure_relief(zone, goal);}extern malloc_zone_t **malloc_zones; // 定义在malloc.c内extern int32_t malloc_num_zones; //定义在malloc.c内malloc_zone_t *NanoCrashGuardInitialize3(){    //    return NULL;        s_guard_zone = malloc_create_zone(getpagesize(), 0);    malloc_set_zone_name(s_guard_zone, "GuardZone");    s_default_zone = malloc_default_zone();    mprotect(s_default_zone, sizeof(malloc_zone_t), PROT_READ | PROT_WRITE);    OSMemoryBarrier();        //此时已有多线程,需用atomic    OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_malloc, (void *)s_default_zone->malloc, (void *volatile *) &s_default_zone_origin_malloc);    OSAtomicCompareAndSwapPtr((void *)s_default_zone->malloc, (void *)default_zone_malloc, (void *volatile *) &s_default_zone->malloc);    OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_free, (void *)s_default_zone->free, (void *volatile *) &s_default_zone_origin_free);    OSAtomicCompareAndSwapPtr((void *)s_default_zone->free, (void *)default_zone_free, (void *volatile *) &s_default_zone->free);    OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_size, (void *)s_default_zone->size, (void *volatile *) &s_default_zone_origin_size);    OSAtomicCompareAndSwapPtr((void *)s_default_zone->size, (void *)default_zone_size, (void *volatile *) &s_default_zone->size);        //...替换malloc_zone_t的其它函数//    OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_calloc, (void *)s_default_zone->calloc, (void *volatile *) &s_default_zone_origin_calloc);//    OSAtomicCompareAndSwapPtr((void *)default_zone_calloc, (void *)s_default_zone->calloc, (void *volatile *) &s_default_zone->calloc);//    OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_valloc, (void *)s_default_zone->valloc, (void *volatile *) &s_default_zone_origin_valloc);//    OSAtomicCompareAndSwapPtr((void *)default_zone_valloc, (void *)s_default_zone->valloc, (void *volatile *) &s_default_zone->valloc);//    OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_realloc, (void *)s_default_zone->realloc, (void *volatile *) &s_default_zone_origin_realloc);//    OSAtomicCompareAndSwapPtr((void *)default_zone_realloc, (void *)s_default_zone->realloc, (void *volatile *) &s_default_zone->realloc);//    OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_batch_malloc, (void *)s_default_zone->batch_malloc, (void *volatile *) &s_default_zone_origin_batch_malloc);//    OSAtomicCompareAndSwapPtr((void *)default_zone_batch_malloc, (void *)s_default_zone->batch_malloc, (void *volatile *) &s_default_zone->batch_malloc);//    OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_batch_free, (void *)s_default_zone->batch_free, (void *volatile *) &s_default_zone_origin_batch_free);//    OSAtomicCompareAndSwapPtr((void *)default_zone_batch_free, (void *)s_default_zone->batch_free, (void *volatile *) &s_default_zone->batch_free);//    OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_memalign, (void *)s_default_zone->memalign, (void *volatile *) &s_default_zone_origin_memalign);//    OSAtomicCompareAndSwapPtr((void *)default_zone_memalign, (void *)s_default_zone->memalign, (void *volatile *) &s_default_zone->memalign);//    OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_free_definite_size, (void *)s_default_zone->free_definite_size, (void *volatile *) &s_default_zone_origin_free_definite_size);//    OSAtomicCompareAndSwapPtr((void *)default_zone_free_definite_size, (void *)s_default_zone->free_definite_size, (void *volatile *) &s_default_zone->free_definite_size);//    OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_pressure_relief, (void *)s_default_zone->pressure_relief, (void *volatile *) &s_default_zone_origin_pressure_relief);//    OSAtomicCompareAndSwapPtr((void *)default_zone_pressure_relief, (void *)s_default_zone->pressure_relief, (void *volatile *) &s_default_zone->pressure_relief);            OSMemoryBarrier();    mprotect(s_default_zone, sizeof(malloc_zone_t), PROT_READ);    return s_guard_zone;}

//方案2,替换helperzone的所有分配空间方法,当分配到的空间错误到0x17地址,则重新申请,并释放错误空间//效果,只要地址错误一次之后就经常申请错误,导致会反复的申请释放空间malloc_zone_t *NanoCrashGuardInitialize1();static malloc_zone_t *s_helper_zone = NanoCrashGuardInitialize1();typedef void *(*GuardMalloc)(struct _malloc_zone_t *zone, size_t size);typedef void * (*GuardCalloc)(struct _malloc_zone_t *zone, size_t num_items, size_t size);typedef void * (*GuardValloc)(struct _malloc_zone_t *zone, size_t size);typedef void *(*GuardRealloc)(struct _malloc_zone_t *zone, void *ptr, size_t size);typedef unsigned (*GuardBatchMalloc)(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested);GuardMalloc s_helper_zone_origin_malloc = NULL;GuardCalloc s_helper_zone_origin_calloc = NULL;GuardValloc s_helper_zone_origin_valloc = NULL;GuardRealloc s_helper_zone_origin_realloc = NULL;GuardBatchMalloc s_helper_zone_origin_batch_malloc = NULL;void *helper_zone_malloc(struct _malloc_zone_t *zone, size_t size){    void *ptr = s_helper_zone_origin_malloc(zone, size);    while ((uintptr_t)ptr>>28 == 0x17 ) {        //地址申请出错,重新申请新的内存(先释放再申请的话,不确定是不是还是分配到原地址,所以先申请再释放)        void *againPtr = s_helper_zone_origin_malloc(zone, size);                //立即释放原内存        zone->free(zone, ptr);        ptr = againPtr;    }    return ptr;}void *helper_zone_calloc(struct _malloc_zone_t *zone, size_t num_items, size_t size){    void *ptr = s_helper_zone_origin_calloc(zone, num_items, size);    while ((uintptr_t)ptr>>28 == 0x17 ) {        //地址申请出错,重新申请新的内存(先释放再申请的话,不确定是不是还是分配到原地址,所以先申请再释放)        void *againPtr = s_helper_zone_origin_calloc(zone, num_items, size);                //立即释放原内存        zone->free(zone, ptr);        ptr = againPtr;    }    return ptr;}// same as malloc, but block returned is set to zero and is guaranteed to be page alignedvoid *helper_zone_valloc(struct _malloc_zone_t *zone, size_t size){    void *ptr = s_helper_zone_origin_valloc(zone, size);    while ((uintptr_t)ptr>>28 == 0x17 ) {        //地址申请出错,重新申请新的内存(先释放再申请的话,不确定是不是还是分配到原地址,所以先申请再释放)        void *againPtr = s_helper_zone_origin_valloc(zone, size);                //立即释放原内存        zone->free(zone, ptr);        ptr = againPtr;    }    return ptr;}void *helper_zone_realloc(struct _malloc_zone_t *zone, void *ptr, size_t size){    void *reallocPtr = s_helper_zone_origin_realloc(zone, ptr, size);    while ((uintptr_t)reallocPtr>>28 == 0x17 ) {        //地址申请出错,重新申请新的内存        reallocPtr = s_helper_zone_origin_realloc(zone, reallocPtr, size);    }    return reallocPtr;}// Optional batch callbacks; these may be NULL // given a size, returns pointers capable of holding that size; returns the number of pointers allocated (maybe 0 or less than num_requested)unsigned helper_zone_batch_malloc(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested){    unsigned number = s_helper_zone_origin_batch_malloc(zone, size, results, num_requested);    uintptr_t ptr = (uintptr_t)*results;    while (ptr>>28 == 0x17 ) {        //地址申请出错,重新申请新的内存        void *wrongResults = (void *)*results;        unsigned againNumber = ptr = s_helper_zone_origin_batch_malloc(zone, size, results, num_requested);        ptr = (uintptr_t)*results;                //立即释放原内存        zone->batch_free(zone, &wrongResults, number);        number = againNumber;    }    return number;}extern malloc_zone_t **malloc_zones; // 定义在malloc.c内extern int32_t malloc_num_zones; //定义在malloc.c内malloc_zone_t *NanoCrashGuardInitialize1(){    //    return NULL;        s_helper_zone = malloc_zones[1];    mprotect(s_helper_zone, sizeof(malloc_zone_t), PROT_READ | PROT_WRITE);    OSMemoryBarrier();        //此时已有多线程,需用atomic    OSAtomicCompareAndSwapPtr((void *)s_helper_zone_origin_malloc, (void *)s_helper_zone->malloc, (void *volatile *) &s_helper_zone_origin_malloc);    OSAtomicCompareAndSwapPtr((void *)s_helper_zone->malloc, (void *)helper_zone_malloc, (void *volatile *) &s_helper_zone->malloc);    OSAtomicCompareAndSwapPtr((void *)s_helper_zone_origin_calloc, (void *)s_helper_zone->calloc, (void *volatile *) &s_helper_zone_origin_calloc);    OSAtomicCompareAndSwapPtr((void *)s_helper_zone->calloc, (void *)helper_zone_calloc, (void *volatile *) &s_helper_zone->calloc);    OSAtomicCompareAndSwapPtr((void *)s_helper_zone_origin_valloc, (void *)s_helper_zone->valloc, (void *volatile *) &s_helper_zone_origin_valloc);    OSAtomicCompareAndSwapPtr((void *)s_helper_zone->valloc, (void *)helper_zone_valloc, (void *volatile *) &s_helper_zone->valloc);    OSAtomicCompareAndSwapPtr((void *)s_helper_zone_origin_realloc, (void *)s_helper_zone->realloc, (void *volatile *) &s_helper_zone_origin_realloc);    OSAtomicCompareAndSwapPtr((void *)s_helper_zone->realloc, (void *)helper_zone_realloc, (void *volatile *) &s_helper_zone->realloc);    OSAtomicCompareAndSwapPtr((void *)s_helper_zone_origin_batch_malloc, (void *)s_helper_zone->batch_malloc, (void *volatile *) &s_helper_zone_origin_batch_malloc);    OSAtomicCompareAndSwapPtr((void *)s_helper_zone->batch_malloc, (void *)helper_zone_batch_malloc, (void *volatile *) &s_helper_zone->batch_malloc);        OSMemoryBarrier();    mprotect(s_helper_zone, sizeof(malloc_zone_t), PROT_READ);    return s_helper_zone;}

/*//方案3,替换nanozone的所有释放空间方法,每次释放空间前都先检查是否属于helper分区分配的空间,若属于,则转到helper zone释放//效果,目前是最好的效果,在iOS10.1.1测试#include <malloc/malloc.h>#include <unistd.h>#include <sys/mman.h>#include <stdatomic.h>#include <libkern/OSAtomicDeprecated.h>malloc_zone_t *NanoCrashGuardInitialize2();static malloc_zone_t *s_default_zone = NanoCrashGuardInitialize2();static malloc_zone_t *s_helper_zone = NULL;typedef void (*GuardFree)(struct _malloc_zone_t *zone, void *ptr);typedef void *(*GuardRealloc)(struct _malloc_zone_t *zone, void *ptr, size_t size);typedef void (*GuardBatchFree)(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed);typedef void (*GuardFreeDefiniteSize)(struct _malloc_zone_t *zone, void *ptr, size_t size);GuardFree s_default_zone_origin_free = NULL;GuardRealloc s_default_zone_origin_realloc = NULL;GuardBatchFree s_default_zone_origin_batch_free = NULL;GuardFreeDefiniteSize s_default_zone_origin_free_definite_size = NULL;#pragma mark default zone methods++++++++++++++++void default_zone_free(struct _malloc_zone_t *zone, void *ptr){    size_t s = s_helper_zone->size(s_helper_zone, ptr);    if (s) {        return s_helper_zone->free(s_helper_zone, ptr);    }    return s_default_zone_origin_free(zone, ptr);}void *default_zone_realloc(struct _malloc_zone_t *zone, void *ptr, size_t size){    size_t s = s_helper_zone->size(s_helper_zone, ptr);    if (s) {        s_helper_zone->realloc(s_helper_zone, ptr, size);    }    return s_default_zone_origin_realloc(zone, ptr, size);}// frees all the pointers in to_be_freed; note that to_be_freed may be overwritten during the processvoid default_zone_batch_free(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed){    size_t s = s_helper_zone->size(s_helper_zone, to_be_freed);    if (s) {        return s_helper_zone->batch_free(s_helper_zone, to_be_freed, num_to_be_freed);    }    return s_default_zone_origin_batch_free(zone, to_be_freed, num_to_be_freed);}// free a pointer known to be in zone and known to have the given size. The callback may be NULL. Present in version >= 6.void default_zone_free_definite_size(struct _malloc_zone_t *zone, void *ptr, size_t size){    size_t s = s_helper_zone->size(s_helper_zone, ptr);    if (s) {        return s_helper_zone->free_definite_size(s_helper_zone, ptr, size);    }    return s_default_zone_origin_free_definite_size(zone, ptr, size);}extern malloc_zone_t **malloc_zones; // 定义在malloc.c内extern int32_t malloc_num_zones; //定义在malloc.c内malloc_zone_t *NanoCrashGuardInitialize2(){        s_default_zone = malloc_default_zone();    s_helper_zone = malloc_zones[1];    mprotect(s_default_zone, sizeof(malloc_zone_t), PROT_READ | PROT_WRITE);    OSMemoryBarrier();            OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_free, (void *)s_default_zone->free, (void *volatile *) &s_default_zone_origin_free);    OSAtomicCompareAndSwapPtr((void *)s_default_zone->free, (void *)default_zone_free, (void *volatile *) &s_default_zone->free);    OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_realloc, (void *)s_default_zone->realloc, (void *volatile *) &s_default_zone_origin_realloc);    OSAtomicCompareAndSwapPtr((void *)default_zone_realloc, (void *)s_default_zone->realloc, (void *volatile *) &s_default_zone->realloc);    OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_batch_free, (void *)s_default_zone->batch_free, (void *volatile *) &s_default_zone_origin_batch_free);    OSAtomicCompareAndSwapPtr((void *)default_zone_batch_free, (void *)s_default_zone->batch_free, (void *volatile *) &s_default_zone->batch_free);    OSAtomicCompareAndSwapPtr((void *)s_default_zone_origin_free_definite_size, (void *)s_default_zone->free_definite_size, (void *volatile *) &s_default_zone_origin_free_definite_size);    OSAtomicCompareAndSwapPtr((void *)default_zone_free_definite_size, (void *)s_default_zone->free_definite_size, (void *volatile *) &s_default_zone->free_definite_size);            OSMemoryBarrier();    mprotect(s_default_zone, sizeof(malloc_zone_t), PROT_READ);    return malloc_default_zone();}




用定时器去申请内存,测试代码如下:

    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {        while (1) {            uintptr_t ptr = (uintptr_t)malloc(NANO_MAX+1);            ptrs.push_back(ptr);            if (ptr>>28 == 0x17) {                NSLog(@"timer one time!!!!!");                break;            }        }    }];    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];


0 0