Windows 8 SpellChecking API

在Windows 8下,多了一套很有趣的API,SpellChecking,这套API的作用也是一目了然,是做拼写检查的。这么有趣的一套API怎么能不写个程序玩玩呢,于是我写了个小程序,看了看对英文拼写检查的效果,如图。
20150518202121
拼写检查会给出三个结果,分别是删除,替换和建议,根据不同的结果我们可以调用不同的接口来获得最佳的体验。代码如下:

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
// SpellCheck.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <atlbase.h>
#include <atlstr.h>
#include <Spellcheck.h>
class CCoInitialize {
public:
CCoInitialize() {
CoInitializeEx(NULL, COINIT_MULTITHREADED);
}
~CCoInitialize() { CoUninitialize(); }
};
LPCWSTR kActionStrings[] = {
L"CORRECTIVE_ACTION_NONE",
L"CORRECTIVE_ACTION_GET_SUGGESTIONS",
L"CORRECTIVE_ACTION_REPLACE",
L"CORRECTIVE_ACTION_DELETE"
};
int _tmain(int argc, _TCHAR* argv[])
{
CCoInitialize com_init;
CComPtr spell_checker_factory;
HRESULT hr = CoCreateInstance(__uuidof(SpellCheckerFactory), NULL, CLSCTX_INPROC_SERVER, __uuidof(spell_checker_factory),
reinterpret_cast(&spell;_checker_factory));
if (FAILED(hr)) {
return 1;
}
LPCWSTR lang_tag = L"en-US";
BOOL suppored = FALSE;
spell_checker_factory->IsSupported(lang_tag, &suppored;);
if (!suppored) {
return 1;
}
CComPtr spell_checker;
hr = spell_checker_factory->CreateSpellChecker(lang_tag, &spell;_checker);
if (FAILED(hr)) {
return 1;
}
WCHAR my_text[] = L"Helloo world, I am am new heere, hvae fun";
wprintf(L"%s\n\n", my_text);
CComPtr spell_errors;
hr = spell_checker->Check(my_text, &spell;_errors);
if (FAILED(hr)) {
return 1;
}
CComPtr spell_error;
while (spell_errors->Next(&spell;_error) == S_OK) {
ULONG index, length;
if (SUCCEEDED(spell_error->get_StartIndex(&index;)) && SUCCEEDED(spell_error->get_Length(&length;))) {
CStringW tmp_str(my_text + index, length);
wprintf(L"%-10s ", tmp_str.GetString());
CORRECTIVE_ACTION action;
if (SUCCEEDED(spell_error->get_CorrectiveAction(&action;))) {
wprintf(L"%-40s ", kActionStrings[action]);
}
if (action == CORRECTIVE_ACTION_DELETE) {
wprintf(L"delete %s\n", tmp_str.GetString());
}
else if (action == CORRECTIVE_ACTION_GET_SUGGESTIONS) {
CComPtr spell_suggestions;
hr = spell_checker->Suggest(tmp_str.GetString(), &spell;_suggestions);
if (FAILED(hr)) {
break;;
}
WCHAR *suggestion_str;
while (spell_suggestions->Next(1, &suggestion;_str, NULL) == S_OK) {
wprintf(L"%s ", suggestion_str);
CoTaskMemFree(suggestion_str);
}
wprintf(L"\n");
}
else if (action == CORRECTIVE_ACTION_REPLACE) {
WCHAR *replace_str;
hr = spell_error->get_Replacement(&replace;_str);
wprintf(L"%s\n", replace_str);
CoTaskMemFree(replace_str);
}
}
spell_error.Release();
}
return 0;
}

Tips

Windows 8.1 GenericMapping对应的ACCESS_MASK

我们在创建或者打开对象的时候需要指定ACCESS_MASK,有的时候为了方便,我们会在ACCESS_MASK的参数中填GenericRead,GenericWrite这样的值,那么对于这些对象来说,这些GenericXXX究竟是什么样的ACCESS_MASK都是保存在对象的GenericMapping中,以下就是Windows 8.1中所有对象的GenericMapping了。
20150502010045

