zoukankan      html  css  js  c++  java
  • 通往WinDbg的捷径(一

    原文:http://www.debuginfo.com/articles/easywindbg.html
    译者:arhat
    时间:2006年4月13日
    关键词:CDB WinDbg 

    导言 
    你 钟情什么样的调试器?如果你问我这个问题,我会回答是“Visual Studio + WinDbg”。我比较喜欢Visual Studio那朴实无 华且易操作的接口,更喜欢它能迅速把我需要的信息以可视的形式展示出来。但遗憾的是,Visual Studio调试器无法获取某些信息。例如,假设我想 知道哪个线程正在占用特殊的临界区?或者是哪个函数占用了大部分的栈空间?不用担心,有WinDbg呢。它的命令能回答这些问题,以及调试过程中出现的其 它有趣的问题。甚至不退出Visual Studio,WinDbg就可以附上目标应用程序??谢谢WinDbg支持入侵模式的调试(本文后面会详细讨 论),我们可以把Visual Studio GUI和WinDbg的命令行结合起来使用。
    唯一的问题是WinDbg不太好用。需要花些时间适应 它的用户界面,而掌握它的命令则要花更多的时间。但是假设你现在就需要它,马上用它调试紧急的问题?有什么快速简便的方法吗?当然。WinDbg的小弟 CDB,功能和WinDbg差不多;因为它是基于命令行的,所以用起来更简单一些。在这篇文章里,我将把CDB作为Visual Studio调试器的补 充,介绍怎样使用CDB。在这篇文章里,你将会看到怎样配置CDB,怎样用它解决实际的问题。另外,我还会提供一些批处理文件,它们可以隐藏CDB命令行 接口的大部分复杂性,也让你少打几个字。

    安装与配置
    安装
     
    当然,在使用CDB前,必须先安装并配置它。WinDbg和CDB是Debugging Tools for Windows   的一部分,可以从这里 下 载。安装很简单,你可以用默认设置安装,除非你准备用WinDbg SDK开发应用程序。(如果你准备用SDK,需要选择定制安装,并启用SDK安装;推 荐你把它安装在不包含空格的目录名的目录中)。安装完成后,安装目录里将包含所有必需的文件,包括WinDbg(windbg.exe)和 CDB(cdb.exe)。
    调试工具也支持“xcopy”类型的安装。也就是说,在一台机器上安装后,如果你想在其它的机器上使用,不用再安装,直接把已经安装的目录直接拷过去就行了。

    符号文件服务器路径  
    如 果不能访问操作系统DLL的最新的符号文件,有些重要的WinDbg命令将不能正常工作。在以往,我们可以从微软的FTP服务器上下载巨大的符号文件包, 然后从中找出需要的符号文件。这非常浪费时间,而且在操作系统更新或升级后,符号文件就过时了(因此也就变得毫无用处)。幸运的是,现在有更简便的方法来 获得符号文件??符号文件服务器。WinDbg和Visual Studio都支持这个方法,在需要时直接从微软维护的服务上下载最新的符号文件。有了符 号文件服务器,我们再也不用下载整个符号文件包了(那实在是太大了),因为调试器知道需要用到哪个DLLs,所以直接下载单个符号文件就行了。如果符号文 件在操作系统更新或升级以后过时了,调试器会注意到这种情形,并再次下载必需的符号文件。
    为了使符号文件服务器起作用,我们应该让调试器知道符号文件服务器的路径。最简单的方法是在_NT_SYMBOL_PATH环境变量里指定符号文件服务器的路径。可以用如下的路径:
    "srv*c:\symbolcache*http://msdl.microsoft.com/download/symbols" 
    (c:\symbolcache目录将被用来保存从符号文件服务器下载下来的符号文件;当然,你可以用任何有效的本地或网络路径)。例如:
      set _NT_SYMBOL_PATH=srv*c:\symbols*http://msdl.microsoft.com/download/symbols
    在你设置_NT_SYMBOL_PATH环境变量之后,就可以使用符号文件服务器了。关于符号文件服务器的更多信息,相关设置,以及可能会用到的排除故障的小技巧,可以从WinDbg的文档中找到(Debuggers | Symbols section)。
    如果你需要从一台需登录的代理服务器后访问符号文件服务器。参见本篇文章中CDB and proxy servers部分,以了解更多信息。

    CDB 命令行基础介绍 
    启动调试会话
     
    当我们使用新的调试器时,第一个问题通常是:怎样开始调试会话呢?像大多数调试器一样,CDB允许我们调试应用程序的新实例,或者附上一个已经运行的过程。启动新实例就象下面一样简单:
      cdb c:\myapp.exe
    如果我们想附上已经运行的过程,可能会用上下列某个选项:
    ----------------------------------------------------------------------------------------------------------------------
    选项                描述                                                                            例子
    ----------------------------------------------------------------------------------------------------------------------
    -p Pid              这个选项允许CDB附上指定进程ID的进程。可以用任务管理器或类似的工具得到进程ID。   cdb -p 1034
    ----------------------------------------------------------------------------------------------------------------------
    -pn ExeName         这个选项允许CDB用指定的可执行文件名(.exe)附上进程。这个选项比“-p Pid”更
                        方便,因为我们通常知道执行的程序名,不必在任务管理器中寻找进程的ID。但是如果
                        多个进程使用同一个名字(CDB将报错),就不能用这个选项了。                       cdb -pn myapp.exe
    ----------------------------------------------------------------------------------------------------------------------
    -psn ServiceName    这个选项允许CDB附上指定服务的进程。例如,假如你想附上Windows Management 
                        Instrumentation服务,应该用WinMgmt作为服务名。                                  cdb -psn MyService
    ----------------------------------------------------------------------------------------------------------------------

    CDB也可以分析故障转储。用-z选项打开故障转储:
      cdb -z DumpFile
    例如:
      cdb -z c:\myapp.dmp

    结束调试会话  
    启动新的调试会话后,CDB会显示它自己的命令行提示符。你可以在这个提示符下执行CDB支持的任何命令。
    http://www.debuginfo.com/articles/pic/cdbconsole.jpg   
    'q'命令结束调试会话并退出CDB:
    0:000> q
    quit:
    >
    警告:当你结束调试会话,退出CDB时,操作系统也将终止被调试的程序。如果你想退出CDB并保持被调试程序,可以用.detach命令(Windows XP或更新的操作系统才支持),或者用非入侵的模式(下面讨论)。

    运行命令  
    虽然可以在CDB命令行提示符下执行调试器命令,但在命令行里指定需要的命令通常更快一些,用-c选项。
      cdb -pn myapp.exe -c "command1;command2"
    (用分号分隔多个命令)
    例如,下列命令行将把CDB附上我们的应用程序,显示已加载的模块,然后退出:
      cdb -pn myapp.exe -c "lm;q"
    注意,在命令列表的结尾加上'q'命令??将在所有的调试器命令执行后关闭CDB。

    入侵模式调试 
    在 默认情况下,当我们用CDB调试一个已经运行的进程时,它通常作为全功能的调试器附上进程(使用Win32 Debugging API)。在这种模式 下,可以设置断点,单步调试代码,得到各种调试事件的通知(例如,异常,加载/卸载模块,启动/退出线程,等等)。Visual Studio也可以做到 这些,并提供更友好的用户界面。另外,每个进程每次只能被一个调试器附上。这是否意味着如果我们用Visual Studio调试器调试应用程序,就不能 再用CDB得到它的附加信息了?不,不完全是这样,因为除了全功能调试模式外,CDB还支持入侵调试模式。
    CDB以入侵模式附上目标进程时,并没 有使用Win32 Debugging API,而是先暂停目标进程的所有线程,执行用户指定的命令。在所有的命令执行之后,CDB退出之前,恢复暂停的 线程。因此,目标进程可以继续运行,好像什么事也没发生一样。即使像Visual Studio之类的全功能调试器正在调试目标进程,CDB仍可以用入侵 模式附上它,并获得所需要的信息。在CDB完成任务并分离附上的进程后,我们可以继续用Visual Studio调试器调试这个应用程序。
    怎么启用CDB的入侵模式?用-pv命令行选项。例如,下列命令行将以入侵模式附上应用程序,显示已加载模块的列表,然后退出。在CDB退出之后,应用程序将继续运行。
      cdb -pv -pn myapp.exe -c "lm;q"

    把输出内容保存到日志文件  
    有 些CDB命令的输出内容可能会很长,从控制台窗口阅读十分不便。因此,把输出内容保存到日志文件,再用其它的编辑器查看会更好一些,CDB允许我们用 -loga和-logo选项来实现('-loga <filename>'把输出内容追加到指定文件的结尾;而'- logo <filename>'将覆盖原有的文件,如果文件已经存在的话)。
    在我们的例子命令(列出目标进程里的模块)里增加记录功能,把输出内容保存到当前目录的out.txt文件里:
      cdb -pv -pn myapp.exe -logo out.txt -c "lm;q"

    源行号信息  
    CDB支持的另外一个重要选项是-lines。这个选项打开源行号信息支持,例如,当报告调用栈时,允许CDB显示源文件及源行号。(在默认情况下,源行号支持是关闭的,CDB不显示源文件/行号信息)。

    CDB 和代理服务器  
    如果你在需要登录的代理服务器后用CDB,在默认情况下,将不能访问符号文件服务器。原因是在默认配置下,当CDB尝试连接符号文件服务器时,不显示代理服务器的登录提示。为了更改这个行为,使我们可以访问符号文件服务器,需要在命令行之前加上两条命令:
      !sym prompts;.reload
    例如:
      cdb -pv -pn myapp.exe -logo out.txt -c "!sym prompts;.reload;lm;q"

    启动消息 
    当 CDB调试新应用程序,附上已经存在的进程,或打开故障转储时,将显示一系列的启动消息。CBD命令(可以用-c选项指定,或手动输入)的输出内容跟在这 些消息之后。通常情况下,启动消息只显示一些无关紧要信息;但是如果在执行时出错了,它将包含这个问题的描述,有时候也会提供解决方法。
    例如,下列输出内容通知我们没有设置符号路径,因此,有些调试器命令不能工作:
    D:\Progs\DbgTools>cdb myapp.exe

    Microsoft (R) Windows Debugger  Version 6.5.0003.7
    Copyright (c) Microsoft Corporation. All rights reserved.

    CommandLine: myapp.exe
    Symbol search path is: *** Invalid ***
    ****************************************************************************
    * Symbol loading may be unreliable without a symbol search path.           *
    * Use .symfix to have the debugger choose a symbol path.                   *
    * After setting your symbol path, use .reload to refresh symbol locations. *
    ****************************************************************************

    总结 
    这里是一些常见的CDB命令行模板,本篇文章的剩下部分将会用到它们(我们总是用同样的模板,然后根据我们要解决的问题,改变-c选项内部的命令行列表)。
    用入侵模式附上运行的进程(通常是进程ID),执行一组命令,并把输出内容保存在out.txt文件里:
      cdb -pv -p <processid> -logo out.txt -lines -c "command1;command2;...;commandN;q"
    用入侵模式附上运行的进程(用可执行文件名),执行一组命令,并把输出内容保存在out.txt文件里:
      cdb -pv -pn <exename> -logo out.txt -lines -c "command1;command2;...;commandN;q"
    用入侵模式附上运行的进程(通常是服务名),执行一组命令,并把输出内容保存在out.txt文件里:
      cdb -pv -psn <servicename> -logo out.txt -lines -c "command1;command2;...;commandN;q"
    打开故障转储文件,执行一组命令,并把输出内容保存在out.txt文件里:
      cdb -z <dumpfile> -logo out.txt -lines -c "command1;command2;...;commandN;q"
    如果我们在需要登录的代理服务器后使用CDB,要访问符号文件服务器,需要增加两条命令。例如:
      cdb -pv -pn <exename> -logo out.txt -lines -c "!sym prompts;.reload;command1;command2;...;commandN;q"
    好像要打好多字?其实不是这样,稍后,我将提供一些批处理文件,它们将为我们隐藏重复的命令行选项,把要我们输入的内容减至最小。

    解决实际的问题
    调试死锁问题
     
    当 我们的应用程序挂起或停止响应时,最自然的问题是:它现在正在做什么?它在哪里被困住了?当然,我们可以用Visual Studio调试器附上应用程 序,检查所有线程的调用栈。但我们同样可以用CDB,而且会更快一些。下列命令将使CDB以入侵模式附上应用程序,打印所有的调用栈,把结果保存在日志文 件里,然后退出:
      cdb -pv -pn myapp.exe -logo out.txt -lines -c "~*kb;q"
    ('kb'命令要求CDB打印当前线程的调用栈;'~*'前缀要求CDB在进程所有已存在的线程里重复执行'kb'命令)。
    [/URL] DeadLockDemo.cpp 是一个演示典型的死锁问题的例子。如果你编译并运行,它的工作线程马上会被困住,如果我们运行上述的命令来查看应用程序的线程正在做什么,将看到下列类似的内容(在这,以及后面,我们将省略启动消息):
    .  0  Id: 6fc.4fc Suspend: 1 Teb: 7ffdf000 Unfrozen
    ChildEBP RetAddr  Args to Child              
    0012fdf8 7c90d85c 7c8023ed 00000000 0012fe2c ntdll!KiFastSystemCallRet
    0012fdfc 7c8023ed 00000000 0012fe2c 0012ff54 ntdll!NtDelayExecution+0xc
    0012fe54 7c802451 0036ee80 00000000 0012ff54 kernel32!SleepEx+0x61
    0012fe64 004308a9 0036ee80 a0f63080 01c63442 kernel32!Sleep+0xf
    0012ff54 00432342 00000001 003336e8 003337c8 DeadLockDemo!wmain+0xd9 
      [c:\tests\deadlockdemo\deadlockdemo.cpp @ 154]
    0012ffb8 004320fd 0012fff0 7c816d4f a0f63080 DeadLockDemo!__tmainCRTStartup+0x232 
      [f:\rtm\vctools\crt_bld\self_x86\crt\src\crt0.c @ 318]
    0012ffc0 7c816d4f a0f63080 01c63442 7ffdd000 DeadLockDemo!wmainCRTStartup+0xd 
      [f:\rtm\vctools\crt_bld\self_x86\crt\src\crt0.c @ 187]
    0012fff0 00000000 0042e5aa 00000000 78746341 kernel32!BaseProcessStart+0x23

       1  Id: 6fc.3d8  Suspend: 1 Teb: 7ffde000 Unfrozen
    ChildEBP RetAddr  Args to Child              
    005afc14 7c90e9c0 7c91901b 000007d4 00000000 ntdll!KiFastSystemCallRet
    005afc18 7c91901b 000007d4 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc
    005afca0 7c90104b 004a0638 00430b7f 004a0638 ntdll!RtlpWaitForCriticalSection+0x132
    005afca8 00430b7f 004a0638 005afe6c 005afe78 ntdll!RtlEnterCriticalSection+0x46
    005afd8c 00430b15 005aff60 005afe78 003330a0 DeadLockDemo!CCriticalSection::Lock+0x2f 
      [c:\tests\deadlockdemo\deadlockdemo.cpp @ 62]
    005afe6c 004309f1 004a0638 f3d065d5 00334fc8 DeadLockDemo!CCritSecLock::CCritSecLock+0x35 
      [c:\tests\deadlockdemo\deadlockdemo.cpp @ 90]
    005aff6c 004311b1 00000000 f3d06511 00334fc8 DeadLockDemo!ThreadOne+0xa1 
      [c:\tests\deadlockdemo\deadlockdemo.cpp @ 182]
    005affa8 00431122 00000000 005affec 7c80b50b DeadLockDemo!_callthreadstartex+0x51 
      [f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c @ 348]
    005affb4 7c80b50b 003330a0 00334fc8 00330001 DeadLockDemo!_threadstartex+0xa2 
      [f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c @ 331]
    005affec 00000000 00431080 003330a0 00000000 kernel32!BaseThreadStart+0x37

       2  Id: 6fc.284  Suspend: 1 Teb: 7ffdc000 Unfrozen
    ChildEBP RetAddr  Args to Child              
    006afc14 7c90e9c0 7c91901b 000007d8 00000000 ntdll!KiFastSystemCallRet
    006afc18 7c91901b 000007d8 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc
    006afca0 7c90104b 004a0620 00430b7f 004a0620 ntdll!RtlpWaitForCriticalSection+0x132
    006afca8 00430b7f 004a0620 006afe6c 006afe78 ntdll!RtlEnterCriticalSection+0x46
    006afd8c 00430b15 006aff60 006afe78 003332e0 DeadLockDemo!CCriticalSection::Lock+0x2f 
      [c:\tests\deadlockdemo\deadlockdemo.cpp @ 62]
    006afe6c 00430d11 004a0620 f3e065d5 00334fc8 DeadLockDemo!CCritSecLock::CCritSecLock+0x35 
      [c:\tests\deadlockdemo\deadlockdemo.cpp @ 90]
    006aff6c 004311b1 00000000 f3e06511 00334fc8 DeadLockDemo!ThreadTwo+0xa1 
      [c:\tests\deadlockdemo\deadlockdemo.cpp @ 202]
    006affa8 00431122 00000000 006affec 7c80b50b DeadLockDemo!_callthreadstartex+0x51 
      [f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c @ 348]
    006affb4 7c80b50b 003332e0 00334fc8 00330001 DeadLockDemo!_threadstartex+0xa2 
      [f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c @ 331]
    006affec 00000000 00431080 003332e0 00000000 kernel32!BaseThreadStart+0x37
    调 用栈(和源行号)暗示ThreadOne正在占用临界区CritSecOne并等待临界区CritSecTwo,然而ThreadTwo正占用临界区 CritSecTwo并等待临界区CritSecOne。这是典型的“lock acquisition order”死锁例子,在那里,两个线程需要得 到同一组同步的对象,以不同的顺序使用。如果你想避免这种类型的死锁,必须保证所有的线程以相同的顺序得到所需的同步对象(在这个例子 里,ThreadOne和ThreadTwo能同意首先得到CritSecOne,然后得到CritSecTwo来避免死锁)。
    在默认情况下,'kb'命令只显示调用栈的前20帧。如果你想查看更多的栈帧,你可以显式指明显示的栈帧数量(例如,'kb100'命令要求调试器显示100帧)。在WinDbg会话里,可以用.kframes命令改变随后命令的默认限制。
    我 们的例子只包含了三个简单的线程,很容易看出哪个线程应该为死锁负责。在大应用程序里,很难找出可疑的线程并进行验证。那我们应该怎么做呢?在大部分情况 下,我们应该知道那个没有正常运转的线程(否则,我们怎么会注意到应用程序出现异常了呢?)。通常,这个线程是在等待同步对象,这个对象因为某些原因暂时 不可用。这个对象为什么不可用呢?如果我们知道哪个线程正在占用这个对象(拥有它,换句话说),应该能答出这个问题。如果这个对象碰巧在临界 区,!locks命令应该能帮助我们识别出它的当前所有者。当不带参数使用时,这条命令显示应用程序线程正在占用的临界区的列表。输出的内容不包括已释放 的临界区。
    让我看看实际使用中的!locks命令:
      cdb -pv -pn myapp.exe -logo out.txt -lines -c "!locks;q"
    下面是这条命令的输出内容(同样以DeadLockDemo.cpp 为例):
    CritSec DeadLockDemo!CritSecOne+0 at 004A0620
    LockCount          1
    RecursionCount     1
    OwningThread       3d8 
    EntryCount         1
    ContentionCount    1
    *** Locked

    CritSec DeadLockDemo!CritSecTwo+0 at 004A0638
    LockCount          1
    RecursionCount     1
    OwningThread       284 
    EntryCount         1
    ContentionCount    1
    *** Locked

    仔细查看了40个临界区
    查 看!locks命令的输出(尤其是OwningThread字段),我们可以推断出临界区CritSecOne被ID为0x3d8的线程占用,临界区 CritSecTwo被ID为0x284的线程占用。我们可以在'kb'命令的输出内容(在前面的输出里)里找出这些IDs对应的线程。
    如果应用程序使用其它种类的同步对象(例如,互斥),识别它们的所有者将更难一些(需要内核调试器),我准备在以后的文章中再介绍这部分内容。

    调试CPU高消耗的问题  
    对 大多数软件来说,太高的CPU消耗率(根据任务管理器的显示,在单CPU上接近100%)明显指出软件中有bug。通常意味着应用程序的某个线程陷入了死 循环。当然,调试这个问题的、最普通的方法是用Visual Studio调试器附上这个进程,查找哪个线程在捣乱。但是我们应该检查哪个线程呢?CDB 为我们提供了简便的方法??!runaway命令。当不带参数使用时,这条命令显示应用程序每个线程执行用户模式代码时所花的时间(使用另外的参数,可以 显示在内核模式下所花的时间,自线程启动后占用的时间等)。
    如下是在CDB下使用这条命令的示例:
      cdb -pv -pn myapp.exe -logo out.txt -c "!runaway;q"
    下面是!runaway命令的输出示例:
    0:000> !runaway
     User Mode Time
      Thread       Time
       1:358       0 days 0:00:47.408
       2:150       0 days 0:00:03.495 
       0:d8        0 days 0:00:00.000
    看 起来好像是ID为0x358的线程占用了大部分的CPU时间。但这个消息还不足以证明线程0x358就是罪魁祸首,因为这条命令显示的CPU时间是线程在 它整个生命期中所花的。我们还需要进一步查看线程所用CPU时间的变化情况。让我们再次运行这条命令。这次,我们可以看到类似于下列的内容:
    0:000> !runaway
     User Mode Time
      Thread       Time
       1:358       0 days 0:00:47.408
       2:150       0 days 0:00:06.859
       0:d8        0 days 0:00:00.000
    现在,我们可以把这个输出内容与上次的输出内容做个比较,找出CPU时间增长最快的线程。在这个例子里,很明显就是线程0x150。现在,我们可以用Visual Studio调试器附上这个应用程序,切换到这个线程下,检查它为什么转个不停。

    调试栈溢出 
    当 我们想找出栈溢出异常的原因时,CDB也非常有帮助。当然,无控制的递归调用是栈溢出最典型的原因,通常来说,查看损坏了的线程的调用栈,找出它从哪里脱 离控制就可以了。Visual Studio在这方面可以做的很好,那为什么还要用CDB呢?让我们设想一个更复杂的例子。例如,假设我们的应用程序中包 含一个依赖递归的算法?我们在设计算法时使用有符号数,在所有可能的情形下控制递归的运行,但某个时候栈仍溢出了。为什么?或许是因为在某种情况下,算法 使用的某些函数占用了太多的栈空间。我们怎么确定函数占用的总的栈空间呢?不幸地是,Visual Studio调试器没有简便的方法可以做到。
    即使调用栈没有显示任何递归的迹象时,应用程序也可能会出现栈溢出异常。例如,查看StackOvfDemo.cpp 例子。如果你编译,并在调试器下运行它,将立刻出现栈溢出。但此刻的调用栈看起来一切正常:
    StackOvfDemo.exe!_woutput
    StackOvfDemo.exe!wprintf
    StackOvfDemo.exe!ProcessStringW
    StackOvfDemo.exe!ProcessStrings
    StackOvfDemo.exe!main
    StackOvfDemo.exe!mainCRTStartup
    KERNEL32.DLL!_BaseProcessStart@4
    显然,调用栈上的某个函数使用了太多的栈空间。但是我们怎么找出这个函数呢?不用担心,有了CDB的'kf'命令的帮助,可以显示每个函数在调用栈上占用的字节数。在应用程序还停在Visual Studio调试器里的时候,我们可以运行下列命令:
      cdb -pv -pn stackovfdemo.exe -logo out.txt -c "~*kf;q"
    ('kf' 默认显示调用栈上最后的20帧,像我们在“调试死锁问题”部分讨论的那样。如果你想多显示一些,可以增加前缀,例如,~*kf1000。另外要注意的 是,~*kf将报告所有线程的调用栈。如果应用包含大量的线程,它就不太适合了,这时,可以把它改成'~~[tid]kf', 'tid'是目标线程的线 程ID(例如,'~~[0x3a8]kf'))
    这条命令显示的内容如下:
    .  0  Id: 210.3a8 Suspend: 1 Teb: 7ffde000 Unfrozen
      Memory  ChildEBP RetAddr  
              00033440 0041aca5 StackOvfDemo!_woutput+0x22
           44 00033484 00415eed StackOvfDemo!wprintf+0x85
           d8 0003355c 00415cc5 StackOvfDemo!ProcessStringW+0x2d
        fc878  0012fdd4 00415a44 StackOvfDemo!ProcessStrings +0xe5
          108 0012fedc 0041c043 StackOvfDemo!main+0x64
           e4 0012ffc0 7c4e87f5 StackOvfDemo!mainCRTStartup+0x183
           30 0012fff0 00000000 KERNEL32!BaseProcessStart+0x3d
    注意第一列的内容??它报告栈上函数所占用的字节数。很显然,ProcessStrings函数用了可用栈空间的最大份额,因此,它可能要为栈溢出负责。

    如 果你想知道ProcessStrings函数为什么需要如此多的栈空间,这里有一些解释。这个函数使用ATL的A2W宏把字符串从ANSI格式转换成 Unicode格式,这个宏在内部用_alloca函数在栈上分配内存。用_alloca分配的内存只有当它的调用者(在这个例子里是 ProcessStrings)返回后才被释放。直到ProcessStrings返回控制之前,A2W(因此,也就是_alloca)在栈上为每个后续 的调用分配另外的空间,这将迅速耗尽栈空间。
    底线:不要在循环里使用_alloca。

    摘自:http://blog.csdn.net/arau_sh/archive/2009/05/22/4209008.aspx

  • 相关阅读:
    读书笔记之理想设计的特征
    一些javascript 变量声明的 疑惑
    LINQ 使用方法
    Google MySQL tool releases
    读书笔记之设计的层次
    EF之数据库连接问题The specified named connection is either not found in the configuration, not intended to be used with the Ent
    转载 什么是闭包
    javascript面向对象起步
    Tips
    数据结构在游戏中的应用
  • 原文地址:https://www.cnblogs.com/lzjsky/p/1886711.html
Copyright © 2011-2022 走看看