【C++软件异常排查实战经验分享系列 ①】报错窗口点击重试 | 0xfeeefeee等异常值 | OutputDebugString | 数据断点 | if条件断点 | 历史版本比对法 | 汇编代码

目录

1、在Debug下调试,弹出报错提示框,点击重试,跳转到问题代码处

2、调试代码时遇到0xccccccc、0xcdcdcdcd、0xfeeefeee等常见异常值时,从异常值的含义入手进行针对性的排查

2.1、异常值0xcccccccc和0xcdcdcdcd

2.2、异常值0xfeeefeee

2.3、异常值0xdddddddd

3、调用系统API接口OutputDebugString,将打印日志输出到调试器输出窗口中

4、调试时程序发生异常闪退,调试器也退出了调试状态,可以尝试到VS的Output窗口中查看打印日志找线索

5、当代码中出现内存越界时,可以尝试使用数据断点去排查

6、在代码中临时添加if语句,去人为构造条件断点,比IDE中的条件断点要好用很多

7、将VS动态附加到程序进程上去调试单个模块的代码

8、当软件出现问题,无从下手排查或者找不到排查的突破口时,可以尝试使用历史版本比对法,找到首次出现问题的那个版本

9、有时我们要编写测试代码,去验证代码的有效性,或者去试探性地研究实现某一功能的方法

10、使用VS和IDA反汇编工具查看汇编代码


       在开发调试C++软件的过程中会遇到各式各样的问题,通过排查这些项目问题可以积累大量的实战排查经验和处理技巧,本文对这些经验和技巧做个总结和分享,以供大家借鉴或参考。