将ACCESS_MASK数字转换成我们看得懂的宏,可以使用我写的一个小网页:
http://0cch.com/accessmask.html

Tips

防止Global Windows Hooks注入的一个方法

我们都知道SetWindowsHookEx可以设置全局钩子,让自己的dll注入到有窗口的进程中去。注入原理就不再赘述了,网上资料很多,简单看一下调用堆栈方便我们说明怎么去防注入。
kernel32!LoadLibraryExW
USER32!__ClientLoadLibrary
ntdll!KiUserCallbackDispatcher
nt!KiCallUserMode
nt!KeUserModeCallback
win32k!ClientLoadLibrary
win32k!xxxLoadHmodIndex
win32k!xxxCallHook2
win32k!xxxCallHook
win32k!xxxCreateWindowEx
win32k!NtUserCreateWindowEx
nt!KiFastCallEntry
ntdll!KiFastSystemCallRet
ntdll!KiUserCallbackDispatcher
USER32!NtUserCreateWindowEx
USER32!_CreateWindowEx

看着个堆栈,防注入的方法这里就可以大概说出三种:

  1. 被创建窗口程序了。
  2. Hook LoadLibraryExW,判断是否是自己的模块。
  3. Hook __ClientLoadLibrary,替换为空函数。

第一个方法其实也谈不上方法,也就是说控制台程序就不用担心这些了。第二个方法需要是否是判断自己的模块,这个方法也挺麻烦的,因为你得放过一些不是自己的模块,比如微软的模块。所以这里重点说第三个方法,我们去Hook ClientLoadLibrary,这样我们就只是避免了全局钩子的注入了。这里我们不用去Inline Hook该函数,Inline Hook比较麻烦。我们的做法是修改user32!apfnDispatch这个数组,直接替换对应于ClientLoadLibrary所在位置的值。这样摆在我们面前的稍微麻烦一点的事情有两个,一个是确定数组开始的地址,第二就是确定__ClientLoadLibrary在数组中的index。
那么分别来解决这两个问题:

  1. 组数的位置
    其实就是PEB的KernelCallbackTable,虽然PEB没有文档化,但是也没见过他变过什么。所以我们可以写死KernelCallbackTable的偏移。说稍微有点麻烦就是指的,这个偏移在32bit和64bit的系统上是不同的而已,32位系统的偏移是0x2c,64位系统是0x58。另外一个就是获得PEB的方法了,32位程序你既可以写点汇编从fs中获取,也能调用readfsdword获得,64位程序会麻烦点,你需要先获得TEB,然后从TEB里得到PEB,至于获得TEB的方法,你可以直接调用readgsqword获得,也可以调用ntdll的NtCurrentTeb获得。

  2. ClientLoadLibrary在数组中的index
    这个就稍微繁琐点,我们需要把我们关心的系统用windbg带上符号都看一眼才能知道是多少了。
    我这里提供几个常用系统中
    ClientLoadLibrary在数组中的index:
    XP=0x42,Win7=0x41,Win8.1=0x47

好了,知道了这些,后面的就不用说太详细了,无非就是这三步:

  1. 写个空的__ClientLoadLibrary函数MyClientLoadLibrary。
  2. VirtualProtect设置KernelCallbackTable + index * sizeof(PVOID)地址内存保护属性为PAGE_READWRITE。
  3. 替换__ClientLoadLibrary为MyClientLoadLibrary,再把内存属性换回原来的。

OK,大功告成了。

NTInternalsTips

发现File System Minifilter的一处问题

这两天有个朋友一直问我用用户普通权限连接minifilter server port的问题。给他解答的同时,也发现了这个方面的一个问题。首先说用普通用户权限连接port的方法,其实就是设置FltCreateCommunicationPort参数里ObjectAttributes的SecurityDescriptor,加入everyone的ACE就行了。那么加入everyone的ace你就要指定一个ACCESS_MASK,在MSDN里,介绍了两个可以使用的MASK

20150326155009

