分类 调试 下的文章

1、对于反应性事件:“将工程师带到现场,因为这样可以更容易地隔离问题。”

这是我听到的最常见的误解。让我解释一下:大多数复杂的问题都需要深入的调试会话。
收集必要的信息是很容易的,可以远程或由客户完成。但是,调试转储文件可能需要几个小时或几天。由于我们可能无法访问我们的私有符号,也无法与具有特定技术知识的同事协作访问,因此在现场工作实际上会减慢进程。
很多时候,在现场工作的一个重要价值是充当远程工程师的耳目,或者更好地理解我们无法通过电子邮件或电话很好理解的复杂问题。

2、“我们需要代码检查,因为我们的应用程序有性能问题。”

有时我会收到代码评审请求,但实际上,客户需要的是问题隔离。那有什么区别,我怎么知道我需要什么?
代码检查的目标是检查源代码并指出代码中不遵循最佳实践的部分,或者表示安全漏洞的部分,或者还可以针对速度进行优化的部分。
问题隔离的目标是隔离导致特定应用程序症状的问题。例如,崩溃、挂起、内存泄漏和性能瓶颈。
让我解释一下:想象一个ASP.NET应用程序性能不佳的场景。如果我代码审查应用程序,我可能会找到可以优化速度的方法。但是,如果应用程序的性能很慢,因为在数据库端或网络上存在瓶颈,那么通过代码检查获得的性能增益并不能解决问题。最坏的情况是,它甚至可能不明显。
如果您希望确保应用程序不存在通过实现最佳实践可以避免的潜在问题,或者如果您认为可以进一步优化应用程序以获得更高的速度,那么代码检查非常好。但是,只有在应用程序没有遇到问题时有一个基线,才能测量速度增益。重要的是,通常性能的提高不如消除瓶颈那么重要。

3、”因此,在修复这个问题后,性能/内存问题将被规范化,对吗?”

实际情况是,可能会有不同的问题导致相同的症状,如性能低下、挂起或内存问题。
这是什么意思?这意味着在解决了最重要和最明显的问题之后,我们需要监视应用程序,因为其他小问题可能会导致相同的症状。此外,在修复了主要瓶颈之后,这些其他小问题应该变得可见和易于隔离。识别和解决应用程序问题是一个迭代过程。

4、“我们使用的是.NET,所以我不需要担心内存管理。”

如果你有一个纯.NET的应用程序,我倾向于同意。然而,大多数商业应用程序都与本机世界有某种交互,比如C Dlls、COM对象或API调用。

CLR非常适合管理纯.NET应用程序中的内存。如果应用程序正在与本机代码交互,则开发人员有责任确保资源已释放/关闭。

5、我需要收集什么信息?我需要收集多少信息?

在信息不足和信息过多之间有一条细线。对我们来说最重要的是得到正确的信息。当遇到这种症状时,从有问题的应用程序收集一个转储文件是非常有价值的。运行正常时从有问题的应用程序中收集的五个转储文件很可能没有太大帮助。
如果应用程序崩溃,则希望在应用程序崩溃时收集转储文件。如果您在任何其他时间收集转储文件,它将没有来自异常的信息。如果强制收集一个巨大的内核转储文件,则最终将从计算机的所有进程中获得一个巨大的转储文件,但同样,该转储文件不会包含导致应用程序崩溃的异常的信息。

6、“我们需要对体系结构进行检查,因为我们的应用程序存在性能问题。”

这与上文第2项类似。架构评审并不是解决当前问题的最佳方法。
此外,架构评审甚至可能不是解决大多数应用程序问题的正确方法,因为这些应用程序问题通常过于细粒度。这意味着客户的应用程序从架构的角度正确设计,但与架构的设计方式无关的问题。
让我举几个例子。假设您还没有为.NET框架安装影响应用程序的重要更新。或者您的SharePoint应用程序没有释放它正在使用的内部SharePoint对象。在这些例子中,架构评审不会揭示这些问题。

7、有时候,找到合适的起点是最困难的。

