[前言]

在张银奎老师的《软件调试》一书中,详细地讲解了使用内存的分支记录机制——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. XAMPP打不开Apache服务的解决办法

    XAMPP打不开Apache服务的解决办法 不用修改设置,应该是80端口被占用了,直接先IIS的网站给停了就OK

  2. opencv Mat.at

    opencv c++ mat获取像素及像素赋值 For accessing pixel's channel value : ; i < image.cols; i++) { ; j < i ...

  3. 微信小程序开发之真机预览

    1:真机预览时上传组件的坑: 当在真机里面使用上传组件,当进入选择相片或者拍照的时候,小程序会进入后台,调用APP onHide()方法,选择完返回小程序是会调用App Onshow()方法,然后调用 ...

  4. 15.oauth2 + oidc 实现 server部分

    OAuth主要做授权. OpenIdConnect简历在OAuth2.0基础之上的,相结合 客户端.授权中心.Resource Owner用户本身(资源的拥有者).Resource Server 通过 ...

  5. MyEclipse10.0安装SVN的三种方法

    最简单的一种: 首先下载zip包(svn:http://subclipse.tigris.org) 1.直接在MyEclipse10安装目录下的dropins文件夹下新建一个svn文件夹 2.把解压出 ...

  6. 图数据库初探之Neo4j

    图数据库初试之Neo4j 自从进入了移动互联网时代,各种新事物出现的速度都好像坐上了宇宙飞船,几乎隔几天一个新概念.就拿数据库而言,什么Oracle.DB2.SQL Server.MySQL,这些你都 ...

  7. E20190308-hm

    sweep vt. 扫除; 打扫,清理; 彻底搜索; 掠过; vi. 打扫; 扫过; 蜿蜒; 大范围伸展; n. 打扫; 延伸; 挥动; 全胜;

  8. HTML基本标签元素

    HTML:  超文本标记语言(HyperText   Mark-up  Language ) 1.作用:写网页结构  2.HTML不区分大小写,建议小写   3.文件后缀 .html  或者  .ht ...

  9. c语言里面你不知道的break与switch,contiune的用法

    前言:最近上完课在宿舍闲来无事,就拿起了C Primer Plus 这本书看,是自己入门编程的第一门语言:看了一些基本语法知识点,最让我需要总一下的是就是标题所说的这个语法知识点,记得大一的时候去考计 ...

  10. C 语言实例 - 字符串中各种字符计算

    C 语言实例 - 字符串中各种字符计算 C 语言实例 C 语言实例 计算字符串中的元音.辅音.数字.空白符. 实例 #include <stdio.h> int main() { ]; i ...