崩溃现象
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)
网友评论