Dump stl的vector,list,map的三个windbg脚本

用Windbg查看stl的容器实在是已经让人悲伤的事情,为了方便,所以写了这么3个脚本
vector:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
r? $t0 = ${$arg1}
.if (${/d:$VectorType}) {
r? $t0 = @@C++(*((${$VectorType} *)@$t0))
}
.if (${/d:$arg2}) {
.if ($sicmp("${$arg2}", "-c") == 0) {
r $t2 = 0
aS ${/v:command} "${$arg3}"
}
}
.else {
r $t2 = 1
aS ${/v:command} " "
}
r? $t1 = @@C++(@$t0._Mylast)
r? $t0 = @@C++(@$t0._Myfirst)
.printf "size = %d\n", @@C++((@$t1 - @$t0))
.while (@$t0 != @$t1) {
.if ($t2 == 1) {
?? @@c++(@$t0->_Bx)
}
.else {
r? $t9 = @$t0->_Bx
command
}
}
ad command

list:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
r? $t0 = ${$arg1}
.if (${/d:$ListType}) {
r? $t0 = @@C++(*((${$ListType} *)@$t0))
}
.if (${/d:$arg2}) {
.if ($sicmp("${$arg2}", "-c") == 0) {
r $t2 = 0
aS ${/v:command} "${$arg3}"
}
}
.else {
r $t2 = 1
aS ${/v:command} " "
}
.printf "size = %d\n", @@C++(@$t0._Mysize)
r? $t1 = @@C++(@$t0._Myhead)
r? $t0 = @@C++(@$t0._Myhead)
r? $t0 = @@C++(@$t0->_Next)
.while (@$t0 != @$t1) {
.if ($t2 == 1) {
?? @@c++(@$t0->_Myval._Bx)
}
.else {
r? $t9 = @$t0->_Myval._Bx
command
}
r? $t0 = @@C++(@$t0->_Next)
}
ad command

map:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
.if ($sicmp("${$arg1}", "-n") == 0) {
.if (@@C++(@$t0->_Left) != @@C++(@$t1)) {
.push /r /q
r? $t0 = @$t0->_Left
$$>a< ${$arg0} -n
.pop /r /q
}
.if (@@C++(@$t0->_Isnil) == 0) {
.if (@$t2 == 1) {
.printf /D "%p\n", @$t0, @$t0
.printf "key = "
?? @$t0->_Myval.first
.printf "value = "
?? @$t0->_Myval.second
}
.else {
r? $t9 = @$t0->_Myval
command
}
}
.if (@@C++(@$t0->_Right) != @@C++(@$t1)) {
.push /r /q
r? $t0 = @$t0->_Right
$$>a< ${$arg0} -n
.pop /r /q
}
}
.else {
r? $t0 = ${$arg1}
.if (${/d:$MapType}) {
r? $t0 = @@C++(*((${$MapType} *)@$t0))
}
.if (${/d:$arg2}) {
.if ($sicmp("${$arg2}", "-c") == 0) {
r $t2 = 0
aS ${/v:command} "${$arg3}"
}
}
.else {
r $t2 = 1
aS ${/v:command} " "
}
.printf "size = %d\n", @@C++(@$t0._Mysize)
r? $t0 = @$t0._Myhead->_Parent
r? $t1 = @$t0->_Parent
$$>a< ${$arg0} -n
ad command
}

Debugging

使用ATLTRACE打造轻量级Debug Log

众所周知,Debug Log是非常好的调试手段。所以我经常也尝试各种各样的第三方Log库。Log库分很多类型,例如可以给服务器使用的功能完备Log,也有轻量级的Log库,只是为Debug所设计。作为客户端开发,我还是比较喜欢后者这种Log库。不过使用第三方库有一个这样的麻烦事,走到哪你都得下一个,然后添加到自己的代码里。对于Log这样的功能,几乎所有程序都是需要的,使用的极其频繁。所以我就想找到一种方法,它可以使用SDK现有功能,来完成一个轻量级Log的功能。对我来说,不需要这个Log有多么高效,完备,唯一需要的就是方便,拿来就可以用。