其中FLT_PORT_CONNECT=1,FLT_PORT_ALL_ACCESS=1F0001。看到这里,多数人都可能会认为如果只想让everyone连接上去,不给他所用权限,那么在这个ACE里加入FLT_PORT_CONNECT就可以了。然后就掉到微软的坑里了,和我那个朋友一样:)。

20150326154028

实际上指定FLT_PORT_CONNECT会让R3的程序无法连接驱动的port,原因就是FilterConnectCommunicationPort函数没有让你指定你需求的ACCESS,而是在底层打开port的时候直接请求FLT_PORT_ALL_ACCESS。这个时候如果你的ACE里面是FLT_PORT_CONNECT,那当然无法连接上去了。所以这里把ACE里的ACCESS_MASK设置为FLT_PORT_ALL_ACCESS就行了。

NTInternalsTips

Windbg查看Object Hook的脚本

学好Windbg,基本上可以代替很多工具,这次分享一个查看Object Hook的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
r @$t0 = 2;
r? @$t1 = ((nt!_OBJECT_TYPE**)@@(nt!ObTypeIndexTable))[@$t0];
.while ((@$t1 & 0xffffffff) != 0) {
.printf "Type Name:%-20msu\t", @@C++(&@$t1->Name);
.printf /D "detail\n", @$t1;
.printf "DumpProcedure : %y\n", @@C++(@$t1->TypeInfo.DumpProcedure);
.printf "OpenProcedure : %y\n", @@C++(@$t1->TypeInfo.OpenProcedure);
.printf "CloseProcedure : %y\n", @@C++(@$t1->TypeInfo.CloseProcedure);
.printf "DeleteProcedure : %y\n", @@C++(@$t1->TypeInfo.DeleteProcedure);
.printf "ParseProcedure : %y\n", @@C++(@$t1->TypeInfo.ParseProcedure);
.printf "SecurityProcedure : %y\n", @@C++(@$t1->TypeInfo.SecurityProcedure);
.printf "QueryNameProcedure : %y\n", @@C++(@$t1->TypeInfo.QueryNameProcedure);
.printf "OkayToCloseProcedure : %y\n\n", @@C++(@$t1->TypeInfo.OkayToCloseProcedure);
r @$t0 = @$t0 + 1;
r? @$t1 = ((nt!_OBJECT_TYPE**)@@(nt!ObTypeIndexTable))[@$t0];
};

20150324095806

Tips

一个解析INI文件的类

