vc6.0远程调试

vc6.0远程调试

分类: MFC   465人阅读   评论(0)   收藏   举报

先明确下概念,远程调试嘛,自然是两个机器之间调试。程序运行在目标机器上,调试器运行在本机。当然,目标机器上还是要有少许辅助程序才能跟本机的调试器connect上,以便通讯。一般来说,只需要copy四个文件到目标机器上就行了:MSVCMON.EXE、DM.DLL、TLN0T.DLL和MSDIS110.DLL。这四个文件都能在VC6目录的Common/MSDEV98/Bin目录下面找到。copy过去之后,运行msvcom.exe,看下图片~

有个Settings的按钮,不用管。直接点Connect就行了~

 

接着看看本机这边调试器的设置。首先设置好远程调试开关,在Build菜单下有个Debuger Remote Connecting的子菜单,点之。出现个窗口,默认是在Local项,我们要选的是Network(TCP/IP),然后点设定。会弹出一个对话框,输入目标机器的ip或者机器名,最后点OK就行了。

本机调试设置

 

接下来把工程打开,设置最后一步。假设生成的可执行程序名为RemoteDebug.exe,在目标机器上的路径为d:/Prj/Remote.exe,那么,在本机的Project Settings里面,选择Debug页面的Remote executable path and file name下面的编辑框中输入目标机器中程序的路径:d:/Prj/RemoteDebug.exe。注意,这里写的是从目标机器的角度所看到的路径。

 

项目设置

然后编译一下程序,把新编译出来的RemoteDebug.exe复制到目标机器的d:/Prj下面,就可以在本机像平常一样调试了。

 

 

要注意的事项:

1.要求本机与目标机器上的版本要完全一样才行。

2.在本机设置远程调试路径时一定要填目标机器上看到的路径,而不是本机看到的网络路径

3.调试开始时,会提示些符号信息的东东,都确定就行了

4.远程调试的设置是全局设置,跟项目无关。实际上,上面提到本机调试器设置时都没打开工程。所以,当不需要远程调试时,要从Build菜单下面的Debuger Remote Connecting的子菜单设置回Local模式。否则每次都会问你要远程的信息噢~~~

 

调试Dll文件

基本流程还是一样的,只不过调试动态库的话,除了需要动态库本身外,还需要调用该动态库的执行程序。这里假设要调试的动态库为TestDLL.dll,其中导出一个函数int Add(int a, int b);在可执行程序TestCSDN.exe中会调用TestDll中的Add函数。

 

        调试步骤如下:

        1.在编译完动态库TestDll.dll和可执行程序TestCSDN.exe之后,将这俩复制到目标机器上,比如目标机器的D:/DebugEasy/RemoteDebugDll目录下。

       

        2.设置好本机和目标机器的远程调试选项

        参见前一篇blog: VC++6.0调试篇:远程调试http://blog.csdn.net/coding_hello/archive/2008/11/23/3357384.aspx

 

        3.在本机打开TestDll工程,在Build --> Project and Setting菜单中作设置:

       

本机远程调试动态库

 

        上面是本机中TestCSDN.exe所在的位置,下面是远程目标机器上程序所在的位置。注意,这里都是设置的可执行文件的名字,而不是我们要调试的动态库的名字。

 

        4.点OK后,在Add函数中打个断点,然后按F5开始调试。会提示要定位动态库位置,第一个一般是NTDLL.dll,如图:

Host_For_Dll_Symbol

        如果我们是调试EXE,那么去掉"Try to locate other DLLs"选项,点OK就行了。但是我们这次的目标是调试TestDLL.dll,所以请有点耐心,直接点OK。可能有很多,直到看到你想要的:

Host_For_TargetDll_Symbol

看到没,提示要目标机器上的Testdll.dll文件在本机中的位置,点Browse,选中本机中的位置:

Locate_Local_Module

找到本机中的位置后,点"打开",剩下的DLL就都忽略吧,信息已经足够了。F5之~

 

        6.守得云开见月明:

目标机器

        断点生效了噢,都能看到变量a的值是100了。

 

 

==================================================================================================================

