Foxmail无法响应

Foxmail是一款不错的邮件客户端软件,小巧实用,我在公司就是用的它。早上一如既往的先打开Foxmail,然后去倒杯水,回来发现Foxmail还在收取邮件的状态,“这也太慢了”,我心想。用鼠标点了点,出现了程序挂起的特征,标题栏上显示无法响应,程序界面变白。经验告诉我挂起的问题70%都还是比较容易调的,好吧,就让我看看这是怎么回事。

打开Windbg,Attach到Foxmail上,习惯做的第一件事情就是保存Full dump

1
.dump /ma /u e:\foxmail.dmp

由于程序的主界面出现了挂起的现象,而一般情况下主线程就是程序的界面线程,所以此时根本没必要去查看所有线程的情况,直接看看主线程的栈信息吧。

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
0:000> k
ChildEBP RetAddr
0018f454 770fd993 ntdll!ZwWaitForSingleObject+0x15
0018f4b8 770fd877 ntdll!RtlpWaitOnCriticalSection+0x13e
0018f4e0 770f84ca ntdll!RtlEnterCriticalSection+0x150
0018f514 76734e8c ntdll!RtlLockHeap+0x3d
0018f558 004091af KERNELBASE!GlobalAlloc+0x92
WARNING: Stack unwind information not available. Following frames may be wrong.
0018f578 0042bf97 Foxmail+0x91af
0018f588 0042c115 Foxmail+0x2bf97
0018f5a4 007c5f25 Foxmail+0x2c115
0018f9fc 007c50ce Foxmail+0x3c5f25
0018fa34 007c516a Foxmail+0x3c50ce
0018fa44 007c9e57 Foxmail+0x3c516a
0018faac 007ca28b Foxmail+0x3c9e57
0018fae0 007c9370 Foxmail+0x3ca28b
0018fb10 007ca3fb Foxmail+0x3c9370
0018fb1c 00dd8a96 Foxmail+0x3ca3fb
0018fb44 007c93bb Foxmail+0x9d8a96
0018fb64 007c9479 Foxmail+0x3c93bb
0018fba8 00dd0492 Foxmail+0x3c9479
0018fcd4 00dd019d Foxmail+0x9d0492
0018fd00 00dd454a Foxmail+0x9d019d
0018fd44 00dcf20d Foxmail+0x9d454a
0018fd60 007ca59d Foxmail+0x9cf20d
0018fd84 0044e9d6 Foxmail+0x3ca59d
0018fd94 0044e844 Foxmail+0x4e9d6
0018fdbc 00434a96 Foxmail+0x4e844
0018fdd4 74aa62fa Foxmail+0x34a96
0018fe00 74aa6d3a user32!InternalCallWinProc+0x23
0018fe78 74aa77c4 user32!UserCallWinProcCheckWow+0x109
0018fed8 74aa7bca user32!DispatchMessageWorker+0x3bc
0018fee8 00dc5e7a user32!DispatchMessageA+0xf
0018ff04 00dc5ee4 Foxmail+0x9c5e7a
0018ff2c 00dc61bf Foxmail+0x9c5ee4
0018ff5c 00ded3a0 Foxmail+0x9c61bf
0018ff88 75b9336a Foxmail+0x9ed3a0
0018ff94 770f9882 kernel32!BaseThreadInitThunk+0xe
0018ffd4 770f9855 ntdll!__RtlUserThreadStart+0x70
0018ffec 00000000 ntdll!_RtlUserThreadStart+0x1b

可以看到界面线程调用GlobalAlloc的时候在等HeapLock被释放。

1
2
3
4
5
6
7
0:000> kb L5
ChildEBP RetAddr Args to Child
0018f454 770fd993 00000698 00000000 00000000 ntdll!ZwWaitForSingleObject+0x15
0018f4b8 770fd877 00000000 00000000 00000000 ntdll!RtlpWaitOnCriticalSection+0x13e
0018f4e0 770f84ca 014d0138 0044e9b8 0018f4ac ntdll!RtlEnterCriticalSection+0x150
0018f514 76734e8c 014d0000 4d5402d1 00000000 ntdll!RtlLockHeap+0x3d
0018f558 004091af 00000002 00002000 0042c07c KERNELBASE!GlobalAlloc+0x92