虽然微软强烈推荐用注册表代替ini来记录软件配置,但是由于写ini文件的方便性和可读性的优势,还是让很多程序员选择把配置记录到ini文件中。但是用Windows API操作ini文件有个缺点,就是每次调用如GetPrivateProfileInt,WritePrivateProfileString等函数,都会产生一次文件打开关闭以及读写操作,并且对ini文件重新解析,这是非常低效的。所以如果需要大量的操作ini文件,例如需要读取很多配置信息以启动软件,那么这样的用法无疑会增加软件的冷启动时间。为了解决这个问题,我们就需要自己写一个模块,他能够一次性读取并且解析好ini文件。在需要读取的时候直接从内存读取,需要些的时候先全部写到内存里,最后在刷新到文件上。所以我写了一个ParseIni的类,来完成这个工作。代码如下:

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
#ifndef __PARSE_INI_H__
#define __PARSE_INI_H__
#include <vector>
#include <string>
#include <fstream>
#include <windows.h>
#define INI_KEY_LINE 1
#define INI_SECTION_LINE 2
#define INI_COMMENT_LINE 3
#define INI_NC_KEY_LINE 4
#define INI_WORNG_SYNTAX 0
using namespace std;
class CParseIniA {
public:
CParseIniA() {}
~CParseIniA() {}
BOOL Open(const string &IniPath;, BOOL OpenAlways = TRUE);
vector EnumSectionNames();
vector EnumKeyNamesInSection(const string &SectionName;);
string GetSectionKeyValue(const string &SectionName;, const string &KeyName;, const string &DefaultValue;);
int GetSectionKeyValueInt(const string &SectionName;, const string &KeyName;, int DefaultValue);
BOOL SetSectionKeyValueInt(const string &SectionName;, const string &KeyName;, int value, BOOL CreateNew = TRUE);
BOOL SetSectionKeyValue(const string &SectionName;, const string &KeyName;, const string &Value;, BOOL CreateNew = TRUE);
BOOL Flush();
VOID Close();
private:
ULONG SyntaxCheck(const string &KeyLine;);
vector::iterator CreateSection(const string &SectionName;);
VOID CreateKeyValue(vector::iterator it, const string &KeyName;, const string &Value;);
BOOL IsSection(string &Line;);
BOOL SetKeyValue(string &Line;, const string &Value;);
string GetKeyValue(string &Line;);
string GetSectionName(string &Line;);
string GetKeyName(string &Line;);
string trim(const string& s ,const string& drop = " ");
vector m_IniContext;
string m_IniPath;
};
inline BOOL CParseIniA::Open( const string &IniPath;, BOOL OpenAlways)
{
ifstream IniFile(IniPath.c_str());
INT FileSize;
vector::iterator it;
if (!IniFile.is_open()) {
if (!OpenAlways) {
return FALSE;
}
m_IniPath = IniPath;
return TRUE;
}
m_IniPath = IniPath;
IniFile.seekg(0, std::ios_base::end);
FileSize = IniFile.tellg();
IniFile.seekg(0, std::ios_base::beg);
if (FileSize == 0) {
return TRUE;
}
while (IniFile) {
string IniLine;
getline(IniFile, IniLine);
m_IniContext.push_back(IniLine);
}
it = m_IniContext.end();
it--;
while (trim(*it).empty()) {
m_IniContext.pop_back();
it = m_IniContext.end();
it--;
}
return TRUE;
}
inline BOOL CParseIniA::IsSection( string &Line; )
{
string SectionLine = trim(Line);
BOOL Ret = FALSE;
if (SectionLine[0] == '[' && SectionLine[SectionLine.length() - 1] == ']') {
Ret = TRUE;
}
return Ret;
}
inline vector CParseIniA::EnumSectionNames()
{
vector SectionNames;
vector::iterator it;
for (it = m_IniContext.begin(); it != m_IniContext.end(); ++it) {
if (IsSection(*it)) {
SectionNames.push_back(GetSectionName(*it));
}
}
return SectionNames;
}
inline vector CParseIniA::EnumKeyNamesInSection(const string &SectionName; )
{
vector::iterator it;
vector KeyNames;
ULONG ScanState = 0;
for (it = m_IniContext.begin(); it != m_IniContext.end(); ++it) {
if (ScanState == 0) {
if (!IsSection(*it) || GetSectionName(*it) != SectionName) {
continue;
}
ScanState = 1;
}
else if (ScanState == 1) {
if (IsSection(*it)) {
break;
}
KeyNames.push_back(GetKeyName(*it));
}
}
return KeyNames;
}
inline string CParseIniA::GetSectionName( string &Line; )
{
INT Count = Line.length();
INT i;
BOOL Start = FALSE;
string SectionName;
for (i = 0; i < Count; i++) {
if (Line[i] == '[') {
Start = TRUE;
}
else if (Line[i] == ']') {
if (Start) {
break;
}
}
else {
if (Start) {
SectionName += Line[i];
continue;
}
}
}
return SectionName;
}
inline string CParseIniA::GetKeyName( string &Line; )
{
string KeyName;
KeyName = Line.substr(0, Line.find_first_of('='));
return trim(KeyName);
}
inline string CParseIniA::trim(const string& s, const string& drop)
{
string t(s);
string r = t.erase(t.find_last_not_of(drop) + 1);
return r.erase(0,r.find_first_not_of(drop));
}
inline int CParseIniA::GetSectionKeyValueInt( const string &SectionName;, const string &KeyName;, int DefaultValue )
{
char DefaultValueString[32] = {0};
sprintf_s(DefaultValueString, "%d", DefaultValue);
string ValueString = GetSectionKeyValue(SectionName, KeyName, DefaultValueString);
return atoi(ValueString.c_str());
}
inline string CParseIniA::GetSectionKeyValue(const string &SectionName;, const string &KeyName;, const string &DefaultValue; )
{
vector::iterator it;
ULONG ScanState = 0;
string Value = DefaultValue;
for (it = m_IniContext.begin(); it != m_IniContext.end(); ++it) {
if (ScanState == 0) {
if (!IsSection(*it) || GetSectionName(*it) != SectionName) {
continue;
}
ScanState = 1;
}
else if (ScanState == 1) {
if (IsSection(*it)) {
break;
}
if (SyntaxCheck(*it) != INI_KEY_LINE) {
continue;
}
if (GetKeyName(*it) == KeyName) {
Value = GetKeyValue(*it);
}
}
}
return Value;
}
inline string CParseIniA::GetKeyValue( string &Line; )
{
string KeyName;
KeyName = Line.substr(Line.find_first_of('=') + 1);
return trim(KeyName);
}
inline BOOL CParseIniA::SetKeyValue( string &Line;, const string &Value; )
{
INT Pos = Line.find_first_of('=');
if (Pos == string::npos) {
return FALSE;
}
Pos = Line.find_first_not_of(' ', Pos + 1);
if (Pos == string::npos) {
Pos = Line.find_first_of('=') + 1;
}
Line.erase(Pos);
Line += Value;
return TRUE;
}
inline BOOL CParseIniA::SetSectionKeyValueInt( const string &SectionName;, const string &KeyName;, int Value, BOOL CreateNew /*= TRUE*/ )
{
char ValueString[32] = {0};
sprintf_s(ValueString, "%d", Value);
return SetSectionKeyValue(SectionName, KeyName, ValueString, CreateNew);
}
inline BOOL CParseIniA::SetSectionKeyValue( const string &SectionName;, const string &KeyName;, const string &Value;, BOOL CreateNew )
{
vector::iterator it;
ULONG ScanState = 0;
BOOL Ret = FALSE;
for (it = m_IniContext.begin(); it != m_IniContext.end(); ++it) {
if (ScanState == 0) {
if (!IsSection(*it) || GetSectionName(*it) != SectionName) {
continue;
}
ScanState = 1;
}
else if (ScanState == 1) {
if (IsSection(*it)) {
break;
}
if (SyntaxCheck(*it) == INI_KEY_LINE || SyntaxCheck(*it) == INI_NC_KEY_LINE) {
if (GetKeyName(*it) == KeyName) {
Ret = SetKeyValue(*it, Value);
}
}
}
}
if (CreateNew && !Ret) {
if (ScanState == 0) {
it = CreateSection(SectionName);
CreateKeyValue(it, KeyName, Value);
}
else if (ScanState == 1) {
it--;
CreateKeyValue(it, KeyName, Value);
}
Ret = TRUE;
}
return Ret;
}
inline BOOL CParseIniA::Flush()
{
ofstream IniFile(m_IniPath.c_str());
vector::iterator it;
if (!IniFile.is_open()) {
return FALSE;
}
for (it = m_IniContext.begin(); it != m_IniContext.end(); ++it) {
IniFile << it->c_str() << endl;
}
return TRUE;
}
inline VOID CParseIniA::Close()
{
}
inline vector::iterator CParseIniA::CreateSection( const string &SectionName; )
{
string FullSectionName;
vector::iterator it;
FullSectionName += '[';
FullSectionName += SectionName;
FullSectionName += ']';
m_IniContext.push_back(FullSectionName);
it = m_IniContext.begin() + m_IniContext.size() - 1;
return it;
}
inline VOID CParseIniA::CreateKeyValue( vector::iterator it, const string &KeyName;, const string &Value; )
{
string KeyInfo;
KeyInfo += KeyName;
KeyInfo += " = ";
KeyInfo += Value;
while (it != m_IniContext.begin()) {
if (!trim(*it).empty()) {
break;
}
it--;
}
m_IniContext.insert(it + 1, KeyInfo);
}
inline ULONG CParseIniA::SyntaxCheck( const string &KeyLine; )
{
string Line = trim(KeyLine);
INT Pos, CommentPos1, CommentPos2;
string KeyName;
string Value;
if (IsSection(Line)) {
return INI_SECTION_LINE;
}
else if (Line[0] == ';' || Line[0] == '#') {
return INI_COMMENT_LINE;
}
else {
Pos = Line.find_first_of('=');
if (string::npos == Pos) {
return INI_WORNG_SYNTAX;
}
KeyName = trim(Line.substr(0, Pos));
if (KeyName.empty()) {
return INI_WORNG_SYNTAX;
}
Value = trim(Line.substr(Pos + 1));
if (Value.empty()) {
return INI_NC_KEY_LINE;
}
CommentPos1 = Value.find_first_of(';');
CommentPos2 = Value.find_first_of('#');
if (CommentPos1 == string::npos && CommentPos2 == string::npos) {
return INI_KEY_LINE;
}
CommentPos1 = CommentPos1 < CommentPos2 ? CommentPos1 : CommentPos2;
if (Value.erase(CommentPos1).empty()) {
return INI_NC_KEY_LINE;
}
return INI_KEY_LINE;
}
}
#endif

