调试和优化随笔

1.发现Assert断言出错,此时知道被断言的指针值为NULL,怎么知道到底在哪给它赋NULL呢?
(1)用VC->breakpoint->data->enter the expression...
     注意:如果出现cannot set break point...,必须先调试进入表达式变量所在类或函数,再编辑。
    但是对于频繁改变的变量,调试速度会很慢,这种方法行不通,怎么办呢?
(2)在给该指针变量赋值的地方埋下TRACE语句,看在断言出错之前是哪里对它进行赋值。

2、ASSERT作用。
   如,程序在某地方由于某变量错值荡掉,可是发布的Debug程序没法知道哪里出错,如果此时加了断言,

就能提醒用户,原来在哪里错了。

3、知道指针变量的值为0xdddddddd,怎么找到导致该值无效的代码?
   导致指针变量为0xdddddddd的原因有两个:
  (1)变量未赋初值。这容易解决。
  (2)变量指向的空间被Delete,但指针没被赋NULL。这需要在给该变量赋值的地方埋下TRACE语句,在

Delete该指针可能指向空间的地方埋下TRACE语句,在new该指针可能指向的空间的地方埋下TRACE语句,当程

序因为变量的值为0xdddddddd出错时,可以看下哪里它赋值0xdddddddd,给它赋0xdddddddd的变量在成为

0xdddddddd之前的值是什么,这之前的值就是被new出的空间,后被delete的空间。然后看下为什么,

delete该空间后却为什么没给该变量赋NULL。

4、当某个函数被多个函数调用时,却在该函数中出现错误,要想知道是哪个函数调用它时,可以用VC

Callback函数栈查看。

5、VC trace工具比较烂,它只能在DEBUG环境下调试;他在一个列表中列出所有TRACE内容,以前的TRACE内

容影响当前TRACE内容的查看。
用自己做的TraceWindow做工具会比较好。
最好用共享内存的方法否则速度太慢。
要支持定期保存。

6、有时程序突然死掉,怎么找到死掉的原因能?
如果没有更好的方法,不妨试一下用排除法,即去掉程序的一些模块看问题是否还在,不断缩小包围圈,以

解决问题。


7、当程序扩展了功能后,出现了很多bug,一时很难解决,怎么办?找老版本的程序代码找出来,想一下怎

么更好的扩展功能而不影响原来程序。

8、内存泄漏的检测:
(1)先用BoundsChecker或Rational purify等工具自动检测程序内存泄漏。
(2)最好的内存泄漏检测方法是自己编写内存检测代码。但须花时间。

方法:
(1)Performance Monitor自动检测程序内存泄漏,不用嵌入代码。
(2)先用BoundsChecker自动检测程序内存泄漏,不用嵌入代码。
(3)Rational purify自动检测程序内存泄漏,不用嵌入代码。
(4)virtual studio Enterprice自带的profile,没用过。
(5)CMemoryState检测内存泄漏,嵌入代码。
(6)CRT内存泄漏检测,嵌入代码。
(7)Visual Leak Detector检测内存,嵌入代码。
(8)MFC自带的内存泄漏检测,在Debug下,不用嵌入代码。

9、程序优化:
(1)先用Rational Quality自动扫描性能信息,再优化。
(2)最好的扫描性能信息方法是自己编写性能扫描代码。但须花时间。

10、书籍:《程序代码的优化》、《Debugging   Windows   Programs》(中文版译作《Windows程序调试》,中国电力出版社出版)、《Debugging   Applications》、《Writing   Clean   Code——Microsoft   Techniques   for   Developing   Bug-free   C   Programs》(中文版译作《编程精粹——Microsoft编写优质无错C程序秘诀》或者叫做《零错误程序》)

11、怎么知道程序在哪死掉,如碰见死锁?
最坏的方法是:在每个锁前加个Trace,注意Trace的内容包括线程的ID,这样可以知道是那个线程死锁。

 

12、程序内存读写出错(如越界)用CheckBound工具可以知道吗?

 

13、多线程访问同一资源冲突,怎么处理?

(1)给资源加锁。

(2)把行为集中到一个线程。如,发送消息,自定义命令队列。

