[前言]

在张银奎老师的《软件调试》一书中,详细地讲解了使用内存的分支记录机制——BTS机制(5.3),并且给出了示例工具CpuWhere及其源代码。但实际运行(VMware XP_SP3 单核)并没有体现应有的效果,无法读取到分支记录。查看了源代码并没有发现任何问题,与书中所讲一致。既然软件本身没有问题,那会不会是在虚拟机中运行的问题呢?

翻出了闲置多年的老机器,奔腾Dual+XP_SP3,在启动配置中增加/numproc=1,设置单核启动,测试结果依然没有什么改变。网上搜索几遍也是无果,毕竟是很小众的东西,只找到了一个论坛上有针对多核的修改版本,由于我没有该论坛的账号,也没能下载测试。

最后翻看了Intel手册,发现了问题的原因:如果DS机制使用DTES64模式,CPU会将分支记录的大小扩充为64位。

经测试,在奔腾Dual+XP_SP3的配置下,DTES64模式是启用的,所以问题应该就在这里。VMware为何无效暂时不清楚,只是发现操作DS和BTS相关的MSR寄存器时没有效果(无法读写),也许是没有配置好虚拟机,也可能是VMware未对DS和BTS机制进行虚拟化,所以请尽量不要在虚拟机中测试和使用此类工具。

[正确的使用BTS机制的详细步骤]

一、使用CPUID指令判断是否支持DS机制和RDMSR/WRMSR指令;读IA32_MISC_ENABLE寄存器判断是否支持BTS机制。

1. EAX=1时,EDX中表示DS和RDMSR/WRMSR支持情况的标志位分别为:

2. IA32_MISC_ENABLE寄存器表示的BTS机制支持情况:

3. 代码如下:

 BOOLEAN IsSupported()
{
DWORD _edx = ;
DWORD _eax = ; _asm
{
mov eax,
cpuid
mov _edx,edx
} if ((_edx & ( << BIT_DS_SUPPORTED)) == )
{
DBGOUT(("Debug store is not supported."));
return FALSE;
} if ((_edx & ( << BIT_RWMSR_SUPPORTED)) == )
{
DBGOUT(("RDMSR/WRMSR is not supported."));
return FALSE;
} ReadMSR(IA32_MISC_ENABLE, &_edx, &_eax);
if ((_eax & ( << BIT_BTS_UNAVAILABLE)) != )
{
DBGOUT(("Branch trace store is not supported."));
return FALSE;
} return TRUE;
}

二、使用CPUID指令判断当前DS机制是否为DTES64模式。若是,则DS结构中BTS的基地址、索引、边界地址和中断阈值都应为64位,BranchRecord中的来源地址、目标地址以及标志数据也应为64位,PEBS同理。

1. EAX=1时,ECX中表示是否为DTES64模式的标志位是:

2. 代码如下:

 BOOLEAN IsDTES64()
{
DWORD _ecx = ; _asm
{
mov eax,
cpuid
mov _ecx,ecx
} return ((_ecx & ( << BIT_DTES64)) != ) ? TRUE : FALSE;
}

三、根据第二步的结果来设置相应的DS和BTS(仅编写了DTES64模式的代码,非DTES64模式的情况请参照原书)。

1. DTES64模式下,DS和BranchRecord的结构:

2. DTES64模式下,DS和BranchRecord的结构声明如下:

 typedef struct _DEBUG_STORE
{
ULONG64 btsBase;
ULONG64 btsIndex;
ULONG64 btsAbsolute;
ULONG64 btsInterruptThreshold;
ULONG64 pebsBase;
ULONG64 pebsIndex;
ULONG64 pebsAbsolute;
ULONG64 pebsInterruptThreshold;
ULONG64 pebsCounterReset;
ULONG64 reserved;
} DEBUG_STORE, *PDEBUG_STORE;
 typedef struct _BRANCH_RECORD
{
ULONG64 from;
ULONG64 to;
ULONG64 flags;
} BRANCH_RECORD, *PBRANCH_RECORD;