Tips

使用Windows未使用内存原理和应用

20150202104522

配置4G内存,并且使用过32bit Windows系统的人都知道,虽然自己有4G的物理内存,但是Windows还是明确的告诉你,它只会用其中的3GB多,还有几百MB物理内存是用不到的,即使你开启了PAE。当然如果你用的服务器系统,那就当我没说。至于微软为啥服务端32bit系统可以用4GB以上,而限制普通客户端系统,按照Windows Internals的说法,是为了考虑驱动程序的兼容性问题。我这里想介绍的是,如何使用这些没有使用的物理内存。

20150202105145

首先,要想使用这些内存,我们必须找到他们,但是找到他们之前,我们还得了解物理内存地址是怎么分配。物理内存地址除了要给RAM提供地址之外,还需要给设备内存提供地址。为了考虑驱动的兼容性,这些设备内存被分配到4G以内的地址上,这样一来,就会有部分RAM不得不分配到4G以外的地址上了,所以我们无法使用它们。

20150202110332

知道了这些,我们就需要聚焦到如何访问超过4GB内存的方法上了。不过方法也很简单,就是MmMapIoSpace函数,这个函数可以访问64bit的物理内存地址,并且将其映射到我们可以访问的虚拟内存上。

说到这里,程序的代码仿佛就呈现在脑海了,不过等等,还忽略了一个最困难的问题!到底有存在多少RAM内存在4GB以上的地址空间呢?说这个问题最为困难,是因为你需要根据不同的情况做出不同的选择。

