目录
1、IDA工具介绍
2、产品及问题场景描述
3、查看Tombstone文件
4、使用IDA打开.so动态库文件,查看汇编代码的上下文,到C++源码中定位发生崩溃的那行代码
4.1、使用IDA打开.so动态库文件
4.2、切换到Text View文本视图模式
4.3、根据相对于函数的偏移,在汇编代码中找到对应位置,查看附近的汇编上下文
4.4、通过汇编代码上下文,找到对应的C++源代码位置
4.5、使用空指针去调用一个类成员函数,为啥会崩溃在函数调用处?
5、最后
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html 我们在排查软件异常时,有时可能会用到IDA反汇编工具去查看二进制文件的汇编代码,去辅助分析问题。最近在排查安卓系统中的程序崩溃时,就使用了IDA工具,使用IDA打开.so动态文件查看汇编代码,结合安卓系统自动生成的Tombstone文件中的信息,最终找到了引发崩溃的原因。今天我们就来讲述一下这个问题的详细排查问题,以供大家借鉴或参考。
1、IDA工具介绍
IDA是比利时Hex-Rays公司出品的一款强大的交互式静态反汇编工具。它可以直接反汇编出二进制文件的汇编代码,是目前软件逆向与安全分析领域最好用、最强大的一个静态反汇编软件,已成为众多软件安全分析人员不可缺少的利器!它支持Windows、Linux等多个平台,支持Intel X84、X64、ARM、MIPS等数十种CPU指令集。 IDA既支持打开Windows平台的.dll库文件,也支持打开Linux平台的.so库文件。
IDA是俄罗斯天才程序员llfak Guilfanov(尔法克-吉尔法诺威)开发的,llfak Guilfanov作为创始人兼首席执行官与2005年在比利时创建了Hex-Rays公司。
可能有很多人不知道,俄罗斯在软件领域很厉害,是个盛产天才程序员的地方,比如我们这里讲的最强大静态反汇编工具IDA就是俄罗斯人写的!备受欢迎的IDE开发工具IDEA、PyChram和WebStorm所属公司Jetbrains公司就是三个俄罗斯天才程序员创建的!在IT领域广泛使用的负载均衡与反向代理开源库Nginx以及大数据领域鼎鼎有名的ClickHouse开源库都是俄罗斯人写的!
俄罗斯人的逻辑思维很强,俄罗斯盛产数学家和化学家,比如伟大的数学家欧拉和高斯、发明元素周期表的化学家门捷列夫!
华为自研的数据库就是以高斯命名的(GaussDB),华为的服务器操作系统则是以欧拉命名的(EulerOS)!向伟人们致敬!
2、产品及问题场景描述
本文中出现问题的是一台嵌入式硬件设备,设备中使用的是安卓系统,设备运行的是我们的应用程序。应用程序的主体是用Java开发的安卓app程序,app程序通过C++实现的底层业务模块实现和远端服务器的通信交互。
app程序通过调用封装好的JNI接口去操作底层的C++模块,本案例中的崩溃是发生在底层的C++模块中。崩溃虽然发生在底层的C++模块中,但底层C++模块也是运行在app程序进程中的,所以直接导致了app程序的崩溃。
app程序发生崩溃时,安卓系统感知到了,并生成了包含异常崩溃信息的Tombstone(墓碑)文件,这个文件类似于Linux系统中生成的CoreDump文件,我们在分析软件异常崩溃时,就是去分析这些文件。
我们先是打开了Tombstone文件,查看了发生异常崩溃的具体异常类型以及崩溃时的函数调用堆栈,但通过Tombstone文件只知道崩溃发生在哪个函数中,函数中的代码篇幅比较长,无法定位崩溃发生在哪一行。
于是乎,我们使用IDA打开函数调用堆栈中的显示的.so模块文件去查看汇编代码,查看发生崩溃的那条汇编代码附近的上下文,最终找到了发生崩溃的那行C++源码。
3、查看Tombstone文件
从嵌入式设备中取来发生崩溃时安卓系统自动生成的Tombstone文件,打开该文件,看到了如下的信息:
首先,看到当前崩溃的原因为:null pointer dereference,空指针引用,即程序中使用了空指针引发了崩溃。紧接着,看到发生崩溃时的函数调用堆栈,从堆栈上看,崩溃发生在libxxservice_hddll.so动态库中的CXXXServiceHMpHandler::OnTextImageCreateBannerInfoRsp函数中。调用堆栈中能看到具体的函数名,说明.so动态库文件中是有函数符号的。但看不到具体的代码行号,只能看到相对于函数CXXXServiceHMpHandler::OnTextImageCreateBannerInfoRsp的偏移:
#00 pc 0000000000075200 /xxxkyui/lib64/libxxservice_hddll.so (CXXXServiceHMpHandler::OnTextImageCreateBannerInfoRsp(mtmsg::CMtMsg*, unsigned int, unsigned int)+1048) (BuildId: d6e3064a3e1a03d9bea3c4496e78cb4942d187d1)
而CXXXServiceHMpHandler::OnTextImageCreateBannerInfoRsp函数中的代码篇幅比较长,我们没法确定崩溃发生在哪一行代码上。
当然如果问题是必现的,或者很好复现,则可以添加打印,将函数中用到的所有指针的值打印出来,崩溃后我们查看日志就能确定了。但有些问题是很难复现的,我们还是要多掌握一些分析手段才好!
既然从函数调用堆栈中能看到具体的函数名及相对于函数(首地址)的偏移,我们可以使用IDA反汇编工具去查看函数所在模块libxxservice_hddll.so的汇编代码上下文,去辅助定位问题。
4、使用IDA打开.so动态库文件,查看汇编代码的上下文,到C++源码中定位发生崩溃的那行代码
4.1、使用IDA打开.so动态库文件
我们的嵌入式硬件设备使用的安卓系统,主控CPU是基于ARM架构的,程序的代码也是在ARM平台的环境中编译的,IDA反汇编工具是支持ARM平台的,所以可以直接使用IDA打开程序的二进制文件查看汇编代码的。
直接在Windows系统中启动IDA,会弹出如下的窗口:
点击New按钮去反汇编一个新的文件,然后弹出IDA的主窗口。然后直接将libxxservice_hddll.so文件拖入到IDA主窗口中打开,会提示使用64位的IDA打开该文件,如下:
libxxservice_hddll.so动态库是64位程序,所以要使用64位的IDA打开:
我们之前打开的是32位的IDA。
于是通过桌面快捷方式找到安装路径,到路径中启动64版本IDA,然后将libxxservice_hddll.so拖入到IDA主窗口中,然后会弹出选择打开二进制文件的格式,如下所示:
IDA会自动识别二进制文件的类型,按默认的文件格式打开即可。
4.2、切换到Text View文本视图模式
打开二进制文件后,会默认显示Graphic View视图模式:
要右键单击,在弹出的右键菜单中单击“Text View”菜单项,切换到文本模式页面,如下:
4.3、根据相对于函数的偏移,在汇编代码中找到对应位置,查看附近的汇编上下文
首先,我们需要找到在IDA显示的汇编代码中找到CXXXServiceHMpHandler::OnTextImageCreateBannerInfoRsp函数的位置。在菜单栏中中点击Jump -> Jump to function...,打开如下的窗口:
点击窗口下方的Search按钮,在弹出的窗口中输入函数名OnTextImageCreateBannerInfoRsp:
然后点击OK按钮,在函数列表中搜索到该函数,双击之直接跳转到该函数的汇编代码中,如下所示:
可以看到该函数的函数地址(函数首地址)为0x0000000000074DE8,根据Tombstone文件中显示的相对函数的偏移:
#00 pc 0000000000075200 /xxxkyui/lib64/libxxservice_hddll.so (CXXXServiceHMpHandler::OnTextImageCreateBannerInfoRsp(mtmsg::CMtMsg*, unsigned int, unsigned int)+1048) (BuildId: d6e3064a3e1a03d9bea3c4496e78cb4942d187d1)
计算新的地址:
0x0000000000074DE8 + 0x418(对应于10进制的1048)= 0x0000000000075200
然后在IDA中搜索该地址0x0000000000074DE8,找到对应的汇编代码行。具体的做法是,将鼠标点进汇编代码窗口中(使该窗口获得焦点),然后按下快捷键g,弹出Jump to address窗口,输入上面计算出来的地址0x0000000000075200:
点击OK,就会跳转到对应的行,如下所示:
4.4、通过汇编代码上下文,找到对应的C++源代码位置
我们平时看惯了X86平台的汇编代码,看这个ARM架构的汇编代码很不习惯,无论是汇编指令的名称,还是寄存器的名称,都有很大的差异。感觉还是X86平台的汇编代码好阅读一些。
上面我们在汇编代码中定位到了位置,但与汇编代码对应的C++源码是哪一行呢?此外,Release下编译时编译器会对C++代码进行优化(有些变量或函数调用可能会被优化掉),导致汇编代码和C++代码是不完全一致。
该怎么将汇编代码与C++源码对应起来呢?难道我们要一句一句汇编代码去啃?强行去阅读汇编代码上下文,是需要有一定的汇编功底的,一般人比较难做到。一般我们借助汇编上下文中的注释信息去辅助阅读,本例中我们就是使用注释信息快读定位的。
0x0000000000075200地址对应的汇编代码行,该行代码下面紧接就看到注释,是常量值字符串的注释:
但看不到完整的字符串。这个地方有个技巧,可以将鼠标移动到变量上,就会以TooTip的方式显示变量中的完整内容,如下所示:
这个地方巧了,这样的字符串是打印日志中的,于是到C++源码中找到CXXXServiceHMpHandler::OnTextImageCreateBannerInfoRsp函数,在函数中找“[CMtServiceHMpHandler::OnTextImageCreateBannerInfoRsp] dispatch”这样的打印,确实有这行打印,如下所示:
所以就找到了0x0000000000075200地址对应的C++源码大概的行了,所以本例中的空指针问题应该就是上图中的ptTip指针,即该指针值为空,结果使用该指针调用value接口产生了崩溃。
关于IDA工具介绍及详细使用说明,可以参见我之前写的文章:
IDA反汇编工具使用详解https://blog.csdn.net/chenlycly/article/details/120635120使用IDA查看汇编代码上下文去辅助排查C++软件异常问题https://blog.csdn.net/chenlycly/article/details/128942626使用反汇编工具IDA查看发生异常的汇编代码的上下文去辅助分析C++软件异常https://blog.csdn.net/chenlycly/article/details/132158574
4.5、使用空指针去调用一个类成员函数,为啥会崩溃在函数调用处?
为啥崩溃在函数调用处呢?如果调用的函数是个普通函数(非虚函数),函数调用处一般不会崩溃的,如果函数内部访问了所在类的成员变量,则使用空指针调用会崩溃在被调用函数内部,而不是崩溃call函数时!
有一种可能该函数是虚函数,对于虚函数的调用,需要通过二次寻址到虚函数表中找到虚函数的地址(虚函数代码段地址,此处需要区分一下代码段地址与数据段地址),在二次寻址的过程中将空指针作为内存地址去访问内存,就触发了内存访问违例,引发崩溃。
还有一种情况,被调用函数种定义了占用很大栈内存的局部变量(比如使用一个定义很大的结构体去定义了一个局部变量),导致当前线程栈溢出(当前线程占用的栈空间达到了线程创建时分配的栈空间的上限)。对于这种线程栈溢出的场景,可能就会崩溃在函数调用的地方,这类问题我们在项目中遇到过。
5、最后
本文详细讲述了如何将Tombstone文件和IDA反汇编工具结合起来快速定位崩溃的完整过程,希望能给大家提供一定的借鉴与参考。此外,通过该问题详细讲解了如何使用IDA反汇编工具。