Skip to main content

UUID提取指引

UUID是用来区别一个模块的唯一标识,在bugly管理端查看android native还原前的堆栈可以看到每一帧后面会有so对应的UUID,bugly后台会通过这个UUID查找对应的符号表并使用符号表对地址进行还原。

Android So UUID

Android NDK生成UUID的时候有两种方式:

1. 编译时生成

读取.note.gnu.build-id这个Section,如果发现有值,则使用编译生成的uuid。

readelf -x .note.gnu.build-id libBugly.so

Hex dump of section '.note.gnu.build-id':
0x000001c8 04000000 14000000 03000000 474e5500 ............GNU.
0x000001d8 c21c5005 a226dedc 83a5a5b3 1611a3d6 ..P..&..........
0x000001e8 27713fd9 'q?.

其中:

  • 0x000001c8 表示.note.gnu.build-id所在elf的偏移地址
  • 04000000 表示name size,对应10进制是4byte
  • 14000000 表示uuid所占的大小,对应的10进制是20个byte
  • 03000000 表示type
  • 474e5500 是4个byte,对应名称GNU
  • c21c5005 a226dedc 83a5a5b3 1611a3d6 27713fd9 合计20个byte,对应编译过程生成的uuid。编译生成的uuid是160bit,bugly采用16个字节即128bit,如果超出128bit,bugly会舍弃前32bit。因此此处实际上bugly最终得到的uuid是a226dedc83a5a5b31611a3d627713fd9,也就是最后16个字节。

注意:Bugly最后使用的是16个字节的UUID,也就是编译生成的UUID中的最后16个字节。

2. 运行时生成

如果elf不存在.note.gnu.build-id这个Section,bugly会计算.text这个section前4096字节对应的hash并生成一个byte[16]的数组作为uuid。 可以看到这种计算方法是存在缺陷的, 比如:两份代码的.text section只有第10000字节存在差异, 此时编译生成的两个uuid是不一样的,但是bugly这种算法得到的uuid会冲突。 如果我们将计算.text字节数扩大到整个节区会不会有所改善,做个实验:

两份代码只相差几行空格,此时.text section内容是没有变化的,此时编译生成的两个uuid是不一样的,但是bugly这种算法得到的uuid会冲突。

可见即使计算整个代码区仍然无法保证uuid不冲突,并且在代码运行过程做整个.text节区的计算生成Uuid是非常耗时的,会影响到程序的运行效率。 有时候,开发者考虑到编译效率可能没有打开生成.note.gnu.build-id。但是相比运行时的耗时影响程序效率,编译过程的耗时相对来说能够接受。因此严格来说,如果希望保证uuid绝对不冲突,需要在so构建的时候配置生成.note.gnu.build-id。

如何在编译过程开启生成.note.gnu.build-id?

NDK构建: 在Android.mk配置LOCAL_LDFLAGS += -Xlinker --build-id

clang构建:参考

Mach-O UUID

Mach-O UUID是在构建时生成,存储在Mach-O header中,查询一个dsym的UUID方法有两种。

1. 命令行读取

dwarfdump -u path/to/compile/executable

2. 代码读取

#import <mach-o/ldsyms.h>
NSString *executableUUID()
{
const uint8_t *command = (const uint8_t *)(&_mh_execute_header + 1);
for (uint32_t idx = 0; idx < _mh_execute_header.ncmds; ++idx) {
if (((const struct load_command *)command)->cmd == LC_UUID) {
command += sizeof(struct load_command);
return [NSString stringWithFormat:@"%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
command[0], command[1], command[2], command[3],
command[4], command[5],
command[6], command[7],
command[8], command[9],
command[10], command[11], command[12], command[13], command[14], command[15]];
} else {
command += ((const struct load_command *)command)->cmdsize;
}
}
return nil;
}

UUID什么时候会相同

Each binary file in an app—the main app executable, frameworks, and app extensions—has its own dSYM file. The compiled binary and its companion dSYM file are tied together by a build UUID, that’s recorded by both the built binary and dSYM file. If you build two binaries from the same source code but with different Xcode versions or build settings, the build UUIDs for the two binaries won’t match. A binary and a dSYM file are only compatible with each other when they have identical build UUIDs. Keep the dSYM files for the specific builds you distribute, and use them when diagnosing issues from crash reports.

上面的解释是引用文章 Building Your App to Include Debugging Information,这个规则对android native / linux平台同样适用。

只有两个条件同时具备,模块UUID才会相等:

  1. 代码完全相同;
  2. 构建平台配置完全相同;