20150202095553

1.主板支持通过Bios查询RAM内存分配情况,在这种情况下,我们可以调用中断来获得最真实的RAM分配表。
2.主板不支持通过Bios查询RAM内存分配情况,那么我们很无奈的必须采用一个简单粗暴的方法,Write & Test来获得4GB以外到底有多少RAM可用。

对于第二种情况,我们的做法是通过MmMapIoSpace函数,把4GB以上的物理地址逐一映射到虚拟内存中,然后写入特殊值,写入后马上读取,看是否写入成功,成功则证明RAM可用。
对于第一种情况,这里又要分为两个分支:
首先你的系统是NT6内核以下的情况,这时可以调用Ke386CallBios函数,调用中断接口。
而对于NT6的内核,我们需要调用新的x86BiosCall等一套函数。
需要调用的中断函数是0x15以及AX=E820,通过这个中断和功能号,我们就能获得SYSTEM MEMORY 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
INT 15 - newer BIOSes - GET SYSTEM MEMORY MAP
AX = E820h
EAX = 0000E820h
EDX = 534D4150h ('SMAP')
EBX = continuation value or 00000000h to start at beginning of map
ECX = size of buffer for result, in bytes (should be >= 20 bytes)
ES:DI -> buffer for result (see #00581)
Return: CF clear if successful
EAX = 534D4150h ('SMAP')
ES:DI buffer filled
EBX = next offset from which to copy or 00000000h if all done
ECX = actual length returned in bytes
CF set on error
AH = error code (86h) (see #00496 at INT 15/AH=80h)
Notes: originally introduced with the Phoenix BIOS v4.0, this function is
now supported by most newer BIOSes, since various versions of Windows
call it to find out about the system memory
a maximum of 20 bytes will be transferred at one time, even if ECX is
higher; some BIOSes (e.g. Award Modular BIOS v4.50PG) ignore the
value of ECX on entry, and always copy 20 bytes
some BIOSes expect the high word of EAX to be clear on entry, i.e.
EAX=0000E820h
if this function is not supported, an application should fall back
to AX=E802h, AX=E801h, and then AH=88h
the BIOS is permitted to return a nonzero continuation value in EBX
and indicate that the end of the list has already been reached by
returning with CF set on the next iteration
this function will return base memory and ISA/PCI memory contiguous
with base memory as normal memory ranges; it will indicate
chipset-defined address holes which are not in use and motherboard
memory-mapped devices, and all occurrences of the system BIOS as
reserved; standard PC address ranges will not be reported
SeeAlso: AH=C7h,AX=E801h"Phoenix",AX=E881h,MEM xxxxh:xxx0h"ACPI"
Format of Phoenix BIOS system memory map address range descriptor:
Offset Size Description (Table 00580)
00h QWORD base address
08h QWORD length in bytes
10h DWORD type of address range (see #00581)
(Table 00581)
Values for System Memory Map address type:
01h memory, available to OS
02h reserved, not available (e.g. system ROM, memory-mapped device)
03h ACPI Reclaim Memory (usable by OS after reading ACPI tables)
04h ACPI NVS Memory (OS is required to save this memory between NVS
sessions)
other not defined yet -- treat as Reserved
(from http://www.ctyme.com/intr/rb-1741.htm

获得了4GB内存以上的RAM范围以后,再加上MmMapIoSpace,我们就能够访问系统没有使用的内存了。

说了这么多,这个有什么具体应用呢?我见过最多利用这个特性做的产品就是内存盘了。也没看到什么用的特别好的其他应用的方面,我想原因主要有三点,第一,即使获得了物理内存,最后还是映射了4GB以内的虚拟内存上,内存操作终究无法访问直接更多物理内存;第二,这部分内存没有系统统一管理,同类型的软件无法共存;第三,有4G内存赶紧快去换个64位系统吧=v=。

NTInternals

分享SSD TRIM代码

14年的早些时候发布过SSD TRIM技术的文章,文章里面记录了关于TRIM技术的一些东西,其中就包括在Windows上如何使用TRIM。当然Windows 7及其更高版本的系统都是自带TRIM功能的,只有XP没有这个功能,当时我就写了个工具,在XP下进行TRIM。现在想想确实也没啥意思,就把代码分享说来吧。

https://github.com/0cch/SSDTrim.git

Tips

用Service Tag区分共享类型服务线程

Windows中有一种共享类型的服务,这种服务的特点是,他们可能同时有多个不同服务运行在同一个进程内。这些服务通常都是一些dll,他们被加载到宿主进程内运行,这个宿主进程我们见到最多的就是svchost了,如下图所示:

20150124220922

Windows这样做的好处就是尽可能的节约资源,当然不好地方就是,如果出了问题那么难以调试和定位。所以,为了更好的定位共享服务的工作线程,微软支持了一种叫做Service Tag的东西。Service Tag简单的说就是标注线程属于哪个服务的,这个是由Service Control Manager支持的。在TEB中有一个SubProcessTag字段,每当一个服务注册并且运行的时候,Service Control Manager会分配给服务线程一个id,这个id就是SubProcessTag,它能够唯一的表示服务,而且这个值是一直都被继承的,也就是说,如果服务线程再创建线程,那么新的线程的SubProcessTag也会被标记为父线程的id。当然,也有一种例外,那就是如果用了线程池,就不会继承SubProcessTag了。

在Advapi32.dll中有一个函数,叫做I_QueryTagInformation,这个函数可以根据SubProcessTag去查询Service的信息,有兴趣的同学可以看看下面的链接:http://wj32.org/wp/2010/03/30/howto-use-i_querytaginformation/

DebuggingTips