结合这些目的,我第一个想到的就是ATL的ATLTRACE。但是,ATLTRACE输出的日志都是显示在Debug Output窗口。如果想将信息输出到文件或者控制台上,这就够呛了。那么,接下来就要想办法改变ATLTRACE的输出设备了。由于ATL是有代码的,所以很容易的可以看到代码运行的脉络。看完这份代码的第一个收获就是知道了ATLTRACE运行效率不会很高,不过这个对我来说并不重要。另外一个就是,找到了改变输出设备的方法。

在没有定义_ATL_NO_DEBUG_CRT的情况下,ATLTRACE最终的输出是通过_CrtDbgReport实现的,而如果定义了这个宏,那么输出是直接调用OutputDebugString。但一般程序都不会使用_ATL_NO_DEBUG_CRT这个宏,所以大部分情况下ATLTRACE都是调用的_CrtDbgReport。那么办法就来了_CrtDbgReport输出的数据,是可以通过_CrtSetReportMode和_CrtSetReportFile来改变输出设备的。例如我们想输出到控制台,我们只需要这样:
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);

如果要输出到文件也只需要这样:
HANDLE log_file;
log_file = CreateFile(“c:\log.txt”, GENERIC_WRITE,
FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
_CrtSetReportFile(_CRT_WARN, log_file);
CloseHandle(log_file);
或者
freopen( “c:\log2.txt”, “w”, stdout);
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDOUT);

好了,这样就能解决输出设备的问题。既然已经说到这里,继续介绍下ATLTRACE很少人知道的其他优点吧。
1.可以通过ATL/MFT TRACE Tool 随时设定Log的输出Filter,并且可以保持配置(工具用法很简单,具体直接用用就知道了)。
2.通过AtlDebugAPI的接口,可以给自己的代码中添加读取配置文件的函数。这样每次修改配置文件就能改变ATLTRACE的行为。
3.通过AtlDebugAPI的接口,可以直接制定输出内容,不用配置文件也可以。
这三条涉及到的接口有:
AtlTraceOpenProcess
AtlTraceModifyProcess
AtlTraceCloseProcess
AtlTraceLoadSettings