3. 必须为DS和BTS申请非分页内存:

4. 代码如下:

 BOOLEAN InitDebugStore()
{
g_pDebugStore = ExAllocatePoolWithTag(NonPagedPool, sizeof(DEBUG_STORE), (ULONG)"SD__");
if (g_pDebugStore == NULL)
{
DBGOUT(("Failed to allocate memory for debug store."));
return FALSE;
}
memset(g_pDebugStore, , sizeof(DEBUG_STORE)); return TRUE;
}
 BOOLEAN InitBranchTraceStore()
{
g_pBranchTraceStore = ExAllocatePoolWithTag(NonPagedPool, sizeof(BRANCH_RECORD) * MAX_RECORD, (ULONG)"STB_");
if (g_pBranchTraceStore == NULL)
{
DBGOUT(("Failed to allocate memory for branch trace store."));
return FALSE;
}
memset(g_pBranchTraceStore, , sizeof(BRANCH_RECORD) * MAX_RECORD); return TRUE;
}

5. 设置DS的代码如下:

 VOID SetDebugStore()
{
g_pDebugStore->btsBase = (ULONG64)g_pBranchTraceStore;
g_pDebugStore->btsIndex = (ULONG64)g_pBranchTraceStore;
g_pDebugStore->btsAbsolute = (ULONG64)g_pBranchTraceStore + sizeof(BRANCH_RECORD) * MAX_RECORD;
g_pDebugStore->btsInterruptThreshold = (ULONG64)g_pBranchTraceStore + sizeof(BRANCH_RECORD) * (MAX_RECORD + );
WriteMSR(IA32_DS_AREA, HIDWORD(g_pDebugStore), LODWORD(g_pDebugStore));
}

PS:此处让我了解了C语言强制类型转换的原理(小类型转大类型)。

双机调试时,查看g_pDebugStore强制转为ULONG64后的内存,其高32位为0xFFFFFFFF。我一直以为小类型转大类型是在其高位补0,出现这种情况令我十分不解,于是查看反汇编发现了原因:

由于32位程序可使用的寄存器最大宽度为32位,所以当要表示一个64位数时,其形式为[Reg:Reg],如EDX:EAX。当要把一个32位数据扩充为64位时,CPU使用CDQ指令将该数值的符号位复制到EDX中的每一位,这样EDX:EAX即表示一个64位的数据。

回到代码中,因为这是一个驱动程序,运行在Ring0,所以系统分配的虚拟地址一定大于0X7FFFFFFF,这样一来,对于32位宽度的数据来说,这表是一个负数。负数的符号位是1,用1填满EDX即为0xFFFFFFFF,这样可以保证0xFFFFFFFF~XXXXXXXX和原值相等,如果补0就变成了正数,自然是不对的。

此次事件再次教育了我:凡事不能想当然,要求甚解。

四、启用BTS机制

1. IA32_DEBUGCTL寄存器中表示分支启用、分支记录方式和是否缓冲区满时触发中断的标志位分别为:

2. 设置TR和BTS位为1来启用BTS机制,设置BTINT位为0来表示一个环形缓冲区,代码如下:

 VOID EnableBranchTraceStore()
{
DWORD _edx = ;
DWORD _eax = ; ReadMSR(IA32_DEBUGCTL, &_edx, &_eax);
_eax |= << BIT_TR;
_eax |= << BIT_BTS;
_eax &= ~( << BIT_BTINT);
WriteMSR(IA32_DEBUGCTL, _edx, _eax);
}

五、将以上步骤写入DriverEntry例程

1. 根据顺序依次调用即可在DTES64模式下顺利启用BTS机制:

 NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegisterPath)
{
UNREFERENCED_PARAMETER(pRegisterPath); DBGOUT(("DriverEntry()")); pDriverObject->DriverUnload = MyCpuWhereUnload; if (!IsSupported())
{
return STATUS_FAILED_DRIVER_ENTRY;
} if (IsDTES64())
{
DBGOUT(("Running on DTES64 mode."));
}
else
{
DBGOUT(("Not running on DTES64 mode."));
} if (!InitDebugStore())
{
return STATUS_FAILED_DRIVER_ENTRY;
} if (!InitBranchTraceStore())
{
return STATUS_FAILED_DRIVER_ENTRY;
} SetDebugStore(); EnableBranchTraceStore(); return STATUS_SUCCESS;
}

