设置线程名

给线程命名的作用主要还是为了调试方便。其他的好处也没有了,至少我没想出来。这里说一下MS_VC_EXCEPTION这个异常,调试器(vs,windbg)默认情况下应该会在收到这个异常的时候,他会自动处理这个异常,具体操作应该是记录下线程对应的名称,然后将异常设置为Handle状态。什么意思呢?就是说,即使下面这段代码中的RaiseException不在try-except中,在调试器attach的情况下也能顺畅执行,调试器不会因为异常把执行中断下了,而是默默设置了线程名之后继续后面的代码。而下面的代码之所以要放在try-except中,是因为希望没有调试器的情况下,也能顺利执行不被中断。另外一点,windbg可以设置让这个异常中断下来(命令 sxe vcpp),而vs貌似没有这样的方法,可能是我vs调试器用的比较少,没找到吧。对于托管代码,设置这个就更简单了,详见 http://msdn.microsoft.com/en-us/library/581hfskb(v=vs.100).aspx.aspx)

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
#include
const DWORD MS_VC_EXCEPTION=0x406D1388;
#pragma pack(push,8)
typedef struct tagTHREADNAME_INFO
{
DWORD dwType; // Must be 0x1000.
LPCSTR szName; // Pointer to name (in user addr space).
DWORD dwThreadID; // Thread ID (-1=caller thread).
DWORD dwFlags; // Reserved for future use, must be zero.
} THREADNAME_INFO;
#pragma pack(pop)
void SetThreadName( DWORD dwThreadID, char* threadName)
{
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = threadName;
info.dwThreadID = dwThreadID;
info.dwFlags = 0;
__try
{
RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info; );
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
}
}

Tips

R3卸载任意消息钩子

这是工作中遇到的一个问题。一个程序每次起来后会去挂鼠标键盘低级钩子,这类钩子恶心的地方是如果你用调试工具attach上去,鼠标键盘就会急剧的延时,就跟挂起差不多了,根本无法使用键盘鼠标。MSDN上面指明,设置LowLevelHooksTimeout可以帮助解决这个问题。但是无奈的是,似乎没起什么作用,渣英语,不知道是不是我的理解有误。

其他比较好的解决方案也有。比较好的一个就是用远程调试的方式,这种方式可以在hook存在的情况下调试程序,甚至调试hook的函数。另一个办法就是attach之前,卸载低级钩子。对于调试和钩子无关地方的时候,第二个选择也还是不错的。所以,Xuetr的卸载消息钩子的功能派上了用场。每次调试此程序之前,都先卸载钩子。但是还是有问题,我们都有这样的经验,在进行调试的时候经常需要restart程序,并且重新开始调试。这样可就恶心了,每次都要卸载一次钩子。于是,我就写了一个程序,循环查询低级鼠标键盘钩子,发现后立刻卸载,这样调试这个程序就会比较轻松了。

虽然说用驱动写这个功能看起来比较轻松,实际上R3实现也很简单。这里用到的关键之时是Desktop Heap会在GUI进程中映射到用户态内存上,这也就给了我们可乘之机。简单介绍一些Desktop Heap是什么。我们都知道一个桌面都有个Desktop Object的对象,而实际上美国Desktop Object都会有一个对应他的Desktop Heap。Desktop Heap主要存储用户交互对象(user interface objects),这其中就包括Window,Menu,Hook等等。既然Hook存储在Desktop heap,而且Desktop heap又刚好映射到R3内存,那么我们就可以顺利的读取他了。这里,可能会有一个疑问,怎么知道Hook存储在Desktop Heap,而不是Share Heap或者其他。实际上Windows的Win32k中有一个Handle Information Table,指明了每种Object的存储类型。

现在是已经知道了可以去读Hook对象,但是上哪去读就是要解决的问题了。这里就要提到老生常谈的Sharedinfo了。用户态的Sharedinfo获取方法很多,顺手就行。与上面问题相关的就是Sharedinfo里面会有一个Handle Entry Table,里面存储的就是包括Hook在内的User Object。Entry的结构如下(来自reactos):

1
2
3
4
5
6
7
8
9
10
11
typedef struct _HANDLEENTRY
{
PHEAD pHead;
PVOID pOwner;
BYTE bType;
BYTE bFlags;
WORD wUniq;
} HANDLEENTRY, *PHE, *PHANDLEENTRY;

其中pHead指向Object,bType表示object类型。HOOK的bType是5。所以这里我们只需要在bType为5的时候继续下面的操作。