想象一下这个场景:
“我们需要一个IIS工程师,因为我的W3WP.EXE占用了太多内存。这可能是一个IIS错误,“用户、管理员和开发人员将如何体验这个问题?

  • 最终用户:我认为浏览器有问题,应用程序很慢。
  • IIS管理员:我认为问题出在ASP.NET应用程序上。
  • 开发人员:ASP.NET应用程序运行良好;问题可能出在数据库方面。
  • DBA:SQL服务器运行良好;我认为瓶颈与网络有关。
  • 网络管理员:网络没有问题。

我们作为开发人员PFEs的目标是帮助我们的客户跨不同的技术隔离问题,并在现场或远程提供不同团队之间的跨组协作。

8、我需要什么技能来帮助我调试应用程序?

如果应用程序需要调试,则不需要知道如何管理或安装产品的工程师。你需要的是一个了解应用程序内部以及如何调试它们的工程师。好消息是,这些知识并不依赖于应用程序。
即使Microsoft工程师以前从未见过您的应用程序,他/她也可以调试您的应用程序。这同样适用于我们自己的产品。
如果在某个时候我们将问题隔离到我们的某个产品上,那么我们就需要产品团队中的一名工程师,因为他/她对产品中的问题和缺陷有很深的了解支持。

9、“我执行了!clrstack和大多数运行很长时间的线程正在尝试从数据库中检索数据。瓶颈可能在数据库方面。”

让我告诉你一件事:我曾经对我们的新工程师或那些想了解更多关于.NET调试的人说,如果你想在.NET调试中胜过,你必须学习本机代码调试,这也暗示了C/C++编程的一些知识。
不相信我?如果你最喜欢的博主只知道.NET调试,那就问问他们.NET调试的情况。
就这么说!clrstack是学习.NET调试的人最喜欢的命令。很酷;您可以看到调用堆栈的托管端,它通常比本机端的级别更高。然而,有时您仍然需要看到本机端才能真正理解线程在做什么,否则,如果只关注托管端,您可能会得出错误的结论。
底线是:如果您想提高.NET调试技能,请进一步了解本机调试。

10、“我的两个服务器是相同的,但问题只发生在服务器XYZ上。”

当对这样的场景进行故障排除时,永远不要假设服务器是相同的。相反,收集数据来证明这一点。
一个很好的开始是运行MPSReport/SPSReport工具。此工具将从每个服务器收集所有信息并进行比较。至少在一个服务器完全相同的情况下,根本的问题是应用程序正在访问其中一个服务器,因此它正在过载。

11、“从事件日志中,我可以看到导致应用程序崩溃且调用堆栈指向Windows的异常。我认为这是一个Windows错误。”

这与上文第7项和一个常见的误解有关。有时从第二次机会呼叫堆栈
异常(应用程序未处理的异常,从而导致应用程序崩溃)将来自Windows的dll作为顶部框架。这是正常的,并不意味着Windows会导致崩溃。

例子:

 ChildEBP RetAddr
0013bcd0 7c90de7a ntdll!KiFastSystemCall+0x2
0013bdd0 7c81cdfe kernel32!_ExitProcess+0x62
0013bde4 79f944b0 kernel32!ExitProcess+0x14
0013c00c 79f2c09a mscorwks!SafeExitProcess+0x11b
0013c018 79eff585 mscorwks!DisableRuntime+0xd1
0013c0a8 79011628 mscorwks!CorExitProcess+0x242
0013c0b8 77c39d3c mscoree!CorExitProcess+0x46
0013c0c4 77c39e78 msvcrt!__crtExitProcess+0x29
0013c0d4 77c39e90 msvcrt!_cinit+0xee
0013c0e8 0e68d21e msvcrt!exit+0x12
0013c580 0e256834 testappl!FuTestInterface::init+0x34 <<< This is where you should start the investigation.
0013c5a4 0e1d8c01 testapp!WBNARiskReportInterface::getResults+0x442a

因此,不要认为是ntdll或kernel32导致了这个问题。这些操作系统dll中的api是由于应用程序可能导致的异常而调用的。尝试将最新的应用程序方法调用标识为初始调查点。在上面的例子中,这是testappl!FuTestInterface::init。分析它,如有必要,分析前一帧等。

