使用IDA查看汇编代码上下文去辅助排查C++软件异常问题

news2024/11/28 18:47:59

目录

1、概述

2、汇编指令能最直接反映异常崩溃的原因

2.1、查看异常Code码及对应的异常类型

2.2、查看发生崩溃的那条汇编指令

3、阅读汇编代码上下文需要掌握一定的基础汇编知识

4、Windbg中显示的函数调用堆栈中的C++代码行号,和最新的代码对不上了

5、Windbg中指示的发生崩溃的C++代码行上有多个函数调用,很难判断哪个函数调用出问题了

6、最后


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931       在分析C++软件异常崩溃时,可能需要使用IDA工具去查看exe或dll二进制文件的汇编代码去辅助定位问题。今天我们就来讨论一下使用IDA工具去查看汇编代码相关细节问题。

1、概述

       一般我们会在C++软件中内置异常捕获模块(比如Google开源的CrashReport异常捕获库),当软件发生异常崩溃时,异常捕获就会感知到,将异常发生时的上下文信息保存到dump文件中。事后我们取来dump文件,用Windbg打开,使用.ecxr命令切换到发生遗产的上下文,然后使用kn、kp或kv命令查看发生异常时的函数调用堆栈。根据函数调用堆栈中的代码行号,去查看C++源代码,就能很快定位问题了。

一般异常崩溃是发生在某个线程中,导致所在进程发生崩溃,只要切换到这个出问题的线程中查看发生异常时的函数调用堆栈即可。

       在查看出问题的C++源码上下问去确定发生问题的原因时,我们甚至需要到Windbg查看函数调用堆栈中某个函数中的局部变量或所在C++类对象中成员变量的值,去辅助分析问题。项目中我们多次使用到该方法去快速定位问题,之前也写了几篇这方面的分析实例,可以参见下列文章:
通过查看Windbg中的变量值去定位C++软件异常问题https://blog.csdn.net/chenlycly/article/details/125731044通过查看windbg中变量值去定位C++软件异常的又一典型案例分享https://blog.csdn.net/chenlycly/article/details/125793532
       但在部分场景下仅使用Windbg分析还不够,还需要使用IDA工具去查看发生异常的模块的汇编代码上下文,将C++源码与汇编代码结合着看,去找出引发问题的原因。

2、汇编指令能最直接反映异常崩溃的原因

       C++软件发生崩溃,最终是崩溃在某个模块的某条汇编指令上,汇编指令会最直接最本真的反映出为啥会崩溃,所以我们在用Windbg分析异常时首先需要去看一下发生崩溃的那条汇编指令及发生崩溃的异常Code值,有时通过这两点信息可以初步估计出发生异常的原因。

2.1、查看异常Code码及对应的异常类型

       查看异常Code码及对应的异常类型,有时能助我们快速地定位问题。用Windbg打开dump文件时就能看到异常Code码及对应的异常类型,比如下图:

异常Code码是0xc00000fd,该错误码的含义是Stack overflow,即线程栈溢出,即问题线程当前的函数调用堆栈中的所有函数占用的栈空间超过了给线程分配的栈空间上限。

线程的栈空间是有上限的,函数中的局部变量是在栈上分配内存的,主调函数传递给被调函数的参数也是通过栈传递的(将参数值push到栈上)。用户创建线程时,系统会给每个线程分配指定大小的栈空间,Windows系统中默认分配1MB栈空间,Linux系统中默认会分配2MB的栈空间。

        看到上述异常类型,我们就能明确了,是某个线程发生线程栈溢出了,结合引发线程栈溢出的常见原因:

1)函数递归调用的深度过深
       因为一直在递归调用,在到达最底下的那层调用之前,递归函数一直没返回,栈空间一直没有释放,导致当前线程占用的栈空间越来越多,达到上限。
2)消息上触发函数的死循环调用
       消息触发的函数死循环调用,因为死循环调用了,函数的栈空间一直没释放,导致当前线程占用的栈空间越来越多。这个问题我们在实际项目中遇到过两次。
3)定义了一个占用内存很大的局部变量
       比如定义了一个很庞大的结构体,在一个函数中用该结构体定义了一个局部变量,假设该结构体接近或者大于1MB,则会直接导致线程栈溢出。
4)函数中使用switch...case语句,包含了大量的case分支
       每个case分支中都定义了局部变量,导致当前函数占用了大量的栈空间。case分支中的局部变量的生命周期是在case分支中的,即代码运行到对应的case分支中时该分支中的局部变量才有“生命”,但其实这个局部变量的栈空间已经在函数入口处分配好栈空间了,并不是代码执行到case子句中才分配栈空间的。这点可以通过编写测试代码,查看函数入口处给当前函数分配栈空间的汇编代码就能看出来了,可以先顶一个变量查看汇编代码看看分配了多少栈空间,然后再增加一个变量,看看分配的栈空间是否变大。