pHead肯定是指向的内核内存,所以我们无法直接访问HOOK的内部情况。我们需要找到这个Object映射到R3的内存地址才行。幸运的是这种关系也比较简单明了。在Teb->Win32ClientInfo.ulClientDelta就存放了对应的关系Delta值。计算方法如下

ObjectInR3 = HANDLEENTRY.pHead - Teb->Win32ClientInfo.ulClientDelta。

20121226220202

这样也就得到了HOOK Object。接下来的事情就好办了,HOOK Object的第一项就是HHOOK。只需要UnhookWindowsHookEx((HHOOK)Hook->head.h);就能卸载钩子了。

NTInternals

关于整型数符号位扩展的一点心得

最近写的程序中遇到了整型数符号位扩展的小问题。稍稍看了下,写在这里备忘。

这里举个例子:

1
2
3
4
// case 1
long i = -1;
long long q = i;
1
2
3
4
// case 2
long i = -1;
unsigned long long q = i;
1
2
3
4
// case 3
unsigned long i = 0xffffffff;
long long q = i;
1
2
3
4
// case 4
unsigned long i = 0xffffffff;
unsigned long long q = i;

那么这4肿情况中q都是多少呢?
实验结果是case 1 和 2,他们的q的值(这里都表示为无符号)0xffffffffffffffff,而case 3 和 4 中q的值为0x00000000ffffffff。
看到这里,大概就能推测c++的转换策略。即以源操作数的类型为依据,对其进行扩展,然后赋值到目标操作数,他并不在乎目标的类型有无符号。

看了相关编译完成后的汇编代码可以确认这一点,case 1和2的汇编代码完全相同,而3和4也是一样。更具体一点来说。有符号的情况下调用了cdq对符号位进行扩展,然后将edx赋值到q的高位,而无符号的情况下,简单xor了寄存器,然后赋值到q的高位。

Tips

dbgLua,让lua脚本也能控制windbg进行调试(更新1.0.1.1)

关于dbgLua:
这是一个让windbg支持lua脚步的扩展程序。写这个程序的主要目的是希望能简单的取代windbg本身的脚本。因为我确实不喜欢windbg那种形式的脚本。

使用方法:将dbgLua.dll拷贝到windbg的winext下,编写lua脚本。调试的时候,在输入框中输入“!dbgLua.run d:\sample.lua”其中“d:\sample.lua”是你的脚步路径。

