在 前一家公司的时候,我负责做基础框架,用COM+实现业务逻辑层。COM+这种东西不像standalone的application,调试和错误定位 都很麻烦,而且它导出的接口是由外部模块执行,很多错误只能在后期才能发现。因此通常的排除故障的做法是在觉得可能出错的地方多输出一些LOG,通过 LOG信息逐步定位问题-这有点儿像嵌入式系统的调试。在复杂的场合下,知道在什么地方出错可能还不够,还得知道它是怎么执行到这一步的,换句话说,还得 记录代码执行的路径。但是从另一个方面来看,输出太多的信息不但会影响系统性能,而且无关的信息也会干扰故障的诊断。因此,显而易见的理想情况是,系统正 常时LOG越少越好,在出错的时候应该输出尽可能多的信息。我当时就想用C++做这么一个东西,可惜不久以后我就离开了那家公司,搞起了嵌入式,只留下一 个prototype。
如何才能做到仅在出错的时候输出错误信息?我的基本想法是,在那些比较重要的代码路径上记录一些信息,在函数退出的时候根据函数的返回值决定是否输出到 LOG文件,在出现异常的情况下,利用C++的栈对象会在异常展开时被析构这一事实,异常情况也能cover。看下面的例子:
APPLOG_FUNCTION_ENTRY2分别在栈上定义一个本地C++对象
,在函数foo()和bar()返回或者抛出异常时C++对象的析构函数会被调用,在析构函数里结合返回值和异常决定是否输出LOG。
在这里析构函数的实现是关键,它必须识别出正常路径的析构和异常展开的析构两种情况,这得用到一些汇编知识。正常路径下析构函数的调用汇编代码大致是这样:
call AutoScopeTrace::~AutoScopeTrace
mov eax, AutoScopeTrace::~AutoScopeTrace
call eax
call eax
AutoScopeTrace::~AutoScopeTrace(int var)
{
DWORD retaddr = (reinterpret_cast<DWORD*>(&var))[-1];
}
{
DWORD retaddr = (reinterpret_cast<DWORD*>(&var))[-1];
}
#pragma optimize( "g" , off )
#pragma optimize( "y" , on )
AutoScopeTrace::~AutoScopeTrace()
{
// the first two instructions in the function are:
// push ebp
// mov ebp,esp
}
#pragma optimize("", on)
#pragma optimize( "y" , on )
AutoScopeTrace::~AutoScopeTrace()
{
// the first two instructions in the function are:
// push ebp
// mov ebp,esp
}
#pragma optimize("", on)
这样[ebp+4]就是函数的返回地址,根据返回地址得到call指令编码,我们就能区分正常析构和异常析构。完整的析构函数的实现大致如下:
下面是前面的例子的LOG输出:
SCOPE TRACE started at Fri Jan 12 14:41:07 2007 in module: testscopetrace.exe(pid:2028, tid:3348)
testscopetrace.cpp(17): foo() Enter
testscopetrace.cpp(11): bar() Enter
testscopetrace.cpp(11): bar() Leave failed due to return value: bool=false
testscopetrace.cpp(17): foo() Leave failed due to exception
SCOPE TRACE stopped at Fri Jan 12 15:15:03 2007 in module: testscopetrace.exe(pid:2028, tid:3348)
testscopetrace.cpp(17): foo() Enter
testscopetrace.cpp(11): bar() Enter
testscopetrace.cpp(11): bar() Leave failed due to return value: bool=false
testscopetrace.cpp(17): foo() Leave failed due to exception
SCOPE TRACE stopped at Fri Jan 12 15:15:03 2007 in module: testscopetrace.exe(pid:2028, tid:3348)
这里可以下载一个比较完整的例子。
更新:
movieqiu网友回复说这个例子在VC6下编译通不过,这个我以前也发现了,但是没怎么管它。
支持VC6要解决两个问题。一是
#ifndef __FUNCTION__
#define __FUNCTION__ ""
#endif // __FUNCTION__
#define __FUNCTION__ ""
#endif // __FUNCTION__