14、一个很奇怪的BUG:程序启动后,界面会变黑,此时刷新下界面,界面才正常。

调试了半天,后来无意中发现,窗口消息异常多。再看消息内容,最后才知道是CEdit派生类CSkinEdit::PreTranslateMessage函数中没有调用CEdit::PreTranslateMessage函数,导致同一个消息在不断循环发送。所以其他窗口消息无法被执行,导致窗口黑屏。

由此得出一个调试UI迟缓的方法,用SPY++查看UI消息,看是否有异常消息。

 

15、try catch的使用,有时候在构造函数中可能会去读文件,此时如果出错程序就只能退出,如果用try catch就不用。

同时内存当耗尽时,程序也只能退出,如果用try catch就不用。有时点击界面某个按钮,程序会一层一层去调用下层函数,如果最下层的出了错,此时需撤销按钮的所有动作,用try catch比用if else return要好。
 其实try catch无法捕获内存溢出等内存出错问题,需用__try __except 来捕获内存错误。

16、怎么用rational purify or rational quanlity or checkbound来调试动态库,并定位到动态库的源代码中。

答:这三个工具都有source file search path把它定位到源代码所在即可。

 

17、用VC调试程序时,有时会提示“应用程序发生异常,位置为0x004017b7",诸如此类的错误,其实0x004017b7正是错误代码段的位置,可以配合VC 产生的map文件定位到错误所在。当然如果该地址处于system dll中的话,则无法用此方法。

 (1)如果出错的是动态库,则用WinDBG获取DLL所处的起始、终止位置,然后计算“出错地址”在动态库中的偏移位置,再到动态库map里找到偏移地址所处的那个函数(找到小于偏移地址的最大地址对应的函数)。

(2)如果是exe则直接在exe map中计算偏移,再获取所在函数即可。

(3)WinDBG直接定位错误的代码位置。步骤如下:

        OpenExecutable->lm查看模块信息,定位错误所在模块->ld ModuleName加载模块符号->x /a查看按地址排序

        好的模块符号地址,最接近的就是你要找的出错的地方。(或者ln Address得到地址对应的符号。)

(4)获取出错地址所在函数有时并不能确定问题的症结,这就需要用到CALLSTACK技术,就是当出错时把这时的CALLSTACK给TRACE出来。而Release程序是不用VC工具调试的,所以CALLSTACK只能自己编程截取。如下是截取CALLSTACK代码。

#ifndef _WZ_DEBUG_H_
#define _WZ_DEBUG_H_

#ifndef _DS_EXT_DSDEBUG_CLASS
#define _DS_EXT_DSDEBUG_CLASS _declspec(dllimport)
#endif
#pragma comment(lib, "dsDebug.lib")
#pragma message("Automatically linking with dsDebug.dll")
struct CALLSTACKITEM
{
 void *vAddress;

};


class _DS_EXT_DSDEBUG_CLASS CWzDebug
{
public:
 CWzDebug();
 virtual ~CWzDebug();
 
 void GetCallStack(CONTEXT *context, CALLSTACKITEM *(&pCallStackItem), int& nCount);
 void ReleaseCallStack(CALLSTACKITEM *(&pCallStackItem));
 BOOL Init();
 BOOL UnInit();

};

 


#endif

 

#include "StdAfx.h"
#include "wzDebug.h"

#include <dbghelp.h>
#pragma comment( lib, "dbghelp.lib" )
#include <assert.h>


#define JMP_INSTR 0xE9
#define SYM_BUFF_SIZE 512
static BYTE g_stSymbol [ SYM_BUFF_SIZE ];

CWzDebug::CWzDebug()
{


}

CWzDebug::~CWzDebug()
{

 

}

