C静态库符号冲突,引起的iOS随机崩溃问题

崩溃现象

iOS工程引入三方SDK后(GDTMobSDK-iOS V4.14.0),运行App,会产生随机崩溃,最终指向malloc相关的线索。

堆栈1:

free.jpg

堆栈2:

    frame #0: 0x00000001b07b0af8 libsystem_malloc.dylib`malloc_error_break
    frame #1: 0x00000001b07bf6e4 libsystem_malloc.dylib`malloc_vreport + 440
    frame #2: 0x00000001b07bf998 libsystem_malloc.dylib`malloc_zone_error + 104
    frame #3: 0x00000001b07a3774 libsystem_malloc.dylib`nanov2_allocate_from_block$VARIANT$mp + 540
    frame #4: 0x00000001b07a29d0 libsystem_malloc.dylib`nanov2_allocate$VARIANT$mp + 140
    frame #5: 0x00000001b07a28f4 libsystem_malloc.dylib`nanov2_malloc$VARIANT$mp + 60
    frame #6: 0x00000001b07b19a0 libsystem_malloc.dylib`malloc_zone_malloc + 156
    frame #7: 0x00000001b07b23b0 libsystem_malloc.dylib`malloc + 32
    frame #8: 0x00000001b06ae428 libsystem_c.dylib`_vasprintf + 144
    frame #9: 0x00000001b06a6d7c libsystem_c.dylib`asprintf + 72
    frame #10: 0x00000001b0aefabc CoreFoundation`-[NSObject(NSObject) __dealloc_zombie] + 72

内存分析

1、按照Xcode打印的提示,设置符号断点 malloc_error_break,调试无果

2、开启僵尸对象,无果

3、开启 Address Sanitizer,基本锁定问题

Address Sanitizer.png

关于 Address Sanitizer可参考博文 https://blog.csdn.net/u014600626/article/details/119506854

Address Sanitizer的原理是当程序创建变量分配一段内存时,将此内存后面的一段内存也冻结住,标识为中毒内存。如图所示,黄色是变量所占内存,紫色是冻结的中毒内存。

error_memory.png.jpeg

当程序访问到中毒内存时(越界访问),就会抛出异常,并打印出相应log信息。调试者可以根据中断位置和的log信息,识别bug。如果变量释放了,变量所占的内存也会标识为中毒内存,这时候访问这段内存同样会抛出异常(访问已经释放的对象)

再次崩溃时,Xcode捕获到静态库c代码中的崩溃:

    thread #66, stop reason = Heap buffer overflow
    frame #0: 0x000000011bcd26f4 libclang_rt.asan_ios_dynamic.dylib`__asan::AsanDie()
    frame #1: 0x000000011bcea1fc libclang_rt.asan_ios_dynamic.dylib`__sanitizer::Die() + 192
    frame #2: 0x000000011bcd0580 libclang_rt.asan_ios_dynamic.dylib`__asan::ScopedInErrorReport::~ScopedInErrorReport() + 1124
    frame #3: 0x000000011bccf858 libclang_rt.asan_ios_dynamic.dylib`__asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) + 1436
    frame #4: 0x000000011bca1a30 libclang_rt.asan_ios_dynamic.dylib`wrap_memcpy + 616
    frame #5: 0x00000001080bfb14 xxx`SHA1Update + 276
    frame #6: 0x00000001080bfbfc xxx`SHA1Final + 204
    frame #7: 0x000000010aa86530 xxx`xxx::xxx::CSha1::final(unsigned char*, unsigned long) + 184
    frame #8: 0x000000010aa6fea4 xxx`xxx::xxx::wsse_calc_digest(char*, char*, char*, char*) + 156
    frame #9: 0x000000010aa6fd90 xxx`xxx::xxx::wsse_make_digest(xxx::xxx::HTTP_WSSE*, char*) + 112
    frame #10: 0x000000010aa22568 xxx`xxx::xxx::GenerateRequest(xxx::xxx::HttpReqPars&, xxx::xxx::HTTP_REC&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&, int) + 36
    frame #11: 0x000000010aa26a64 xxx`xxx::xxx::CDeviceInfo::sendRequest(xxx::xxx::ServerInfo const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) + 276
    frame #12: 0x000000010aa275a0 xxx`xxx::xxx::CDeviceInfo::onInit(xxx::xxx::ServerInfo const&, unsigned long long) + 56
    frame #13: 0x000000010aa27440 xxx`xxx::xxx::CDeviceInfo::heartbeat(xxx::xxx::ServerInfo const&, unsigned long long, unsigned long long) + 72
    frame #14: 0x000000010aa28704 xxx`xxx::xxx::CDeviceInfoMgr::heartbeat(unsigned long long) + 184
    frame #15: 0x000000010aa314e4 xxx`xxx::xxx::CMultiStunClient::threadProc() + 72
    frame #16: 0x000000010aa87ef8 xxx`(anonymous namespace)::InternalThreadBody(void*) + 272

