分类 调试 下的文章

对于纯托管和纯本机调试,只能将1个调试器附加到进程。
为什么?
本机调试器从托管调试器下面窃取调试事件。这会混淆托管调试器并导致其崩溃。本机调试器无法与托管调试器协调。

执行此操作时遇到的问题:
在windows中,操作系统强制一次只能将一个本机调试器附加到调试对象(已调试进程的DebugActiveProcess将失败)。仅托管调试服务具有类似的检查,以强制一次只能将一个托管调试器附加到调试对象。仅托管调试实际上与纯本机调试是分开的。(这与使用辅助线程有关)。如果一个进程只被管理调试,操作系统不会将其视为只进行本机调试。
如果允许同时将1个托管调试器和1个本机调试器附加到应用程序。这样就可以绕过检查。更糟糕的是,我们无法完全检测到这种情况。本机调试api对托管调试一无所知,因此不能强制执行。托管调试api的进程内部分可以调用IsDebuggerPresent来检查是否附加了本机调试器,但是本机调试器可以在这些调用之间附加。即使在检测到这种情况时,托管调试器也没有一种好的响应方式。

为什么有人想这么做?
实际上有一些场景可以激励将多个调试器附加到一个应用程序上。

1) 获得更多的功能-仅本机调试器只能调试应用程序的本机部分。同样,仅托管调试器只能调试应用程序的仅托管部分。那么,如果你想调试一个同时使用托管代码和本机代码的应用程序呢?一种解决方案是附加两个调试器并同时使用它们。(我认为这是您在其他类似场景中必须做的;例如调试VB6调用本机COM对象)。我们开发了互操作调试(也称为“混合模式”)来显式地启用这个场景。它允许单个调试器调试应用程序的托管部分和本机部分。VisualStudio支持混合模式调试。
2)

正在调试服务器。一些服务器应用程序(如ASP.Net+SQL)可以代表多个用户运行代码。理想情况下,每个用户只需同时附加一个调试器并调试服务器上运行的部分代码。我们希望在未来的CLR版本中通过每appdomain调试(而不是仅仅是每个进程的调试)来支持这些场景。

在看代码之前我们先连简单的看下try的处理流程吧

  • 函数入口设置回调函数
  • 函数的异常抛出使用了__CxxThrowException函数,此函数包含了两个参数,分别是抛出一场关键字的throw的参数的指针,另一个抛出信息类型的指针(ThrowInfo *)。
  • 在异常回调函数中,可以得到异常对象的地址和对应ThrowInfo数据的地址以及FunInfo表结构的地址。根据记录的异常类型,进行try块的匹配工作
  • 没找到try块怎么办?先调用异常对象的析构函数,然后反汇ExcetionContinueSearch,继续反回到SEH继续执行。
  • 找到了try块?通过TryBlockMapEntry结构中的pCatch指向catch信息,用ThrowInfo结构中的异常类型遍历查找相匹配的catch块,比较关键字名称,找到有效的catch块。
  • 然后进行栈展开。
  • 析构try块中的对象
  • 跳转到catch块中执行
  • 调用_JumpToContinuation函数,返回catch语句块的结束地址。

上面的步骤,就是典型的异常处理的顺序。

光看文字多无趣,上代码 - 实例分析,我们来跑一遍:






1

Interop-Debugging,互操作调试,又称混合调试。

一般调试背景
当调试进程时,它会生成调试事件,调试器可以监听和响应这些事件。这些事件包括CreateProcess、LoadModule、异常、ExitThread、断点等。
调度调试事件时,调试对象将停止,直到调试器继续调试事件为止。当调试对象停止时,调试器可以在窗口期间检查调试对象。一旦debugger继续运行,它将自由运行,直到下一个调试事件。调试事件以每个线程为基础。
托管+本机调试都共享这个概念模型,尽管它们有不同的调试事件集,以及在停止和运行之间循环的不同方法。
调试器使用调试事件和检查API来实现所有调试操作。本文只涉及解释互操作调试如何路由托管+本机调试事件。如何在这些事件的基础上构建调试器不在本文的讨论范围之内。

本机调试:
本机调试由操作系统实现。操作系统提供用于监听和继续调试事件(WaitForDebugEvent和ContinueDebugEvent)的调试API。在调试事件中,操作系统冻结调试对象。本机调试API非常小。只有少数本机调试事件。
本机调试的关键属性:

-本机调试完全是进程外的(oop)。调试器不需要来自调试对象的额外合作(在操作系统支持之外)。
-我们将本机调试对象停止状态称为“冻结”。操作系统已停止冻结进程,并且在恢复之前不执行任何用户代码。
-本机调试事件可以在进程自由运行的任何时候出现。
-对win32调试API的所有调用都必须在同一线程上进行。我们称这个线程为W32ET,它变成了一个非常特殊的b/c,它决不能阻塞。

 