那么014d0138比如是默认堆的Critical Section了。来看看这个cs的数据

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
0:000> !cs 014d0138 
*************************************************************************
*** ***
*** ***
*** Either you specified an unqualified symbol, or your debugger ***
*** doesn't have full symbol information. Unqualified symbol ***
*** resolution is turned off by default. Please either specify a ***
*** fully qualified symbol module!symbolname, or enable resolution ***
*** of unqualified symbols by typing ".symopt- 100". Note that ***
*** enabling unqualified symbol resolution with network symbol ***
*** server shares in the symbol path may cause the debugger to ***
*** appear to hang for long periods of time when an incorrect ***
*** symbol name is typed or the network symbol server is down. ***
*** ***
*** For some commands to work properly, your symbol path ***
*** must point to .pdb files that have full type information. ***
*** ***
*** Certain .pdb files (such as the public OS symbols) do not ***
*** contain the required information. Contact the group that ***
*** provided you with these symbols if you need this command to ***
*** work. ***
*** ***
*** Type referenced: ntdll!_RTL_CRITICAL_SECTION ***
*** ***
*************************************************************************
Bad symbols for NTDLL (error 3). Aborting.

嗯,ntdll的符号文件的结构体信息没有了!这个问题发生在2015年7月份的,安装KB3071756和KB3060716补丁后产生的。详情可以查看
http://www.osronline.com/showthread.cfm?link=269221

既然新的符号不让用,那就只有用老的了

1
2
3
4
5
6
7
8
9
10
11
12
13
0:000> .reload /f /i E:\WorkSpace\MySymbols\ntdll.dll\49900AFA96000\ntdll.dll=77ffe000   
0:000> lm m ntdll*
start end module name
770c0000 77240000 ntdll (pdb symbols) e:\workspace\mysymbols\wntdll.pdb\FA9C48F9C11D4E0894B8970DECD92C972\wntdll.pdb
77ffe000 78094000 ntdll_77ffe000 (pdb symbols) e:\workspace\mysymbols\ntdll.pdb\6992F4DAF4B144068D78669D6CB5D2072\ntdll.pdb
0:000> dt ntdll_77ffe000!_RTL_CRITICAL_SECTION 014d0138
+0x000 DebugInfo : 0x771c4960 _RTL_CRITICAL_SECTION_DEBUG
+0x004 LockCount : 0n-30
+0x008 RecursionCount : 0n1
+0x00c OwningThread : 0x00001730 Void
+0x010 LockSemaphore : 0x00000698 Void
+0x014 SpinCount : 0xfa0

好了,这样就够用了。可以看到TID=1730的线程正在占用这个cs,马上去看看这个线程在干什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

0:041> k
ChildEBP RetAddr
06e1f214 770fd993 ntdll!ZwWaitForSingleObject+0x15
06e1f278 770fd877 ntdll!RtlpWaitOnCriticalSection+0x13e
06e1f2a0 770f84ca ntdll!RtlEnterCriticalSection+0x150
06e1f2d4 7717fd7d ntdll!RtlLockHeap+0x3d
06e1f3bc 7714de8e ntdll!RtlpQueryExtendedHeapInformation+0xbd
06e1f3fc 7716476b ntdll!RtlQueryHeapInformation+0x4a
06e1f4a0 77143320 ntdll!RtlQueryProcessHeapInformation+0x288
06e1f51c 75c15f4b ntdll!RtlQueryProcessDebugInformation+0x28a
Unable to load image D:\Program Files\Foxmail 7.2\libeay32.dll, Win32 error 0n2
*** ERROR: Symbol file could not be found. Defaulted to export symbols for libeay32.dll -
06e1f54c 0606ad6a kernel32!Heap32Next+0x4d
WARNING: Stack unwind information not available. Following frames may be wrong.
06e1fab0 06069c77 libeay32!RAND_poll+0x5fa
06e1facc 06023db7 libeay32!RAND_SSLeay+0x447
06e1fb00 06069f71 libeay32!CRYPTO_set_ex_data_implementation+0x387
00000000 00000000 libeay32!RAND_SSLeay+0x741

