Mon article précédent sur les compétences de débogage de Visual Studio a suscité tellement d'intérêt que j'ai décidé de partager davantage de connaissances en débogage. Dans la liste suivante, vous pouvez voir des conseils de débogage pour l'écriture de développement natif (numérotés à la suite de l'article précédent). Ces conseils peuvent être appliqués à VS2005 ou aux versions plus récentes (bien sûr, certains peuvent être appliqués aux anciennes versions). Si vous continuez, vous pourrez connaître les détails de chaque astuce.
Astuce 11 : Points d'arrêt des données
Lorsque l'emplacement mémoire des données change, le débogueur se bloque. Cependant, il n'est possible de créer que 4 points d'arrêt de données à la fois avec un tel matériel. Les points d'arrêt de données ne peuvent être ajoutés que pendant le processus de compilation et peuvent être ajoutés via le menu (Compile> Nouveau point d'arrêt> Nouveau point d'arrêt de données) ou via la fenêtre de point d'arrêt.
Vous pouvez utiliser une adresse mémoire ou une expression d'adresse. Même si vous pouvez voir les deux valeurs sur la pile, je pense que cette fonctionnalité n'est généralement utile que lorsque la valeur sur le tas a été modifiée. C'est d'une grande aide pour identifier la corruption de la mémoire.
Dans l'exemple suivant, la valeur du pointeur a été remplacée par la valeur de l'objet pointé. Afin de savoir où les modifications ont été apportées, j'ai défini un point d'arrêt où la valeur du pointeur est stockée, tel que &ptr (notez que cela se produit après l'initialisation du pointeur). Lorsque les données changent, en supposant que quelqu'un ait modifié la valeur du pointeur, le débogueur se termine et peut découvrir quel code a provoqué la modification.
Astuce 12 : Renommer les threads
Lorsque vous déboguez une application multithread, la fenêtre Threads affichera quels threads ont été créés et le thread actuel. Plus il y a de threads, plus il vous est difficile de trouver le thread que vous recherchez (surtout lorsque le même programme est exécuté par plusieurs threads en même temps, vous ne savez pas quelle instance de thread est en cours d'exécution)
Le débogueur vous permet de renommer les threads. Cliquez avec le bouton droit sur un fil de discussion et renommez-le.
Il est également possible de nommer les threads par programme, bien que cela soit un peu délicat et doive être fait après le démarrage du thread, sinon le débogueur le renommera avec sa valeur par défaut convention de dénomination. Initialisation, les fonctions suivantes montrent comment définir et utiliser un thread.
typedef struct tagTHREADNAME_INFO { DWORD dwType; // 必须是两个字节的长度 LPCSTR szName; // 指针指向命名 (同一个地址空间) DWORD dwThreadID; // 线程ID(-1调用线程) DWORD dwFlags; // 保留待用,多数情况下为0 } THREADNAME_INFO; void SetThreadName(DWORD dwThreadID, LPCSTR szThreadName) { THREADNAME_INFO info; info.dwType = 0x1000; info.szName = szThreadName; info.dwThreadID = dwThreadID; info.dwFlags = 0; __try { RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info); } __except (EXCEPTION_CONTINUE_EXECUTION) { } }
Astuce 13 : Définir des points d'arrêt sur des threads spécifiques
Une autre astuce utile pour les applications multithread consiste à définir des points d'arrêt sur des threads, des processus ou même des ordinateurs spécifiques. Vous pouvez utiliser la commande breakpoint Filer pour réaliser cette fonction.
Le débogueur vous permet d'utiliser différentes combinaisons de nom de thread, d'ID de thread, de nom de processus, d'ID de processus et de nom de machine (en utilisant des connexions AND, OR, NOT). Savoir comment définir les noms de thread rend également cette technique de filtrage plus facile à utiliser.
Astuce 14 : Exécution temporelle (inexacte)
Dans mon article précédent, j'ai mentionné l'utilisation de variables factices dans la fenêtre de surveillance. Celui qui n'est pas mentionné est @clk, qui peut afficher la valeur d'un compteur pour obtenir le temps approximatif nécessaire à l'exécution du code entre deux points d'arrêt. L'unité de cette valeur est la milliseconde (ms). Cependant, cette méthode ne peut pas être utilisée pour configurer l'exécution du programme. Vous devez utiliser Visual Studio Profiler ou Performance Timer pour effectuer ces configurations.
Réinitialisez le minuteur en ajoutant @clk=0 dans la fenêtre Regarder ou la fenêtre Immédiat. Par conséquent, si vous devez calculer le temps nécessaire pour exécuter la dernière section de code, procédez comme suit :
Définissez un point d'arrêt au début du bloc de code
Définissez un point d'arrêt à la fin du bloc de code
Ajoutez @clk dans la fenêtre de surveillance
Lorsque le premier point d'arrêt est déclenché, saisissez @clk=0 dans la fenêtre Intermédiaire
Exécutez le programme jusqu'à ce que le la fin du bloc de code est rencontrée. Point d'arrêt à l'emplacement et affichez la valeur de @clk dans la fenêtre de surveillance
Notez qu'il existe des astuces sur Internet selon lesquelles vous devez ajouter deux expressions dans la fenêtre de surveillance : @clk et @clk=0 On dit que vous pouvez l'ajouter à chaque fois. Le timer est réinitialisé au point où le point d'arrêt est exécuté. Cette technique ne peut être utilisée que dans les versions inférieures de Visual Studio, mais ne peut pas être utilisée dans les versions supérieures de VS, telles que VS2005 (l'auteur l'a testé et vs2005 ne prend pas en charge cette technique) et les versions supérieures.
Astuce 15 : Formatage des nombres
当你使用Watch或者Quick Watch窗口查看变量时,显示这些数值是用默认的预定义可视化格式。当变量是数字时,显示形式按照他们的类型(int、float、double)来的,并且使用十进制显示。然而,你可以设置调试器在显示数字的使用使用不同的类型,或者使用不同的进制。
改变变量显示类型可以在变量前添加以下前缀:
by —— unsigned char(unsigned byte)
wo —— unsigned short(unsigned word)
dw —— unsigned long(unsigned double word)
改变变量显示的进制可以在变量前添加以下前缀:
d或i —— 有符号十进制数
u —— 无符号十进制数
o —— 无符号八进制数
x —— 小写十六进制数
X —— 大写十六进制数
技巧16:格式化内存数据
除了数字,debugger还可以在Watch窗口中显示格式化的内存数据,最长为64字节。你可以在表达式(变量或者内存地址)后面添加下面的后缀来格式化数据:
mb或m —— 十六进制显示的16字节数据,后面跟着16个ASCII字符
mw —— 8字(WORD,通常1 WORD = 2 BYTE)数据
md —— 4个双字(DWORD,通常1 DWORD = 4 BYTE)数据
mq —— 2个四字(Quad WORD)数据
ma —— 64个ASCII字符
mu —— 2字节UNICODE字符
技巧17:在系统DLL调用处暂停
有时在DLL的某个函数被调用时暂停是很有用,特别是系统DLL(比如kernel32.dll、user32.dll)。实现这种暂停需要使用原生debugger提供的上下文运算符。你可以设定断点位置、变量名或者表达式:
{[函数],[源代码],[模块]}断点位置
{[函数],[源代码],[模块]}变量名
{[函数],[源代码],[模块]}表达式
大括号内可以是函数名、源代码及模块的任意组合,但是逗号不能省略。
举个例子如果我们需要在CreateThread函数调用时暂停。这个函数是从kernel32.dll导出的,因此上下文运算符应该是这样子的:{,,kernel32.dll}CreateThread。然而,这样并不行,因为该运算符需要CreateThread修饰之后的名字。可以使用 DBH.exe来获得一个特定函数的修饰名(编译器编译生成)。
下面是如何获得CreateThread的修饰名的方法:
C:\Program Files (x86)\Debugging Tools for Windows (x86)>dbh.exe -s:srv*C:\Symbo ls*http://msdl.microsoft.com/Download/Symbols -d C:\Windows\SysWOW64\kernel32.dl l enum *CreateThread* Symbol Search Path: srv*C:\Symbols*http://msdl.microsoft.com/Download/Symbols index address name 1 10b4f65 : _BaseCreateThreadPoolThread@12 2 102e6b7 : _CreateThreadpoolWork@12 3 103234c : _CreateThreadpoolStub@4 4 1011ea8 : _CreateThreadStub@24 5 1019d40 : _NtWow64CsrBasepCreateThread@12 6 1019464 : ??_C@_0BC@PKLIFPAJ@SHCreateThreadRef?$AA@ 7 107309c : ??_C@_0BD@CIEDBPNA@TF_CreateThreadMgr?$AA@ 8 102ce87 : _CreateThreadpoolCleanupGroupStub@0 9 1038fe3 : _CreateThreadpoolIoStub@16 a 102e6f0 : _CreateThreadpoolTimer@12 b 102e759 : _CreateThreadpoolWaitStub@12 c 102ce8e : _CreateThreadpoolCleanupGroup@0 d 102e6e3 : _CreateThreadpoolTimerStub@12 e 1038ff0 : _CreateThreadpoolIo@16 f 102e766 : _CreateThreadpoolWait@12 10 102e6aa : _CreateThreadpoolWorkStub@12 11 1032359 : _CreateThreadpool@4
看起来真实的名字是_CreateThreadStub@24。因此我们可以创建断点,{,,kernel32.dll}_CreateThreadStub@24。
运行程序,当遇到暂停时,直接忽略关于在断点位置无相关源代码的消息提示。
使用调用堆栈窗口来查看调用这个函数的代码。
技巧18:载入符号
当你调试程序的时候,调用堆栈窗口有可能不会显示全部的调用堆栈,其中忽略系统DLL(例如kernel32.dll, user32.dll)的信息。
通过加载这些DLL的符号信息,可以获得全部调用堆栈信息,并且在调用堆栈窗口,使用上下文菜单(右键菜单),直接设置这种效果。你可以从预定义的符号路径或者微软的符号服务器(针对系统DLL)下载这些符号。在这些符号下载并导入到debugger中之后,调用堆栈更新如下:
这些符号也可以从Module窗口导入。
一旦载入之后,这些符号会保存在缓存中,并且可以在Tools>Options>Debugging>Symbols中配置。
技巧19:在MFC中报告内存泄露
如果你想在MFC应用程序中监测内存泄露,你可以使用宏DEBUG_NEW来重定义new运算符,这是new运算符的一个修改版本,可以记录其分配内存的文件名及行数。在Release版中构建的DEBUG_NEW会解析成原始的new运算符。
MFC向导产生的源代码中在#include后米娜包含如下预处理指令:
#ifdef _DEBUG #define new DEBUG_NEW #endif
上面代码就是如何重定义new运算符的方法。
很多STL头文件和这里定义的new运算符不兼容。如果你在重新定义运算符new之后包含了
1>c:\program files (x86)\microsoft visual studio 9.0\vc\include\xmemory(43) : error C2665: 'operator new' : none of the 5 overloads could convert all the argument types 1> c:\program files\microsoft visual studio 9.0\vc\include\new.h(85): could be 'void *operator new(size_t,const std::nothrow_t &) throw()' 1> c:\program files\microsoft visual studio 9.0\vc\include\new.h(93): or 'void *operator new(size_t,void *)' 1> while trying to match the argument list '(const char [70], int)' 1> c:\program files (x86)\microsoft visual studio 9.0\vc\include\xmemory(145) : see reference to function template instantiation '_Ty *std::_Allocate<char>(size_t,_Ty *)' being compiled 1> with 1> [ 1> _Ty=char 1> ] 1> c:\program files (x86)\microsoft visual studio 9.0\vc\include\xmemory(144) : while compiling class template member function 'char *std::allocator<_Ty>::allocate(std::allocator<_Ty>::size_type)' 1> with 1> [ 1> _Ty=char 1> ] 1> c:\program files (x86)\microsoft visual studio 9.0\vc\include\xstring(2216) : see reference to class template instantiation 'std::allocator<_Ty>' being compiled 1> with 1> [ 1> _Ty=char 1> ]
解决方法是在包含这些STL文件之后再使用DEBUG_NEW重定义new运算符。
技巧20:调试ATL
当你开发ATL COM组件时你可以在debugger中查看你所开发的COM对象的QueryInterface、AddRef和Release的调用情况。默认情况下并不支持这些调用的产看,你需要在预处理定义或者预编译头文件中定义两个宏。这两个宏定义之后,关于这些函数的调用会显示在输出(Output)窗口中。
这两个宏是:
_ATL_DEBUG_QI,显示每个被查询接口的名字。必须在atlcom.h头文件被包含之前定义。
_ATL_DEBUG_INTERFACES,每当AddRef或Release被调用时显示当前接口的引用次数以及类名、接口名等信息。必须在atlbase.h包含之前定义。
以上就是本文的全部内容,希望大家结合之前分享过的文章进行学习,熟练掌握Visual Studio调试技巧。
更多分享Visual Studio原生开发的10个调试技巧(2)相关文章请关注PHP中文网!