六、为了简化代码(仅测试用),将以上禁用BTS机制、获取分支记录以及释放DS和BTS内存的所有代码都放入了DriverUnload例程。

1. 在读取BTS缓冲区之前,要先禁用BTS机制(与开启过程一致但标志位值取反):

 VOID DisableBranchTraceStore()
{
DWORD _edx = ;
DWORD _eax = ; ReadMSR(IA32_DEBUGCTL, &_edx, &_eax);
_eax &= ~( << BIT_TR);
_eax &= ~( << BIT_BTS);
WriteMSR(IA32_DEBUGCTL, _edx, _eax);
}

2. 根据BTS的结构来循环读取BranchRecord:

见下

3. 释放之前为DS和BTS申请的非分页内存:

见下

4. 代码如下

 VOID MyCpuWhereUnload(PDRIVER_OBJECT pDriverObject)
{
PBRANCH_RECORD pRecord = NULL;
DWORD count = ; UNREFERENCED_PARAMETER(pDriverObject); DBGOUT(("DriverUnload()")); DisableBranchTraceStore(); pRecord = (PBRANCH_RECORD)LODWORD(g_pDebugStore->btsBase);
for (; pRecord < (PBRANCH_RECORD)LODWORD(g_pDebugStore->btsAbsolute) && count < MAX_RECORD; ++pRecord, ++count)
{
if (pRecord->from == )
{
break;
}
DBGOUT(("%d: From: 0x%08X\n%d: To: 0x%08X", count + , (DWORD)pRecord->from, count + , (DWORD)pRecord->to));
} ExFreePoolWithTag(g_pBranchTraceStore, (ULONG)"STB_");
ExFreePoolWithTag(g_pDebugStore, (ULONG)"SD__");
}

七、由此便完成了在DTES64下启用BTS机制的全部过程,因未支持多核,所以可能会出现不可预料的状况,请谨慎使用。

运行效果:

[总结]

仅作学习而用,并未编写GUI界面和R3&R0的通讯例程,也未实现兼容非DTES64模式的代码,但这几点都可在张银奎老师编写的原版CpuWhere的源码中找到相关代码。

张银奎老师的原版CpuWhere(Bin&Src)

下载并使用这个工具的许可条件是使用者本人购买了《软件调试》一书

下载地址:http://advdbg.org/books/swdbg/t_cpuwhere.aspx

针对DTES64模式的修正版CpuWhere(Src VS2013 + WDK8.1)

下载地址:http://files.cnblogs.com/files/Chameleon/MyCpuWhere.zip

