将blog迁移到了jekyll

上周终于下定决心把blog从wordpress转到jekyll,不是因为wordpress臃肿,也不是因为jekyll的更加Geek,纯粹是因为穷。我一直都觉得wordpress是一个非常伟大的blog程序,虽然臃肿了点,但确实功能强大操作简单,对于我这种懒人和对前端代码完全不熟的程序员来说,wordpress确实是一个非常好的选择。但是问题就出在了webhost上,我使用的webhost刚刚买的时候是50多刀一年,之后每年涨价,今年续费看了下需要100刀左右,这个确实让我心中无数的羊驼奔腾了起来。于是就决定把blog搬离这个地方。

刚开始我只是想找便宜的地方转移wordpress的blog。网上也有很多这类的webhost,第一年进去都很便宜,甚至有1刀一个月的。但是一朝被蛇咬啊,为了防止以后又被迫搬家,于是打消了这个念头。想要便宜和稳定的blog空间,看来是只有伟大的Github。而Github只支持静态程序,那么我也只能放弃wordpress的方便,自己折腾点静态博客程序了。摆在眼前的选择其实很多最基础jekyll,加强版的octopress以及hexo。第一个程序的优点就是简单基础,缺点就是太基础了,而octopress在jekyll的基础之上加上了一些插件,让blog默认的功能变得丰富起来。之后hexo,也是一个自带很多基础功能的程序而且还带了很多非常漂亮的主题,主题控的bloger不妨选择这个,我就特别喜欢他其中的一个默认主题,但是折腾样式的时候jekyll的基本结构都搭建好了,所以就没有更换hexo程序,于是极度痛苦的折腾了一周的css和ruby插件才把现在的blog折腾的和之前的差不多。

简单说下用jekyll在Github上搭建blog的步骤,其实网上很多很多教程,这里记录下就是防止自己忘了把。

  1. 首先在http://rubyinstaller.org/downloads/下载ruby和DevKit,安装分别安装他们,然后运行Devkit,分别执行:
    1) dk.rb init
    2) dk.rb review
    3) dk.rb install
  2. 接下来就是安装jekyll了,安装之前推荐更换Gem的源到https://ruby.taobao.org/ 这样下载程序比较快。具体方式是:
    1) gem source -r (url)
    2) gem source -a (new url)
    3) gem source -u
  3. 然后就可以开始下载jekyll和他的代码高亮程序rouge了,gem install (app name)
  4. 最后记得要设置_config.yml文件,尤其是高亮highlighter: rouge

这样,最基础功能的blog就搭建好了,接下来就是把blog从wordpress转移到jekyll了。方法是使用exitwp这个python脚本。

  1. 先导出wordpress的数据到一个xml里,这个功能wordpress是自带的。
  2. 然后同个这个脚本把数据转换成markdown文件,放在jekyll生产的_post里面。并且把里面的图片和下载的url替换了。
  3. 最后把wordpress的upload目录下载下来,放到jekyll里面即可。

这样我们看到的就是一个最简单的jekyll的blog,要想改变主题,自己去折腾吧。我能做的就是推荐两个jekyll的插件,分别是按日期和分类生成归档网页的,可以在我的Github上看到。

最后要说的是rouge语法高亮有个bug,在使用显示行号linenos参数的时候会出现嵌套错误的问题,解决方法倒是有,不过有了行号之后高亮的显示极其丑陋,所以还是我还是没用这个参数。如果有需求可以使用代码rouge_linenos_patch.rb覆盖”\lib\ruby\gems\2.2.0\gems\jekyll-2.5.3\lib\jekyll\tags\highlight.rb”里对应的函数即可。

Tips

关于Zone.Identifier的一点记录

自从Windows XP SP2开始,微软对文件加入了Zone.Identifier的数据流,所以这个也不算什么新东西了,最近偶然有机会研究了下所以就记录了下来。
说起Zone.Identifier,我们最常见的应用就是在我们从Internet上下载了可执行文件后,运行的时候会弹出如下图的警告窗口:

20150615150628

弹出这个窗口就是因为Explorer在运行这个文件的时候先检查了Zone.Identifier的数据,发现了如下文本
[ZoneTransfer]
ZoneId=3

这个ZoneId=3,就是指明这个文件是由Internet上下载的。根据MSDN,这个id有以下几种:

1
2
3
4
5
6
7
8
9
10
11
12
13

typedef enum tagURLZONE {
URLZONE_INVALID = -1,
URLZONE_PREDEFINED_MIN = 0,
URLZONE_LOCAL_MACHINE = 0,
URLZONE_INTRANET,
URLZONE_TRUSTED,
URLZONE_INTERNET,
URLZONE_UNTRUSTED,
URLZONE_PREDEFINED_MAX = 999,
URLZONE_USER_MIN = 1000,
URLZONE_USER_MAX = 10000
} URLZONE;

查看这个数据流的方法也很简单,用notepad就行了。

20150615153805

另外如果想给添加或者去除这个数据流,我们这里有两种方法:
1.直接读写数据流,其实这个跟普通文件读写没什么两样。
2.调用微软提供的com接口,这个比较是规范的。

对于第一种方法,没什么可说的,无非就是文件操作的那些API。第二种方法我们需要用到以下两个接口:
IPersistFile
IZoneIdentifier

我们先创建IZoneIdentifier接口,然后query出IPersistFile打开文件,最后读取或者写入文件。
代码详见:http://blogs.msdn.com/b/oldnewthing/archive/2013/11/04/10463035.aspx

最后说一下,之所以能有Zone.Identifier这种功能,完全依赖于NTFS文件系统,它允许多个数据流的存在,对它而言,每个数据流无非就是一个属性而已,只不过Zone.Identifier是一个名字为Zone.Identifier的数据流,而文件本身的数据是一个没有命名的数据流而已。用ntfs_study查看,如下图,第一个Data数据没有名字是文件本身的数据,第二个就是Zone.Identifier的数据了。
20150615160110

NTInternalsTips

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