C++软件异常排查从入门到精通系列教程(核心精品专栏,订阅量已达8000多个,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战专栏(重点专栏,专栏文章已更新500多篇,订阅量已达6000多个,欢迎订阅,持续更新中...)https://blog.csdn.net/chenlycly/article/details/140824370C++ 软件开发从入门到实战(重点专栏,专栏文章已更新300多篇,欢迎订阅,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_2276111.html

1、在Debug下调试,弹出报错提示框,点击重试,跳转到问题代码处

       在Debug下调试代码时,有时会遇到这样的报错弹框:

这种情况较简单,只要点击重试按钮,调试器就会中断下来,并跳转到问题代码处:

然后打开VS的调用堆栈(call stack)窗口,去查看此时的函数调用堆栈,通过函数调用堆栈就能大概看出是执行了什么操作引发的异常。

        但有时光看函数调用堆栈,可能并不能找出问题,我们还可以尝试去看Output输出窗口中去查看输出的信息,编译器与程序可能会将一些调试打印信息输出到Output窗口中,从打印信息中可能能找到线索。比如我们在使用duilib界面框架编写窗口的xml文件时,写了非法的xml节点,调试时报异常,输出窗口中就有异常节点相关的打印:

从上图打印的信息可以看出,是duilib库在解析窗口的xml内容时产生了异常,duilib将有异常的xml节点附近的xml上下文打印到输出窗口中。通过这个打印我们就能快速地找到xml中出问题的节点了。上面截图中的问题是问题节点的开始符号“<”和结尾符“/>”不匹配导致的,是我们在手动编写xml文件时手误写错导致的。

2、调试代码时遇到0xccccccc、0xcdcdcdcd、0xfeeefeee等常见异常值时,从异常值的含义入手进行针对性的排查

       在调试代码时会时常遇到0xccccccc、0xcdcdcdcd、0xfeeefeee等常见的异常值,我们需要了解这些常见异常值的含义,这些含义可以给我们指明排查代码的方向,可能是排查问题的关键线索。

       对于0xfeeefeee、0xcccccccc、0xcdcdcdcd和0xdddddddd这些异常值,如果代码中把这些异常值当做地址去访问,则会触发内存访问违例,导致程序崩溃。很多人在调试时都或多或少遇到过这些异常值,如果对这几个异常值的含义比较熟悉,则能够确定问题的排查方向,阅读相关代码的上下文快速定位问题。

为什么访问0xfeeefeee、0xcccccccc、0xcdcdcdcd和0xdddddddd这几个内存地址会触发内存访问违例呢?

因为对于32程序,0xfeeefeee、0xcccccccc、0xcdcdcdcd和0xdddddddd这几个内存地址是属于内核态内存地址的范围,而我们的业务代码一般是运行在用户态的,用户态的代码是禁止访问内核态内存地址的,所以会触发内存访问违例,导致软件异常!

       这几个常见的异常值的说明,如下所示:(我们主要关注0xfeeefeee、0xcccccccc、0xcdcdcdcd和0xdddddddd这4个异常值

* 0xcccccccc : Used by Microsoft's C++ debugging runtime library to mark uninitialised stack memory.
* 0xcdcdcdcd : Used by Microsoft's C++ debugging runtime library to mark uninitialised heap memory.
* 0xfeeefeee : Used by Microsoft's HeapFree() to mark freed heap memory.
* 0xdddddddd:Used by MicroQuill's SmartHeap and Microsoft's C/C++ debug free() function to mark freed heap memory.
* 0xabababab : Used by Microsoft's HeapAlloc() to mark "no man's land" guard bytes after allocated heap memory.
* 0xabadcafe : A startup to this value to initialize all free memory to catch errant pointers.
* 0xbaadf00d : Used by Microsoft's LocalAlloc(LMEM_FIXED) to mark uninitialised allocated heap memory.
* 0xbadcab1e : Error Code returned to the Microsoft eVC debugger when connection is severed to the debugger.
* 0xbeefcace : Used by Microsoft .NET as a magic number in resource files.

2.1、异常值0xcccccccc和0xcdcdcdcd

       对于0xcccccccc和0xcdcdcdcd,在 debug 模式下,Visual Studio会把未初始化的栈内存全部填充成0xcccccccc,当成字符串看就是“烫烫烫烫……”;Visual Studio会把未初始化的堆内存全部填充成 0xcdcdcdcd,当成字符串看就是 “屯屯屯屯……”。所以遇到这两个值时,一般是变量未初始化引起的。

       关于排查0xcdcdcdcd异常值引发异常崩溃的实战排查案例,可以查看我之前写的文章:

排查软件启动时访问了0xcdcdcdcd内存地址导致内存访问违例的崩溃https://blog.csdn.net/chenlycly/article/details/1252667350xcdcdcdcd异常值引发C++程序崩溃问题的详细分析https://blog.csdn.net/chenlycly/article/details/128380751

2.2、异常值0xfeeefeee

       对于0xfeeefeee,是Debug下用来标记堆上已经释放掉的内存,即已经释放的堆内存中会被填充成0xfeeefeee。注意,如果指针指向的内存被释放了,指针变量本身的地址是没做改变的,还是其之前指向的内存的地址,只是其指向的堆内存中被填充成0xfeeefeee。如果该指针是一个类的成员变量,并且该类对象是在堆上分配内存的,则该类对象的堆内存被释放后(对于C++类,通常是执行delete操作),类对象中的指针变量就会被赋值为0xfeeefeee。所以,遇到这个0xfeeefeee时,可能是对应的堆内存被释放了引起的。

      关于排查0xfeeefeee异常值引发异常崩溃的实战排查案例,可以查看我之前写的文章:
排查软件关闭时访问了0xfeeefeee内存地址导致内存访问违例的崩溃https://blog.csdn.net/chenlycly/article/details/125267046

2.3、异常值0xdddddddd

       对于0xdddddddd,是Debug下用来标记堆上已经释放掉的内存,即已经释放的堆内存会被填充成0xdddddddd。之前在项目问题中看到的基本都是0xfeeefeee,没见到过0xdddddddd,但前段时间在Debug下调试代码时遇到了,代码中访问了已经释放的内存,内存中都被置为0xdddddddd,这还是第一次遇到0xdddddddd异常值!正是通过0xdddddddd的说明,得知这个0xdddddddd是用来填充已经释放的堆内存,以这个为线索,快速地定位了问题!后面我们在项目中遇到0xdddddddd异常值几次了!

       关于排查0xdddddddd异常值引发异常崩溃的实战排查案例,可以查看我之前写的文章:
访问0xdddddddd内存地址引发软件崩溃的问题排查https://blog.csdn.net/chenlycly/article/details/139800126访问0xdddddddd内存地址引发软件崩溃的又一问题排查https://blog.csdn.net/chenlycly/article/details/132631020       关于上述0xcccccccc、0xcdcdcdcd、0xfeeefeee 和 0xdddddddd异常值的更详细说明,可以查看我之前写的文章:

C++ 中常见异常内存地址或异常值的说明(0xcccccccc、0xcdcdcdcd、0xfeeefeee 和 0xdddddddd 等)https://blog.csdn.net/chenlycly/article/details/128285918

3、调用系统API接口OutputDebugString,将打印日志输出到调试器输出窗口中

       我们在调试程序时时常会看到一些开源库(比如WebRTC开源库)直接将运行日志输出到调试器的调试窗口中,如果当前在用Visual Studio调试程序,则会输出到Visual Studio调试器的输出窗口中。如果当前在用Windbg动态调试程序,则会输出到Windbg调试器的输出窗口中。

输出到调试器的输出窗口中,目的是让我们看到代码的运行轨迹以及相关变量值的打印,方便研究或排查问题。

       一般是调用系统API接口OutputDebugString将日志打印输出到正在调试的调试器的输出窗口中。那OutputDebugString是如何将打印日志(字符串)输出到调试窗口中的呢?我们可以到ReactOS操作系统的开源代码中查看OutputDebugString的内部实现:(和Windows接口的内部实现基本一致的,我们经常以ReactOS开源系统的接口实现去猜测Windows接口的实现

VOID WINAPI OutputDebugStringA(IN LPCSTR _OutputString)
{
    _SEH2_TRY
    {
        ULONG_PTR a_nArgs[2];
 
        a_nArgs[0] = (ULONG_PTR)(strlen(_OutputString) + 1);
        a_nArgs[1] = (ULONG_PTR)_OutputString;
 
        /* send the string to the user-mode debugger */
        RaiseException(DBG_PRINTEXCEPTION_C, 0, 2, a_nArgs);
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        // 此处代码省略
        // ......
    }
}

从上述代码可以看出,OutputDebugString内部调用了RaiseException接口产生了一个特殊的DBG_PRINTEXCEPTION_C异常(会产生了一个标准的EXCEPTION_RECORD异常结构),该异常会被发到内核的异常处理模块,然后由内核异常处理模块将该异常分发给用户态的调试器,调试器最终将打印日志显示到调试器的输出窗口中。

       我们来看看WebRTC开源库内部是如何调用OutputDebugString接口的。查看到WebRTC如下的源码:


       此外,VS中的TRACE宏也是将打印日志输出到调试器窗口中的,其内部也是调用OutputDebugString接口实现的。TRACE宏的内部实现如下:

#define TRACE                       CONCRT_TRACE
 
// Enable tracing mechanisms
#if defined(_DEBUG) && defined(CONCRT_TRACING)
# define CONCRT_TRACE(...)  ::Concurrency::details::_ConcRT_Trace(__VA_ARGS__)
#else
# define CONCRT_TRACE(...)  ((void)0)
#endif
 
// Trace -- Used for tracing and debugging
void _ConcRT_Trace(
    int trace_level,
    const wchar_t * format,
    ...
    )
{
    InitializeUtilityRoutines();
 
    // Check if tracing is disabled
    if ((g_DesiredTraceLevel & trace_level) == 0) {
        return;
    }
 
    wchar_t buffer[1024+1];
 
    va_list args;
    va_start(args, format);
    ConcRT_FillBuffer(buffer, format, args);
    va_end(args);
 
    buffer[1024] = 0;
 
    if (g_DebugOutFilePtr != NULL)
    {
        fwprintf(g_DebugOutFilePtr, buffer);
        if (g_CommitFrequency > 0 && (g_TraceCount++ % g_CommitFrequency) == 0)
            fflush(g_DebugOutFilePtr);
    }
    else
    {
        OutputDebugStringW(buffer);
    }
}

        TRACE宏只在Debug下才有用,Relase下是无效的。TRACE宏内部在Debug下调用了API函数OutputDebugString,在Relase下该宏定义为空。


       在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)

专栏1:该精品技术专栏的订阅量已达到10000多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,已经更新到200篇以上!欢迎订阅!)

C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931

本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法详细讲述了C++软件的调试方法与手段详细介绍分析C++软件问题的常用分析工具,以图文并茂的方式给出具体的项目问题实战分析实例(详细讲述分析排查过程,很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!

专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!

专栏2:(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,订阅量已达8000多个,专栏文章已经更新到500多篇,持续更新中...)

C/C++实战进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法C++11及以上新特性(开源代码中可能会用到很多新特性(比如WebRTC开源库),日常编码中也会用到部分新特性,面试时也会频繁地涉及到,学习新特性很有必要)、常用C++开源库的介绍与使用(比如SQLite、libcurl、libwebsockets、libevent、jsoncpp/RapidJson、Redis、RabbitMQ、MongoDB、MQTT、ZooKeeper、OpenCV、FFmpeg、SDL、GStreamer、Live555、ReactOS等)、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(引发C++软件异常的常见原因分析与总结、排查C++软件异常的手段与方法、分析C++软件异常的基础知识、使用常用软件分析工具分析C++软件问题、多个项目实战问题分析案例分享等)、设计模式(单例模式、工厂模式、观察者模式、状态模式等)、网络基础知识与网络问题分析进阶内容(实战问题分析实例分享)等。本专栏的内容都是建立在项目实践的基础上,来源于项目实战,服务于项目实战,很有实战参考价值!

专栏3:  

C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795

常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!

专栏4:   

VC++常用功能开发汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/124272585

将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。

专栏5: 

C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.html

根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。


4、调试时程序发生异常闪退,调试器也退出了调试状态,可以尝试到VS的Output窗口中查看打印日志找线索

       在使用开源的duilib实现UI窗口时,如果窗口对应的xml文件中出现不合法的节点时,VS调试窗口会看到“XML Error: Unmatched closing tag”的打印,如下所示:

虽然VS退出了调试,但是输出窗口中打印信息一直都有的。这个异常打印实际上是调用OutputDebugString接口将打印日志输出到调试窗口中的,我们可以看一下duilib中的源码:

CMarkup::_Failed中不仅打印出了XML Error: Unmatched closing tag错误类型,还将附近的xml内容打印出来了,这样更方便我们定位问题。

       使用VS调试程序时发生Stack Overflow线程栈溢出异常,VS退出调试状态,但输出窗口中的信息一直都在,不会清空,可以看到程序中发生了Stack Overflow异常:

5、当代码中出现内存越界时,可以尝试使用数据断点去排查

       数据断点在排查内存越界时非常好用,我们在项目中使用过几次。

       有时会遇到变量值无故被篡改,导致程序逻辑出现异常或产生崩溃这类问题我们遇到好几次了。在最初排查问题时,搜索了操作变量的所有代码,仔细排查了都没问题,并添加了日志打印,运行程序后打印出来的日志显示也没问题,但变量还是被无缘无故地被篡改了!

       这种情况一般都是内存越界导致的,内存越界有全局内存越界、栈内存越界和堆内存越界,一般直接排查代码是很难找到问题的。遇到这种情况,应该第一时间想到数据断点,第一时间让数据断点上场。

       设置数据断点其实就是监控目标变量的内存,一旦内存被修改,就会命中该数据断点,调试器就会中断下来设置数据断点是有技巧的,被监控的目标变量需要在被分配内存后才能被监控,一般在类的构造函数中先设置一般断点,命中断点后,目标监控变量就分配了内存,就可以通过表达式“&变量名”去设置数据断点了:(可以设置监控的内存长度)

0fbe8c72bf0d411f8b96382298dc4295.png

一旦被监控的内存被修改,就会命中对应的数据断点,此时去查看函数调用堆栈就可以看到是什么代码篡改了目标变量的内存了。

       这个地方需要留意一下,正常的赋值也是修改内存中的内容,也会命中数据断点,所以要将正常的赋值和内存越界篡改内存的情况区别开来。

       不仅VS中支持设置数据断点,Windbg动态调试时也可以使用ba命令对某个内存地址设置数据断点。

       关于使用数据断点的实战分析案例,可以查看我的文章:

巧用Visual Studio中的数据断点去排查C++内存越界问题https://blog.csdn.net/chenlycly/article/details/125626617

6、在代码中临时添加if语句,去人为构造条件断点,比IDE中的条件断点要好用很多

       有时我们排查问题时需要针对某些数据或者类对象进行单步调试,但同类型的对象比较多,我们通过条件断点可以过滤出我们要找的对象。但IDE提供的条件断点不太灵活,我们可以手动编写测试代码,在测试代码中设置过滤条件,将目标对象过滤出来,然后进行单步调试。      

       比如要在UI界面库duilib中调试UI窗口中的某个控件对象,如果直接在控件代码中打断点,会因为很多窗口都用到该类控件,会多次命中断点,而我们只想调试某个窗口的控件对象,多次中断下来很麻烦!我们可以添加临时代码,设置if条件语句,通过控件名称去过滤一下,比如:

// 通过控件的name名称找到对应的控件对象
if ( _tcsicmp(GetName(), _T("BtnRefreshArchData")) == 0)
{
	int i = 0;
}

       再比如在加载组织机构数据时,当处理到某个部门或人员时出现问题,需要单独对问题部门或人员进行单步调试。因为组织架构中部门和人员比较多,不能直接在代码中设置断点,否则会多次命中断点,很难精确找到目标节点进行调试。此时我们可以根据部门名称或者人员姓名(或人员id)设置if语句的过滤条件

// 通过部门名称找到对应的部门节点
if ( _tcsicmp(pDepartment->GetName(), _T("音视频算法部")) == 0)
{
	int i = 0;
}

在条件中设置断点,这样就能精准找到目标节点进行调试了。

7、将VS动态附加到程序进程上去调试单个模块的代码

       程序中包含了多个业务模块(业务模块封装成动态库),不同的业务模块由不同的开发团队开发维护。对于最上层的UI主程序模块,维护的团队可以直接调试主程序的代码,但主程序依赖的底层库(底层库都以dll动态库发布过来,主程序模块这边没有代码),则需要其他团队去调试底层模块。如果底层模块出问题,一般会优先使用打印日志去排查,但分析不出来时,则可能需要直接去调试底层模块的代码。而底层模块以dll形式存在,不能独立运行,不能直接进行调试,需要依赖于产品的主程序才能运行。

       维护底层模块的团队可能为了方便底层模块的开发与测试调试,直接基于这些模块编写一个demo程序(这个demo程序可能是MFC写的,也可能是用Python写的),这些情况下可以直接调试底层模块的代码。

       但有些问题在产品主程序中才会出现,这时要调试底层模块的代码,则需要将VS附加到主程序进程上进行调试了:

将VS附加到主程序进程上调试底层模块的操作步骤如下

1)在调试代码的机器上安装产品程序的安装包;
2)在VS中打开待调试的底层模块工程,编译代码,将编译出来的dll动态库拷贝到安装目录中。注意,重新编译生成dll时,需要手动再把新的dll拷贝到安装目录中,因为调试时加载pdb符号文件时需要校验pdb文件的时间戳,时间戳对不上,是不会加载的,也就不能调试了。
3)双击程序,将程序运行起来。
4)在VS中打开的代码中设置断点,点击VS菜单栏中的调试->附加到进程...,在打开的窗口中的进程列表中找到软件进程,选中该进程,点击附加按钮即可开启附加到进程的调试。