12、“我们从崩溃的C++应用程序中收集转储文件。我们认为这是堆损坏,所以调用堆栈应该指示罪魁祸首,对吧?”

堆损坏不像以前那样频繁,因为.NET应用程序越来越常见。然而,在COM对象和C DLL的时代,堆损坏是一个典型的问题。
要从实际损坏堆的方法中获取调用堆栈是为了启用页堆,请重新启动应用程序,以便它可以使用新的堆管理器设置并收集转储文件。使用这种方法,您可以轻松地隔离堆损坏问题。
可以使用不同的工具(如Page Heap.exe、GFlags.exe、Application)启用页堆验证者和其他人。某些页堆设置(如完整页堆)在每次内存分配后创建一个只读页,因此每当应用程序试图覆盖缓冲区时,它都会命中只读页,从而导致访问冲突。

Windbg里的K*命令显示给定线程的堆栈帧以及相关信息,对于我们调试时,进行调用栈回溯有很大的帮助。

一、K*命令使用方式

在不同平台上,K*命令的使用组合如下

User-Mode, x86 Processor

[~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] [FrameCount]
[~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] =BasePtr[FrameCount]
[~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] =BasePtrStackPtrInstructionPtr
[~Thread] kd [WordCount]

Kernel-Mode, x86 Processor

[Processor] k[b|p|P|v] [c] [n] [f] [L] [M] [FrameCount]
[Processor] k[b|p|P|v] [c] [n] [f] [L] [M] =StackPtrFrameCount
[Processor] k[b|p|P|v] [c] [n] [f] [L] [M] =BasePtrStackPtrInstructionPtr
[Processor] kd [WordCount]

User-Mode, x64 Processor

[~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] [FrameCount]
[~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] =StackPtrFrameCount
[~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] =StackPtrInstructionPtrFrameCount
[~Thread] kd [WordCount]

Kernel-Mode, x64 Processor

[Processor] k[b|p|P|v] [c] [n] [f] [L] [M] [FrameCount]
[Processor] k[b|p|P|v] [c] [n] [f] [L] [M] =StackPtrFrameCount
[Processor] k[b|p|P|v] [c] [n] [f] [L] [M] =StackPtrInstructionPtrFrameCount
[Processor] kd [WordCount]

User-Mode, ARM Processor

[~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] [FrameCount]
[~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] =StackPtrFrameCount
[~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] =StackPtrInstructionPtrFrameCount
[~Thread] kd [WordCount]

Kernel-Mode, ARM Processor

[Processor] k[b|p|P|v] [c] [n] [f] [L] [M] [FrameCount]
[Processor] k[b|p|P|v] [c] [n] [f] [L] [M] =StackPtrFrameCount
[Processor] k[b|p|P|v] [c] [n] [f] [L] [M] =StackPtrInstructionPtrFrameCount
[Processor] kd [WordCount]

二、参数说明

  • Thread
    指定要显示其堆栈的线程。如果省略此参数,将显示当前线程的堆栈。只能在用户模式下指定线程。
  • Processor
    指定要显示其堆栈的处理器。


  • b
    显示传递给堆栈跟踪中每个函数的前三个参数。

有时我遇到一些人,他们认为他们只需要知道调试器命令就可以成为一个好的调试人员。
这是一种误解。就像国际象棋:仅仅因为你知道游戏规则和如何移动棋子并不意味着你是一个好玩家。事实上,要想在国际象棋中取得好成绩,你需要学习的不仅仅是基础知识!要想成为象棋高手,你需要学习更多!
调试是一样的。调试、测试和开发代码是相关的主题。如果你是一个优秀的程序员,你就是一个优秀的测试人员和调试器。当然,那些花大部分时间进行测试和创建测试工具的人,拥有更高的软件测试技能。花大部分时间调试的人有更高的软件调试技能。

不管怎样,这三个活动是完全相关的,所以如果你想成为一个好的调试器,首先你应该知道如何编程。
由于很多原因,如果你想了解更多关于托管调试的话,我推荐先熟悉C++,然后C#。