托管调试

托管调试完全是由CLR在用户模式下实现的,因此操作系统不知道何时进行纯托管调试。
CLR在为来自托管调试API的请求提供服务的每个托管进程中都有一个特殊线程(称为帮助线程)。CLR中专门用于托管调试的部分称为Left-Side(LS)。驻留在调试器进程中的ICorDebug的实现称为right-side(RS)。LS和RS通过各种用户模式进程间通信(IPC)机制进行通信,例如命名事件和共享内存块。
托管调试接口(ICorDebug)比其本机接口丰富得多。关键属性:

-托管调试是一个进程内模型。帮助线程必须存在并在调试对象的进程中运行,以便托管调试正常工作。
-我们将托管调试对象停止状态称为“已同步”。从操作系统的角度来看,同步进程是实时的,但是所有托管线程都被CLR停止。
-托管调试事件完全是在用户模式下创建和调度的。
-托管调试事件可以建立在本机调试事件之上。
-托管调试操作只能在托管停止状态下发生(这需要助手线程正在运行)

Managed vs. Native operations.

托管调试和本机调试是两个不同的世界。对于任何调试操作,查看它是在托管世界还是在本机世界中进行都非常重要。CLR调试服务只实现托管调试操作,不实现任何本机调试操作。同样,本机调试API不提供任何托管调试支持。
例如,最终用户只想到“步进”,但实际上有两个离散的操作,“托管步进”和“本机步进”,有两个完全不相交的实现。托管单步执行是通过CLR的ICorDebug API实现的;而本机单步执行是通过使用Win32调试事件的非CLR本机调试库实现的。
本机调试API级别很低,而托管调试API非常丰富。例如,本机执行控制(例如步进和断点)完全在异常之上实现,并且在本机调试API中没有显式的支持。托管调试API显式具有断点和步进器功能。
抽象级别的这种差异阻止了托管+本机调试操作之间的代码共享。

那么什么是互操作调试?
最终用户对互操作调试的看法是能够在单个调试会话中调试应用程序的托管+本机部分。这包括在托管+本机代码和运行混合调用堆栈之间单步执行的能力。托管+本机调试事件完全不相交,由调试器中的不同组件处理(我们称之为托管调试引擎和本机调试引擎)。
如果托管+本机调试器只是天真地同时附加到同一进程上,它们将相互干扰。互操作调试器确保这两个不相交的模型协同工作。
从接口的角度来看,互操作调试是将同一进程上的托管调试接口和本机调试接口公开给单个调试器。这意味着调试器的本机调试引擎和托管调试引擎可以同时运行,只需稍加修改。从理论上讲,这意味着可以轻松地扩展一个只进行托管调试和只进行本机调试的调试器来执行互操作调试。
理想情况下,本机+管理的调试引擎将彼此充分合作,以向最终用户呈现统一的模型。实际上,调试引擎之间的这种通信可能需要对调试器的设计进行重大的更改和规划。

 

随着计算机技术的飞速 发展和普及 , 越来越多的人直接与计算机打交道 , 因而产生 了研 究计算机操作者心理状态 的一 门综合性很强的边缘交叉科学 - 一软件心 理学。 软件心 理学 把实验心 理学 的技术和 认知心理学的概念应用于 计算机和信息科学 , 它 主要研究了计算机 科学 中人机系统和软件设计中人类心 理和 行为活 动的规律。 对软件心理 学的研究 , 在我 国还 是刚刚起步 , 本文对这一领域进 行了探讨和尝试 , 探讨 程序调试过程中人机对话对人的心 理 的影响 , 探讨了人同机器会话的心理 感受 , 探讨程序与 机器速度 的关系 , U N IX 分时系统终端数与模块大小的关系。 进而探讨如 何确定合适的程序模块, 合理的选择机型 , 使程序员不致因为环 境因素造成软件调试过程中的心理 压力 。

心理承受能力实验

首先把用户 (程序员 ) 对机器响应的承 受能力分成七个等级 : 极快 (1 ), 快 (2 ), 较快 (3) ,中等 (4 ) , 稍慢 (5 ), 慢 (6 ), 无 法忍受 (7) 。 本文 主要讨论如下几个问 题:

  • PC 机及兼容机 上编译 C 语言程序时的心理 承受能力分析;
  • U N IX 分时操作系 统环 境下 , 编译 O 程序的 过程 中如何影响程序员的心理 状态 ;
  • 人机会话期间 , 中间信息的输入/ 输 出对程序员的影响。
  • P C 机编译 C 程序的实验 选择 P C /X T 和 C 8 6 2 . 0 编译器 , 把给定的 6 0 个大小不 等的程序共

P C 机编译 C 程序的实验