堆栈说明SHA1Update存在内存分配问题,怀疑是新引入的库,和工程原有的库存在函数冲突,导致App链接出错。

即三方SDK(.a文件)与工程原有的SDK,都为静态库,且均包含了SHA1Update函数,但App却链接了三方SDK中的函数,导致原SDK在使用时产生崩溃。

4、符号文件导出

使用export_symbols.sh脚本导出两个静态库的符号文件

filename="${1%%.*}" #删除第一个.,以及右边的字符串,得到文件名
nm -n $1 > ${filename}_symbols.txt 

5、比较符号文件

通过脚本(见附录-过滤重复符号)对比两个库的符号文件,过滤出符号类型为't'或'T'且名称一致的符号:

PS:常见符号类型

  • A 该符号的值在今后的链接中将不再改变;
  • B 该符号放在BSS段中,通常是那些未初始化的全局变量;
  • D 该符号放在普通的数据段中,通常是那些已经初始化的全局变量;
  • T 该符号放在代码段中,通常是那些全局非静态函数;
  • U 该符号未定义过,需要自其他对象文件中链接进来;
  • W 未明确指定的弱链接符号;同链接的其他对象文件中有它的定义就用上,否则就用一个系统特别指定的默认值。
___clang_at_available_requires_core_foundation_framework
___clang_at_available_requires_core_foundation_framework.cold.1
_SHA1Init
_SHA1Update
_SHA1Final

过滤_SHA1Init、SHA1Update、_SHA1Final三个符号,找到厂商符号文件libGDTMobSDK_symbols.txt对应的重复信息:

                U ___assert_rtn
                U ___stack_chk_fail
                U ___stack_chk_guard
                U _memcpy
0000000000000000 T _SHA1Transform
0000000000000004 C _x
0000000000000004 C _x.10
0000000000000004 C _x.12
0000000000000004 C _x.14
0000000000000004 C _x.16
0000000000000004 C _y
0000000000000004 C _y.11
0000000000000004 C _y.13
0000000000000004 C _y.15
0000000000000004 C _y.17
0000000000002373 T _SHA1Init
000000000000239c T _SHA1Update
0000000000002533 T _SHA1Final 

找到工程SDK符号文件对应的重复信息:

                U ___stack_chk_fail
                U ___stack_chk_guard
                U _memcpy
0000000000000000 T _SHA1Init
0000000000000032 T _SHA1Update
0000000000000b8c T _SHA1Final
0000000000000c80 s _padding

可以看到两个库中都实现了_SHA1Init、_SHA1Update、_SHA1Final三个c函数。

解决方法

解决两个静态库C函数冲突,即可修复链接问题,几种不同途径的修复方法:

  • 函数名增加库标识,代价小,推荐

  • 增加命名空间,代价小,推荐

  • 修改为动态库,代价大,不推荐

经过协商,最终由厂商修复该问题,提供了新的版本V4.14.12。

pod 'GDTMobSDK', '~> 4.14.12'

再次导出libGDTMobSDK.a符号文件,查看GdtSha1.o对应的符号:

libGDTMobSDK.a(GdtSha1.o):
                U ___assert_rtn
                U ___stack_chk_fail
                U ___stack_chk_guard
                U _memcpy
00000000000023a3 T _GDT_SHA1Init
00000000000023cc T _GDT_SHA1Update
00000000000025bc T _GDT_SHA1Final
00000000000027d9 T _gdt_sha1data
0000000000002850 T _tencent8913930322152434853208

可以看到,厂商在_SAH1Init前增加了前缀 GDT。

附录

导出符号 export_symbols.sh

参数传入二进制文件路径

filename="${1%%.*}" #删除第一个.,以及右边的字符串,得到文件名
nm -n $1 > ${filename}_symbols.txt 

过滤重复符号 process.py

def filter_c_symbols(read_lines: [str]) -> {}:
    """
    过滤符号文件,输出至map中
    :param read_lines: 原始的符号
    :return: [:]
    """
    map_lines = {}
    for line in read_lines:
        separators = line.split(" ")
        if (line.__contains__(' t ') or line.__contains__(' T ')) & len(separators) > 0:
            last = separators[len(separators) - 1]
            if not last.__contains__(']'): # 过滤OC符号
                # print(line)
                last = last.replace('\n', '')
                map_lines[last] = line
    return map_lines



if __name__ == '__main__':
    # 读取mobsdk
    file = open(r'libGDTMobSDK_symbol.txt')
    map_mob = filter_c_symbols(file.readlines())

    # 读取lcsdk
    file = open(r'LCSDK_symbols.txt')
    map_lcsdk = filter_c_symbols(file.readlines())

    # 遍历mobsdk,比较lcsdk中是否有重复的符号,并写入
    repeat_symbols = []
    for key in map_mob:
        if map_lcsdk.get(key):
            repeat_symbols.append(key)
            print(map_mob[key])


    # print(repeat_symbols)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值