一个查找指定栈回溯符号并执行命令的Windbg脚本

这是自己没事在家调试程序的一个小需求。

有时候比如IE这样的程序,线程实在是非常的多,我想操作某个特殊线程就比较麻烦,需要先找到线程然后再实行命令,为了偷懒就写了这个脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$$ If a frame of a thread has the target symbol, we execute the command.
$$ Author: nighxie
$$ Blog: 0cch.net
.if (${/d:$arg1} & ${/d:$arg2} & ${/d:$arg3}) {
.for (r @$t0 = 0; @$t0 < 0n${$arg1}; r @$t0 = @$t0 + 1) {
r @$t1 = 0;
~[@$t0]s;
!for_each_frame .if($spat("${@#SymbolName}", "${$arg2}")) {r @$t1 = 1}
.if (@$t1 == 1) {
${$arg3};
}
}
}
.else {
.echo "Usage $$>a<${$arg0} thread_count pattern cmd";
.echo "e.g. $$>a<${$arg0} 5 ntdll* ~n";
}

就如同上面的例子指定线程数量,要匹配的符号,最后就是要执行的命令。
$$>a<${$arg0} 5 ntdll* ~n就表示在前5个线程里寻找栈回溯有关ntdll的线程,然后执行~n命令挂起线程。

Debugging

Windows 8.1中获得系统版本信息的方法

在Windows 8.1之前的系统版本上,我们一直可以使用 GetVersionEx.aspx) 这个函数来获取当前系统的MajorVersion和MinorVersion。但是当Windows系统来到8.1时代,这个API似乎就不好用了。如果在Windows 8.1上调用这个函数,我们更有可能获得的版本号是Windows 8的版本6.2,而不是我们想要的6.3。在MSDN上提供了这样一段说明:

With the release of Windows 8.1, the behavior of the GetVersionEx API has changed in the value it will return for the operating system version. The value returned by the GetVersionEx function now depends on how the application is manifested.

Applications not manifested for Windows 8.1 will return the Windows 8 OS version value (6.2). Once an application is manifested for a given operating system version, GetVersionEx will always return the version that the application is manifested for in future releases.

好了,既然微软都这么说了,也没办法,还好微软也给我们提供了另一套函数,叫做Version Helper functions.aspx) ,看起来是一套很不错的API,能帮助我们方便的判断系统版本。但是,仔细一看,这套函数需要头文件VersionHelpers.h,而这个文件是 Windows 8.1 software development kit 的一部分。对于使用低版本的VS还得装新版SDK,岂不麻烦。那么我们希望能找到一套更好的解决方法。

我第一个能想到了,当然就是万能的WMI,使用Win32_OperatingSystem class中的Version可以获得一个形如6.3.9600的字符串,我们就能通过解析这个获得系统的版本了。但是说实话,不到万不得已我不太喜欢用WMI这套API,总感觉为了一个小功能,牵扯了一堆东西。

那么第二套方案,是我觉得比较满意的,那就是调用VerSetConditionMask.aspx)和VerifyVersionInfo.aspx)来完成对系统版本的判断。具体做法如下:

1
2
3
4
5
6
7
8
9
10
11
12
BOOL IsWindows8Point1()
{
OSVERSIONINFOEX version_info = {0};
version_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
version_info.dwMajorVersion = 6;
version_info.dwMinorVersion = 3;
ULONGLONG mask = VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL);
mask = VerSetConditionMask(mask, VER_MINORVERSION, VER_EQUAL);
return VerifyVersionInfo(&version;_info, VER_MAJORVERSION | VER_MINORVERSION, mask);
}

那么,我们就可以用这个函数来判断系统是否是Windows 8.1,如果不是,我们就可以用老办法,GetVersionEx来获得系统的版本号作判断了。当然了,大家看到这估计也能看出,我们自己也能用这两个函数实现一套所谓的Version Helper functions。举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BOOL WINAPI IsWindowsVersionOrGreater(
WORD wMajorVersion,
WORD wMinorVersion,
WORD wServicePackMajor
)
{
OSVERSIONINFOEX version_info = { 0 };
version_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
version_info.dwMajorVersion = wMajorVersion;
version_info.dwMinorVersion = wMinorVersion;
version_info.wServicePackMajor = wServicePackMajor;
ULONGLONG mask = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
mask = VerSetConditionMask(mask, VER_MINORVERSION, VER_GREATER_EQUAL);
mask = VerSetConditionMask(mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
return VerifyVersionInfo(&version;_info, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, mask);
}

怎么样,是不是达到了以假乱真的效果了。那么最后,咱再看看这么写出来的API的效果如何:

20140413233019

===============想睡觉的分割线====================

更新另外一个方法,在网上看到的,感觉也还行。只不过需要引入其他DLL,可以作为备选方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
ULONG MyGetVersion()
{
LPBYTE raw_data;
ULONG retval = 0;
if (NetWkstaGetInfo(NULL, 100, &raw;_data) == NERR_Success) {
WKSTA_INFO_100 * ws_info = reinterpret_cast(raw_data);
retval = (ws_info->wki100_ver_major << 16) | ws_info->wki100_ver_minor;
NetApiBufferFree(raw_data);
return retval;
}
return retval;
}

Tips

Global Logger打开开机启动ETW日志

在我看来XP确实应该寿终正寝了,因为确实在很多机制方面不如新的系统,比如ETW。而微软的XPERF也没有能在XP上直接安装的。当然,老版本的XPERF还能在XP上运行,只是监控能力有限,到了新版本的XPERF,在XP上就运行不了了。但是,XP却在中国还活的好好的,所以优化其性能必不可少。于是,在没有XPERF支持的情况下,要做到开启ETW日志,我们就需要微软提供的Global Logger机制,打开开机启动的Trace Session。

微软对打开Global Logger的方法做了详细的说明:http://msdn.microsoft.com/en-us/library/windows/hardware/ff546686(v=vs.85).aspx .aspx ),我这里没必要再赘述了,只有一个地方需要注意,要说明一下EnableKernelFlags这个变量,他是一个REG_BINARY类型,他的值是EVENT_TRACE_PROPERTIES的EnableFlags。但是EnableFlags是一个DWORD,而EnableKernelFlags是一个32字节的数组。如果你设置的时候,只是设置了一个DWORD,那么你会发现ETW 日志不会开启。

话说到这,EnableKernelFlags中前4字节的DWORD是EVENT_TRACE_PROPERTIES的组合,那么后面还有28字节是干什么的呢?实际上ETW能记录的标志还有很多,只是没有在EVENT_TRACE_PROPERTIES中说明,他们都被分到8个分组里面去了,这也是为什么有32个字节的Flags。具体怎么分组,还有哪些标志位,可以参考WRK的代码。

我这里写了个小工具可以用来设置Global Flags,不过如果你能找到XP上可以用的XPERF当然是最好的选择了!

20140405104746

下载:GlobalLog

Tips