选择 P C /X T 和 C 8 6 2 . 0 编译器 , 把给定的 6 0 个大小不 等的程序共分成 6 组 , 侮组 」 0 个程序 , 分组 原则是第一组 语 句 2 1 ~ 3 0 行 , 第二 组 3 1 ~ 4 0 , 第 三组 4 J ~ o 6 , 第四组 6 1 ~ 阳 , 第五组 8 1” 1 0 0 , 第六组 10 0 ~ 20 0 。 对每组程序进行编译测试 , 得出的时间求平均位 , 对该 组程序的承受级别求平均值。 以第四组 程序为 例说明测试 的过程和测试结果 : 参加测试的四名程序员是计算机软件专业 本科 四年级的学生。 A , B 男同学 , O , D 女 同学。 测试 过程如下: 先 给 10 个程序编号 P 丸 P Z , … , P I O。 A , O 一 组 , B , D 为 另一 组。 以A、C 组 为例 。 C 开始编译 一个程序 iP , 记录 下开 始 时间 T l 和结束时间 T Z , 当程 序 曰 编 译 结 束时 , A 说出感 受级 别, 记录下 编译时间 口、2一甲1 、 , 0 J 个 程 序都测试一 遍 。 人 . e 角 色 互换再 测试 一 遍 , 得出 O 的承受级别 , 同样测 出 B , D 的 承 受级 别

平均编译时间 2 6.89 秒 , 平均承受级别3 . 8 ( 中等)。 其中二名男同学的平均承 受 级 别4.0 , 二名女同学 3 . 6 。 同样的方法 得 出 6 组程序平均编译时间 、 平 均承受级别如 表1.

 

 顺便说一下 , 每组程序测试的结果都表明, 女同学的承受级别普遍小于男同学10% 左右 , 这 点说明了在调 试过程中, 女同学更有忍耐性 , 更适合做调试工作 (而不是设计)。

 

不 同机型编译 C 程序

选择第六组程序进行实验 (实验过程类似上面实验 1 )。

 

有屏幕输出信 息的 心理测试实验

对 于小程序 , 看屏幕输出与不看屏幕输出对程序员心理 无 明显影响 , 对于 第六 组 稍大一 点的程序 , 看屏幕输出时, 在 S U P E R P O/ X T 机上, 平均编译时间仍为 38 . 2 9 秒 , 但用户平 均感受级 别 4 , 在 P O /X T 2 8 6上 , 平均感受级别仍是 3 。 这一结果指 出了 中间信息的输出 有 时对改善程序员心理有较大作用 , 有时则不 明显。
UN I X 分 时系统编译 实验 ( 680 00 机 )

进行 680 0 0 机编译实验时 , 增补一 些较短的程序 , 被测程序语句行 1 0 ~ 2 0 0, 实验结果如下 :

4 台终端同时工作 , 每个程序编译时间4 0 ~ 62 秒 ;

6 台终端同时工作 , 每个程序编译 时间7 0 ~ 11 0 秒;

9 台终端同时工作 , 最短程序编译时间接近 3 分钟。

从中看 出, 分时系统进行编译或运行 , 对用户心理 压力最大的因素来 自同时工 作终端数 ( C P U 速度) , 其次才是程序规模。 因此 , 安排 学生 在分时系统上 机时 , 应考虑这点。

程序调 试中个体差 异 的实验

本实验是让二名受试者背对机器 , 编译开 始时 , 二名受试者被告之 开始 (事实上 , 根本就 没有编译程序 , 只是发出指令信号 ) , 被测者根据自己感觉的时间 , 说出心理 感受级别, 下 面 是记了结果

 

实验分析与结论