这样当代码执行到底层模块中就会命中断点进行调试了。

      此外,有个技巧需要注意一下。如果问题出在模块初始化的代码中,主程序启动时就会去加载该模块动态库,等程序启动起来后,模块的初始化代码已经跑过去了,就来不及附加调试了。此时可以在初始化的代码执行之前弹出一个MessageBox窗口,将代码阻塞一下,这样就有机会附加调试了。将VS附加上之后,点击弹出的MessageBox中的确定按钮,关闭该弹窗,代码继续运行,开始执行初始化代码,这样就能调试初始化的代码了。

8、当软件出现问题,无从下手排查或者找不到排查的突破口时,可以尝试使用历史版本比对法,找到首次出现问题的那个版本

       有些问题通过查看打印日志和查看代码很难找到排查问题的线索,无从下手,比如内存越界与内存泄漏问题。再者,程序由多个模块构成,很难确定是哪个模块引发的。此外,代码是多个人写的,排查问题的人对其他人写的代码及相关的业务逻辑不熟悉,代码量也比较大,可能很难通过查阅代码去定位问题。

       如果问题好复现或者不难复现(可能找到了复现的规律),则可以尝试使用历史版本比对法。让测试人员在时间维度上采用二分法,安装不同时间点的版本,看看问题是从哪个版本出现的。

       但需要注意的是,使用历史版本比对法需要一个完备的自动化版本编译系统,编译出来的版本的颗粒度要小。只要当天有人修改代码或者有底层库发布过来,当天晚上都会自动编译版本,如下所示:(每天编译出来的版本存放在日期的文件夹中)