以下是1.0.0.1版本所支持的lua函数(后续可能会慢慢添加更多,看需求了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dbgLua 1.0.1.1 API
dprint 输出信息到windbg command窗口,例如dprint("hello")
exec 执行一条windbg命令,例如exec("bp kernel32!CreateFileW")
getreg 获得当前被调试对象的寄存器数据,例如eax_val = getreg("eax")
setreg 设置当前被调试对象的寄存器数据,例如setreg("eax", 123456)
readbyte 读取当前被调试对象的内存器数据,大小1字节
readword 同上,大小为2字节
readdword 同上,大小为4字节,例如mem_val = readxxxx(0x410000)
writebyte 写入当前被调试对象的内存器数据,大小1自己
writeword 同上,大小为2字节
writedword 同上,大小为4字节,例如writexxxx(0x410000, 654321)
readunicode 读取一个unicode字符串,例如str = readunicode(0x410000)
readascii 读取一个ascii字符串,例如str = readascii(0x410000)
wait 等待事件,例如exec("bp kernel32!CreateFileW;g");wait();
evalmasm masm表达式求值,例如val = evalmasm("11+2*3")
evalcpp cpp表达式求值,例如val = evalcpp("sizeof(char)")
getmoduleinfo 通过模块名获得模块基址和大学,例如base,size = getmoduleinfo("kernel32")
search 二进制查找,例如found = search(base, size, "cc 89 75 fc eb ")</blockquote>

具体的结合这些函数进行调试的例子还没有准备好,等有机会了,我会准备好调试案例放到这里来。

另外这是一个初始版本,不保证没有bug,如果你在使用中发现了bug,或者有好的想法,例如添加什么函数功能,不妨联系我。

下载:

dbgLua(v1.0.1.1)

dbgLua(v1.0.0.1)

Debugging

关于判断文件是否存在最高效的函数

判断文件存在方法有很多,例如CreateFile,FindFirstFile,GetFileAttributes,PathFileExists等等。但是哪一种更加高效呢?其实作为常识,可能都能判断出GetFileAttributes和PathFileExists会比较快(而实际上PathFileExists就是调用的GetFileAttributes)。

下面是google一份开源代码中提到的统计结果

1
2
3
4
5
// NOTE: This is the fastest implementation I found. The results were:
// CreateFile 1783739 avg ticks/call
// FindFirstFile 634148 avg ticks/call
// GetFileAttributes 428714 avg ticks/call
// GetFileAttributesEx 396324 avg ticks/call</blockquote>

为什么会这样呢?大概了看了下,原因应该是这样的。

1.CreateFile会创建句柄,需要一个完整IO流程,所以需要的时间比如非常长。
2.FindFirstFile回去查询文件夹的文件,虽然不会真正的打开文件句柄,并且在文件已经被缓存的情况下,走的是fastio流程,所以查询时间大幅下降,但是操作略微繁琐,导致他不是最好的选择。

  1. GetFileAttributes 和GetFileAttributesEx 也设置了QueryOnly标志,不需要获得真正的句柄,并且能够走fastio流程,也没有文件夹查询等工作,所以速度最快。

那么为什么GetFileAttributesEx 会快那么一点点呢?因为这个函数少了一个获取BasicInformation,也就是少了一个fastio流程。所以速度更快。这样看来,自己实现一个PathFileExistsEx效率可以高过PathFileExists了。(其实没多大实际意义)

google就是这样做的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool File::Exists(const TCHAR* file_name) {
ASSERT1(file_name && *file_name);
ASSERT1(lstrlen(file_name) > 0);
// NOTE: This is the fastest implementation I found. The results were:
// CreateFile 1783739 avg ticks/call
// FindFirstFile 634148 avg ticks/call
// GetFileAttributes 428714 avg ticks/call
// GetFileAttributesEx 396324 avg ticks/call
WIN32_FILE_ATTRIBUTE_DATA attrs = {0};
return 0 != ::GetFileAttributesEx(file_name, ::GetFileExInfoStandard, &attrs);
}

NTInternals

PIO读IDE

经过各种代码的东拼西凑、改来改去,总算是把PIO读取硬盘信息的代码“写”好了,上图是读取硬盘的前512字节的效果图。目前看来还是很挫,原因有两点:

1.只支持LBA48的读取方法,不支持CHS,LBA28,虽然这两个方法的读取范围很有限,但是感觉至少要把LBA28给支持了才行。
2.很郁闷的一点,这个读取代码读取成功了,但是IO后返回的状态值是错误的。不知道哪里出了问题,会不会是虚拟硬盘太小而不能用LBA48的问题呢?没有头绪。


补充1.通过IDENTIFY DEVICE命令发现,可能由于设置的虚拟硬盘比较小的原因,虚拟硬盘不支持48bit的地址。IDENTIFY DEVICE会通过PIO方式返回一个256字(512字节)的数据。其中第83个字的第10位表示是否支持48bit的地址。如下图(来自ATA官方手册AT Attachment with Packet Interface - 6)。

补充2.由于不支持LBA48,我还是实现了LBA28。不过这个只能访问128G的硬盘了。至于CHS目前还是不考虑实现。
补充3.PIO写的方式大概也是差不多的。准备慢慢实现,还有DMA读写硬盘也需要了解下。不过好消息是现在基本能看懂ATA的手册了。
补充4.MiniKernel的代码依然写得很挫,暂时不准备共享出来,因为共享出来也没啥用,想学写系统的也看不懂那种烂代码。
补充5.感觉读写硬盘是一个挺有意思地方,完全可以单独拿出来写一个系列的blog。只是有没有时间和懒不懒的问题。
补充6.补充5的最后一句是P话,时间肯定是有的,就是懒而已。。。

一个月一篇文章。。。多一点都没有。。。我果然是个需要被监督的人。。。

MiniKernel

总结2011,展望2012

2011还是过去了,2012来了。觉得有点必要写点东西,总结一下过去,展望一下未来。如果要用几个词来总结我的2011,那应该是天真,失望,浮躁。可以说我对我的2011是比较不满意的。

天真,我自认为自己还算是个性情中人,所以很多事情对我来说就是讲得就是个胃口。我是很愿意把结识的人当作朋友处。但是,在工作中,有些“朋友”确实是建立在利益基础上的。如果把所有说过“我们是朋友”的人当作朋友,很有可能吃亏是自己。工作也是一样,代入太多感情色彩也是很天真的一种做法。为了讲胃口,有时候退一步,多干一些活。做的好当然没事,但是做的不尽如人意有时候给你带来的真的会是麻烦。

失望,在去年对一些人失望了,对工作的事情也失望,对自己也挺失望的。有些事情自己也不想拿出来说,也不想以后看到在想起,能快点忘记就忘记。但是对自己失望需要深刻的自我剖析检讨了。技术上提高真的不大,基本上都看不出自己做成了什么厉害的事情,决定的事情大部分没有坚持下来,空余的时间大部分花在娱乐上。

浮躁,一整年,都是浮躁的。买的书一本一本的增多,耐心看完的,甚至说看了一大半的都没几本。想学习的东西很多很多,但是没有一个耐心去学习的。给自己开的代码项目很多,也没见过几个写成的。做事情的思路大概是这样的:哎哟,这个东西挺好玩,去实现一个呗;恩,找点资料吧;我靠,资料不是很多嘛;晕,环境怎么这么难搭建;耐心点,慢慢来;好,环境搭建好了,可以开始了;哎,细节问题好多啊,一个人写这个真的大丈夫么;妈的,确实很难写,比想的难好多啊;不行了,弄不下去了;哎哟,弄这个意义大么;意义不是很多大吧,哎,还是弄点别的吧……

所以我觉得,我的2011基本上就是失败的。但是,我的字典里面没有后悔,因为后悔不能改变任何事情。而且不也不会激励自己明年一定要怎么样。因为貌似这种自我暗示已经被我免疫了。当然如果从过得怎么样的角度来看,我的2011还是相当精彩和快乐的。只不过说,这个人有点贱,快乐的事情总是记不住。

虽然说我现在已经不喜欢那些所谓的立志大湿,但是还是应该给自己一个2012的展望。怎么说呢?继续浮躁吧,想做的事情很多很多。

1.写完自己的mini kernel。

2.看完几本书,包括:nt 文件系统的后半部分,编译原理,算法导论。说实话,我还是感觉自己能看完其中两本就算不错了。

3.完善自己的BaseLib,加上自己实现各种算法。

4.实现一个简单的脚本语言。

5.最后多看两眼wrk吧。

看吧,我真的很浮躁很浮躁,如果明年的这个时候的总结(当然,前提是别2012-12-21就结束了),其中有三条圆满完成,我就觉得很奇迹了。

最后,无论怎么说,2011已经过去,过去的事情无论好坏都过去了。期待2012自己的改变吧。祝福我的家人,朋友和我自己,新年快乐,健康平安,家庭和睦温馨。

我本来不想写kernel,直到我的膝盖中了一箭

最近中箭体很火,我也凑个热闹。话说自从delete那篇文章过后,又有一个多月没写了。其实不是不想写,是不知道些什么才好。简单的东西不想写,难的东西写不出来。

正像标题写的,恩,我开始写kernel玩了。其实写一个简单,功能单一的kernel并不难。麻烦的只是搭建环境等等。kernel的编写资料也很多,但是可惜的是,绝大部分都是应用在linux环境。我是那种看到linux就头晕的人。所以还是坚持用windows和vc来开发kernel。令人惊喜的是grub能够帮助我们map kernel到内存中,所以boot loader这一步可以想放下。等kernel写了个大概再来写boot loader也不迟。

环境和工具:
环境正如我上面提到了windows xp 和 vs 2008。其他工具包括winimage,virtual pc,bochsdbg(+ IDA)。当然还有grub4dos。

要高效的起步,先要了解mulitBoot的一些知识。还有就是写一套能够在text mode下打印信息的函数,例如printf。这样在不用调试的情况下,就能了解一些信息。说实在的bochsdbg的调试功能真心不好用,但是加上IDA可能是一个比较好的做法。具备以上条件后,就可以开始kernel之旅了。

可以看出内存的基本状况已经可以从boot_info中获取了。接下来要做的事情也很明了。就是需要一个物理内存管理器,实现最基本的物理内存管理器也不算难,不过那就是下篇文章的事情了。现在的kernel大小为7168字节,慢慢玩,看我能坚持多久。

MiniKernel

为什么必须用delete[]释放数组

最近不知哪来的好奇心,对C++产生了一些兴趣,要知道我通常情况下都是cpp文件中写c代码,c代码中嵌汇编。不过,在做了一些大点的项目之后,确确实实发现了,这种编码方式麻烦的一面。有时候甚至自己都难得维护以前写的东西。所以选择性看了google的c++编程规范,而且对scoped_ptr和auto_ptr的区别参数兴趣。然后我得出的结论是这两种实际上是其实差不多,只不过scoped_ptr拷贝构造函数和赋值构造函数都是私有的。这样就避免粗心大意的程序员调用他。其他的区别还真没看出来。

OK,这些都不是记录这篇tip的重点。重点在于boost,或者说是google的scoped_ptr代码里面实现了scoped_array。而要用在数组上使用智能指针,就必须用数组的智能指针类。而auto_ptr刚好没有数组部分,所以对于数组,就不能用auto_ptr了。(为什么不用vector?这也不是重点)

作为一个蹩脚的C++程序员,我这时候开始犯晕了。我们知道只能指针都是帮助程序员去释放资源,让程序员把精力放到更重要的地方。那么在我看来那么所需要做的就是析构的时候 delete 或者 delete[] 就行了。要知道,delete[] 就是调用的delete,他们只是单纯的释放内存。那么数组和非数组又有什么区别?

光想肯定不行,写两个例子。

首先是new 一个char数组,分别用delete和delete[]释放。结果表明,没有任何问题,而且不会产生内存泄露。坑爹么?NO,还没完,其实咱们最怀疑的一直都是数组对象,因为他们都有构造和析构函数。而char这样的系统内建类型,想象得出不会出什么问题。new一个对象数组,分别用delete和delete[] 释放。果然问题暴露了,delete的时候出了问题。

知其然,不足以满足好奇心。下面才是拿手的,精彩的要放在后面嘛。

先看测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include
using namespace std;
class A {
public:
A() {cout << "start 1" << endl;}
~A() {cout << "End 1" << endl;}
};
int main()
{
A *a = new A[10]();
delete[] a;
return 0;
}

编译后看到代码如下

1
2
0040103D push 0Eh
0040103F call operator new[] (403ED0h)

注意到这里传入的大小时0Eh,也就是说申请分配14个字节大小的内存。但是我们知道C++标准中空类的大小应该是1字节。那么多出的dword我们就很容易想到他的用处,应该是记录数组大小的。事实也确实如此。

1
mov dword ptr [eax],0Ah

这里明确交代,给分配内存的第一个dword传入10。

1
2
3
4
5
6
7
8
00401069 push offset A::~A (4011D0h)
0040106E push offset A::A (401120h)
00401073 push 0Ah
00401075 push 1
00401077 mov ecx,dword ptr [ebp-0F8h]
0040107D add ecx,4
00401080 push ecx
00401081 call `eh vector constructor iterator' (40A010h)

这里是调用构造函数,注意是vector版本的构造函数。参数分别是数组的this指针,sizeof(A),数组数量,构造和析构函数。这样,在这个函数内部循环10次调用构造函数,构造完毕。

1
2
3
4
5
6
7
8
9
004010BD mov eax,dword ptr [ebp-14h]
004010C0 mov dword ptr [ebp-0E0h],eax
004010C6 mov ecx,dword ptr [ebp-0E0h]
004010CC mov dword ptr [ebp-0ECh],ecx
004010D2 cmp dword ptr [ebp-0ECh],0
004010D9 je main+0F0h (4010F0h)
004010DB push 3
004010DD mov ecx,dword ptr [ebp-0ECh]
004010E3 call A::`vector deleting destructor' (401230h)

这里是析构部分传入数组的this指针,传入flag(3),调用析构函数。

继续看call之后的代码

1
2
3
4
5
6
7
8
9
10
11
12
00401253 mov eax,dword ptr [ebp+8]
00401256 and eax,2
00401259 je A::`vector deleting destructor'+61h (401291h)
0040125B push offset A::~A (4011D0h)
00401260 mov eax,dword ptr [this]
00401263 mov ecx,dword ptr [eax-4]
00401266 push ecx
00401267 push 1
00401269 mov edx,dword ptr [this]
0040126C push edx
0040126D call `eh vector destructor iterator' (40A920h)
00401272 mov eax,dword ptr [ebp+8]

如果flag中位1是set,那么调用eh_vector_destructor_iterator调用每个析构函数。参数分别是this,sizeof(A),数组个数(这里很明显是从eax-4中拿出来的)以及析构函数地址。

OK,明白了delete[]的做法,我们看看delete为什么失败。

1
2
3
4
5
6
7
8
9
004010BD mov eax,dword ptr [ebp-14h]
004010C0 mov dword ptr [ebp-0E0h],eax
004010C6 mov ecx,dword ptr [ebp-0E0h]
004010CC mov dword ptr [ebp-0ECh],ecx
004010D2 cmp dword ptr [ebp-0ECh],0
004010D9 je main+0F0h (4010F0h)
004010DB push 1
004010DD mov ecx,dword ptr [ebp-0ECh]
004010E3 call A::`scalar deleting destructor' (4012D0h)

这里调用的析构函数都不一样是一个scalar版本的函数。

继续看这个函数的关键部分

1
2
3
4
5
6
7
8
004012F3 mov ecx,dword ptr [this]
004012F6 call A::~A (4011D0h)
004012FB mov eax,dword ptr [ebp+8]
004012FE and eax,1
00401301 je A::`scalar deleting destructor'+3Fh (40130Fh)
00401303 mov eax,dword ptr [this]
00401306 push eax
00401307 call operator delete (40A890h)

这里很清楚的看到,只进行一次析构,然后就释放内存。所以我们看到的现象是只调用一次析构函数。那么为什么会崩溃呢?因为delete错了地址。看上面的对比的值,eax-4才是new返回的地址,所以delete的不应该是eax,而是eax-4。

真相大白?NO,还有一个问题,delete[] 和delete 内建类型真的成功了么?
看看我贴出的代码吧,这里不解释了。

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
;delete[] version
0040101E push 0Ah
00401020 call operator new[] (4014C0h)
00401025 add esp,4
00401028 mov dword ptr [ebp-0E0h],eax
0040102E mov eax,dword ptr [ebp-0E0h]
00401034 mov dword ptr [a],eax
00401037 mov eax,dword ptr [a]
0040103A mov dword ptr [ebp-0D4h],eax
00401040 mov ecx,dword ptr [ebp-0D4h]
00401046 push ecx
00401047 call operator delete[] (401600h) `
;delete version
0040101E push 0Ah
00401020 call operator new[] (4014C0h)
00401025 add esp,4
00401028 mov dword ptr [ebp-0E0h],eax
0040102E mov eax,dword ptr [ebp-0E0h]
00401034 mov dword ptr [a],eax
00401037 mov eax,dword ptr [a]
0040103A mov dword ptr [ebp-0D4h],eax
00401040 mov ecx,dword ptr [ebp-0D4h]
00401046 push ecx
00401047 call operator delete (401600h)

就像我刚刚所说的delete[]会调用delete。所以不会出任何问题。

如果汇编看的头疼的话,这里我写了两个函数的逆向代码(说了精彩的应该放在后面的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void A::'scalar deleting destructor'(unsigned int flags)
{
this->~A();
if (flags & 1) {
A::operator delete(this);
}
void A::'vector deleting destructor'(unsigned int flags)
{
if (flags & 2) {
count = *(int *)((int)this - 4);
'eh vector destructor iterator'(this, sizeof(A), count, A::~A);
if (flags&1) {
A::operator delete((void *)((int)this - 4));
}
}
else {
this->~A();
if (flags & 1) {
A::operator delete(this);
}
}
};

分析到这,终于明白。数组一定要用delete[]释放才安全。所以千万不要用auto_ptr作为数组的智能指针,不然会死的很惨。这里还要提一点,auto_ptr也不要用到容器里面去了,也是不允许的。非要这么做就用shared_ptr吧, C++0x已经在stl中加入的这部分。从vs2008 sp1开始支持。低版本的vs的话就去boost里面找吧。

Tips

Volume snapshot

上个月说了,准备放一份基于卷磁盘快照代码。拖到现在也没有更新了,就把它放出来,留着也没啥用。本来就是为了做一个演示demo,没考虑效率和稳定性,只是提供一种思路,其实思路也很简单,有人已经把他完善的很好了。感觉博客更新确实慢,但是也没什么想写的,其实也写不出来什么。悲剧啊!

和我其他代码一样,这么代码同样没有注释。需要的讲究读读吧。那啥,最近昨天把google的代码规范看了看,确实有很多值得学习的地方。不过呢,有些地方可能个性使然,不太认同。不过风格的问题,其实也没什么。

最近还写了hive文件读解析的代码,还没完善,然后又转去看系统缓存那部分的东西了。有时间完善完善。还要完善了是自己写的一个ini文件解析的类。哎,都是一些自己造轮子的活。不过还挺有意思。

下载:SectorMon

NTInternals