基于BranchTraceStore机制的CPU执行分支追踪工具 —— CpuWhere [修正版 仅驱动]
[前言]
在张银奎老师的《软件调试》一书中,详细地讲解了使用内存的分支记录机制——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 [修正版 仅驱动]的更多相关文章
- 【CPU微架构设计】利用Verilog设计基于饱和计数器和BTB的分支预测器
在基于流水线(pipeline)的微处理器中,分支预测单元(Branch Predictor Unit)是一个重要的功能部件,它负责收集和分析分支/跳转指令的执行结果,当处理后续分支/跳转指令时,BP ...
- 浅谈原子操作、volatile、CPU执行顺序
浅谈原子操作.volatile.CPU执行顺序 在计算机发展的鸿蒙年代,程序都是顺序执行,编译器也只是简单地翻译指令,随着硬件和软件的飞速增长,原来的工具和硬件渐渐地力不从心,也逐渐涌现出各路大神在原 ...
- git跟踪远程分支,查看本地分支追踪和远程分支的关系
跟踪远程分支 如果用git push指令时,当前分支没有跟踪远程分支(没有和远程分支建立联系),那么就会git就会报错 There is no tracking information for the ...
- 并发编程-CPU执行volatile原理探讨-可见性与原子性的深入理解
volatile的定义 Java语言规范第3版中对volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量.Jav ...
- Spark练习之wordcount,基于排序机制的wordcount
Spark练习之wordcount 一.原理及其剖析 二.pom.xml 三.使用Java进行spark的wordcount练习 四.使用scala进行spark的wordcount练习 五.基于排序 ...
- Android的事件处理机制(一)------基于回调机制的事件处理
Android平台的事件处理机制有两种,一种是基于回调机制的,一种是基于监听接口的,现介绍第一种:基于回调机制的事件处理.Android平台中,每个View都有自己的处理事件的回调方法,开发人员可以通 ...
- 流程控制:顺序结构: 代码默认从上到下依次执行 分支结构: 细分在分为如下 循环结构: while .. for ..
# ### 流程控制: ''' 流程: 代码执行的过程 流程控制: 对代码执行的过程进行管控 顺序结构: 代码默认从上到下依次执行 分支结构: 细分在分为如下 循环结构: while .. for . ...
- 深入理解 JS 引擎执行机制(同步执行、异步执行以及同步中的异步执行)
首先明确两点: 1.JS 执行机制是单线程. 2.JS的Event loop是JS的执行机制,深入了解Event loop,就等于深入了解JS引擎的执行. 单线程执行带来什么问题? 在JS执行中都是单 ...
- Android——事件处理模型一(基于回调机制的事件处理)(转)
Android平台的事件处理机制有两种,一种是基于回调机制的,一种是基于监听接口的,现介绍第一种:基于回调机制的事件处理.Android平台中,每个View都有自己的处理事件的回调方法,开发人员可以通 ...
随机推荐
- Sandy and Nuts
题意: 现在有一个$n$个点的树形图被拆开,现在你知道其中$m$条边,已经$q$对点的$LCA$,试求原先的树有多少种可能. 解法: 考虑$dp$,$f(x,S)$表示$x$的子树内的点集为$S$(不 ...
- 3.1 HiveServer2.Beeline JDBC使用
https://cwiki.apache.org/confluence/display/Hive/HiveServer2+Clients 一.HiveServer2.Beeline 1.HiveSer ...
- Java简单的数组用法尝试,和C语言很不一样
public class Main { static int ARRY_LONGTH=100; static int[] getRandomArr(int n){ int[] randomArr; r ...
- Angular6之ng build | ng build --aot | ng build --prod 差异
由于写了大半年的项目终于要告一段落并且即将进行第二阶段优化开发,emmm 基础版本已经二十多个模块了,必不可少的优化是很重要的,尽管项目上使用多层嵌套懒加载,但是在首屏加载的时候,任然很慢啊,因为一直 ...
- 336. Palindrome Pairs(can't understand)
Given a list of unique words, find all pairs of distinct indices (i, j) in the given list, so that t ...
- VC++11 编译中的一些问题的解决办法
1. vc++ 的编译器的错误往往定位在错误的那一处,但是那一处可能在库的底层,而我们知道库,一般都不会错. 这时候应该好好看看我们自己的头文件是否正确,有可能头文件中的一些错误引发了连锁反应. 2 ...
- Ajax登陆,使用Spring Security缓存跳转到登陆前的链接
Spring Security缓存的应用之登陆后跳转到登录前源地址 什么意思? 用户访问网站,打开了一个链接:(origin url)起源链接 请求发送给服务器,服务器判断用户请求了受保护的资源. 由 ...
- vijos次小生成树
xiaomengxian的哥哥是一个游戏迷,他喜欢研究各种游戏.这天,xiaomengxian到他家玩,他便拿出了自己最近正在研究的一个游戏给xiaomengxian看.这个游戏是这样的:一个国家有N ...
- mysql ERROR 2003 (HY000): Can't connect to MySQL server on '' (10060
关闭防火墙即可连接成功: systemctl stop firewalld
- mysql状态查询
在监控中,都是去探测这些状态数据,然后换算到时间刻度上,像zabbix. show status like 'uptime'; --查看select语句的执行数 show [global] statu ...