从栈的信息看来,这个线程是OpenSSL的一个线程,正在做随机数处理,而枚举Heap的信息应该也是随机数的一个组成部分。在枚举Heap的时候也处于等待一个HeapLock的情况,来具体看看

1
2
3
4
5
6
7
0:041> kb L5
ChildEBP RetAddr Args to Child
06e1f214 770fd993 00000b10 00000000 00000000 ntdll!ZwWaitForSingleObject+0x15
06e1f278 770fd877 00000000 00000000 06e1f428 ntdll!RtlpWaitOnCriticalSection+0x13e
06e1f2a0 770f84ca 04560138 00000000 00000000 ntdll!RtlEnterCriticalSection+0x150
06e1f2d4 7717fd7d 04560000 71edefe5 06e1f468 ntdll!RtlLockHeap+0x3d
06e1f3bc 7714de8e 06e1f428 771640d3 00000000 ntdll!RtlpQueryExtendedHeapInformation+0xbd

看来正在等一个基地址是04560000的Heap的cs。顺藤摸瓜看看这个cs又被谁占用了。

1
2
3
4
5
6
7
0:041> dt ntdll_77ffe000!_RTL_CRITICAL_SECTION 04560138 
+0x000 DebugInfo : 0x0151df40 _RTL_CRITICAL_SECTION_DEBUG
+0x004 LockCount : 0n-6
+0x008 RecursionCount : 0n1
+0x00c OwningThread : 0x00001994 Void
+0x010 LockSemaphore : 0x00000b10 Void
+0x014 SpinCount : 0xfa0