一个好的调试人员应该知道以下几点:

  • 一种编程语言。建议使用C++。学习C++,你也会学习C语言。一定要学会如何使用面向对象的编程在C++中编程。我实际上学会了C,然后C++。
  • 了解Windows内部的知识,如API、消息流等。.
  • 能够读取反汇编代码。
  • 故障排除工具。
  • 如何(何时)使用调试器。
  • 如何根据基于假设的科学方法来隔离问题。

下面我列出了我最喜欢的书:

C/C++

Effective C++ (Meyers)

More Effective C++ (Meyers)

Efficient C++ (Bulka, Mayhew)

C++ Coding Standards ( Sutter, Alexandrescu)

Exceptional C++ and More Exceptional C++ (Herb Sutter)

C++ Common Knowledge (Stephen C. Dewhurst)

C++ Strategies and Tactics (Robert B. Murray)

Designing Object Oriented C++ Applications using the Booch Method (Martin)

C#

Programming Windows with C# (Petzold)

C# Programmer’s Cookbook (Jones)

Programming .NET Components (Lowy)

C# Cookbook (Hilyard & Teilhet)

CLR via C# 2nd Edition (Richter)

Applied Microsoft .NET Framework Programming (Jeffrey Richter)

Security

Writing Secure Code 2nd edition (Howard, LeBlanc)

Buffer Overflows (Foster)

Secure Coding in C and C++ (Seacord)

Rootkits (Greg Hoglund, James Butler)

Shellcoder’s Programming Uncovered (Kris Kaspersky)

Shellcoder’s Handbook (Koziol, Litchfield, Aitel, Anley, Eren, Mehta, Hassell)

Sockets, Shellcode, Porting & Coding (James C. Foster)

Exploiting Software (Greg Hoglund, Gary McGraw)

Hunting Security Bugs (Gallagher, Jeffries, Landauer)

Practical Malware Analysis (Sikorski, Honig)

Managed Code Rootkits: Hooking into Runtime Environments (Erez Metula)

Debugging

Debugging by Thinking (Metzger)

Debugging ASP.NET (Goodyear, Peek, Fox)

The Science of Debugging (Telles, Hsieh)

Software Exorcism (Blunden)

Debugging Applications for .NET and Windows (Robbins)

Debugging .Net 2.0 Applications (Robbins)

Debugging – The 9 Indispensable Rules for Finding Even the Most Elusive Software and Hardware problems (Agans)

Debugging Windows Programs (McKay, Woodring)

Why Programs Fail: A guide to Systematic Debugging (Zeller)

Advanced Windows Debugging (Hewardt, Pravat)

Memory Dump Analysis Anthology - Volume 1 (Vostokov)

Memory Dump Analysis Anthology - Volume 2 (Vostokov)

Advanced .NET Debugging (Herwardt, Mario)

Windows Debugging Notebook - Essential User Space WinDbg Commands (Vostokov, Farah)

Inside Windows Debugging - A Practical Guide to Debugging and Tracing Strategies in Windows (Soulami, Tarik)

异常处理是C++的一项语言机制,用于在程序中处理异常事件。异常事件在C++中表示为异常对象。异常事件发生时,程序使用throw关键字抛出异常表达式,抛出点称为异常出现点,由操作系统为程序设置当前异常对象,然后执行程序的当前异常处理代码块,在包含了异常出现点的最内层的try块,依次匹配catch语句中的异常对象(只进行类型匹配,catch参数有时在catch语句中并不会使用到)。若匹配成功,则执行catch块内的异常处理语句,然后接着执行try…catch…块之后的代码。如果在当前的try…catch…块内找不到匹配该异常对象的catch语句,则由更外层的try…catch…块来处理该异常;如果当前函数内所有的try…catch…块都不能匹配该异常,则递归回退到调用栈的上一层去处理该异常。如果一直退到主函数main()都不能处理该异常,则调用系统函数terminate()终止程序。

一、抛异常---throw

throw是个关键字,与抛出表达式构成了throw语句。其语法为:throw 表达式;throw语句必须包含在try块中,也可以是被包含在调用栈的外层函数的try块中。执行throw语句时,throw表达式将作为对象被复制构造为一个新的对象,称为异常对象。异常对象放在内存的特殊位置,该位置既不是栈也不是堆,在window上是放在线程信息块TIB中。这个构造出来的新对象与本级的try所对应的catch语句进行类型匹配。如下:

在vc++里,throw关键字其实是对RaiseException函数的封装,代码如下:

__declspec(noreturn) extern "C" void __stdcall
#if !defined(_M_ARM) || defined(_M_ARM_NT)
_CxxThrowException(
#else
__CxxThrowException(
#endif
        void*           pExceptionObject,   // The object thrown
        _ThrowInfo*     pThrowInfo          // Everything we need to know about it
) {
        EHTRACE_ENTER_FMT1("Throwing object @ 0x%p", pExceptionObject);

        static const EHExceptionRecord ExceptionTemplate = { // A generic exception record
            EH_EXCEPTION_NUMBER,            // Exception number
            EXCEPTION_NONCONTINUABLE,       // Exception flags (we don't do resume)
            NULL,                           // Additional record (none)
            NULL,                           // Address of exception (OS fills in)
            EH_EXCEPTION_PARAMETERS,        // Number of parameters
            {   EH_MAGIC_NUMBER1,           // Our version control magic number
                NULL,                       // pExceptionObject
                NULL,
#if _EH_RELATIVE_OFFSETS
                NULL                        // Image base of thrown object
#endif
            }                      // pThrowInfo
        };
        EHExceptionRecord ThisException = ExceptionTemplate;    // This exception

        ThrowInfo* pTI = (ThrowInfo*)pThrowInfo;
        if (pTI && (THROW_ISWINRT( (*pTI) ) ) )
        {
            ULONG_PTR *exceptionInfoPointer = *reinterpret_cast<ULONG_PTR**>(pExceptionObject);
            exceptionInfoPointer--; // The pointer to the ExceptionInfo structure is stored sizeof(void*) infront of each WinRT Exception Info.

            WINRTEXCEPTIONINFO** ppWei = reinterpret_cast<WINRTEXCEPTIONINFO**>(exceptionInfoPointer);
			pTI = (*ppWei)->throwInfo;

			(*ppWei)->PrepareThrow( ppWei );
        }

        //
        // Fill in the blanks:
        //
        ThisException.params.pExceptionObject = pExceptionObject;
        ThisException.params.pThrowInfo = pTI;
#if _EH_RELATIVE_OFFSETS
        PVOID ThrowImageBase = RtlPcToFileHeader((PVOID)pTI, &ThrowImageBase);
        ThisException.params.pThrowImageBase = ThrowImageBase;
#endif

        //
        // If the throw info indicates this throw is from a pure region,
        // set the magic number to the Pure one, so only a pure-region
        // catch will see it.
        //
        // Also use the Pure magic number on Win64 if we were unable to
        // determine an image base, since that was the old way to determine
        // a pure throw, before the TI_IsPure bit was added to the FuncInfo
        // attributes field.
        //
        if (pTI != NULL)
        {
            if (THROW_ISPURE(*pTI))
            {
                ThisException.params.magicNumber = EH_PURE_MAGIC_NUMBER1;
            }
#if _EH_RELATIVE_OFFSETS
            else if (ThrowImageBase == NULL)
            {
                ThisException.params.magicNumber = EH_PURE_MAGIC_NUMBER1;
            }
#endif
        }

        //
        // Hand it off to the OS:
        //

        EHTRACE_EXIT;
#if defined(_M_X64) && defined(_NTSUBSET_)
        RtlRaiseException( (PEXCEPTION_RECORD) &ThisException );
#else
        RaiseException( ThisException.ExceptionCode,
                        ThisException.ExceptionFlags,
                        ThisException.NumberParameters,
                        (PULONG_PTR)&ThisException.params );
#endif
}

每次启动windbg时,我都会运行一些命令,我突然意识到,也许是时候将这些命令放到脚本中,让windbg自动执行它了。

首先,创建一个包含要运行的所有命令的文件。

对于本例,让我创建一个名为“dbg-prep.wds”的文件

C:\Users\ilhoye\Desktop\WinDbg> type dbg-prep.cmd
.symfix
.reload
.load mex
.load kdexts
aS
!pr !process