然后使用kn、kp或kv命令查看发生异常的线程的函数调用堆栈,看看函数调用堆栈中都调用了哪些函数,去做进一步分析,很快就能确定问题了。

2.2、查看发生崩溃的那条汇编指令

       分析异常时,除了先看异常Code码及其对应的异常类型,我们还要去看发生崩溃的那条汇编指令。通过查看发生异常崩溃的汇编指令,我们可能得到一些线索,初步估计出可能是什么原因引发的。

       Windbg打开dump文件后,不需要执行任何Windbg命令,会首先看到发生异常的异常Code码机器对应的异常类型,那如何去查看发生异常的那条汇编指令呢?其实很简单,只要输入.ecxr命令,执行该命令切换到发生异常的上下文线程中,并将发生异常的那条汇编指令及当时的各个寄存器的值,如下所示:

上图中发生异常的汇编指令中访问了地址为0x00000000的内存地址,在Windows系统中,小于64KB地址值的内存是禁止访问的,所以触发了内存访问违例,导致该条汇编指令发生崩溃。

       该条发生异常的汇编指令中,是去访问以ecx寄存器中的内容为地址的内存的,查看此时的ecx寄存器的值为0,而在X86汇编中ecx可以用来传递C++对象首地址的,所以我们初步怀疑可能是空指针引发的问题。然后,再去查看发生异常时的线程的函数调用堆栈及对应的C++源码去进一步分析。

3、阅读汇编代码上下文需要掌握一定的基础汇编知识

       要去阅读汇编代码的上下文,是需要掌握一定的汇编基础知识的,比如了解一些常用寄存器的用途、熟悉一些常用的汇编指令、了解函数调用时的栈分布、了解C++虚函数调用的汇编代码实现(虚函数调用时的二次寻址)等。

这里简单的提一下常用寄存器的用途
在X86汇编指令中,EAX主要用于存放函数调用的返回值;在调用C++成员函数时会使用ECX寄存器用来传递C++对象地址;ESI是源地址寄存器,EDI是目的地址寄存器,主要用于内存拷贝的串操作指令中,比如memcpy的汇编实现中。

       关于分析C++软件异常需要掌握的基础汇编知识,这里就不再赘述了,可以参见我之前写的文章:
分析C++软件异常需要掌握的汇编知识汇总https://blog.csdn.net/chenlycly/article/details/124758670

4、Windbg中显示的函数调用堆栈中的C++代码行号,和最新的代码对不上了

       在有pdb文件的情况下,要将pdb文件的路径设置到Windbg中。Windbg在加载到pdb文件后,在函数调用堆栈中不仅能显示具体的函数名称,还能显示函数中对应代码的行号,效果如下: 

TestDlg!CTestDlgDlg::OnBnClickedButton1+0x67 [d:\vs2010projects\testdlg\testdlg\testdlgdlg.cpp @ 489]

函数CTestDlgDlg::OnBnClickedButton1位于TestDlg.exe模块中,对应的testdlgdlg.cpp源文件的行号为489,于是打开testdlgdlg.cpp文件,找到该文件的489行,如下所示:

这是我之前故意写的一段引发异常的测试代码,代码中定义了一个结构体指针变量pInfo,初始化为空(NULL),然后没有给该指针赋一个有效的结构体地址,直接用这个空指针去访问结构体中的成员cbSize,所以访问了一个地址很小的内存,所以触发了内存访问违例。

       上面的是一个完整的简单示例,但在实际项目中,发生异常崩溃的软件版本可能是几个月或者几年之前的,Windbg中显示的行号是很早之前的cpp文件代码了,最新的cpp文件代码相对这个出问题的版本做了很多修改,所以行号和最新的代码完全对不上了。

       这时候就需要使用IDA去查看发生异常的模块的汇编代码上下文了,看看到底是那一行代码引起的,一般还是要和最新的代码对比着看,看看最新的代码中哪一行代码。接着上面的示例,我们用IDA打开TestDlg.exe二进制文件,查看出问题的函数CTestDlgDlg::OnBnClickedButton1的汇编代码,如下所示:

这个地方要注意一下,IDA也要用到pdb符号库文件,将pdb文件放置在目标二进制文件的同级目录中,IDA会到目标二进制文件所在的目录中搜索其需要的pdb文件。有了pdb文件中符号,IDA打开的汇编代码中就会显示具体函数名和变量标识,以及大量注释信息。