继续看看0x00001994这个线程在做什么事情

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
0:002> kb
ChildEBP RetAddr Args to Child
0438d2c4 770fd993 00000698 00000000 00000000 ntdll!ZwWaitForSingleObject+0x15
0438d328 770fd877 00000000 00000000 014d0000 ntdll!RtlpWaitOnCriticalSection+0x13e
0438d350 770fdaf8 014d0138 7334c875 00078000 ntdll!RtlEnterCriticalSection+0x150
0438d42c 770f2fe3 000001f8 00000200 00000000 ntdll!RtlpAllocateHeap+0x159
0438d4b0 770f7bfb 014d0000 00800000 000001f8 ntdll!RtlAllocateHeap+0x23a
0438d4fc 770f70f1 00000028 7334c9d5 07240048 ntdll!RtlpAllocateUserBlock+0xae
0438d58c 770ee0e2 07240048 07240048 00000000 ntdll!RtlpLowFragHeapAllocFromContext+0x802
0438d600 770f8129 014d0000 00000000 00000020 ntdll!RtlAllocateHeap+0x206
0438d610 770f3f21 04560000 07240048 00000000 ntdll!RtlpAllocateDebugInfo+0x28
0438d64c 770f4262 07240048 00000000 00000000 ntdll!RtlInitializeCriticalSectionEx+0x93
0438d660 771031e2 07240048 07240048 04560000 ntdll!RtlInitializeCriticalSection+0x12
0438d674 771031b0 00000000 00000800 04560000 ntdll!RtlpInitializeLowFragHeap+0x28
0438d684 77102f5a 7334cae5 045600cc 04560000 ntdll!RtlpCreateLowFragHeap+0x28
0438d6bc 77103002 04560000 04560194 0438d7a8 ntdll!RtlpActivateLowFragmentationHeap+0xc9
0438d6cc 77102fce 04560000 7334cbf1 00000000 ntdll!RtlpPerformHeapMaintenance+0x2a
0438d7a8 770f2fe3 00000008 00000010 04560194 ntdll!RtlpAllocateHeap+0x172
0438d82c 6fef1e62 04560000 00000008 00000008 ntdll!RtlAllocateHeap+0x23a
0438d844 6fef20f9 0438d858 04560984 00000007 nlaapi!NlapAllocNlaDataSet+0x18
0438d85c 6fef2096 00000007 00000003 045608f0 nlaapi!QueryCtxtDeserializeTuplesToTrieMap+0x20
0438d888 6fef23a7 045608f0 000003c8 04560958 nlaapi!QueryCtxtDeserializeEntityToTrieMap+0x38
0438d924 6fef58e0 7972514e 00000001 00000000 nlaapi!NLA_QUERY_HANDLE_completion_internal+0x21f
0438d944 6fef5cd7 00000000 00000000 0c7507d0 nlaapi!NlaRegisterQuery+0x7d
0438d99c 6fef5bac 044907d0 4e3f415a 014587b0 nlaapi!GetNlaV2Handle+0x7e
0438d9d0 769d59b1 01435244 0438db88 044907d0 nlaapi!WSM_NSPLookupServiceBegin_v2+0xc9
0438d9ec 769d598d 01435208 0438db88 00000000 ws2_32!NSPROVIDER::NSPLookupServiceBegin+0x1b
0438da08 769d591c 01458960 0438db88 00000000 ws2_32!NSPROVIDERSTATE::LookupServiceBegin+0x1d
0438da6c 769d57cc 014587b0 0438db88 00000ff0 ws2_32!NSQUERY::LookupServiceBegin+0x18d
0438dabc 0fd7fca2 0438db88 00000ff0 0438dbd0 ws2_32!WSALookupServiceBeginW+0x7f
WARNING: Stack unwind information not available. Following frames may be wrong.
0438dd18 0fd0c6a1 0fa9df49 03ac9c20 03b5da80 libcef!cef_time_delta+0x3ddc22
0438e2f0 0fa9ec71 0438e698 03a91e00 0438e310 libcef!cef_time_delta+0x36a621
0438e37c 0fa8d58f 0438e698 03ac9c00 03b5da80 libcef!cef_time_delta+0xfcbf1
0438eb88 0fa9915f 0438ebc8 fffffffe ffffffff libcef!cef_time_delta+0xeb50f
0438ebe8 0fb3605d 03b5da80 fffffffe 03ac9d20 libcef!cef_time_delta+0xf70df
0438ec04 0fb3744d 03b5da80 fffffffe fffffffe libcef!cef_time_delta+0x193fdd
0438ecd8 0fb37ad2 00000003 fffffffe 00000000 libcef!cef_time_delta+0x1953cd
0438edc0 0fb37f5d 03ac9400 11c9bee1 03ac9400 libcef!cef_time_delta+0x195a52
0438edfc 0fa9ab19 03ac9400 03ac9c38 03ac9c00 libcef!cef_time_delta+0x195edd
0438f20c 0fa9b466 03ac9400 00000000 03ac9c38 libcef!cef_time_delta+0xf8a99
0438f220 0fb3c815 00000000 03ac9400 0fb3aee5 libcef!cef_time_delta+0xf93e6
0438f2e8 0fb3d2e3 00000000 03b53384 03ac9c38 libcef!cef_time_delta+0x19a795
0438f690 0fb3ad7b 0438f600 03b53384 03b2ddc0 libcef!cef_time_delta+0x19b263
0438f8ac 0f985613 0438fb64 03b53384 00000001 libcef!cef_time_delta+0x198cfb
0438fbec 0f98016a ffffffff ffffffff 03b53368 libcef!cef_string_multimap_free+0x18b63
0438fc28 0fa208a4 03b53340 03ac0dd0 03a87b00 libcef!cef_string_multimap_free+0x136ba
0438fd70 0fa223ba 0438fdd0 03a878c0 03ac0d00 libcef!cef_time_delta+0x7e824
0438fe0c 0fa4edfc 00000000 03a878c0 03ac0dd0 libcef!cef_time_delta+0x8033a
0438fe3c 0fa4ec2e 00000000 03ac0dd0 03ac0dd0 libcef!cef_time_delta+0xacd7c
0438fe5c 0fa2177f 03ac0dd0 0438ff34 03ac0dd0 libcef!cef_time_delta+0xacbae
0438ff24 0fa3a323 03a8d7d0 0fa1fc56 03ac0dd0 libcef!cef_time_delta+0x7f6ff
0438ff50 0fa39b9b 0438ff74 0fa39c6a 03ac0dd0 libcef!cef_time_delta+0x982a3
0438ff58 0fa39c6a 03ac0dd0 03a80f40 03a8d7d0 libcef!cef_time_delta+0x97b1b
0438ff74 0fa22ad8 00000000 00000000 03a80f40 libcef!cef_time_delta+0x97bea
0438ff88 75b9336a 000001a8 0438ffd4 770f9882 libcef!cef_time_delta+0x80a58
0438ff94 770f9882 03a80f40 7334e38d 00000000 kernel32!BaseThreadInitThunk+0xe
0438ffd4 770f9855 0fa22a80 03a80f40 00000000 ntdll!__RtlUserThreadStart+0x70
0438ffec 00000000 0fa22a80 03a80f40 00000000 ntdll!_RtlUserThreadStart+0x1b