为了更方便使用,我这写了几个宏代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <atlbase.h>
#include <atltrace.h>
#include <atldebugapi.h>
#include <atlpath.h>
#define TRACEHELPA(fmt, ...) \
do \
{ \
SYSTEMTIME tm; \
GetLocalTime(&tm;); \
ATLTRACE("%s: [%02d-%02d-%02d %02d:%02d:%02d:%03d] "fmt, __FUNCTION__, \
tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds, __VA_ARGS__); \
} while (0)
#define TRACEHELPW(fmt, ...) \
do \
{ \
SYSTEMTIME tm; \
GetLocalTime(&tm;); \
ATLTRACE(L"%S: [%02d-%02d-%02d %02d:%02d:%02d:%03d] "fmt, __FUNCTION__, \
tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds, __VA_ARGS__); \
} while (0)
#define TRACEHELPEXA(category, level, fmt, ...) \
do \
{ \
SYSTEMTIME tm; \
GetLocalTime(&tm;); \
ATLTRACE(category, level, "%s: [%02d-%02d-%02d %02d:%02d:%02d:%03d] "fmt, __FUNCTION__, \
tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds, __VA_ARGS__); \
} while (0)
#define TRACEHELPEXW(category, level, fmt, ...) \
do \
{ \
SYSTEMTIME tm; \
GetLocalTime(&tm;); \
ATLTRACE(category, level, L"%S: [%02d-%02d-%02d %02d:%02d:%02d:%03d] "fmt, __FUNCTION__, \
tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds, __VA_ARGS__); \
} while (0)
#define SetAtlTraceOpt(level, enable, category, filename_lineno, report_type, report_file) \
do \
{ \
DWORD_PTR trace_process = AtlTraceOpenProcess(GetCurrentProcessId()); \
AtlTraceModifyProcess(trace_process, level, enable, category, filename_lineno); \
AtlTraceCloseProcess(trace_process); \
_CrtSetReportMode(level, report_type); \
if (report_type == _CRTDBG_MODE_FILE) { \
_CrtSetReportFile(level, report_file); \
} \
} while (0)
#define LoadAtlDebugCfgExA(path) \
do \
{ \
DWORD_PTR trace_process = AtlTraceOpenProcess(GetCurrentProcessId()); \
AtlTraceLoadSettingsA(path, trace_process); \
AtlTraceCloseProcess(trace_process); \
} while (0)
#define LoadAtlDebugCfgExW(path) \
do \
{ \
DWORD_PTR trace_process = AtlTraceOpenProcess(GetCurrentProcessId()); \
AtlTraceLoadSettingsU(path, trace_process); \
AtlTraceCloseProcess(trace_process); \
} while (0)
#define LoadAtlDebugCfgA() \
do \
{ \
CHAR debug_cfg_path[MAX_PATH] = {0}; \
GetModuleFileNameA(NULL, debug_cfg_path, MAX_PATH); \
CPathA debug_cfg_path_obj(debug_cfg_path); \
debug_cfg_path_obj.RemoveExtension(); \
debug_cfg_path_obj.AddExtension(".trc"); \
LoadAtlDebugCfgExA(debug_cfg_path_obj.m_strPath.GetString()); \
} while (0)
#define LoadAtlDebugCfgW() \
do \
{ \
WCHAR debug_cfg_path[MAX_PATH] = {0}; \
GetModuleFileNameW(NULL, debug_cfg_path, MAX_PATH); \
CPathW debug_cfg_path_obj(debug_cfg_path); \
debug_cfg_path_obj.RemoveExtension(); \
debug_cfg_path_obj.AddExtension(L".trc"); \
LoadAtlDebugCfgExW(debug_cfg_path_obj.m_strPath.GetString()); \
} while (0)
int _tmain(int argc, _TCHAR* argv[])
{
SetAtlTraceOpt(_CRT_WARN, true, true, true, _CRTDBG_MODE_FILE, _CRTDBG_FILE_STDOUT);
LoadAtlDebugCfgW();
CTraceCategory MY_CATEGORY(_T("MyCategoryName"));
TRACEHELPEXA(MY_CATEGORY, 0, "test test test\n");
TRACEHELPEXW(MY_CATEGORY, 0, L"test test test\n");
return 0;
}

DebuggingTips

Dump右键菜单的小工具——RightMenuDump

最近没空更新blog,只能把以前写的小工具拿出来充数,表明这个blog还是活着的。这个挺无聊的小工具,主要是看看电脑里右键菜单的情况。

直接运行后当前目录下出现rm.log,内容大概是:

20140629222145

下载:RightMenuDump

Tips

更新:Windbg扩展0cchext.dll

Commands for 0cchext.dll:
!autocmd - Execute the debugger commands.(The config file is
autocmd.ini)
!dpx - Display the contents of memory in the given range.
!dtx - Displays information about structures. (The config file is
struct.ini)
!favcmd - Display the favorite debugger commands.(The config file is
favcmd.ini)
!grep - Search plain-text data sets for lines matching a regular
expression.
!help - Displays information on available extension commands
!hwnd - Show window information by handle.
!init_script_env - Initialize script environment.
!setvprot - Set the protection on a region of committed pages in the
virtual address space of the debuggee process.
!url - Open a URL in a default browser.
!version - Displays the version information for 0cchext.dll
!help will give more information for a particular command

下载:0cchext

Debugging