没有注释信息,直接去阅读汇编代码,是很难读懂的,除非你很强的汇编功底。在实际工作中,是将汇编代码、注释及C++源码结合起来看的,这比单纯地阅读汇编代码要简单很多的!

        对于如何使用IDA查看汇编代码上下文,这个地方就不展开了,后面会专门写一篇文章进行介绍。关于IDA工具的介绍,可以参看之前写的文章:

IDA反汇编工具使用详解icon-default.png?t=N176https://blog.csdn.net/chenlycly/article/details/120635120

5、Windbg中指示的发生崩溃的C++代码行上有多个函数调用,很难判断哪个函数调用出问题了

        Windbg中指示的发生崩溃的C++代码行上有多个函数调用(比如if语句中有多个条件的组合判断),很难直接判断是哪个函数调用出问题了,可以查看汇编代码去确定到底是哪个函数调用出的问题,比如如下的if条件判断语句:

if (pContainer->IsVisible() && GetTargetImplPtr()->IsReady() && pDataProcImpl->IsBuildFinish)
{
    // 代码省略
}

       有人可能会问,既然是if语句的条件中有多个函数调用,是不是应该代码运行到函数内部去了,为啥问题出在函数调用处呢?可能是调用类成员函数时使用的类对象指针有问题,有可能调用的是虚函数,在真正去调用虚函数之前,要到虚函数表中找到虚函数表中的函数地址,即需要进行虚函数表的二次寻址,崩溃可能就发何时能在虚函数的二次寻址的过程中。

       如果调用的不是虚函数,则不会崩溃在函数调用处,如果有崩溃,则肯定是崩溃在成员函数内部,因为成员函数内部要通过传入的类对象地址去访问类对象的数据成员,因为类对象地址有问题了(比如类对象地址为NULL,或者是一个已经释放的类对象地址),则通过该类对象访问其数据成员时则会产生异常。

       对于一行上包含多个C++代码调用,无法确定到底是哪个部分出的问题,此时就需要结合汇编代码,查看汇编代码的上下文去分析,去确定发生崩溃的代码在哪一个部分了。

6、最后

       IDA是业界最强大的静态反汇编工具,功能非常强大,我们在分析C++软件异常问题时,只是简单地使用IDA工具,即用IDA打开二进制文件查看文件中的汇编代码,以辅助分析问题。本文并没有详细讲述IDA工具的功能,感兴趣的朋友,可以去阅读一下IDA经典书籍《IDA Pro权威指南》。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/331382.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

openGL学习之GLFW和GLAD的下载和编译

背景:为什么使用GLFW和GLADOPenGL环境 目前主流的桌面平台是GLFW和GLAD之前使用的GLUT和Free GLUT已经基本淘汰了,所以记录一下如何下载GLFW和GLAD并且编译.GLFW下载:An OpenGL library | GLFW复制到你想存放的位置,我这里就存放到C盘Libaray文件夹下了,这里是我存放…

算法训练营 day37 贪心算法 K次取反后最大化的数组和 加油站 分发糖果

算法训练营 day37 贪心算法 K次取反后最大化的数组和 加油站 分发糖果 K次取反后最大化的数组和 1005. K 次取反后最大化的数组和 - 力扣(LeetCode) 给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组: 选择某个下标…

Eclipse 版本升级记录

前言 Eclipse 不是不能在线升级,至少没有找对方法,下面就让我来一步一步带你学会它、使用它吧! 一、概念 Eclipse主要分为两个概念,一个是在线升级 Eclipse 新版本,另一个是在线升级 Eclipse 插件,这两个是有很大区…

【记录】ChatGPT使用记录

文章目录2023年02月08日数学哲学Java其他2023年02月09日ChatGPT网络根据对话的日期、问题的类型进行整理 2023年02月08日 数学 感想:数学应该没啥问题,感觉只要自然语言没理解错了,剩下就不是事 积分 代数 哲学 哲学问题的回答就属于模棱…

JetpackCompose从入门到实战学习笔记7—Dialog的简单使用