这个线程正在为04560000这个heap创建LowFragHeap,但是在获取014d0138的cs的时候被卡住了。这里就发现问题所在了!014d0138不正是我们主线程在等待的cs么,这个线程的10f0和1994都在等待1730的cs=014d0138,而1730却在等待1994的cs=04560138。

20150908002438

反汇编RtlpQueryExtendedHeapInformation就能知道这里造成死锁的真正原因。实际上Heap32Next函数枚举所有Heap信息的时候,先统一锁住进程里面所有的Heap,然后做枚举工作,最后再统一释放锁。所以它在正在锁住所有HeapLock过程中的时候,例如锁住了一半,正在尝试锁住另一半,另外一个线程正好拥有他没锁住的HeapLock,但是不巧的时候他在给这个Heap创建LowFragHeap,而创建LowFragHeap需要初始化HeapLock,其中HeapLock的DebugInfo又是用默认Heap去分配内存的,默认堆的锁正好在第一个线程被锁住的那一半里,这就是事情的真相!

那么微软为什么要提供一个会造成死锁的API,而且不去修复呢?按照Raymond的说法,这个系列的函数目的只是诊断,性能很低,不应该用于普通程序中。
http://blogs.msdn.com/b/oldnewthing/archive/2012/03/23/10286665.aspx
但是,我觉得既然是文档化的接口,而且文档里面没有提到会造成死锁,那么它就应该是安全的。

debugging

介绍三个有趣的API

####PickIconDlg

相信给快捷方式指定过图标的朋友肯定看过一个这样的对话框吧,如果你看到过,你肯定已经知道了这个API是怎么一回事。这个API会弹出一个选择图标的窗口给你选择,确定后返回图标在资源中的索引值。这样你可以通过这个索引值和ExtractIcon函数获得这个图标的句柄。

20150827113011

示例代码如下:

1
2
3
4
5
6
7
8
9
10
int Index = 2;
const DWORD BuffSize = MAX_PATH;
TCHAR Path[BuffSize] = _T("c:\\windows\\system32\\shell32.dll");
const int Sel = PickIconDlg(NULL, Path, BuffSize, &Index);
if(Sel)
{
HMODULE hMod = ::LoadLibrary(Path);
HICON hIcon = ExtractIcon(hMod, Path, Index);
FreeLibrary(hMod);
}

####WNetConnectionDialog和WNetConnectionDialog1