系统进程创建管理员进程的方法

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
namespace ProcessHelper {
typedef BOOL (__stdcall *CREATEENVIRONMENTBLOCK)(LPVOID *lpEnvironment,
HANDLE hToken,
BOOL bInherit);
BOOL CreateProcessAsExplorer(LPCTSTR AppName, LPTSTR CommandLine, PPROCESS_INFORMATION pi)
{
ULONG ExplorerID = 0;
HANDLE ExplorerHandle;
HANDLE Snapshot;
ULONG CreationFlags = 0;
CREATEENVIRONMENTBLOCK CreateEnvironmentBlock;
HANDLE ExplorerToken;
HANDLE NewToken = 0;
LPVOID Environment = NULL;
ULONG ReturnLength = 0;
TOKEN_LINKED_TOKEN LinkedToken = {0};
STARTUPINFO si = {0};
PROCESSENTRY32 pe;
BOOL Ret;
HMODULE UserenvModule;
LUID Luid = {0};
Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (Snapshot == INVALID_HANDLE_VALUE) {
return FALSE;
}
pe.dwSize = sizeof(pe);
if (Process32FirstW(Snapshot, &pe;)) {
for(;;) {
if (_tcsicmp(pe.szExeFile, L"explorer.exe") == 0) {
ProcessIdToSessionId(pe.th32ProcessID, &Luid.LowPart;);
ExplorerID = pe.th32ProcessID;
break;
}
if (!Process32Next(Snapshot, &pe;)) {
break;
}
}
}
CloseHandle(Snapshot);
if (ExplorerID == 0) {
return FALSE;
}
ExplorerHandle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ExplorerID);
if (ExplorerHandle == NULL) {
return FALSE;
}
if (!OpenProcessToken(ExplorerHandle, TOKEN_ALL_ACCESS_P, &ExplorerToken;)) {
CloseHandle(ExplorerHandle);
return FALSE;
}
CloseHandle(ExplorerHandle);
if (GetTokenInformation(ExplorerToken,
TokenLinkedToken,
&LinkedToken;,
sizeof(TOKEN_LINKED_TOKEN),
&ReturnLength;)) {
NewToken = LinkedToken.LinkedToken;
}
else {
LookupPrivilegeValueW(0, L"SeDebugPrivilege", &Luid;);
DuplicateTokenEx(ExplorerToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &NewToken;);
}
CloseHandle(ExplorerToken);
UserenvModule = LoadLibrary(TEXT("Userenv.dll"));
if (UserenvModule == NULL) {
return FALSE;
}
CreateEnvironmentBlock = (CREATEENVIRONMENTBLOCK)GetProcAddress(UserenvModule, "CreateEnvironmentBlock");
if (CreateEnvironmentBlock == NULL) {
FreeLibrary(UserenvModule);
return FALSE;
}
if (CreateEnvironmentBlock(&Environment;, NewToken, TRUE)) {
CreationFlags = CREATE_UNICODE_ENVIRONMENT;
}
si.cb = sizeof(si);
Ret = CreateProcessAsUser(
NewToken,
AppName,
CommandLine,
NULL,
NULL,
FALSE,
CreationFlags,
Environment,
NULL,
&si;,
pi);
CloseHandle(NewToken);
if (Environment != NULL) {
DestroyEnvironmentBlock(Environment);
}
if (!Ret) {
return FALSE;
}
return TRUE;
}
}

Tips

一个查找指定栈回溯符号并执行命令的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

不显示对话框格式化磁盘的方法

我们都知道格式化磁盘会弹出如下图所示的这样的一个对话框。

20140316210304

编写格式化磁盘的程序的时候,我们需要用到SHFormatDrive这个API,同样的,他也会弹出一个对话框。出现对话框当然是为了安全考虑,防止磁盘被用户不知情的情况下格式化,导致数据丢失。但是,某些情况下,确实希望静默的去格式化,而不去打扰用户,例如格式化Ramdisk。那么就需要找点一个办法要求不弹出对话框的格式化磁盘。既然Windows并没有提供这样的API,那我们只能深入分析下调用过程,找出可以使用的API。