这 里讨论的一 些结论全 部基 于心理承 受级别不能超过 4 ( 中等) , 承受级别 5 ~ 7 时 , 对 程序员的程序调试有很大的心理 和身 体影响。 长期的心理 压力也影响程序调 试 的 准 确 性 , 增加出错率 , 导致程序调 试的逆反心 理。
1、机器速度对程 序员心理 的影响 表 1 中数据表明大于 8 1 行语句的程序在 S U P E R P O / X T 上编译时, 承受级 别 5 , 而在 邹 6 _ ! 几编 译至少 级别是 3 。 这说 明了大于 8 1 一 行的程序模块至少 要在 2 86 上调 试 。 P C 机 及 卫C / x T 的低档机只 可 做简易 的实习用。
2、屏幕输出对程 序 员心理 的影响 实验 3 的结论指 示 了增加屏幕输出有助于 改善调试 程序的心 理压 力 , 但 太 多 的 增 加 I / O 信息又 延长编译时间 , 是否 有数据可 参考呢 ? 实验 3 数据表明 3 8~ 4 0 秒的 界 限是心理 危机的开始, 因而编译程序应在运行 3 8 秒左右时提供一 些 信息。 如 p O T O O I J 一工具进行磁 盘O O p Y 时 , 有磁道的一些信息输 出。 用户 自身的程序运行时, 3 8 秒同样是个界值 , 用户 应 在相应的程序处加上 1 / 0 信息 , 由于机器主频不一致 , 用户 自己根据情 况处理 。 爪 l ) J E R 卫o / x T 一般 10 0 ~ 20 0 语句 , 2 8 6 一般 ` 1 0 0~ 加 0 等 , 这只是笔者之 建议 , 供参考 。

3、速度祖的机器 运行大程 序心 理调整 由于 P O 及 兼容机 (1 . OX ) 速度较慢 , 运行大一点程序 (1 0 0 行 ) , 用户的心理压 力就很 大 , 一般应做心 理 调整或更换编译器。 表 2 中的数据表明更换编译器 可使 承 受 级 别 提 高1 、 2 级。 另 外一点是在上机前就应“ 明确” , 程序很大时 , 编译时间长 。

4、UN I X 分 时 系统影响 用户 心理 的讨 论 前面 已讨论过 , 分时系统对用户的心 理影响主要是机器 速度 , 其次是编译软件。 因而 除 更换 编译软件和心 理 自身调 节外 , 就必 须对机器更新换代了。 另外 , 笔者在 A ST 2 3 6 上调 试 1 0 0 0 ~ 2 0 0 0 行的模块时 , 发现 仍有较大的心理 压力 , 承 受级别 4 ~ 5 , 这也说 明心理 压力 的调节主要靠硬件更新 。
5、音响、 图像对心理 因素的影响 笔者对 4 名受测者测试 , 给受试者 M S一 O 和 T U R B O 一 O 二 种编译器 (受 试者 以前从未 使用过 C ) , 受试者都愿使用 T U R B O O , 理 由是 画面直观 , 操作方便。 但当笔者介绍 了 M S哪 . 0 的 O O D E V I E W , M A K E 和 Q i l t c k一 O 等后 , 受试者表示也愿意选择 M S 一 O 。

心理调节能力与衡量标准

表 1 中数据和实验 5 中相比较 , 可 以看出表 1 中承受级别的时间明显 高于实验 5 中数 据。 图 4 是 图 1 , 图 2 和图 3 的合成图。

 

 

人们会问“为什么本机调试器不能调试托管代码?”.
原因是CLR提供了许多在典型的本地C++应用程序中所获得的酷服务,例如:运行在虚拟机/JITEN、动态类布局、类型系统、垃圾收集、反射等等。每一个都对调试器提出了特殊的挑战。换句话说,一个完成了所有这些功能的本地应用程序根本无法与传统的本地调试器进行调试。

1) 本机调试可以在硬件级别抽象,但是托管调试需要在IL级别抽象。托管代码不能仅仅被压缩成C/C++本地调试范例。一个原因是这可能会限制CLR执行IL的选项。例如,尽管目前(从v2.0起)jit-IL,我们还是希望为诸如解释IL、推销很少使用的jitted代码、甚至重新jitting代码等事情敞开大门。如果ICorDebug对所有内容都使用本机代码偏移量,它将无法调试解释的IL。
2) 托管调试需要很多信息,直到运行时才可用。对于托管代码,编译器只生成IL,真正的调试信息直到运行时才得到解析。例如,JIT将在运行时将IL编译为本机代码,加载程序将在运行时动态确定大多数类的布局。类型系统可以在运行时创建新类型. 对于本机代码,这都是在编译时确定的。托管调试器需要某种方法在运行时获取所有这些信息。一些解决方案包括:

a、 让CLR在运行时在信息确定时创建辅助pdb。这可能是一个巨大的性能命中率,如果没有附加调试器,我们不愿意这样做。但是如果我们在没有附加调试器的情况下不这样做,那么如果以后调试器附加了调试器,它可能就不可用了。
b、 让托管调试器检查相关的CLR数据结构(直接从进程外或通过进程内运行的“helper”线程)。这里的一个重要警告是确保当CLR数据结构处于不一致状态时,调试器不会请求此类信息。CLR当前使用帮助线程。

3) 托管调试器需要与垃圾回收器(GC)协调。CLR有一个标记-清除压缩GC。这意味着GC将移动对象来整理堆碎片,并在整个过程中相应地更新所有引用(“GC根”)。这会从几个方面影响调试:

a、 调试对象在GC期间暂时处于不一致的状态。调试器必须与GC协调,以确保在此窗口期间不会检查调试对象。
b、 调试器可以让用户更改变量的值。此更新必须与GC的更新相协调。
c、 没有方便的对象标识。在本机代码中,对象的原始指针值唯一地标识该对象,因为对象不会四处移动。