经常有人问我关于内存断点的问题,刚才看帖又看到一个哥们问起这事。干脆就写一篇简单教程吧。

        翻出了一个快排的程序。初始化了一个待排序数组,这时候如果我想看看arr[0]这个内存单元的值是何时开始变化的,那么就可以在这个内存地址直接下数据断点。打开Edit-->Breakpoints菜单,出现Breakpoints窗口,选Data叶面。内存断点需要在调试过程中使用,这时候能直接看到arr的地址是0x12ff58,或者写arr也可以。不过代码量大的话,还是直接下内存地址比较靠谱。如下图:

          

 

 长度那个地方默认是1,我改成了4,因为int类型是4个字节。注意,这个时候arr[0]=0x26。然后F5运行程序。当arr[0]的值变化时,IDE会给出提示消息框,如下图:

清楚的提示我们起始地址0x12ff58,长度为4的这个内存单元发生了变化。点击确定,再看看数据。

原来的0x26变成了0x17。然后看到代码运行的地方,看到没?就是上面的交换数据的那三行代码。

        嗯,大概就是这样子吧。VC6的集成调试环境还是挺不错的。

=================================================================================================================

在多线程程序的开发中,因为效率的关系,通常会选用CriticalSection作为同步的机制。初学者在设计开发多线程程序时经常会出现死锁的情况,昨天就看到有个哥们在发帖问这个(明显是郁闷中阿)。这里通过一个例子说下不用Intel的Thread Checker,Thread Profiler,也不用强大的WinDbg,只是用土土的VC6自带的调试器如何来轻松的定位这种死锁问题。

 

先贴下死锁例子的代码:

 

  1. /*******************************************************/ 
  2. /* 程序:DeadLock                                      */ 
  3. /* 功能:多线程环境中出现CriticalSection对象导致的死锁 */ 
  4. /* 作者:coding (http://blog.csdn.net/coding_hello)    */ 
  5. /* 日期:2008-12-09                                    */ 
  6. /*******************************************************/ 
  7. #include <windows.h>
  8. #include <stdio.h>
  9. CRITICAL_SECTION g_cs1;
  10. CRITICAL_SECTION g_cs2;
  11. DWORD WINAPI Worker(LPVOID lpParam)
  12. {
  13.     if((int)lpParam == 7)
  14.     {
  15.         while(1)
  16.         {
  17.             printf("Worker[%x]: 就我活着/n", GetCurrentThreadId());
  18.             Sleep(1000);
  19.         }
  20.     }
  21.     else
  22.     {
  23.         printf("Worker[%x]:准备进入临界段g_cs2/n", GetCurrentThreadId());
  24.         EnterCriticalSection(&g_cs2);
  25.         printf("Worker[%x]:成功进入临界段g_cs2/n", GetCurrentThreadId());
  26.         
  27.         printf("Worker[%x]:准备进入临界段 g_cs1/n", GetCurrentThreadId());
  28.         EnterCriticalSection(&g_cs1);
  29.         printf("Worker[%x]:成功进入临界段 g_cs1/n", GetCurrentThreadId());
  30.     }
  31.     return 0;
  32. }
  33. int main(int argc, char* argv[])
  34. {
  35.     InitializeCriticalSection(&g_cs1);
  36.     InitializeCriticalSection(&g_cs2);
  37.     printf("main[%x]: 准备进入临界段 g_cs1/n", GetCurrentThreadId());
  38.     EnterCriticalSection(&g_cs1);
  39.     printf("main[%x]: 成功进入临界段 g_cs1/n", GetCurrentThreadId());
  40.     HANDLE hThread[8];
  41.     for(int i=0; i<8; i++)
  42.     {
  43.         hThread[i] = CreateThread(NULL, 0, Worker, (LPVOID)i, 0, NULL);
  44.     }
  45.     printf("main[%x]: 准备进入临界段 g_cs2/n", GetCurrentThreadId());
  46.     EnterCriticalSection(&g_cs2);
  47.     printf("main[%x]: 成功进入临界段 g_cs2!/n", GetCurrentThreadId());
  48.     return 0;
  49. }

首先编译上面的程序。把Project  --〉Setting  --〉C++ --〉 Catalog中选中Coding Generation,然后在User run-time Library中选中Debug MultiThreaded。按F7 build之,然后F5直接调试运行。可看到下面的输出内容:

 

show deadlock

 

