关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »深入理解Android NDK日志符号化

    深入理解Android NDK日志符号化

    2015-07-21 00:00:00 出处:androidperformance
    分享

    前言

    为了进行代码及产品保护,几乎所有的非开源App都会进行代码混淆。这样,当收集到崩溃信息后,就需要进行符号化来还原代码信息,以便开发者可以定 位Bug。基于使用SDK和NDK的不同,Android的崩溃分为两类:Java崩溃和C/C++崩溃。Java崩溃通过mapping.txt文件进 行符号化,比较简单直观。而C/C++崩溃的符号化则需要使用Google自带的一些NDK工具,比如ndk-stack、addr2line、 objdump等。本文不去讨论如何使用这些工具,有兴趣的朋友可以参考之前尹春鹏写的另一篇文章《 如何定位Android NDK开发中遇到的错误》,里面做了详细的描述。

    基于NDK的Android开发都会生成一个动态链接库(so),它是基于C/C++编译生成的。动态链接库在Linux系统下广泛使用,而Android系统底层是基于Linux的,所以NDK so库的编译生成遵循相同的规则,只不过Google NDK把相关的交叉编译工具都封装了。

    Ndk-build编译时会生成的两个同名的so库,位于不同的目录/project path/libs/armeabi/xxx.so和/project path/obj/local/armeabi/xxx.so,比较两个so文件会发现体积相差很大。前者会跟随App一起发布,所以尽可能地小,而后者包含了很多调试信息,主要为了gdb调试的时候使用,当然,NDK的日志符号化信息也包含其中。

    深入理解Android NDK日志符号化

    深入分析so动态库组成结构

    本文主要针对这个包含调试信息的so动态库,深入分析它的组成结构。在开始之前,先来说说这样做的目的或者好处。现在的App基本都会采集 上报崩溃时的日志信息,无论是采用第三方云平台,还是自己搭建云服务,都要将含调试信息的so动态库上传,实现云端日志符号化以及云端可视化管理。

    移动App的快速迭代,使得我们必须存储管理每一个版本的debug so库,而其包含了很多与符号化无关的信息。假如我们只提取出符号化需要的信息,那么符号化文件的体积将会呈现数量级的减少。同时可以在自定义的符号化文 件中添加App的版本号等定制化信息,实现符号化提取、上传到云端、云端解析及可视化等自动化部署。另外,从技术角度讲,开发者将不再害怕看到 “unresolved symbol” linking errors,可以更从容地debugging C/C++ crash或进行一些hacking操作。

    首先通过readelf来看看两个不同目录下的so库有什么不同。

    深入理解Android NDK日志符号化

    从中可以清楚看到,包含调试信息的so库多了8个.debug_开头的条目以及.symtab和.strtab条目。符号化的本质,是通过堆栈中的地址信息,还原代码本来的语句以及相应的行号,所以这里只需解析.debug_ line和.symtab,最终获取到如下的信息就可以实现符号化了。

    c85    c8b     willCrash       jni/hello-jni.c:27-29
    c8b    c8d     willCrash       jni/hello-jni.c:32
    c8d    c8f     JNI_OnLoad      jni/hello-jni.c:34
    c8f    c93     JNI_OnLoad      jni/hello-jni.c:35
    c93    c9d     JNI_OnLoad      jni/hello-jni.c:37

    通常,目标文件分为三类:relocatable文件、executable文件和shared object文件,它们格式称为ELF(Executable and Linking Format),so动态库属于第三类shared object,它的整体组织结构如下:

    深入理解Android NDK日志符号化

    ELF Header

    ELF Header文件头的结构如下,记录了文件其他内容在文件中的偏移以及大小信息。这里以32bit为例:

    typedef struct {
            unsigned char   e_ident[EI_NIDENT];
            Elf32_Half      e_type;          // 目标文件类型,如relocatable、executable和shared object
            Elf32_Half      e_machine;   // 指定需要的特定架构,如Intel 80386,Motorola 68000
            Elf32_Word      e_version;   // 目标文件版本,通e_ident中的EI_VERSION
            Elf32_Addr      e_entry;       // 指定入口点地址,如C可执行文件的入口是_start(),而不是main()
            Elf32_Off       e_phoff;   // program header table 的偏移量
            Elf32_Off       e_shoff;   // section header table的偏移量
            Elf32_Word      e_flags;  // 处理器相关的标志
            Elf32_Half      e_ehsize;  // 代表ELF Header部分的大小
            Elf32_Half      e_phentsize; // program header table中每一项的大小
            Elf32_Half      e_phnum;   // program header table包含多少项
            Elf32_Half      e_shentsize;  // section header table中每一项的大小
            Elf32_Half      e_shnum;  // section header table包含多少项
            Elf32_Half      e_shstrndx;  //section header table中某一子项的index,该子项包含了所有section的字符串名称
    } Elf32_Ehdr;

    其中e_ident为固定16个字节大小的数组,称为ELF Identification,包含了处理器类型、文件编码格式、机器类型等,具体结构如下:

    深入理解Android NDK日志符号化

    Sections

    该部分包含了除ELF Header、program header table以及section header table之外的所有信息。通过section header table可以找到每一个section的基本信息,如名称、类型、偏移量等。

    先来看看Section Header的内容,仍以32-bit为例:

    typedef struct {
    	Elf32_Word	sh_name;  // 指定section的名称,该值为String Table字符串表中的索引
    	Elf32_Word	sh_type; // 指定section的分类
    	Elf32_Word	sh_flags; // 该字段的bit代表不同的section属性
    	Elf32_Addr	sh_addr; // 假如section出现在内存镜像中,该字段表示section第一个字节的地址
    	Elf32_Off	sh_offset; // 指定section在文件中的偏移量
    	Elf32_Word	sh_size; // 指定section占用的字节大小
    	Elf32_Word	sh_link; // 相关联的section header table的index
    	Elf32_Word	sh_info; // 附加信息,意义依赖于section的类型
    	Elf32_Word	sh_addralign; // 指定地址对其约束
    	Elf32_Word	sh_entsize;   // 假如section包含一个table,该值指定table中每一个子项的大小
    } Elf32_Shdr;

    通过Section Header的sh_name可以找到指定的section,比如.debug_line、.symbol、.strtab。

    String Table

    String Table包含一系列以/0结束的字符序列,最后一个字节设置为/0,表明所有字符序列的结束,比如:

    深入理解Android NDK日志符号化

    String Table也属于section,只不过它的偏移量直接在ELF Header中的e_shstrndx字段指定。String Table的读取方法是,从指定的index开始,直到遇到休止符。比如要section header中sh_name获取section的名称,假设sh_name = 7, 则从string table字节流的第7个index开始(注意这里从0开始),一直读到第一个休止符(index=18),读取到的名称为.debug_line。

    Symbol Table

    该部分包含了程序符号化的定义相关信息,比如函数定义、变量定义等,每一项的定义如下:

    # Symbol Table Entry
    typedef struct {
    	Elf32_Word	st_name;   //symbol字符串表的索引
    	Elf32_Addr	st_value;  //symbol相关的值,依赖于symbol的类型
    	Elf32_Word	st_size;   //symbol内容的大小
    	unsigned char	st_info;   //symbol的类型及其属性
    	unsigned char	st_other;  //symbol的可见性,比如类的public等属性
    	Elf32_Half	st_shndx;  //与此symbol相关的section header的索引
    } Elf32_Sym;

    Symbol的类型包含以下几种:

    深入理解Android NDK日志符号化

    其中STT_FUNC就是大家要找的函数symbol。然后通过st_name从symbol字符串表中获取到相应的函数名(如 JNI_OnLoad)。当symbol类型为STT_FUNC时,st_value代表该symbol的起始地址,而 (st_value+st_size)代表该symbol的结束地址。

    回顾之前提到的.symtab和.strtab两个部分,对应的便是Symbol Section和Symbol String Section。

    DWARF(Debugging With Attributed Record Formats)

    DWARF是一种调试文件格式,很多编译器和调试器都通过它进行源码调试(gdb等)。尽管它是一种独立的目标文件格式,但往往嵌入在ELF文件中。前面通过readelf看到的8个.debug_* Section全部都属于DWARF格式。本文将只讨论与符号化相关的.debug_line部分,更多的DWARF信息请查看参考文献的内容。

    .debug_line部分包含了行号信息,通过它可以将代码语句和机器指令地址对应,从而进行源码调试。.debug_line由很多子项组成,每个子项都包含类似数据块头的描述,称为Statement Program Prologue。Prologue提供了解码程序指令和跳转到其他语句的信息,它包含如下字段,这些字段是以二进制格式顺序存在的:

    深入理解Android NDK日志符号化

    这里用到的机器指令可以分为三类:

    深入理解Android NDK日志符号化

    这里不做机器指令的解析说明,感兴趣的,可以查看参考文献的内容。

    通过.debug_line,我们最终可以获得如下信息:文件路径、文件名、行号以及起始地址。

    最后,我们汇总一下整个符号化提取的过程:

    从ELF Header中获知32bit或者64bit,以及大端还是小端,基于此读取后面的内容; 从ELF Header中获得Section Header Table在文件中的位置; 读取Section Header Table,从中获得.debug_line、.symtab以及.strtab三个section在文中的位置; 读取.symtab和.strtab两个section,最后获得所有function symbol的名称、起始地址以及结束地址; 读取.debug_line,按照DWARF格式解析获取文件名称、路径、行号以及起始地址; 对比步骤4和5中获取的结果,进行对比合并,形成最终的结果。

    参考文献

    如何定位Android NDK开发中遇到的错误 How debuggers work: Part 3 – Debugging information ELF (Executable and Linking Format) The DWARF Debugging Standard

    作者简介:

    贾志凯 Testin技术总监,主要负责崩溃分析项目Android平台架构设计、性能及稳定性优化。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    SQL汉字转换为拼音的函数

    windows 7系统无法运行Photoshop CS3的解决方法

    巧用MySQL加密函数对Web网站敏感数据进行保护

    MySQL基础知识简介

    Windows7和WinXP下如何实现不输密码自动登录系统的设置方法介绍

    windows 7系统ip地址冲突怎么办?windows 7系统IP地址冲突问题的

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。