BOOL CWzDebug::Init()
{
 /** init dbghelp.dll
 if( SymInitialize( GetCurrentProcess(), NULL, TRUE ) )
 {
  return TRUE;
 }

 return FALSE;


}

BOOL CWzDebug::UnInit()
{
 if( SymCleanup( GetCurrentProcess() ) )
 {
  return TRUE;
 }
 return FALSE;
}

 

void CWzDebug::GetCallStack(CONTEXT *context, CALLSTACKITEM *(&pCallStackItem), int& nCount)
{
   
 STACKFRAME sf;
 memset( &sf, 0, sizeof( STACKFRAME ) );

 sf.AddrPC.Offset = context->Eip;
 sf.AddrPC.Mode = AddrModeFlat;
 sf.AddrStack.Offset = context->Esp;
 sf.AddrStack.Mode = AddrModeFlat;
 sf.AddrFrame.Offset = context->Ebp;
 sf.AddrFrame.Mode = AddrModeFlat;

 DWORD machineType = IMAGE_FILE_MACHINE_I386;

 HANDLE hProcess = GetCurrentProcess();
 HANDLE hThread = GetCurrentThread();

 while(TRUE)
 {
  if( !StackWalk(machineType, hProcess, hThread, &sf, context, 0, SymFunctionTableAccess, SymGetModuleBase, 0 ) )
  {
   break;
  }

  if( sf.AddrFrame.Offset == 0 )
  {
   break;
  }

  BYTE symbolBuffer[ sizeof( SYMBOL_INFO ) + 1024 ];
  PSYMBOL_INFO pSymbol = ( PSYMBOL_INFO ) symbolBuffer;

  pSymbol->SizeOfStruct = sizeof( symbolBuffer );
  pSymbol->MaxNameLen = 1024;

  DWORD64 symDisplacement = 0;
  if( SymFromAddr( hProcess, sf.AddrPC.Offset, 0, pSymbol ) )
  {
   wzTrace("GetCallStack", "Addr:%0x, Function : %s/n", sf.AddrPC.Offset, pSymbol->Name );
  }
  else
  {
   wzTrace("GetCallStack", "Function Addr:%0x /n", sf.AddrPC.Offset);
  }


  IMAGEHLP_LINE lineInfo = { sizeof(IMAGEHLP_LINE) };
  DWORD dwLineDisplacement;

  if( SymGetLineFromAddr( hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo ) )
  {
   wzTrace("GetCallStack", "[Source File : %s]/n", lineInfo.FileName );
      wzTrace("GetCallStack", "[Source Line : %u]/n", lineInfo.LineNumber );
  }
  else
  {
   wzTrace("GetCallStack", "SymGetLineFromAddr failed!/n" );
  }

 }


}

void CWzDebug::ReleaseCallStack(CALLSTACKITEM *(&pCallStackItem))
{

 if(pCallStackItem != NULL)
 {
  delete[] pCallStackItem;
  pCallStackItem = NULL;
 }


}

18、添加以下语句可以让你轻松定位到内存泄漏的地方。

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

 

19.唯一实例要在程序运行之前判断,可是如果在Main函数里才判断的话,此时全局构造函数已经被调用,这是不允许的。下面方法可以在程序全局类构造函数被调用之前调用。

int myinit()
{
 printf("myinit/n");
 return 0;
}


typedef int cb(void);
#pragma data_seg(".CRT$XIU")
static cb *autostart[] = { myinit };
#pragma data_seg()

 

20.“First-chance exception in wd5.exe (KERNEL32.DLL): 0xE06D7363: Microsoft C++ Exception.”

有时在DEBUG时,会发现以上错误,虽然它是可以忽略的错误,但是也是可以找到引起该错误的原因,并把它解决掉的。

解决方法是:用WinDBG调试,在命令行中输入Sxe 0xE06D7363,如果出现以上错误时,WinDBG马上停止运行,此时查看CallStackFunction可以知道程序在哪里出错,以及此时它的上下文。

参考文章如下:

 

在现在C++Java.Net代码大行其道的时候,很多代码错误(Bug)都是通过异常的形式表现出来的。由于工期紧或者种种原因,很多程序员在碰到程序发生未处理的异常的第一反应就是try catch (Exception e) { }。然而代码开发到后期的时候,这种简单粗暴的解决代码错误(Bug)的方式就会在其他不相干的地方表现出来,有的时候甚至导致程序随机的不稳定,而且很难调试。比如执行下面的代码你会发现输出的值是12345.6789

 

 

 

using System;

 

 

 

public class Class1

 

{

 

    public static void Main()

 

    {

 

        Console.WriteLine(Calculate("12345.6789 + 987654321l"));

 

    }

 

 

 

    private static double Calculate(string expression)

 

    {

 

        string[] numbers = expression.Split('+');

 

 

 

        return RedundantParseForDemoOnly(numbers[0].Trim()) +

 

               RedundantParseForDemoOnly(numbers[1].Trim());

 

    }

 

 

 

    private static double RedundantParseForDemoOnly(string number)

 

    {

 

        try

 

        {

 

            return double.Parse(number);

 

        }

 

        catch

 

        {

 

            return 0;

 

        }

 

    }

 

}

 

 

 

 

当然啦,你可以说只要强迫项目组所有程序员不要catch通用异常就可以了,但是老虎总是有打盹的时候,而且大部分项目组因为人才梯队建设的问题都会有那么几个新人……

 

 

 

我在工作中发现很多程序员都不理解,或者说根本就没有去注意过表2里面的输出,浪费了很多好的调试机会。因此我看到很多优秀的程序员都是非常辛苦的单步调试,一步步地跟踪代码。这个过程不仅辛苦,而且非常容易出错,因为单步跟踪代码容易让人犯晕。实际上你可以利用Windows提供的结构化异常处理(SEH)来帮助你快速找到问题所在。

 

 

 

在程序运行的时候,特别是程序里面发生异常的时候,如果你使用windbg.execdb.exe等命令行调试器的时候,或者如果你留心Visual Studio的输出( output)窗口里的文本输出的时候,你将会看到类似下面的输出:

 

First-chance exception at 0x004116a9 in 异常处理.exe: 0xC0000005: Access violation writing location 0x00000000.

 

……

 

First-chance exception at 0x7c812aeb in 异常处理.exe: Microsoft C++ exception: char at memory location 0x0012fd88..

 

 

 

 

异常机制简介

CPU运行到一些非法的指令,例如除零错误,访问内存页失败等指令,CPU会生成一个硬件异常,不同的异常有固定的异常代码作为标识符,异常产生以后CPU暂时不能继续执行后续的指令—因为后续的指令有可能也是无效的。当然不能让整个计算机系统就这么当掉,因此CPU内置了一个异常处理表—这个异常处理表只有运行在内核模式的代码才能访问,操作系统在启动的时候初始化这个异常处理表,为每一个异常注册一个异常处理程序,因此这个表看起来就像:

 

0xC0000005

 AccessViolationExceptionHandler()

 

……

 

0x80000003

 LaunchOrNotifyDebugger()

 

 

 

 

CPU产生异常以后,根据异常代码在异常处理表里面查找对应的异常处理程序,通过调用异常处理程序执行了合适的处理之后再继续执行其他的代码。

 

 

 

处理CPU自定义的硬件异常以外,异常处理表里面还有一些空的表项,这样操作系统可以通过添加自定义的异常代码和相应的异常处理程序实现软件异常。在Windows操作系统中,程序可以通过调用Win32RaiseException函数来触发软件异常。因此实际上C++.NET里面的异常都是通过调用RaiseException来实现的,然而异常处理表不是很大,只能保存不超过256个异常处理程序信息,因此Windows只定义了几个通用的异常码来表示C++ .NET异常。

 

 

 

0xE06D7363

 表示C++ 异常

 

0xE0434f4D

 表示CLR(或者说.NET)异常

 

 

 

 

对于CPU来说,硬件异常、软件异常、C++ 异常和.NET异常的触发和处理的方式都是一样的,这种方式在Windows中叫做结构化异常处理(SEH)。

 

异常处理

1.         当程序里面有一个异常发生以后;

 

2.         SEH首先通过回溯堆栈内容,查找程序内部是否有一个异常处理块(就是我们通常知道的catch 块);如果找到一个异常处理块可以处理这个异常,那么Windows将异常处理块所在的函数下面的堆栈释放,并且执行这个异常处理块里面的代码。

 

3.         如果Windows没有发现任何一个异常处理块处理掉这个异常的话,也就是到程序入口(main)函数也没有找到一个合适的异常处理块的话,Windows会使用它自带的异常处理块处理这个异常;

 

4.         Windows自带的异常块会检查你的程序是否附加了一个调试器,如果是的话,Windows中断程序并将控制权交给调试器。

 

5.         如果没有调试器附加到你的程序上,Windows启动注册在注册表里面的默认验尸调试器,一般情况下,Windows的默认调试器是Dr Watson,这个调试器的工作就是弹出著名的“调试还是终止,这个问题”对话框(或者是“是否将错误报告发送给微软?”对话框):

 

 

 

 

 

在上面的处理步骤中,第一步通过RaiseException触发异常的时候,Windows会先检查你的程序是否正在被调试,如果有一个调试正附加在你的程序上,Windows向调试发送一个调试消息,通知调试器程序里面有一个异常发生,询问调试器是否要中断程序执,默认情况下调试器会忽略这条消息—因为我们纯洁的调试器总是相信程序员都能写出优良的代码出来。这个步骤在调试术语中叫做第一次机会(First Chance--第一次在异常发生的现场观察触发异常的环境。

 

 

 

而第3步以后,操作系统自己来处理这个异常的时候,在调试术语中叫做第二次机会(Second Chance),由于这个时候程序实际上已经挂了(不会有任何生命活动了)—不是病危,所以这个时候即使你将调试器附加上去来检查触发异常的环境时,就像法医在检查人非正常死亡的原因一样—验尸调试。

 

设置调试器不要忽略first chance异常

在程序开发的时候,我们当然是希望在程序病危(first chance异常)的时候检查代码错误(Bug)的原因啦,因此如果程序发生一些莫名其妙的Bug时,在调试程序的时候,最好通知调试器不要忽略first chance异常。因为如果异常被程序内部的代码catch掉的话,很有可能会导致我们忽略重要的线索。

 

 

 

Windbg里面,使用下面的命令来通知调试器不要忽略first chance异常:

 

sxe 异常代码

 

注意sxe后面有一个空格,例如在调试.NET程序的时候,可以使用命令sxe 0xE0434f4D来使调试器在catch块执行之前中断程序的执行。

 

可以使用命令

 

sxd 异常代码

 

来启用忽略first chance异常的功能。

 

 

 

Visual Studio里面,请参考我的另外一篇文章学习如何设置:CLR Debugger - 在程序抛出异常的时候中断程序的执行

 

如何知道调试器正在忽略first chance异常?

通过查看调试器里面的输出信息,你可以了解调试器是否启用了在first chance异常触发时中断程序执行的功能。

 

 

 

Visual Studio里面,可以查看“输出”窗口里面的文本输出:

 

这个输出就说明调试器忽略了first chance异常

 

 

 

First-chance exception at 0x004116a9 in 异常处理.exe: 0xC0000005: Access violation writing location 0x00000000.

 

……

 

First-chance exception at 0x7c812aeb in 异常处理.exe: Microsoft C++ exception: char at memory location 0x0012fd88..

 

 

 

 

Windbg里面,类似下面的输出表示调试器忽略了first chance异常:

 

0:000> g

 

(f08.f38): Access violation - code c0000005 (first chance)

 

First chance exceptions are reported before any exception handling.

 

This exception may be expected and handled.

 

(f08.f38): C++ EH exception - code e06d7363 (first chance)

 

 

附录

你可以使用下面的代码去尝试一下上面讲解的概念:

 

// 异常处理.cpp : Defines the entry point for the console application.

 

//

 

 

 

#include "stdafx.h"

 

#include <iostream>

 

#include <windows.h>

 

#include <excpt.h>

 

 

 

using namespace std;

 

 

 

int ExceptionFilter(unsigned int code, struct _EXCEPTION_POINTERS *ep)

 

{

 

    if ( code == EXCEPTION_ACCESS_VIOLATION )

 

        return EXCEPTION_EXECUTE_HANDLER;

 

    else

 

        return EXCEPTION_CONTINUE_SEARCH;

 

}

 

 

 

void CppTestFunction()

 

{

 

    try

 

    {

 

        throw "Test Cpp Exception";

 

    }

 

    catch ( char * )

 

    {

 

        cout << "char * exception caught" << endl;

 

    }

 

}

 

 

 

int _tmain(int argc, _TCHAR* argv[])

 

{

 

    int *p = NULL;

 

 

 

    __try

 

    {

 

        *p = 1;

 

    }

 

    __except ( ExceptionFilter(GetExceptionCode(), GetExceptionInformation()))

 

    {

 

        cout << "ACCESS VIOLATION CAUGHT" << endl;

 

    }

 

 

 

    CppTestFunction();

 

 

 

    return 0;

 

}

 

 

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Donjuan/archive/2009/02/02/3859160.aspx

 

 

21、让Error无处遁形。

 

注意这段话:

“if( !SymInitialize( GetCurrentProcess(), NULL, TRUE ) )
 {

  //return 0;
 } ”

 

return必须注销掉,否则会使WzUnhandledExceptionFilter漏掉很多错误,

如,当系统内存快用完时,可能SymInitialize会执行失败,此时出现内存分配失败的问题,

这时如果返回就漏掉了该问题。

 

LONG WINAPI CWzDebug::WzUnhandledExceptionFilter(
struct _EXCEPTION_POINTERS *lpTopLevelExceptionFilter)
{
 // TODO: MiniDumpWriteDump
// CWzDebug::m_lplefOld(lpTopLevelExceptionFilter);

 if( !SymInitialize( GetCurrentProcess(), NULL, TRUE ) )
 {

  //return 0;
 }

 CWzDebug::GetCallStack(lpTopLevelExceptionFilter->ContextRecord, "None", "D://debug.txt");
 MessageBox(NULL, _T("System error! Backup the error log firstly or notify the engineer!"),
        _T("Error"), MB_OK); 
 if( !SymCleanup( GetCurrentProcess() ) )
 {

  return 0;
 }
 return EXCEPTION_CONTINUE_SEARCH;
}

BOOL CWzDebug::AllowSetUnhandledExceptionFilter()
{
 HMODULE hKernel32 = LoadLibrary(_T("kernel32.dll"));
 if (hKernel32 == NULL) return FALSE;
 void *pOrgEntry = GetProcAddress(hKernel32,
  "SetUnhandledExceptionFilter");
 if(pOrgEntry == NULL) return FALSE;

 DWORD dwOldProtect = 0;
 SIZE_T jmpSize = 5;
#ifdef _M_X64
 jmpSize = 13;
#endif
 BOOL bProt = VirtualProtect(pOrgEntry, jmpSize,
  PAGE_EXECUTE_READWRITE, &dwOldProtect);
 BYTE newJump[20];
 void *pNewFunc = &CWzDebug::WzUnhandledExceptionFilter;
#ifdef _M_IX86
 DWORD dwOrgEntryAddr = (DWORD) pOrgEntry;
 dwOrgEntryAddr += jmpSize; // add 5 for 5 op-codes for jmp rel32
 DWORD dwNewEntryAddr = (DWORD) pNewFunc;
 DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr;
 // JMP rel32: Jump near, relative, displacement relative to next instruction.
 newJump[0] = 0xE9;  // JMP rel32
 memcpy(&newJump[1], &dwRelativeAddr, sizeof(pNewFunc));
#elif _M_X64
 // We must use R10 or R11, because these are "scratch" registers
 // which need not to be preserved accross function calls
 // For more info see: Register Usage for x64 64-Bit
 // http://msdn.microsoft.com/en-us/library/ms794547.aspx
 // Thanks to Matthew Smith!!!
 newJump[0] = 0x49;  // MOV R11, ...
 newJump[1] = 0xBB;  // ...
 memcpy(&newJump[2], &pNewFunc, sizeof (pNewFunc));
 //pCur += sizeof (ULONG_PTR);
 newJump[10] = 0x41;  // JMP R11, ...
 newJump[11] = 0xFF;  // ...
 newJump[12] = 0xE3;  // ...
#endif
 SIZE_T bytesWritten;
 BOOL bRet = WriteProcessMemory(GetCurrentProcess(),
  pOrgEntry, newJump, jmpSize, &bytesWritten);

 if (bProt != FALSE)
 {
  DWORD dwBuf;
  VirtualProtect(pOrgEntry, jmpSize, dwOldProtect, &dwBuf);
 }
 return bRet;
}

 

BOOL CWzDebug::PreventSetUnhandledExceptionFilter()
{
 HMODULE hKernel32 = LoadLibrary(_T("kernel32.dll"));
 if (hKernel32 == NULL) return FALSE;
 void *pOrgEntry = GetProcAddress(hKernel32,
  "SetUnhandledExceptionFilter");
 if(pOrgEntry == NULL) return FALSE;

 DWORD dwOldProtect = 0;
 SIZE_T jmpSize = 5;
#ifdef _M_X64
 jmpSize = 13;
#endif
 BOOL bProt = VirtualProtect(pOrgEntry, jmpSize,
  PAGE_EXECUTE_READWRITE, &dwOldProtect);
 BYTE newJump[20];
 void *pNewFunc = &CWzDebug::WzUnhandledExceptionFilter;
#ifdef _M_IX86
 DWORD dwOrgEntryAddr = (DWORD) pOrgEntry;
 dwOrgEntryAddr += jmpSize; // add 5 for 5 op-codes for jmp rel32
 DWORD dwNewEntryAddr = (DWORD) pNewFunc;
 DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr;
 // JMP rel32: Jump near, relative, displacement relative to next instruction.
 newJump[0] = 0xE9;  // JMP rel32
 memcpy(&newJump[1], &dwRelativeAddr, sizeof(pNewFunc));
#elif _M_X64
 // We must use R10 or R11, because these are "scratch" registers
 // which need not to be preserved accross function calls
 // For more info see: Register Usage for x64 64-Bit
 // http://msdn.microsoft.com/en-us/library/ms794547.aspx
 // Thanks to Matthew Smith!!!
 newJump[0] = 0x49;  // MOV R11, ...
 newJump[1] = 0xBB;  // ...
 memcpy(&newJump[2], &pNewFunc, sizeof (pNewFunc));
 //pCur += sizeof (ULONG_PTR);
 newJump[10] = 0x41;  // JMP R11, ...
 newJump[11] = 0xFF;  // ...
 newJump[12] = 0xE3;  // ...
#endif
 SIZE_T bytesWritten;
 BOOL bRet = WriteProcessMemory(GetCurrentProcess(),
  pOrgEntry, newJump, jmpSize, &bytesWritten);

 if (bProt != FALSE)
 {
  DWORD dwBuf;
  VirtualProtect(pOrgEntry, jmpSize, dwOldProtect, &dwBuf);
 }
 return bRet;
}

#ifndef _M_IX86

#error "The following code only works for x86!"

#endif
void CWzDebug::DisableSetUnhandledExceptionFilter()
{
 void *addr = (void*)GetProcAddress(LoadLibrary(_T("kernel32.dll")),
  "SetUnhandledExceptionFilter");
 if (addr)
 {
  unsigned char code[16];
  int size = 0;
  //xor eax,eax;
  code[size++] = 0x33;
  code[size++] = 0xC0;
  //ret 4
  code[size++] = 0xC2;
  code[size++] = 0x04;
  code[size++] = 0x00;
  DWORD dwOldFlag, dwTempFlag;
  VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);
  WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
  VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
 }
}

BOOL CWzDebug::StartDetectError()
{

    m_lplefOld = SetUnhandledExceptionFilter(WzUnhandledExceptionFilter);
 DisableSetUnhandledExceptionFilter();

 return TRUE;
}

 

 以上代码通过SetUnhandledExceptionFilter函数可以实现把没有__try到的Error发送到自定义的函数里,

在自定义函数中打印出出错的函数堆栈。这样所有Exception error都能捕获到,并告知出错的位置及上下文。

 

我面试的时候,曾经有个问题是这样的,如果系统运行的时候无端Crash down,你应该怎么办?其实在这里可以回答。

答案:通过SetUnhandledExceptionFilter把Exception error信息引导到自定义的结构化异常回调函数中,并在回调函数中打印出错函数堆栈信息,可以找到Exception error出错的源代码及其上下文,从而解决问题。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值