这里的Worker是main中创建的16个线程,[]里面的是线程的ID。现在程序已经死锁了,没有退出,只有一个线程还在每秒钟输出一次。要的就是这种效果,现在进入我们的主题。把死锁的原因定位出来(当然,这里的例子很简单就能看出来,但是实际的情况往往要复杂得多,而分析定位的方法是不变的)。

 

第一步,暂停程序的运行。可以通过Debug工具栏里那个表示暂停的小按钮(哪怕你第一次用VC6,只要用过随身听,录音机,mp3,vcd,dvd的都认识那个钮,不认识的打pp)。不知道怎么找到Debug工具栏的,去看看VC的程序员指南。或者在菜单里面的Debug --〉Break也行。

 

第二步,暂停下来之后,通常看到的是满屏的汇编代码。不要慌,把Call Stack窗口打开(按快捷键Alt + 7,或者是通过菜单View --〉Debug Window --〉Call Stack来激活),大概会看到下面的内容:

 

 

show call stack

 

这里看到的是当前线程的调用堆栈。不出意外的话,这个线程应该是那个还活着不停输出的线程(没想清楚的再复习一下操作系统中线程调度的内容,推荐Windows Internals)。实际上,从上面选中的那一行Worker(void * 00000007。。。)中就看到了这就是i=7时的那个线程。双击这一行进去看看,如下图:

 

 

show running thread来看看

 

这几个圈起来的内容。第一个是lpParam,我们看到它的值是7,正是我们创建的第8个线程。它在这不停的循环输出着。第二个圈中的内容是g_cs2,第三个是g_cs1。

 

第三步:看看两个全局变量CriticalSection我们看看目前是什么状况。分别QuickWatch这两个变量。

 

 

 

show global cs

 

时间比较紧,说一下最关心的字段OwningThread。我们看到g_cs1的OwningThread是0x3f18,g_cs2的OwningThread是0x36b4。这表示CriticalSection g_cs1被线程0x3f18占着,而g_cs2被线程0x36b4占着。

 

第四步:看看占着这两个临界区的线程在干什么。选中菜单Debug --〉Thread,看到下面的输出:

 

 

 

show all thread

 

我们看到前面蓝色背景的一行,最开头有个'*'号,这表示是调试器的当前线程。前面的7640是线程的ID,后面的[Worker]是当前运行到的位置。我们关心的是0x3f18和0x36b4这两个线程,往上一看就有了。0x3f18就是main线程,0x36b4是一个Worker线程。先看一下main线程,在0x3f18那行双击一下。很可能又是满屏的汇编。

 

第五步:跟第二步一样操作,从Call Stack中找到我们代码的位置。然后就发现代码停在下面这行上:

EnterCriticalSection(&g_cs2);

说明什么呢?说明main线程在等待进入临界区g_cs2。刚才已经提到main线程已经进入了g_cs1,而g_cs2被线程0x36b4占着,所以如果g_cs2不被释放的话,main就会一直占着g_cs1不放,死等g_cs2。重复第四,五步看看0x36b4在干吗,我们看到它也停在类似代码行上:

EnterCriticalSection(&g_cs1);

 

嗯,很明显了。这个线程已经进入了g_cs2,但是还要g_cs1;g_cs1又被main占着,main也非要进g_cs2,这就形成了死锁的条件:各自占有,循环等待。

 

第六步:解决问题。弄清楚了死锁的原因,剩下的就是破坏死锁的条件了,依赖于业务解决吧。

 

        怎么样,这个土土的VC6调试器还是很有用吧。当然,尽管本文是以VC6作为环境,其他的开发环境应该也有类似的功能。基本的思路就是确认被死锁的线程或者被死锁的变量。这里的例子是先看到的没死锁的线程,刚好能看到那两个g_cs1,2的值,所以就能确认是哪俩线程有问题。实际的情况通常是根据业务大致确认死锁的线程,然后看看是哪几个线程占着哪几个CriticalSection对象在循环等待,最后把关系理清楚,去掉耦合性来解决死锁。

 

        时间比较紧,可能有的地方写得还不清楚,不到位,比如CriticalSection数据结构中的其他几个字段,以后有机会再说吧,赶紧吃饭,上班去了。 大家好胃口,88~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值