这两个函数是帮助我们在程序中显示映射网络驱动器对话框的,虽然用的不多,但是也应该见到过它。这两个函数区别不大,只不过WNetConnectionDialog1比WNetConnectionDialog提供了更多的参数去设置。

20150827114516

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#pragma comment(lib, "Mpr.lib")

CONNECTDLGSTRUCT condlg = { 0 };
condlg.cbStructure = sizeof(condlg);
condlg.hwndOwner = GetConsoleWindow();
condlg.dwFlags = CONNDLG_USE_MRU;

NETRESOURCE nr = { 0 };
nr.dwScope = RESOURCE_GLOBALNET;
nr.dwType = RESOURCETYPE_DISK;
nr.lpRemoteName = NULL;
nr.dwDisplayType = RESOURCEDISPLAYTYPE_DOMAIN;

condlg.lpConnRes = &nr;

const int RetVal = WNetConnectionDialog1(&condlg);

####SHOpenWithDialog

这个API所显示的对话框我们应该是最多见的,它显示了一个打开方式的对话框。不过有点可惜的是,XP并不支持这个API,我们只能将它用在Vista开始的系统上。

20150827115225

示例代码如下:

1
2
3
4
OPENASINFO Info = { 0 };
Info.pcszFile = _T("C:\\Windows\\win.ini");
Info.oaifInFlags = OAIF_EXEC | OAIF_ALLOW_REGISTRATION;
SHOpenWithDialog(NULL, &Info);

Tips

OleFileView 查看Ole文件数据结构的工具

OleFileView是一个查看结构化存储文件的工具,我们熟悉的Ole存储的文件就是这种格式,虽然很老了,但是依旧被广泛使用,例如老版本Office的文件doc,xls等,包括msi,jumplist等,都是采用的这种格式。
所以我也就抽了点时间研究了一下这个数据结构,可以说这就是个小型的文件系统,虽然比不上NTFS,但是对于一般的存储可以说是小菜一碟。

20150803003316

下载:OleFileView.zip

Tips

使用WMI监控进程创建和结束

Windows Management Instrumentation (WMI) 是微软实现的一套可以通过网页管理计算机的系统,我们可以通过WMI查询计算机的方方面面。从Vista开始,这个机制增加了Instance Event的提醒机制,这个机制可以帮助我们监控各种Instance的创建、删除和修改。所以,我们可以想到的是进程也是在WMI里的Win32_Process有Instance的记录,这样我们就可以跟踪到进程的创建和结束了。当然,我们还可能监控到文件等等WMI里的各种Instance。下面是一个监控进程的例子:

20150712232158

下载:MonitorProcessWithWMI.zip

Tips

用Windbg script将内存中的PE文件dump出来

最近看到有些恶意程序,从网络上下载PE文件后,直接放在内存里重定位和初始化,为了能将其dump出来,所以写了这个Windbg脚本。

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
.foreach( place  { !address /f:VAR,MEM_PRIVATE,MEM_COMMIT /c:"s -[1]a %1 %2 \"MZ\"" } ) 
{
ad *
.catch {
r @$t2 = place;
r @$t0 = place;
r @$t1 = @@C++(((ntdll!_IMAGE_DOS_HEADER *)@$t0)->e_lfanew);
r @$t0 = @$t0 + @$t1;
r @$t1 = $vvalid(@$t0, 4);

.if (@@C++(@$t1 && @@C++(((ntdll!_IMAGE_NT_HEADERS *)@$t0)->Signature) == 0x00004550))
{
r @$t1 = @@C++(((ntdll!_IMAGE_NT_HEADERS *)@$t0)->OptionalHeader.SizeOfImage);
.printf "%08x %08x\n", @$t2, @$t1;
aS /x start_addr @$t2
aS /x dump_size @$t1
.block {
aS target_file e:\\${start_addr}.dll
}

.block {
.printf "${target_file}"
.writemem "${target_file}" ${start_addr} L?${dump_size}
}
}
}
}

Tips

将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