有这样以天为颗粒度的版本编译系统,才好使用历史版本比对法。如果没有自动化版本编译系统,版本是开发人员手动编译出来的零散版本(每个版本之间可能间隔很多天),则使用历史版本比对法的意义可能就不大了。

       在时间维度上使用二分法安装不同时间点的版本,确定问题是从哪个版本出现的,然后在svn或git上查看前一天的代码提交记录以及库发布记录,问题基本就出在前一天提交的代码或者发布的库中了。

       我们在项目上多次使用历史版本比对法,虽然操作起来相对笨拙了一些,但能精准地确定问题的排查范围,非常有效!比如我们在排查GDI对象泄漏、内存越界与内存泄漏时都用过此方法。

       使用历史版本比对法排查软件问题的实战分析案例,可以查看我的文章:
使用GDIView工具排查GDI对象泄漏导致程序UI界面绘制异常的问题(使用历史版本比对法) https://blog.csdn.net/chenlycly/article/details/128625868使用历史版本比对法排查C++程序中的内存泄漏问题(同时总结了引发内存泄漏的常见场景)https://blog.csdn.net/chenlycly/article/details/144766453

9、有时我们要编写测试代码,去验证代码的有效性,或者去试探性地研究实现某一功能的方法

       比如我们在研究与QQ、企业微信、PC版微信进行剪切板数据对通(主要针对图片与文字内容的混合复制的场景)时,遇到了多个问题,在研究过程中提出了多个假设,并多次修改代码进行测试验证,最终找到问题的完美解决办法。对应的实战案例,已整理成文章,可以去查看:

复制文字与图片的混合内容到QQ、PC版微信以及企业微信中的剪切板数据对通深入研究及问题解决总结https://blog.csdn.net/chenlycly/article/details/147134261       再比如,之前在研究我们软件多年前实现的一个功能时,当时通过目标窗口调用SetParent阴差阳错地实现了某个奇特的功能,但对目标窗口调用SetParent后窗口的行为与特性,与我们的认知出现了很大的偏差。尝试从代码中去寻找特殊处理的代码,但并没有找到。于是决定添加测试代码,对我们开发的另一个窗口调用SetParent去研究函数调用后的窗口行为,与目标研究窗口做个对比,然后结合deepseek大模型,最终找到了答案。最终搞清楚了对窗口调用SetParent接口后对窗口行为与特性造成的诸多影响,这些内容是以前不知道的。对应的实战案例,可以查看文章:

对窗口调用SetParent后改变了窗口的诸多行为和特性说明与总结 https://blog.csdn.net/chenlycly/article/details/148627449       很多时候,我们在遇到问题时,搞清楚问题的来龙去脉,可能需要通过构建测试代码去验证、去做试探性的研究!

10、使用VS和IDA反汇编工具查看汇编代码

        熟悉汇编代码,不仅可以理解很多高级语言无法理解的代码执行细节,而且可以辅助排查 C++软件异常问题。

       对于C++软件,最终执行的是一条一条二进制机器码,等同于汇编代码(汇编代码是二进制机器码的助记符),可以理解为执行的是一条一条汇编代码。从汇编代码的角度,可以看到更多的代码执行细节。

       此外,C++软件发生异常崩溃时,是崩溃某条汇编指令上:

在执行这条汇编指令时发生了异常崩溃,比如汇编指令访问了一个很小的地址引发内存访问违例。再比如,在汇编指令中访问了一个很大的内核态内存地址,而用户态的程序是禁止访问内核态内存地址,所以也会引发内存访问违例,导致程序崩溃。

汇编指令能直观地反映出崩溃的原因。要搞清楚崩溃的根本原因,可以尝试查看汇编代码。

       但查看汇编代码需要有一定的汇编基础,关于学习汇编代码的好处以及如何学习汇编,可以查看我的专题文章:

C/C++程序员为什么要了解汇编?了解汇编有哪些好处?如何学习汇编? https://blog.csdn.net/chenlycly/article/details/142795872       从汇编的角度去理解高级语言无法理解的代码执行细节以及通过汇编去辅助排查问题的实战案例,可以查看我的文章:
根据发生异常的汇编指令以及函数调用堆栈,从内存的角度出发,估计出问题的可能原因,确定排查方向,快速定位C++软件问题 https://blog.csdn.net/chenlycly/article/details/143138724使用反汇编工具IDA查看动态库中的汇编代码上下文,结合安卓系统生成的Tombstone文件,排查安卓app程序底层C++库崩溃问题 https://blog.csdn.net/chenlycly/article/details/135484104使用反汇编工具IDA查看发生异常的汇编代码的上下文去辅助分析C++软件异常 https://blog.csdn.net/chenlycly/article/details/132158574       查看汇编代码可以有两种方式,一种是在用Visual Studio调试C++代码时转到汇编代码页面查看代码,另一种是用反汇编工具IDA打开二进制文件查看二进制文件中的汇编代码。

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dvlinker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值