怎么找的就不想说了,无非用ProcMon看一下堆栈就清楚明白了。在fmifs.dll中有一些导出的API可以帮助完成这一的任务,比如FormatEx,FormatEx2。

这里简单的描述下FormatEx的用法:
函数原型是

1
2
3
4
5
6
7
8
9
10
VOID WINAPI FormatEx(
LPCWSTR DriveRoot,
MEDIA_TYPE MediaType,
LPCWSTR FileSystemTypeName,
LPCWSTR Label,
BOOL QuickFormat,
ULONG ClusterSize,
FILE_SYSTEM_CALLBACK Callback
);

参数
DriveRoot —— 盘符,如”K:\”
MediaType —— 磁盘类型,如FixedMedia
FileSystemTypeName —— 要格式化的文件系统,如”NTFS”
Label —— 标签,随便写吧
QuickFormat —— 快速格式化
ClusterSize —— 簇大小
Callback —— 状态回调函数

回调函数原型

1
2
3
4
5
6
typedef BOOLEAN (__stdcall *FILE_SYSTEM_CALLBACK)(
ULONG Command,
ULONG Action,
PVOID pData
);

Command 表示Action和pData的意义,比如Command = 0表示pData是进度,Command = 11表示完成。还有其他的状态,例如错误等等,这些google一下就知道了。这个函数返回TRUE表示函数继续运行,FALSE表示停止格式化。

比如,下面是格式化K盘:

1
2
FormatEx(L"K:\", FixedMedia, L"NTFS", L"0CCh", TRUE, 4096, FormatExCallback);

Tips

一个有意思的warning —— C4930

C4930是微软的C++编译器提示的一个警告,在维基百科中,把造成这种警告的语句描述成最让人为难的解析的语句。那么这里我们看看到底是有多么为难,这个可以帮助我们进一步了解C++和编译器。

那么首先,我最开始发现这个问题是在类似这样的代码中碰到的。

1
2
3
std::ifstream s("d:\\xxx.txt");
std::string str(std::istream_iterator<char>(s), std::istream_iterator<char>());

如果编译这个,编译器会毫不留情地扔给你一个C4930警告,提示编译器不知道怎么做,所以跳过编译。如果你没看懂这警告,后果就是这句话根本不会编译进去,即使编译通过了,运行也会和设想的不同。

先说个简单的C4930的例子吧。

1
2
MyClass sample();

这句代码非常简单,也很容易明白。如果这么写,那么编译器就混乱,因为这句话可以是描述:
1.一个变量的定义,调用默认构造。
2.一个函数的声明。
所以编译器就傻了,他把这个认为是函数声明,所以不会做任何事情。
同样的事情发生在

1
2
std::string str(std::istream_iterator<char>(s), std::istream_iterator<char>());

这里,我们实际上就是给string的构造函数传入iterator来构造这个string。但是编译器可不是这么觉得,他认为这句话应该这样解析:一个返回string的函数,函数名为str,函数参数有两个并且类型相同,都是istream_iterator,不同的是一个有参数名s,一个省略了参数名!

编译器这么解析,也真没错,我们就只能通过修改代码来明确目的了。通常的做法是给第一个iterator外加上括
号:

1
2
std::string str((std::istream_iterator<char>(s)), std::istream_iterator<char>());

不过,我写这两行代码也就是为了偷懒读取一个文件的字符串,所以我可以干脆改成一行:

1
2
std::string str(std::istream_iterator<char>(std::ifstream("d:\\xxx.txt")), std::istream_iterator<char>());

这样就能顺利编译运行了。

最近在看新版的《The C++ Standard Library A Tutorial and Reference》里面也看到了这个东西,很有意思所
以拿出来说下。而且新版的书中,已经包含了C++11的解释,很有意思。

Tips