基于BranchTraceStore机制的CPU执行分支追踪工具 —— CpuWhere [修正版 仅驱动]的更多相关文章

  1. 【CPU微架构设计】利用Verilog设计基于饱和计数器和BTB的分支预测器

    在基于流水线(pipeline)的微处理器中,分支预测单元(Branch Predictor Unit)是一个重要的功能部件,它负责收集和分析分支/跳转指令的执行结果,当处理后续分支/跳转指令时,BP ...

  2. 浅谈原子操作、volatile、CPU执行顺序

    浅谈原子操作.volatile.CPU执行顺序 在计算机发展的鸿蒙年代,程序都是顺序执行,编译器也只是简单地翻译指令,随着硬件和软件的飞速增长,原来的工具和硬件渐渐地力不从心,也逐渐涌现出各路大神在原 ...

  3. git跟踪远程分支,查看本地分支追踪和远程分支的关系

    跟踪远程分支 如果用git push指令时,当前分支没有跟踪远程分支(没有和远程分支建立联系),那么就会git就会报错 There is no tracking information for the ...

  4. 并发编程-CPU执行volatile原理探讨-可见性与原子性的深入理解

    volatile的定义 Java语言规范第3版中对volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量.Jav ...

  5. Spark练习之wordcount,基于排序机制的wordcount

    Spark练习之wordcount 一.原理及其剖析 二.pom.xml 三.使用Java进行spark的wordcount练习 四.使用scala进行spark的wordcount练习 五.基于排序 ...

  6. Android的事件处理机制(一)------基于回调机制的事件处理

    Android平台的事件处理机制有两种,一种是基于回调机制的,一种是基于监听接口的,现介绍第一种:基于回调机制的事件处理.Android平台中,每个View都有自己的处理事件的回调方法,开发人员可以通 ...

  7. 流程控制:顺序结构: 代码默认从上到下依次执行 分支结构: 细分在分为如下 循环结构: while .. for ..

    # ### 流程控制: ''' 流程: 代码执行的过程 流程控制: 对代码执行的过程进行管控 顺序结构: 代码默认从上到下依次执行 分支结构: 细分在分为如下 循环结构: while .. for . ...

  8. 深入理解 JS 引擎执行机制(同步执行、异步执行以及同步中的异步执行)

    首先明确两点: 1.JS 执行机制是单线程. 2.JS的Event loop是JS的执行机制,深入了解Event loop,就等于深入了解JS引擎的执行. 单线程执行带来什么问题? 在JS执行中都是单 ...

  9. Android——事件处理模型一(基于回调机制的事件处理)(转)

    Android平台的事件处理机制有两种,一种是基于回调机制的,一种是基于监听接口的,现介绍第一种:基于回调机制的事件处理.Android平台中,每个View都有自己的处理事件的回调方法,开发人员可以通 ...

随机推荐

  1. java 内存管理机制

    垃圾收集算法 1.标记清理算法:效率不高(标记和清理过程效率都不高).会形成内存碎片 2.复制算法:把内存分为两部分,当进行回收时,把使用部分的存活对象复制到未使用部分,然后两部分内存角色互换(一个为 ...

  2. 1.1- 1.2 hive入门

    一.hive是什么 由Facebook开源用于解决海量结构化日志的数据统计: Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射成一张表, 并提供类SQL查询功能: 构建在Had ...

  3. c++拷贝函数详解(转)

    一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如 int a = 100; int b = a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变 ...

  4. Java读入优化

    之前被软院校赛卡了一波T,很亏啊.以下抄袭自Codeforces的神仙Petr. 可能得系统研究Java怎么写了?缺点是不能使用hasNext(),可能需要在main()中解决. import jav ...

  5. HTML5 中的meter 标签的样式设置

    meter { -webkit-appearance: none; position: relative; display: block; margin: 8px auto; width: 100px ...

  6. Codeforces691A【读题-水】

    妈蛋wa了两次.. 时尚的定义是length大于1的要破个洞,一定要破个洞.. According to rules of the Berland fashion, a jacket should b ...

  7. Unity3D脚本:更改脚本和类名,且不破坏现有脚本引用的方法

    Unity开发过程中,难免遇到需要修改类名(以及对应脚本名)的时候,但如果草率地在MonoDevelop里把类名直接改掉,会破坏现有场景以 及Project Assets中所有引用该脚本的链接,届时还 ...

  8. IT兄弟连 JavaWeb教程 Servlet会话跟踪 获取Session对象

    Session对象的获取有两种: ●  有参方法: HttpSession request.getSession(boolean isNew) 参数: true:获取一个Session对象,如果之前S ...

  9. P1290-关灯

    描述 Description 宁智贤得到了一份有趣而高薪的工作.每天早晨她必须关掉她所在村庄的街灯.所有的街灯都被设置在一条直路的同一侧.宁智贤每晚到早晨5点钟都在晚会上,然后她开始关灯.开始时,她站 ...

  10. 了解cookie

    1.cookie数据会自动在Web浏览器和Web服务器之间传输的,因此服务端脚本就可以读,写存储在客户端的cookie值. 2.在javascript中使用cookie不会采用任何加密机制,因此是不安 ...