JetpackCompose从入门到实战学习笔记7—Dialog的简单使用 1.Dialog对话框 Dialog的参数如下: Composable fun Dialog(onDismissRequest: (() -> Unit)?, //关闭对话框的回调properties: DialogProperties! DialogProperties(), //自定义对话框的属性content: ( Compose…

智能无障碍轮椅——汇总

文章目录一、设计内容二、控制理论三、材料列表四、控制图五、硬件介绍1、TB6612FNG电机驱动模块2、DX-BT04 2.0蓝牙模块3、MPU6050陀螺仪模块4、电源模块5、520编码器直流减速电机六、PID理论与算法控制七、代码解析八、参考博文一、设计内容 使用 STM32 作为主处理器进行开发…

Web3.0:互联网新阶段

Web3.0 定义:从后端生产关系革新开始。 Web3.0 是结合了去中心化和代币(Token)经济学等概念,基于区块链技术的全新的互联网迭代方向,意在解决 Web2.0 带来的生态不平衡、发展不透明等问题。与 AR/VR 等前端创新相比&am…

oracle外键约束、级联删除

根据约束名称查询:select * from user_constraints t where t.CONSTRAINT_NAME 约束名称举例:字段解析:1、CONSTRAINT_NAME:约束名称。2、CONSTRAINT_TYPE:约束类型。3、TABLE_NAME:约束所在的表。4、R_CO…

FreeModbus RTU 移植指南

FreeModbus 简介 FreeModbus 是一个免费的软件协议栈,实现了 Modbus 从机功能: 纯 C 语言支持 Modbus RTU/ASCII支持 Modbus TCP 本文介绍 Modbus RTU 移植。 移植环境: 裸机Keil MDK 编译器Cortex-M3 内核芯片(LPC1778/88&…

Intel x86_64 PMU简介

文章目录前言一、性能监控概述二、CPUID information三、架构性能监控3.1 架构性能监控 Version 13.1.1 架构性能监控 Version 1 Facilities3.1.2 预定义的体系结构性能事件3.1.3 cmask demo测试参考资料前言 Intel 64 和 IA-32 架构提供了 PMU(Performance Monito…

Oracle Data Guard Apply服务

1. Apply服务介绍 Apply服务自动应用redo到备数据库来保持与主数据库的同步和允许事务一致性访问数据。 缺省情况下,Apply服务等待备redo日志文件进行归档,然后再应用归档日志文件包含的redo。然而,可以启用实时Apply,允许当前的…

CentOS8基础篇2:文件系统

一、文件系统概述 1.文件系统的基本概念 操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。它规定了文件的存储方式及文件索引方式等信息。文件系统主要由三部分组成,分别是与文件管理相关的软件、被管理的文件和实施文件管…

ip-guard如何查看客户端连接的服务器IP地址?

在客户端上通过“运行”输入win.ini打开文件(在目录C:\\Windows\\),可以从里面找到一个字段SIP,比如SIP=3232237616 再将其换算成16进制数为c0a80830

每天10个前端小知识 【Day 8】

前端面试基础知识题 1. Javascript中如何实现函数缓存?函数缓存有哪些应用场景? 函数缓存,就是将函数运算过的结果进行缓存。本质上就是用空间(缓存存储)换时间(计算过程), 常用于…

在CANoe/CANalyzer中给CAN Log.asc/blf文件“瘦身”

案例背景(共7页精讲): 该篇博文将告诉您,如何给离线文件CAN Log.asc/blf文件“瘦身”:批量删除/过滤 CAN Log中,不需要的CAN ID和CAN channel。 目录 1 准备工作 2 插入CAN Filter 3 保存“瘦身” 后的…

一种RK3399+MIPI+FPGA的高速工业相机的设计方案(一)

目 前 , 嵌 入 式 相 机 逐 渐 代 替 了 传 统 相 机 进 入 大 众 的 视 野 , 应 用 在 公 安 刑 侦 、 生 物 医 学和 文 物 保 护 等 诸 多 领 域 。 但 是 随 着 人 们 对 图 像 视 觉 成 像 质 量 追 求 的 提 升 , 图 像 传 感 器 的 特…

ESP32S3系列--SPI主机驱动详解(一)

一、目的SPI是一种串行同步接口,可用于与外围设备进行通信。ESP32S3自带4个SPI控制器外设(Master),其中SPI0/SPI1内部专用,共用一组信号线,通过一个仲裁器访问外部Flash和PSRAM;SPI2/3各自使用一组信号线;开…

【C++】二叉树的前序中序后序非递归实现

文章目录二叉树的前序遍历二叉树的中序遍历二叉树的后序遍历总结二叉树的前序遍历 前序遍历的顺序是根、左、右。任何一颗树都可以认为分为左路节点,左路节点的右子树。先访问左路节点,再来访问左路节点的右子树。把访问左路节点的右子树看成一个子问题…

VUE3 插件的开发和使用

在构建 Vue 项目的过程中,离不开各种开箱即用的插件支持,用以快速完成需求,避免自己造轮子。 在 Vue 项目里,可以使用针对 Vue 定制开发的专属插件,也可以使用无框架依赖的通用 JS 插件,插件的表现形式也是…

51单片机学习笔记_11 蜂鸣器,识简谱,根据简谱编写蜂鸣器代码

蜂鸣器实验 蜂鸣器简单地说,就是电磁线圈和磁铁对振动膜的作用。 单片机的是无源蜂鸣器,不能一直充电,需要外部控制器发送震荡信号,可以改变频率产生不同的音色、音调。 大多数有源蜂鸣器则没有这个效果,有源蜂鸣器…