﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-Ay's Blog@CNSSUESTC</title><link>http://www.cppblog.com/ay19880703/</link><description /><language>zh-cn</language><lastBuildDate>Wed, 08 Apr 2026 20:48:10 GMT</lastBuildDate><pubDate>Wed, 08 Apr 2026 20:48:10 GMT</pubDate><ttl>60</ttl><item><title>让xp加载指定的内核版本--别以为xp加载的内核总是来自于ntoskrnl.exe!!!</title><link>http://www.cppblog.com/ay19880703/archive/2013/04/06/199145.html</link><dc:creator>__ay</dc:creator><author>__ay</author><pubDate>Sat, 06 Apr 2013 05:16:00 GMT</pubDate><guid>http://www.cppblog.com/ay19880703/archive/2013/04/06/199145.html</guid><wfw:comment>http://www.cppblog.com/ay19880703/comments/199145.html</wfw:comment><comments>http://www.cppblog.com/ay19880703/archive/2013/04/06/199145.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ay19880703/comments/commentRss/199145.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ay19880703/services/trackbacks/199145.html</trackback:ping><description><![CDATA[之前ida+windbg调内核发现内核的地址和ida中不匹配....搞了半天,终于发现是这个原因:<br />我ida是ntoskrnl.exe .... &nbsp;而windows中加载的版本是<span style="font-size: 14px;">ntkrpamp.exe<br /><br /></span>忘了操作系统会根据处理器型号加载不同版本内核了....<br />正好看到高端调试上有关于这个的讨论:&nbsp;<span style="font-size: 14px;">http://advdbg.org/forums/2142/ShowPost.aspx<br /><br />但是我的分析是基于ntoskrnl的，没办法，只好强制指定系统加载ntosknrl了,得做以下几个工作<br /><br />1 把vm的processor调成单核单处理器<br />2 开windows虚拟机, 为了告诉系统现在是单核单处理器模式,得跑一下这个命令&nbsp;</span><span style="color: #323e32; font-family: simsun; font-size: 14.399999618530273px; line-height: 21.600000381469727px; background-color: #dcbc8f;">rundll32.exe setupapi,InstallHinfSection ACPIAPIC_UP_HAL 131 %windir%\inf\hal.inf</span><span style="font-size: 14px;"><br /></span>参考:&nbsp;如何修改Windows&nbsp;XP系统的内核类型&nbsp;&nbsp;http://blog.sina.com.cn/s/blog_5918846401000bik.html<br />3 当然,这里还没结束,如果处理器支持PAE 那么系统会加载ntoskrnla &nbsp;所以还得禁用PAE. 到<span style="font-family: arial, 宋体, sans-serif; font-size: 14px; line-height: 24px; text-indent: 30px; background-color: #ffffff;">BOOT.INI里面, 启动设置中如果有</span><span style="font-family: arial, 宋体, sans-serif; font-size: 14px; line-height: 24px; text-indent: 30px; background-color: #ffffff;">/noexecute=optin就替换改成/execute,没有的话就加上</span><span style="font-family: arial, 宋体, sans-serif; line-height: 24px; text-indent: 30px; background-color: #ffffff;">/execute<br /><br />完了后就会加载ntoskrnl了,效果图:<br /><img src="http://www.cppblog.com/images/cppblog_com/ay19880703/QQ截图20130406131446.png" width="526" height="108" alt="" /><br /></span>指定其它系统方法类似,在此仅抛砖引玉&nbsp;<br /><br /><br /><br /><div></div><img src ="http://www.cppblog.com/ay19880703/aggbug/199145.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ay19880703/" target="_blank">__ay</a> 2013-04-06 13:16 <a href="http://www.cppblog.com/ay19880703/archive/2013/04/06/199145.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ATL的offsetofclass中,为啥_ATL_PACKING是8的解释</title><link>http://www.cppblog.com/ay19880703/archive/2012/01/12/164053.html</link><dc:creator>__ay</dc:creator><author>__ay</author><pubDate>Thu, 12 Jan 2012 05:52:00 GMT</pubDate><guid>http://www.cppblog.com/ay19880703/archive/2012/01/12/164053.html</guid><wfw:comment>http://www.cppblog.com/ay19880703/comments/164053.html</wfw:comment><comments>http://www.cppblog.com/ay19880703/archive/2012/01/12/164053.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/ay19880703/comments/commentRss/164053.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ay19880703/services/trackbacks/164053.html</trackback:ping><description><![CDATA[
		<p>解答出处</p>
		<p>
				<a href="http://stackoverflow.com/questions/5014061/whats-the-use-of-atl-packing-constant-when-computing-distance-from-the-start-o">http://stackoverflow.com/questions/5014061/whats-the-use-of-atl-packing-constant-when-computing-distance-from-the-start-o</a>
		</p>
		<p> </p>
		<p>C++标准规定:</p>
		<p>编译器的类的类型转换对空指针(NULL)特殊处理,即你NULL进行强制类型转化的结果还是NULL,所以….</p>
		<pre>
				<code>static_cast类型转化的时候,_ATL_PACKING的值得是非零就好</code>
		</pre>
		<pre>
				<code>
				</code> </pre>
		<p>
				<strong>C++ Standard 4.10/3 Pointer conversions [conv.ptr]:</strong>
		</p>
		<p>An rvalue of type “pointer to cv D,” where D is a class type, can be converted to an rvalue of type “pointer to cv B,” where B is a base class (clause 10) of D. If B is an inaccessible (clause 11) or ambiguous (10.2) base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion is a pointer to the base class sub-object of the derived class object. <strong><font color="#ff0000">The null pointer value is converted to the null pointer value of the destination type.</font></strong></p>
<img src ="http://www.cppblog.com/ay19880703/aggbug/164053.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ay19880703/" target="_blank">__ay</a> 2012-01-12 13:52 <a href="http://www.cppblog.com/ay19880703/archive/2012/01/12/164053.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>WINDBG的堆调试&amp;mdash;full page heap的堆破坏检测原理</title><link>http://www.cppblog.com/ay19880703/archive/2012/01/05/163604.html</link><dc:creator>__ay</dc:creator><author>__ay</author><pubDate>Thu, 05 Jan 2012 01:17:00 GMT</pubDate><guid>http://www.cppblog.com/ay19880703/archive/2012/01/05/163604.html</guid><wfw:comment>http://www.cppblog.com/ay19880703/comments/163604.html</wfw:comment><comments>http://www.cppblog.com/ay19880703/archive/2012/01/05/163604.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ay19880703/comments/commentRss/163604.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ay19880703/services/trackbacks/163604.html</trackback:ping><description><![CDATA[<p>@作者: ay @文章出处: <a href="http://cnss-ay.com/">cnss-ay的博客</a>@Notice: <font color="#ff0000">转载请注明出处！若文章显示不完整，可以到文章出处阅读。</font></p> <p><font color="#ff0000"></font>&nbsp;</p> <p><font size="3">此文会涉及到一些普通堆的知识，这些内容可以参见我之前的文章 </font><a href="http://www.cppblog.com/ay19880703/archive/2011/10/30/159364.html" target="_blank"><font size="3">WINDBG的堆调试--了解HEAP组织</font></a></p> <h1>堆破坏</h1> <p><font size="2">所谓的堆破坏，是说没控制好自己的指针，把不属于你分配的那块内存给写覆盖了。这块内存可能是你程序的数据，也可能是堆的管理结构。那么这个会导致怎样的后果呢？可能的情况我们来yy下</font></p> <ol> <li><font size="2">把程序里的计算结果覆盖了，这也许会让你重复看了N次代码，校验了N次计算逻辑也搞不明白为何计算结果还是有问题 </font> <li><font size="2">堆管理结构被破坏了，new/delete，或者malloc/free操作失败 </font> <li><font size="2">等等等等~</font></li></ol> <p><font size="2">堆破坏较为理想的情况是被修改的数据会马上导致程序crash，最差的情况是你的堆数据莫名其妙在今天被改了，但明天才crash。这个时候在去分析crash，就如我们的警察叔叔现在接手一桩10年前的案子一般----无从下手。老外称之为heap corruption是很贴切的，有时候咱堆数据被意外篡改是无声无息的，你也许没法从界面甚至日志文件中看到它被篡改的一点迹象，当到某一个时刻，这种错误会暴露出来，然而这个时候查看堆信息也许会是毫无头绪。所以对于堆破坏，咱的策略是尽早发现我们的堆被篡改了，最好能够在堆数据被意外篡改的那一时刻诱发一个异常来提醒我们----兄弟，你的堆被腐蚀了。</font></p> <p><font size="2">微软提供了一些方案，来帮助我们诊断堆破坏。一般来说，堆破坏往往都是写数据越界造成的（yy的第二种情况，如果是第一种情况其实还简单，下个内存断点就好），所以微软在堆分配上，给程序员门额外提供了2种堆分配模式--完全页堆（full page heap），准页堆(normal page heap)，用来检测堆被写越界的情况。</font></p> <h1></h1> <h1>完全页堆（full page heap）</h1> <h2>检测原理</h2> <p><font size="2">完全页堆的检测基本思路是通过分配相邻的一个页，并将其设为不可访问属性，然后用户数据块会被分配到内存页的最末端，从而实现越界访问的检测。当我们对堆中分配的内存读写越界后便会访问到那个不可读的页，系统捕获到改次异常后会试图中断执行并将该异常上报给debugger，或者崩溃。具体的内存组织结构如下图</font></p> <p><a href="http://www.cppblog.com/images/cppblog_com/ay19880703/Windows-Live-Writer/918bb68ffcd3_1405A/image_15.png"><font size="2"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/ay19880703/Windows-Live-Writer/918bb68ffcd3_1405A/image_thumb_5.png" width="468" height="184"></font></a></p> <p><font size="2">摘自《软件调试》</font></p> <p><font size="2">&nbsp;</font></p> <p><font size="2">与普通堆不同的是，内存块前面的HEAP_ENTRY结构被DPH_BLOCK_INFORMATION结构取代，这个结构内部记录了页堆模式下这个内存块的一些基本信息。如果用户数据区前面的数据，也就是DPH_BLOCK_INFORMATION结构被破坏了，那么在释放内存块的时候系统会报错，如果编程者对这块内存块读写越界了，当然，这里越界有几种情况：</font></p> <ol> <li><font size="2">读越界，但只是访问了块尾填充部分数据，那么系统不会报错 </font> <li><font size="2">写越界，但只篡改了图中块尾填充的部分，那么在堆块释放的时候会报错 </font> <li><font size="2">读越界，且超过了块尾填充的部分，访问到了栅栏页，那么系统会立即抛出一个异常并中断执行 </font> <li><font size="2">写越界，且超过了块尾填充部分，写到了栅栏页，那么系统会立即抛出一个异常并中断执行</font></li></ol> <p><font size="3"><font size="2">这里需要注意的还是<font color="#ff0000">块尾填充不一定存在</font>，块尾填充是因为要满足堆内存的最小分配粒度，如果本身内存块的分配粒度就已经是最小分配粒度的倍数了，那么块尾填充就不存在了，比如堆内存分配粒度是是8 bytes，那么如果申请了14 bytes的话会有2 bytes的大徐小的块尾填充块，如果申请了24bytes，那么就没有块尾填充了，因为24正好是8的倍数</font>。</font></p> <p>&nbsp;</p> <h2>示例</h2> <p><font size="2">开启全页堆（用windbg目录下的gflags或者装一个appverifier都可以开启），通过自己写的一个heap.exe来看一下如何使用全页堆检测堆破坏情况heap.exe代码如下：</font></p> <div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:e8007c4f-f636-4682-8278-4037e4c00777" class="wlWriterSmartContent"><pre class="c" name="code">#include "windows.h"

int main()
{
	HANDLE heap_handle = HeapCreate( NULL , 1024 , 0 ) ;
	char *temp = NULL ;

	char *buffer = (char*)HeapAlloc(heap_handle , NULL , 128) ;
	char *buffer1 = (char*)HeapAlloc(heap_handle , NULL , 121) ;
	temp = buffer ;

	for( int i = 0 ; i &lt; 138 ; ++i )
	{
			*(temp++) = 'a' ;
	}

	HeapFree(heap_handle, 0 , buffer ) ;
	HeapFree(heap_handle, 0 , buffer1 ) ;
	HeapDestroy( heap_handle) ;
	return 0 ;
}</pre></div>
<p><font size="2">在第14行向buffer写入138字节，这显然越界了，然后在用windbg启动heap.exe，直接运行，会发现报错如下</font></p>
<p><font size="2">0:000&gt; g<br>(1f50.1f54): Access violation - code c0000005 (first chance)<br>First chance exceptions are reported before any exception handling.<br>This exception may be expected and handled.<br>eax=00000080 ebx=00000000 ecx=02596000 edx=02596000 esi=00000001 edi=00193374<br>eip=00191068 esp=0016fdc8 ebp=0016fddc iopl=0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nv up ei ng nz ac pe cy<br>cs=001b&nbsp; ss=0023&nbsp; ds=0023&nbsp; es=0023&nbsp; fs=003b&nbsp; gs=0000&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; efl=00010297<br>heap!main+0x68:<br>00191068 c60161&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; byte ptr [ecx],61h&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ds:0023:02596000=??<br></font></p>
<p><font size="2">报了一个内存访问错误，然后看一下调用堆栈</font></p>
<p><font size="2">0:000&gt; kb<br>ChildEBP RetAddr&nbsp; Args to Child&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>0016fddc 0019120f 00000001 023fbfd0 0239df48 heap!main+0x68 [d:\projects\heap\<font color="#ff0000">main.cpp @ 14</font>]<br>0016fe20 765b1114 7ffd3000 0016fe6c 778eb429 heap!__tmainCRTStartup+0x10f [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 582]<br>0016fe2c 778eb429 7ffd3000 757369d8 00000000 kernel32!BaseThreadInitThunk+0xe<br>0016fe6c 778eb3fc 00191357 7ffd3000 00000000 ntdll!__RtlUserThreadStart+0x70<br>0016fe84 00000000 00191357 7ffd3000 00000000 ntdll!_RtlUserThreadStart+0x1b</font></p>
<p><font size="2">可以看到是第14行报的错，但是14行的代码运行了那么多次，我们再看一下这个时候变量i的值是多少</font></p>
<p><font size="2">0:000&gt; dv i<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i = 0n128</font></p>
<p><font size="2">显然，在填充第128字节的时候，我们的temp指针访问到了栅栏页，从而报出了一个内存违规的异常。</font></p>
<p><font size="2">这里顺带看一下如果我们分配的内存不是8 bytes的情况（一般堆内存分配粒度是8 bytes，所以申请128 bytes的内存时是不会有块尾填充部分的）</font></p>
<p><font size="2">那我们接下来看另外一段代码</font></p>
<p><font color="#ff0000" size="2">我们把第10行的temp = buffer改成temp = buffer1</font></p>
<p><font color="#000000" size="2">因为buffer1申请了121 bytes，也就是说它有7 bytes的填充字节</font></p>
<p><font size="2">0:000&gt; g<br>(1ba0.1ba4): Access violation - code c0000005 (first chance)<br>First chance exceptions are reported before any exception handling.<br>This exception may be expected and handled.<br>eax=00000080 ebx=00000000 ecx=024c8000 edx=024c8000 esi=00000001 edi=00033374<br>eip=00031068 esp=002cfb80 ebp=002cfb94 iopl=0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nv up ei ng nz ac pe cy<br>cs=001b&nbsp; ss=0023&nbsp; ds=0023&nbsp; es=0023&nbsp; fs=003b&nbsp; gs=0000&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; efl=00010297<br>heap!main+0x68:<br>00031068 c60161&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp; byte ptr [ecx],61h&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ds:0023:024c8000=??<br>0:000&gt; dv i<br><font color="#ff0000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i = 0n128</font></font></p>
<p><font size="2">可以看到变量i还是128，也就是说我们还是在访问到第128字节后才引发访问异常，而不是我们期望的121字节后就引发异常。</font></p>
<p><font size="2">这里也就是说<font color="#ff0000">如果我们的代码中对申请的堆内存写越界了，写数据覆盖块尾填充部分的时候并不会引发异常！</font></font></p>
<p><font size="2">但是，这并不代表我们的写越界问题不会被发现。块尾填充部分是会被填充上固定数据的，系统在适合的时机（比如销毁堆的时候）会校验块尾填充块，如果发现块尾填充块数据有变，那么便会报一个verifier异常，比如我们把代码中的for循环次数改为124</font></p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:14cc24bb-3177-401d-b544-0ea75212c964" class="wlWriterSmartContent"><pre class="c" name="code">    for( int i = 0 ; i &lt; 124 ; ++i )</pre></div>
<p><font size="2">那么windbg会中断在第19行</font></p>
<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:27622578-bab1-454b-bf14-dc22531576c3" class="wlWriterSmartContent"><pre class="c" name="code">    HeapDestroy( heap_handle) ;</pre></div>
<p><font size="2">提示内容如下<br>=======================================<br><font color="#0000ff">VERIFIER STOP 0000000F</font>: pid 0x1E3C: Corrupted suffix pattern for heap block. </font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; <font color="#ff0000">025A1000</font> : Heap handle used in the call.<br>&nbsp;&nbsp;&nbsp; <font color="#ff0000">025A7F80</font> : Heap block involved in the operation.<br>&nbsp;&nbsp;&nbsp; 00000079 : Size of the heap block.<br>&nbsp;&nbsp;&nbsp; <font color="#ff0000">025A7FF9</font> : Corruption address.</font></p>
<p><br><font size="2">=======================================<br>This verifier stop is not continuable. Process will be terminated <br>when you use the `go' debugger command.</font></p>
<p><font size="2">=======================================</font></p>
<p><font size="2">(1e3c.143c): Break instruction exception - code 80000003 (first chance)<br>eax=6c75e994 ebx=6c75cf58 ecx=00000002 edx=002bf461 esi=00000000 edi=000001ff<br>eip=6c753c38 esp=002bf6b4 ebp=002bf8b8 iopl=0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nv up ei pl nz na po nc<br>cs=001b&nbsp; ss=0023&nbsp; ds=0023&nbsp; es=0023&nbsp; fs=003b&nbsp; gs=0000&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; efl=00000202<br>vrfcore!VerifierStopMessageEx+0x543:<br>6c753c38 cc&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp; 3</font></p>
<p><font size="2">提示说的很清楚了，appverifier指出了堆和具体的内存块，我们这个时候查看buffer1的值是<font color="#ff0000">0x025a7f80</font> ，正好就是出问题的堆块，出问题的地址是0x025a7ff79，正好就是buffer1内存块的边界，错误原因是Corrupted suffix pattern for heap block，也就是说咱块尾填充部分（suffix pattern for heap block）被破坏（corrupted）了</font></p>
<p><font size="2">结论：只要写越界，系统都能够检测出来，只不过如果写越界写到了栅栏页会理解触发异常中断，而写越界只写了块尾填充部分，那么系统在适当时机（比如堆被销毁，或者这块内存被重新分配等时机）会对块尾填充部分做完整性检测，如果发现被破坏了，就会报错。当然，你可以根据错误号（<font color="#0000ff">蓝色字体部分</font>）信息去appverifier的帮助文档中查找更详细的错误说明。</font></p>
<h2>结构详解</h2>
<p><font size="2">这次咱来倒叙，先从最基本的内存堆块结构DPH_BLOCK_INFORMATION开始介绍，DPH_BLOCK_INFORMATION结构微软也有对应文档介绍</font></p>
<p><font size="2"><img alt="ms220938.Local_-1265171613_fphbs(en-US,VS.80).gif" src="http://i.msdn.microsoft.com/dynimg/IC171455.gif" width="505" height="190"></font></p>
<h2><font size="2">（摘自MSDN）</font></h2>
<p><font size="2"></font>&nbsp;</p>
<p><font size="2">其中prefix start magic和prefix end magic是校验块，用来检测DPH_BLOCK_INFORMATION是否被破坏，这些检测部分属于DPH_BLOCK_INFORMATION结构。我们先来用windbg探究下DPH_BLOCK_INFORMATION这个最基本的结构.再一次,我们打开windbg调试heap.exe.运行到第10行,这个时候变量的值是</font></p>
<p><font size="2">0:000&gt; dv heap_handle<br>&nbsp;&nbsp;&nbsp; heap_handle = <font color="#ff0000">0x024a0000</font><br>0:000&gt; dv buffer<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; buffer = <font color="#ff0000">0x024a5f80</font> "???"<br>0:000&gt; dv buffer1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; buffer1 = <font color="#ff0000">0x024a7f80</font> "???"</font></p>
<p><font size="2">这里可以看到一个很有趣的现象,buffer1和buffer的地址正好相差8K,也就是两个页的大小.这当然是因为页堆的原因啦,其实这两块内存分配是相邻着的,虚拟内存结构如下图所示</font></p>
<table border="1" cellspacing="0" cellpadding="2" width="801">
<tbody>
<tr>
<td valign="top" width="200"><font size="2">buffer内存块（4K）</font></td>
<td valign="top" width="200"><font size="2">栅栏页（4K）</font></td>
<td valign="top" width="200"><font size="2">buffer1内存块(4K)</font></td>
<td valign="top" width="199"><font size="2">栅栏页(4K)</font></td></tr></tbody></table>
<p><font size="2"></font>&nbsp;</p>
<p><font size="2">由于buffer和buffer1分配的大小是一样的（buffer1加上尾部填充块和buffer的大小相同），所以这两块内存正好相差8K</font></p>
<p><font size="2">而DPH_BLOCK_INFORMATION就在我们申请的内存块指针的前0x20字节处，用dt命令看的结果如下:</font></p>
<p><font size="2">0:000&gt; dt _DPH_BLOCK_INFORMATION 0x024a5f80-0x20<br>verifier!_DPH_BLOCK_INFORMATION<br>&nbsp;&nbsp; +0x000 StartStamp&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 0xabcdbbbb<br>&nbsp;&nbsp; +0x004 Heap&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 0x024a1000 Void<br>&nbsp;&nbsp; +0x008 RequestedSize&nbsp;&nbsp;&nbsp; : 0x80<br>&nbsp;&nbsp; +0x00c ActualSize&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 0x1000<br>&nbsp;&nbsp; +0x010 Internal&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : _DPH_BLOCK_INTERNAL_INFORMATION<br>&nbsp;&nbsp; +0x018 StackTrace&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 0x003d9854 Void<br>&nbsp;&nbsp; +0x01c EndStamp&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 0xdcbabbbb</font></p>
<p><font size="2"></font>&nbsp;</p>
<p><font size="2">0x024a5f80-0x20就是DPH_BLOCK_INFORMATION结构的地址。DPH_BLOCK_INFORMATION结构在已分配和已释放的状态下，StartStamp和EndStamp（也就是MSDN图中的prefix start magic和prefix end magic）是不同的，显然dt输出的结果看来，这个内存块是已分配状态。StackTrace记录了分配这个内存块时的调用栈，可以用dds来看一下这个内存块被分配时候的调用栈</font></p>
<p><font size="2">0:000&gt; dds 0x003d9854 <br>003d9854&nbsp; 00000000<br>003d9858&nbsp; 00004001<br>003d985c&nbsp; 00090000<br>003d9860&nbsp; 5b3b8e89 verifier!AVrfDebugPageHeapAllocate+0x229<br>003d9864&nbsp; 776d5c4e ntdll!RtlDebugAllocateHeap+0x30<br>003d9868&nbsp; 77697e5e ntdll!RtlpAllocateHeap+0xc4<br>003d986c&nbsp; 776634df ntdll!RtlAllocateHeap+0x23a<br>003d9870&nbsp; 003b1030 heap!main+0x30 [d:\projects\heap\main.cpp @ 8]<br>003d9874&nbsp; 003b120c heap!__tmainCRTStartup+0x10f [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 582]<br>003d9878&nbsp; 76451114 kernel32!BaseThreadInitThunk+0xe<br>003d987c&nbsp; 7766b429 ntdll!__RtlUserThreadStart+0x70<br>003d9880&nbsp; 7766b3fc ntdll!_RtlUserThreadStart+0x1b</font></p>
<p><font size="2">输出结果我们可以看到这个内存块是在main.cpp,也就是我们的示例代码的第8行分配的，第8行是char *buffer = (char*)HeapAlloc(heap_handle , NULL , 128) 正好就是分配buffer内存的那条语句。这个结构的其它字段，顾名思义，ActualSize指明了实际分配字节数，0x1000 bytes也就是4K大小，Internal这个字段保存了个内部结构，用windbg也看不出这个结构信息。</font></p>
<p><font size="2">当然为了防止内存块前面的数据被冲刷掉，除了DPH_BLOCK_INFORMATION外，系统还通过DPH_HEAP_BLOCK保存了所分配内存块的信息，</font></p>
<p><font size="2">通过!heap –p –h [address] 可以查看到页堆的信息</font></p>
<p><font size="2">0:000&gt; !heap -p -h <font color="#ff0000">0x024a0000&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //heap_handle的值</font><br>&nbsp;&nbsp;&nbsp; _DPH_HEAP_ROOT @ 24a1000<br>&nbsp;&nbsp;&nbsp; Freed and decommitted blocks<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DPH_HEAP_BLOCK : VirtAddr VirtSize<br>&nbsp;&nbsp;&nbsp; Busy allocations<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DPH_HEAP_BLOCK : UserAddr&nbsp; UserSize - VirtAddr VirtSize<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#0000ff">024a1f6c</font> : 024a5f80 00000080 - 024a5000 00002000<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 024a1f38 : 024a7f80 00000079 - 024a7000 00002000<br></font></p>
<p><br><font size="2">可以看到，buffer内存块对应的DPH_HEAP_BLOCK结构地址是<font color="#0000ff">024a1f6c</font></font></p>
<p><font size="2">0:000&gt; dt _DPH_HEAP_BLOCK 024a1f6c<br>verifier!_DPH_HEAP_BLOCK<br>&nbsp;&nbsp; +0x000 NextFullPageHeapDelayedNode : 0x024a1020 _DPH_HEAP_BLOCK<br>&nbsp;&nbsp; +0x004 DelayQueueEntry&nbsp; : _DPH_DELAY_FREE_QUEUE_ENTRY<br>&nbsp;&nbsp; +0x000 LookasideEntry&nbsp;&nbsp; : _LIST_ENTRY [ 0x24a1020 - 0x0 ]<br>&nbsp;&nbsp; +0x000 UnusedListEntry&nbsp; : _LIST_ENTRY [ 0x24a1020 - 0x0 ]<br>&nbsp;&nbsp; +0x000 VirtualListEntry : _LIST_ENTRY [ 0x24a1020 - 0x0 ]<br>&nbsp;&nbsp; +0x000 FreeListEntry&nbsp;&nbsp;&nbsp; : _LIST_ENTRY [ 0x24a1020 - 0x0 ]<br>&nbsp;&nbsp; +0x000 TableLinks&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : _RTL_BALANCED_LINKS<br>&nbsp;&nbsp; +0x010 pUserAllocation&nbsp; : 0x024a5f80&nbsp; "???"<br>&nbsp;&nbsp; +0x014 pVirtualBlock&nbsp;&nbsp;&nbsp; : 0x024a5000&nbsp; "???"<br>&nbsp;&nbsp; +0x018 nVirtualBlockSize : 0x2000<br>&nbsp;&nbsp; +0x01c Flags&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : _DPH_HEAP_BLOCK_FLAGS<br>&nbsp;&nbsp; +0x020 nUserRequestedSize : 0x80<br>&nbsp;&nbsp; +0x024 AdjacencyEntry&nbsp;&nbsp; : _LIST_ENTRY [ 0x24a1f5c - 0x24a1fc4 ]<br>&nbsp;&nbsp; +0x02c ThreadId&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 0x3f4<br>&nbsp;&nbsp; +0x030 StackTrace&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 0x003d9854 Void</font></p>
<p><font size="2">从dt的数据看来，这个结构大小为0x34，buffer和buffer1的DPH_HEAP_BLOCK结构首地址正好也是相差0x34，说明这两个结构是紧挨着的，下一步在让我们来看看DPH_HEAP_BLOCK结构是如何组织的。</font></p>
<p><a href="http://www.cppblog.com/images/cppblog_com/ay19880703/Windows-Live-Writer/918bb68ffcd3_1405A/image_3.png"><font size="2"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/ay19880703/Windows-Live-Writer/918bb68ffcd3_1405A/image_thumb.png" width="555" height="218"></font></a></p>
<p><font size="2">摘自《软件调试》</font></p>
<p><font size="2"></font>&nbsp;</p>
<p><font size="2">这个是整个的页堆结构图，我们先来说说DPH_HEAP_BLOCK的组织吧，在图中0x16d00000是页堆的首地址，也就是页堆的句柄，我们调试器中，页堆首地址则是<font color="#ff0000">0x024a0000</font>，为了数据统一，我还是拿0x024a0000作为堆句柄来讲解。我们的DPH_HEAP_BLOCK其实就在堆块节点池里边，我们可以近似把这个节点池看成一个大型的DPH_HEAP_BLOCK数组，但有个地方在软件调试中没有提到，就是在win7下，运行时这些DPH_HEAP_BLOCK结构都是以二叉平衡数的结构来组织的，这个树的结构的入口正是在TableLinks字段内，这么做的原因也大概是因为能够在分配时更快的索。我们再看看DPH_HEAP_ROOT结构，这个结构储存了整个页堆的必要信息，它就相当于普通堆的_HEAP结构。</font></p>
<p><font size="2">0:000&gt; dt _dph_heap_root 24a1000<br>verifier!_DPH_HEAP_ROOT<br>&nbsp;&nbsp; +0x000 Signature&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 0xffeeddcc<br>&nbsp;&nbsp; +0x004 HeapFlags&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 0x1002<br>&nbsp;&nbsp; +0x008 HeapCritSect&nbsp;&nbsp;&nbsp;&nbsp; : 0x024a16cc _RTL_CRITICAL_SECTION<br>&nbsp;&nbsp; +0x00c NodesCount&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 0x2c<br>&nbsp;&nbsp; +0x010 VirtualStorageList : _LIST_ENTRY [ 0x24a1fa0 - 0x24a1fa0 ]<br>&nbsp;&nbsp; +0x018 VirtualStorageCount : 1<br>&nbsp;&nbsp; +0x01c PoolReservedLimit : 0x024a5000 Void<br>&nbsp;&nbsp; +0x020 <font color="#0000ff">BusyNodesTable</font>&nbsp;&nbsp; : _RTL_AVL_TABLE<br>&nbsp;&nbsp; +0x058 NodeToAllocate&nbsp;&nbsp; : (null) <br>&nbsp;&nbsp; +0x05c nBusyAllocations : 2<br>&nbsp;&nbsp; +0x060 nBusyAllocationBytesCommitted : 0x4000<br>&nbsp;&nbsp; +0x064 pFreeAllocationListHead : (null) <br>&nbsp;&nbsp; +0x068 FullPageHeapDelayedListTail : (null) <br>&nbsp;&nbsp; +0x06c DelayFreeQueueHead : (null) <br>&nbsp;&nbsp; +0x070 DelayFreeQueueTail : (null) <br>&nbsp;&nbsp; +0x074 DelayFreeCount&nbsp;&nbsp; : 0<br>&nbsp;&nbsp; +0x078 LookasideList&nbsp;&nbsp;&nbsp; : _LIST_ENTRY [ 0x24a1078 - 0x24a1078 ]<br>&nbsp;&nbsp; +0x080 LookasideCount&nbsp;&nbsp; : 0<br>&nbsp;&nbsp; +0x084 UnusedNodeList&nbsp;&nbsp; : _LIST_ENTRY [ 0x24a1ed0 - 0x24a16e4 ]<br>&nbsp;&nbsp; +0x08c UnusedNodeCount&nbsp; : 0x28<br>&nbsp;&nbsp; +0x090 nBusyAllocationBytesAccessible : 0x2000<br>&nbsp;&nbsp; +0x094 GeneralizedFreeList : _LIST_ENTRY [ 0x24a1f04 - 0x24a1f04 ]<br>&nbsp;&nbsp; +0x09c FreeCount&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 1<br>&nbsp;&nbsp; +0x0a0 PoolCommitLimit&nbsp; : 0x024a2000 Void<br>&nbsp;&nbsp; +0x0a4 NextHeap&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : _LIST_ENTRY [ 0x5b3e9a58 - 0x23a10a4 ]<br>&nbsp;&nbsp; +0x0ac ExtraFlags&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 3<br>&nbsp;&nbsp; +0x0b0 Seed&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 0xfed6f13a<br>&nbsp;&nbsp; +0x0b4 NormalHeap&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 0x027d0000 Void<br>&nbsp;&nbsp; +0x0b8 CreateStackTrace : 0x003d9824 _RTL_TRACE_BLOCK<br>&nbsp;&nbsp; +0x0bc ThreadInHeap&nbsp;&nbsp;&nbsp;&nbsp; : (null) <br>&nbsp;&nbsp; +0x0c0 BusyListHead&nbsp;&nbsp;&nbsp;&nbsp; : _LIST_ENTRY [ 0x24a10c0 - 0x24a10c0 ]<br>&nbsp;&nbsp; +0x0c8 SpecializedFreeList : [64] _LIST_ENTRY [ 0x24a10c8 - 0x24a10c8 ]<br>&nbsp;&nbsp; +0x2c8 DelayFreeListLookup : [257] (null) <br>&nbsp;&nbsp; +0x6cc HeapCritSectionStorage : _RTL_CRITICAL_SECTION<br></p></font>
<p><font size="2">这里边维护了很多运行时信息，比如说DPH_BLOCK_INFORMATION中的那个二叉树入口其实就是保存在<font color="#0000ff">BusyNodesTable </font>字段，这里面记录了所有被分配了的内存块所对应的DPH_BLOCK_INFORMATION。当然，这里面一些信息软件调试里面都有介绍，很多看名字也能够猜到大概意思，看名字猜不到啥意思的字段，其实我也猜不到。。。-_-|||在创建页堆后，所有内存分配都分配在页堆中，通过分配的地址也能看得出来（我们分配的内存都是024a打头），而非普通页堆中，普通页堆也仅仅只是保存一些系统内部使用的数据。一般来说，堆块节点池加上DPH_HEAP_ROOT结构大小正好是4个内存页，也就是16K。</font></p>
<h2>优缺点</h2>
<h3>缺点：消耗大量虚拟内存，每块内存的分配粒度是2个页（8K），</h3>
<h3>优点：能够立即捕获越界读写操作，通过调用栈就可以追溯到问题源头。能够快速定位问题代码。</h3>
<h3>使用建议：32位下不适宜跑配置文件结构比较复杂的软件，让我们来假设一个xml配置文件下有3000个节点，每个节点有5个字符串描述属性，如果把这些配置文件信息转化为stl结构来保存，那么每个节点则需要为此分配5*8K的空间，3000项配置则需要3000*5*8K=117MB虚拟内存，如果每个节点信息再多一些呢？这样会导致虚拟内存耗尽从而出现一系列内存问题（比如，new失败）。当然64位就不存在这种问题了7T的虚拟内存空间，现在看来应该是够用了。</h3>
<p>&nbsp;</p>
<p><font color="#ff0000">对于调试堆破坏来说，其实我们只要了解DPH_BLOCK_INFORMATION结构和DPH_HEAP_BLOCK中的基本字段就差不多了，这样更方便我们定位出错源头。比如在appverifier报错后（或者你程序自己莫名其妙崩溃或者数据被篡改后，要知道appverifier并不总是可信的），我们可以自己手动调试出错的堆块结构（DPH_BLOCK_INFORMATION，DPH_HEAP_BLOCK和DPH_HEAP_ROOT），检测以下这些点：</font></p>
<ol>
<li><font color="#ff0000">检测堆块管理结构的校验字段是否完整</font> 
<li><font color="#ff0000">是否块尾填充部分有被修改过</font> 
<li><font color="#ff0000">检测到未释放或者重复释放堆资源时，查看问题的堆块被分配时的调用栈</font></li></ol>
<p><font color="#ff0000">其实页堆还好，它有较强的实时性，所以并不需要太多手工调试的操作，越界读写都会立即触发异常并且中断，所以从这点看来，它是一些软件用来检测堆资源是否正确使用的必备良药~ 但是相对于页堆，准页堆的调试则需要更好的去了解准页堆工作原理了，因为它提供的堆块检测不是实时的，所以发现问题后，需要咱“精湛的调试内功“去找出源头，关于准页堆的东西，下回再说吧，敬请期待~</font></p><img src ="http://www.cppblog.com/ay19880703/aggbug/163604.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ay19880703/" target="_blank">__ay</a> 2012-01-05 09:17 <a href="http://www.cppblog.com/ay19880703/archive/2012/01/05/163604.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>工作这半年</title><link>http://www.cppblog.com/ay19880703/archive/2012/01/03/163490.html</link><dc:creator>__ay</dc:creator><author>__ay</author><pubDate>Tue, 03 Jan 2012 08:32:00 GMT</pubDate><guid>http://www.cppblog.com/ay19880703/archive/2012/01/03/163490.html</guid><wfw:comment>http://www.cppblog.com/ay19880703/comments/163490.html</wfw:comment><comments>http://www.cppblog.com/ay19880703/archive/2012/01/03/163490.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ay19880703/comments/commentRss/163490.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ay19880703/services/trackbacks/163490.html</trackback:ping><description><![CDATA[<p>遥想上一次这么清静下来思考还是在去年7月初了，当时还是算迷茫，比较泄气的是没能去希望的那个小组，但后面看来并不算坏事，起码现在这个小组在我看来是很理想的一个状态，凝聚力，上进，分享，有种当初凝聚的感觉，一个房间，一个团队，天天嬉皮笑脸，却不忘一起追寻自己的梦想。大半年工作下来，也是该回顾下自己半年的工作</p> <p>&nbsp;</p> <ul> <li>量化代码的作用</li></ul> <blockquote> <p>和大学时不一样，我的代码并不会马马虎虎赶完去参加比赛后就弃之不理了，这并不是一次性代码，它需要维护，更需要体现其价值。优化完了一个功能，甚至不能说出它比以前好在哪里，也没有数据可量化，更要命的是我就这么让这项目过去了，花了1个月做出来的东西，并没有任何人知道，也没任何人去关注，没有任何测试数据来说明我让这个功能有了很大程度上的进展。就像以前对凝聚的娃儿们说的，写出来的软件没人用，不管用到的技术再精湛，也是堆废代码。切记切记，不论如何，要量化自己的工作，有数据的对比别人才知道是进步。</p></blockquote> <ul> <li>沟通好重要</li></ul> <blockquote> <p>从来到公司到现在，终于知道什么叫超出期望了。as一个合格程序员，你得按时完成老大布置的任务。但事情远远不止这些，老大很多时候只是给你指明方向，并不会告诉你每一步怎么走，这样做的好处能够让你自由发挥，坏处是容易迷茫。这也许是好多新人过来时候的通病，被派指任务后无从下手。这个可是个沟通的活，首先必须要精确了解老大的预期，记住，我这里说的是精确了解，当然老大的预期并不一定完全正确，但是，在你没法辩驳用数据辩驳他不正确的时候，你要做的就是收集资料，然后分析可行性，再然后给老大复述你的计划，在我看来，我其实并不希望我的计划和老大的预想完全一致，更多的讨论才会产生一个完美的方案，如果你仅仅只是按老大说的去做，那么就是那个qualified programmer，一定一定要记住，你最好得有自己的观点，观点必须得碰撞，碰撞中才会反思各放观点的优劣，一个权衡的观点才会出来。其实需求确认这一步可能很多人看来很冗余，但是在我看来很是必需，因为口口相传的需求并不会精确到哪里去，所以需要确认确认再确认，省的做完后又得打翻重做。</p></blockquote> <ul> <li>技术的事，不可糊涂</li></ul> <blockquote> <p>技术的事情，千万别糊涂，你的程序crash了，你得知道为啥，你的new失败了，你也得知道为啥，因为随着程序越来越大，你debug的难度会越来越高，有问题，早解决，而且对所有问题，你必须知其所以然。当然，那些恶心的没有文档而且注释只注释了一半的第三方库除外，用这种库还不如自己重新写一个去，一直认为这种成品的存在就是个鸡肋。编程里面有一句话叫donot repeat yourself，你得管理好并且总结你的代码，总有一天，你或者别人还能用得到。</p></blockquote> <ul> <li>动态更新优先级</li></ul> <blockquote> <p>这是我最大的一个毛病了吧，其实放在互联网公司这种催进度好比催命般的大环境下，这算是坏习惯，我工作模式一般是串行化且关中断模式，一件事必须得先做完才开始做下一件事情，中间容不得有任何注意力的转移。但是往往中间会有更加重要的事情插播进来，当然，如果出现这种情况那是项目管理上的悲哀，但事实如此，等待现状改变还不如去适应。清楚的知道优先级，这其实非常重要的，因为很有可能插播进来的任务关联到其他同事，处理不好，会出现项目发布前N个人等你的这种状况，说难听点，这时候你就成了后腿，重点是别人怎么拖也拖不动。</p></blockquote> <ul> <li>锻炼啊锻炼</li></ul> <blockquote> <p>说好的每周一次运动在工作三个月后就烟消云散了，我是那种做事情的时候容易走火入魔的程序员，事情没解决之前脑子里全部都是相关的事情，但往往这样是最没有效率的，反倒去运动运动，待第二天脑袋清晰了反倒就有结果了，老天喃。。。我关中断的思维模式何时能转过来。。。程序员应该算是最最需要锻炼的群体了吧，一天坐10个小时还不带上厕所的，可以遇见好多人在几年后都会脊椎等地方开始出毛病，但这是我们可以遇见得到的啊。。。竟然都知道了，为何不去阻止其发生呢？不为别人，单是为了现在或者未来的家人，好好爱护自己的身体比任何事情都重要，期待有一个人，，，，能在我走火入魔之际把我拉出来去打一场球，以前有小妖，有番薯，有葡萄哥，而现在呢？没有你们的城市里我也要健康的活着，不为别的，只为留着口气把赚来的钱好好的花出去&nbsp; -_-#，指不定哪天我暴毙了银行里还有好几百万，这丫的不亏大了。</p></blockquote> <p>暂时就这么多吧，希望这些能在我今后半年有所改善，也许在我看来，技术已经不再是全部，做事情的方式才是我今后想各前辈学习的地方。</p><img src ="http://www.cppblog.com/ay19880703/aggbug/163490.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ay19880703/" target="_blank">__ay</a> 2012-01-03 16:32 <a href="http://www.cppblog.com/ay19880703/archive/2012/01/03/163490.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>谁动了我的指针?--记一次windbg内存断点的使用</title><link>http://www.cppblog.com/ay19880703/archive/2012/01/03/163486.html</link><dc:creator>__ay</dc:creator><author>__ay</author><pubDate>Tue, 03 Jan 2012 07:07:00 GMT</pubDate><guid>http://www.cppblog.com/ay19880703/archive/2012/01/03/163486.html</guid><wfw:comment>http://www.cppblog.com/ay19880703/comments/163486.html</wfw:comment><comments>http://www.cppblog.com/ay19880703/archive/2012/01/03/163486.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/ay19880703/comments/commentRss/163486.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ay19880703/services/trackbacks/163486.html</trackback:ping><description><![CDATA[<p>&nbsp;</p> <p>写驱动的时候有个地方老是蓝屏,看了dump发现数据被非法篡改了.</p> <p>数据初始化如下</p> <p>&nbsp;</p> <p>if(record_set_ptr != NULL )<br>{<br>&nbsp;&nbsp;&nbsp; record_set_ptr-&gt;look_aside_pool_ptr = g_user_control_context.look_aside_pools[type] ;<br>&nbsp;&nbsp;&nbsp; record_set_ptr-&gt;type = type ;<br>&nbsp;&nbsp;&nbsp; record_set_ptr-&gt;buffer_size = notify_count * unit_size_of ;<br>&nbsp;&nbsp;&nbsp; record_set_ptr-&gt;units_count = notify_count ;<br>&nbsp;&nbsp;&nbsp; record_set_ptr-&gt;complete_count = 0 ;<br>}<br></p> <p>然后在调用ExFreeToNPagedLookasideList传入record_set_ptr-&gt;look_aside_pool_ptr 的时候挂了,发现record_set_ptr-&gt;look_aside_pool_ptr已经被改了.</p> <p>&nbsp;</p> <p>为了跟踪数据在哪里被修改了,先在数据初始化的地方下断,然后记下record_set_ptr-&gt;look_aside_pool_ptr 的地址:0x85c16018</p> <p>对这个内存下个断点 </p> <p>1: kd&gt; ba w4 85c16018</p> <p>w表示在写入时断下,4表示监控范围,单位是字节&nbsp; </p> <p>整个命令的意思就是让调试器在系统写入内存85c16018-85c1601b这个地址范围的时候中断 </p> <p>OK,命令下完,F5一下就立马断下来了</p> <p>1: kd&gt; g<br>Breakpoint 3 hit<br>nt!memcpy+0x33:<br>8053b583 f3a5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rep movs dword ptr es:[edi],dword ptr [esi]</p> <p><br>此时edi的值: <font color="#ff0000">0x85c16018</font></p> <p><font color="#000000"></font>&nbsp;</p> <p><font color="#000000">最后看一下函数堆栈,发现是字符串拷贝越界覆盖了后面的数据....&nbsp; </font></p> <p>后面又想到,出错时record_set_ptr-&gt;look_aside_pool_ptr 的值是<font color="#ff0000">0x005c0065</font></p> <p><font color="#000000">这么明显的字符串特征竟然没意识到....一看出错值就应该知道是字符串覆盖造成的.....</font></p><img src ="http://www.cppblog.com/ay19880703/aggbug/163486.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ay19880703/" target="_blank">__ay</a> 2012-01-03 15:07 <a href="http://www.cppblog.com/ay19880703/archive/2012/01/03/163486.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>WINDBG的堆调试--了解HEAP组织</title><link>http://www.cppblog.com/ay19880703/archive/2011/10/30/159364.html</link><dc:creator>__ay</dc:creator><author>__ay</author><pubDate>Sun, 30 Oct 2011 11:05:00 GMT</pubDate><guid>http://www.cppblog.com/ay19880703/archive/2011/10/30/159364.html</guid><wfw:comment>http://www.cppblog.com/ay19880703/comments/159364.html</wfw:comment><comments>http://www.cppblog.com/ay19880703/archive/2011/10/30/159364.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ay19880703/comments/commentRss/159364.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ay19880703/services/trackbacks/159364.html</trackback:ping><description><![CDATA[
		<p>@作者: ay @文章出处: <a href="http://cnss-ay.com/" target="_blank">cnss-ay的博客</a><font color="#ff0000">@Notice: 转载请注明出处！若文章显示不完整，可以到文章出处阅读。</font></p>
		<h1>HEAP的概念</h1>
		<p>堆栈堆栈，在操作系统内存中有两种存储空间，一个是堆，一个是栈。堆主要用于存储用户动态分配的变量，而栈呢，则是存储我们程序过程中的临时变量。当然栈的作用远不止用作存储变量，但这不是我们这篇文章的讨论内容。</p>
		<p> </p>
		<p>堆（HEAP）的分配，使用，回收都是通过微软的API来管理的，最常见的API是malloc和new。在往底层走一点呢，这两个函数都会调用HeapAlloc（RtlAllocateHeap）。同样的相关函数还有HeapFree用来释放堆，HeapCreate用来创建自己的私有堆。下面是这些函数的调用链：</p>
		<p>HeapCreate-&gt;RtlCreateHeap-&gt;ZwAllocateVirtualMemory  (这里会直接申请一大片内存,至于申请多大内存,由进程PEB结构中的字段觉得，HeapSegmentReserve字段指出要申请多大的虚拟内存，HeapSegmentCommit指明要提交多大内存，对虚拟内存的申请和提交概念不清楚的童鞋，请参见windows核心编程相关内容~)</p>
		<p>HeapAlloc-&gt;RtlAllocateHeap（至于这里申请的内存，由于HeapCreate已经申请了一大片内存，堆管理器这片内存中划分一块出来以满足申请的需要。这一步申请操作是堆管理器自己维护的，<font color="#ff0000">仅当申请内存不够的时候才会再次调用ZwAllocateVirtualMemory</font> ）</p>
		<p>HeapFree-&gt;RtlFreeHeap （对于释放的内存，堆管理器只是简单的把这块内存标志位已释放让后加入到空闲列表中，<font color="#ff0000">仅当空闲的内存达到一定阀值的时候会调用ZwFreeVirtualMeMory</font> ）</p>
		<p>HeapDestroy-&gt;RtlDestroyHeap-&gt;ZwFreeVirtualMeMory   （销毁我们申请的堆）</p>
		<h1>如何找到我们的HEAP信息？</h1>
		<p>WINDBG观察堆</p>
		<p>源码：</p>
		<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:d5c33111-de2a-4819-a5af-d446f98a496a" class="wlWriterEditableSmartContent">
				<pre name="code" class="c:collapse">#include "windows.h"

int main()
{
	HANDLE heap_handle = HeapCreate( NULL , 0x1000 , 0x2000 ) ;

	char *buffer = (char*)HeapAlloc(heap_handle , NULL , 128) ;

	char *buffer1 = (char*)HeapAlloc(heap_handle , NULL , 121) ;

	HeapFree(heap_handle, 0 , buffer ) ;
	HeapFree(heap_handle, 0 , buffer1 ) ;

	HeapDestroy( heap_handle) ;
	return 0 ;
}</pre>
		</div>
		<p>该源码生成编译生成heap.exe，然后用windbg调试这个程序，在main函数下断，紧接着执行第五行语句，执行结果如下</p>
		<p>0:000&gt; p<br />eax=002e1ca0 ebx=00000000 ecx=6d29b6f0 edx=00000000 esi=00000001 edi=01033374<br />eip=01031012 esp=0022fe8c ebp=0022feac iopl=0         nv up ei pl nz na po nc<br />cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202<br />heap!main+0x12:<br />01031012 ff150c200301    call    dword ptr [heap!_imp__HeapCreate (0103200c)] ds:0023:0103200c={kernel32!HeapCreateStub (769a29d7)}<br /></p>
		<p>0:000&gt; p<br />eax=<font color="#ff0000">002c0000</font> ebx=00000000 ecx=77429897 edx=77498500 esi=00000001 edi=01033374<br />eip=01031018 esp=0022fe98 ebp=0022feac iopl=0         nv up ei pl nz na pe nc<br />cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206<br />heap!main+0x18:<br />01031018 8945fc          mov     dword ptr [ebp-4],eax ss:0023:0022fea8=6d222201<br />0:000&gt; !heap <br />Index   Address  Name      Debugging options enabled<br />  1:   00300000                <br />  2:   00010000                <br />  3:   00020000                <br />  4:   002e0000                <br />  5:   <font color="#ff0000">002c0000</font>       </p>
		<p>HeapCreate执行的返回值存放在eax处，这个函数返回了一个堆句柄：0x002c0000。用!heap命令查看可以看到第五个堆就是我们创建的堆句柄了。</p>
		<p>每个进程都存在多个堆，我们也可以通过PEB结构来得到进程中存在的堆，结果和!heap命令显示的内容是一样的。</p>
		<p>heap!_PEB<br />   +0x018 ProcessHeap      : 0x00300000 Void         ; 进程的默认堆<br />   +0x068 NtGlobalFlag     : 0                                       ; 这个标志位记录了当前堆调试模式,0为普通调试模式<br />   +0x078 HeapSegmentReserve : 0x100000          ; 进程在新建堆的时候默认申请的虚拟内存大小<br />   +0x07c HeapSegmentCommit : 0x2000               ; 进程在每次申请提交的虚拟内存大小，在提交的内存用完后，进程会又在一次提交HeapSegmentCommit中指定的内存大小<br />   +0x080 HeapDeCommitTotalFreeThreshold : 0x10000    ; 当释放的内存大小大于这个阀值，就进行内存解除提交操作<br />   +0x084 HeapDeCommitFreeBlockThreshold : 0x1000     ;  当一次性释放的块大小超过这个阀值，就进行内存解除提交操作，<font color="#ff0000">只有当满足这两个条件时才会调用ZwFreeVirtualMeMory 释放物理内存<br /></font>   +0x088 NumberOfHeaps    : 5                                               ; 当前进程的堆数目,这个数目对应着!heap命令的堆显示个数<br />   +0x08c MaximumNumberOfHeaps : 0x10                          ; 进程所能运行的最大堆数目,若堆数目超过这个值估计HeapCreate就失败了吧<br />   +0x090 ProcessHeaps     : 0x77498500  -&gt; 0x00300000 Void ;存储堆句柄的数组,这里我们可以得到进程的所有堆句柄</p>
		<p>我们可以输入如下命令来查看现有的堆句柄</p>
		<p>0:000&gt; dd 0x77498500  <br />77498500  <font color="#ff0000">00300000 00010000 00020000 002e0000</font><br />77498510  <font color="#ff0000">002c0000</font> 00000000 00000000 00000000<br />77498520  00000000 00000000 00000000 00000000<br />77498530  00000000 00000000 00000000 00000000<br />77498540  00000000 77498340 7749bb08 77498220<br />77498550  00000000 00000000 00000000 00000000<br />77498560  77498220 00317bd0 00000000 00000000<br />77498570  00000000 00000000 00000000 00000000<br /></p>
		<p>可以看得到这里面的内容和!heap命令的输出结果是一样的</p>
		<p>而堆句柄的存放范围,从MaximumNumberOfHeaps 上来看,就是77498500-77498540这0x40个字节，因为每个堆句柄占4个字节，0x10个堆句柄的存放空间就是0x40。</p>
		<h1>HEAP的组织结构</h1>
		<p>堆的管理，我们可以理解为一个内存池，它申请一大块空间，然后负责接管应用程序的申请释放等请求。只有在创建堆，释放堆（注意！是释放堆，不是堆中的空间！）在这之前，我们需要对堆有关的数据结构做一些解释</p>
		<p>我这里观察到的HEAP结构，HEAP_SEGMENT结构和HEAP_ENTRY结构都和软件调试里面描述的不一样，当年奎哥写软件调试的时候估计还没用上WIN7吧。。。我的演示系统是WIN7</p>
		<p>HeapCreate函数返回的堆句柄其实就是一个指向堆管理结构的指针，每个堆都会涉及到这样三个结构：HEAP,HEAP_SEGMENT,HEAP_ENTRY</p>
		<p>HEAP_ENTRY结构：</p>
		<p>在堆管理中，每一块申请下来的内存都会有下面所示的固定模式：</p>
		<table border="1" cellpadding="2" cellspacing="0" width="400">
				<tbody>
						<tr>
								<td valign="top" width="400">
										<p align="center">HEAP_ENTRY（8 bytes）</p>
								</td>
						</tr>
						<tr>
								<td valign="top" width="400">
										<p align="center">我们new或malloc分配的空间</p>
								</td>
						</tr>
						<tr>
								<td valign="top" width="400">
										<p align="center">固定填充空间</p>
								</td>
						</tr>
				</tbody>
		</table>
		<p>这个结构用来记录所分配的空间的信息，包括用户申请的空间，填充的空间，所在的段号等等信息。所以我们new或者malloc的地址减去8就指向该结构。第三部分的固定填充空间是为了内存对齐而生成的，当然这部分空间还有一部分是用来额外记录这块内存的其它信息，这里就不详细做介绍了。</p>
		<p>HEAP_SEGMENT结构：</p>
		<p>我们可以这么认为，堆申请内存的大小是以段为单位的，当新建一个堆的时候，系统会默认为这个堆分配一个段叫0号段，通过刚开始的new和malloc分配的空间都是在这个段上分配的，当这个段用完的时候，如果当初创建堆的时候指明了HEAP_GROWABLE这个标志，那么系统会为这个堆在再分配一个段，这个时候新分配的段就称为1号段了，以下以此类推。每个段的开始初便是HEAP_SEGMENT结构的首地址，由于这个结构也是申请的一块内存，所以它前面也会有个HEAP_ENTRY结构：</p>
		<table border="1" cellpadding="2" cellspacing="0" width="400">
				<tbody>
						<tr>
								<td valign="top" width="400">
										<p align="center">HEAP_ENTRY（8 bytes）</p>
								</td>
						</tr>
						<tr>
								<td valign="top" width="400">
										<p align="center">HEAP_SEGMENT</p>
								</td>
						</tr>
						<tr>
								<td valign="top" width="400">
										<p align="center">HEAP_ENTRY（8 bytes）</p>
								</td>
						</tr>
						<tr>
								<td valign="top" width="400">
										<p align="center">我们new或malloc分配的空间</p>
								</td>
						</tr>
						<tr>
								<td valign="top" width="400">
										<p align="center">固定填充空间</p>
								</td>
						</tr>
				</tbody>
		</table>
		<p>HEAP_SEGMENT结构会记录段的一些基本信息，该段申请的大小，已经提交内存的大小，第一个HEAP_ENTRY结构的入口点。（我观察看貌似段申请的内存并不会一次性全部提交，而是每次提交一个页的大小，比如一个段大小2个页，那么它会先提交一个页内存，若用完了再提交一个页的内存，若内存还用完了那就新建一个段，这个新建的段也会是先提交一个页内存。）但是0号段很特别，<font color="#ff0000">这个段的起始地址就是堆句柄指针指向的值，也就是说，</font><font color="#ff0000">HeapCreate返回的堆句柄总是指向0号段，为什么呢？因为HEAP结构是HEAP_ENTRY,HEAP_SEGMENT的合体加长版~</font></p>
		<p>HEAP结构：</p>
		<p>HEAP结构则是记录了这个堆的信息，这个结构可以找到HEAP_SEGMENT链表入口，空闲内存链表的入口，内存分配粒度等等信息。HEAP的首地址便是堆句柄的值，但是堆句柄的值又是0号段的首地址也是堆句柄，何解？其实很简单，0号段的HEAP_SEGMENT就在HEAP结构里面，HEAP结构类定义如这样：</p>
		<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:abcf8a25-3d48-4f22-9494-d5bf7c4da176" class="wlWriterEditableSmartContent">
				<pre name="code" class="c">struct _HEAP

{

_HEAP_ENTRY Entry ; //HEAP_ENTRY结构，用来描述存储HEAP内存块大小等信息的

_HEAP_SEGMENT Segment ;  //0号段的首地址

……  //对于该HEAP的描述信息

} ;
</pre>
		</div>
		<p>在我们看来，内存组织结构应该如下所示：</p>
		<table border="1" cellpadding="2" cellspacing="0" width="402">
				<tbody>
						<tr>
								<td valign="top" width="400">
										<p align="center">HEAP_ENTRY（8 bytes）</p>
								</td>
						</tr>
						<tr>
								<td valign="top" width="400">
										<p align="center">HEAP_SEGMENT</p>
								</td>
						</tr>
						<tr>
								<td valign="top" width="400">
										<p align="center">HEAP</p>
								</td>
						</tr>
				</tbody>
		</table>
		<p>更确切的说，HEAP结构中本身就包含了HEAP_ENTRY和HEAP_SEGMENT，HEAP_ENTRY结构是HEAP的第一个数据成员，HEAP_SEGMENT是它第二个数据成员。而对于HEAP_SEGMENT,它的第一个数据成员便是HEAP_ENTRY。这里为了方便理解，才在内存组织结构中把它们拆开展示。（注：这里是win7的情况，和软件调试这本书中所描述的有一些差异，也属正常现象，毕竟这部分结构微软并未公开）</p>
		<h1>用WINDBG观察HEAP结构</h1>
		<p>在之前已经演示了如何从PEB结构中找到所有的堆句柄，可以看到<font color="#ff0000">002c0000</font>便是我们创建的句柄。然后我们执示例程序的第7行代码。执行完后结果如下：</p>
		<p>0:000&gt; p<br />eax=002c0000 ebx=00000000 ecx=77429897 edx=77498500 esi=00000001 edi=01033374<br />eip=01031026 esp=0022fe8c ebp=0022feac iopl=0         nv up ei pl nz na pe nc<br />cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206<br />heap!main+0x26:<br />01031026 ff1500200301    call    dword ptr [heap!_imp__HeapAlloc (01032000)] ds:0023:01032000={ntdll!RtlAllocateHeap (774120b5)}<br />0:000&gt; p<br />eax=002c0590 ebx=00000000 ecx=774134b4 edx=002c0180 esi=00000001 edi=01033374<br />eip=0103102c esp=0022fe98 ebp=0022feac iopl=0         nv up ei pl zr na pe nc<br />cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246<br />heap!main+0x2c:<br />0103102c 8945f0          mov     dword ptr [ebp-10h],eax ss:0023:0022fe9c={heap!envp (0103301c)}<br /></p>
		<p>可以看到EAX保存的返回值为002c0590。我们通过两种途径来观察我们申请的内存，通过!heap命令观察和通过dt命令观察</p>
		<h2>
		</h2>
		<h2>通过!heap命令观察</h2>
		<h2>
		</h2>
		<h1>
		</h1>
		<h1>
		</h1>
		<p>输入命令!heap –a 2c0590得到的结果如下：</p>
		<p>0:000&gt; !heap -a 2c0000<br /><font style="background-color: #9bbb59" color="#000000">Index   Address  Name      Debugging options enabled<br />  5:   002c0000 <br />    Segment at 002c0000 to 002c2000 (00001000 bytes committed)<br />    Flags:                00001000<br />    ForceFlags:           00000000<br />    Granularity:          8 bytes<br />    Segment Reserve:      00100000<br />    Segment Commit:       00002000<br />    DeCommit Block Thres: 00000200<br />    DeCommit Total Thres: 00002000<br />    Total Free Size:      0000013a<br />    Max. Allocation Size: 7ffdefff<br />    Lock Variable at:     002c0138<br />    Next TagIndex:        0000<br />    Maximum TagIndex:     0000<br />    Tag Entries:          00000000<br />    PsuedoTag Entries:    00000000<br />    Virtual Alloc List:   002c00a0<br />    Uncommitted ranges:   002c0090<br />            002c1000: 00001000  (4096 bytes)<br />    FreeList[ 00 ] at 002c00c4: 002c0618 . 002c0618  <br />        002c0610: 00088 . 009d0 [100] - free</font></p>
		<p>
				<font style="background-color: #c0504d">    Segment00 at 002c0000:<br />        Flags:           00000000<br />        Base:            002c0000<br />        First Entry:     002c0588<br />        Last Entry:      002c2000<br />        Total Pages:     00000002<br />        Total UnCommit:  00000001<br />        Largest UnCommit:00000000<br />        UnCommitted Ranges: (1)</font>
		</p>
		<p>
				<font style="background-color: #cccccc">    Heap entries for Segment00 in Heap 002c0000<br />        002c0000: 00000 . 00588 [101] - busy (587)<br />        <font color="#ff0000">002c0588: 00588 . 00088 [101] - busy (80)</font><br />        002c0610: 00088 . 009d0 [100]<br />        002c0fe0: 009d0 . 00020 [111] - busy (1d)<br />        002c1000:      00001000      - uncommitted bytes.</font>
		</p>
		<p>这个命令分别提炼出了HEAP（绿色区域）,HEAP_SEGMENT（红色区域）和HEAP_ENTRY（灰色区域）结构中的信息。虽然在灰色区域中，我们找不到2c0590，但是找到了一个2c0588，这个正是2c0590-8的结果，也就是说最右边的地址是每个HEAP_ENTRY的首地址，接着00588这个字段表示了前面一个HEAP_ENTRY所占用的大小，后面的0088表示这个内存块的总大小，即我们申请的内存+HEAP_ENTRY（128+8=0x80+0x8=0x88），[101]是这块内存的标志位，最右边一位为1表示该内存块被占用。然后busy（80）就是解释说这块内存是被占用的（非空闲的），它申请的内存为0x80，转化成十进制正好就是我们申请的128字节大小。</p>
		<p>
				<font color="#ff0000">但是这里用dt _HEAP_ENTRY 2c0588命令却没办法查看对应的结构信息，真是怪哉，有篇博文也提到win2008中HEAP相关结构也有变，看来到NT6后，HEAP结构变得不小，起码windbg中直接dt HEAP_ENTRY是无法原始数据的了，貌似对HEAP_ENTRY做了编码。</font>
				<br />
		</p>
		<h2>通过dt命令观察</h2>
		<p>同样的，已知HEAP的首地址，那么先从HEAP下手好了，dt _HEAP 002c0000可以显示HEAP的数据结构</p>
		<p>ntdll!_HEAP<br />   +0x000 Entry            : _HEAP_ENTRY<br /><font style="background-color: #cccccc">   +0x008 SegmentSignature : 0xffeeffee   <br />   +0x00c SegmentFlags     : 0<br />   +0x010 SegmentListEntry : _LIST_ENTRY [ 0x2c00a8 - 0x2c00a8 ]<br />   +0x018 Heap             : 0x002c0000 _HEAP<br />   +0x01c BaseAddress      : 0x002c0000 Void<br />   +0x020 NumberOfPages    : 2<br />   <font color="#ff0000">+0x024 FirstEntry       : 0x002c0588 _HEAP_ENTRY</font><br />   +0x028 LastValidEntry   : 0x002c2000 _HEAP_ENTRY<br />   +0x02c NumberOfUnCommittedPages : 1<br />   +0x030 NumberOfUnCommittedRanges : 1<br />   +0x034 SegmentAllocatorBackTraceIndex : 0<br />   +0x036 Reserved         : 0<br />   +0x038 UCRSegmentList   : _LIST_ENTRY [ 0x2c0ff0 - 0x2c0ff0 ]</font><br />   +0x040 Flags            : 0x1000<br />   +0x044 ForceFlags       : 0<br />   +0x048 CompatibilityFlags : 0<br />   +0x04c EncodeFlagMask   : 0x100000<br />   +0x050 Encoding         : _HEAP_ENTRY<br />   +0x058 PointerKey       : 0x17c06e63<br />   +0x05c Interceptor      : 0<br />   +0x060 VirtualMemoryThreshold : 0xfe00<br />   +0x064 Signature        : 0xeeffeeff<br />   +0x068 SegmentReserve   : 0x100000<br />   +0x06c SegmentCommit    : 0x2000<br />   +0x070 DeCommitFreeBlockThreshold : 0x200<br />   +0x074 DeCommitTotalFreeThreshold : 0x2000<br />   +0x078 TotalFreeSize    : 0x13a<br />   +0x07c MaximumAllocationSize : 0x7ffdefff<br />   +0x080 ProcessHeapsListIndex : 5<br />   +0x082 HeaderValidateLength : 0x138<br />   +0x084 HeaderValidateCopy : (null) <br />   +0x088 NextAvailableTagIndex : 0<br />   +0x08a MaximumTagIndex  : 0<br />   +0x08c TagEntries       : (null) <br />   +0x090 UCRList          : _LIST_ENTRY [ 0x2c0fe8 - 0x2c0fe8 ]<br />   +0x098 AlignRound       : 0xf<br />   +0x09c AlignMask        : 0xfffffff8<br />   +0x0a0 VirtualAllocdBlocks : _LIST_ENTRY [ 0x2c00a0 - 0x2c00a0 ]<br />   +0x0a8 SegmentList      : _LIST_ENTRY [ 0x2c0010 - 0x2c0010 ]<br />   +0x0b0 AllocatorBackTraceIndex : 0<br />   +0x0b4 NonDedicatedListLength : 0<br />   +0x0b8 BlocksIndex      : 0x002c0150 Void<br />   +0x0bc UCRIndex         : (null) <br />   +0x0c0 PseudoTagEntries : (null) <br />   +0x0c4 FreeLists        : _LIST_ENTRY [ 0x2c0618 - 0x2c0618 ]<br />   +0x0cc LockVariable     : 0x002c0138 _HEAP_LOCK<br />   +0x0d0 CommitRoutine    : 0x17c06e63     long  +17c06e63<br />   +0x0d4 FrontEndHeap     : (null) <br />   +0x0d8 FrontHeapLockCount : 0<br />   +0x0da FrontEndHeapType : 0 ''<br />   +0x0dc Counters         : _HEAP_COUNTERS<br />   +0x130 TuningParameters : _HEAP_TUNING_PARAMETERS<br />就如本文前面所述的，第一个字段是HEAP_ENTRY结构，接着应该是HEAP_SEGMENT，这里只不过把HEAP_SEGMENT结构的字段展开了，可以dt _HEAP_SEGMENT来观察下这个结构的字段</p>
		<p>0:000&gt; dt _heap_segment<br />ntdll!_HEAP_SEGMENT<br />   +0x000 Entry            : _HEAP_ENTRY<br />   +0x008 SegmentSignature : Uint4B<br />   +0x00c SegmentFlags     : Uint4B<br />   +0x010 SegmentListEntry : _LIST_ENTRY<br />   +0x018 Heap             : Ptr32 _HEAP<br />   +0x01c BaseAddress      : Ptr32 Void<br />   +0x020 NumberOfPages    : Uint4B<br />   +0x024 FirstEntry       : Ptr32 _HEAP_ENTRY<br />   +0x028 LastValidEntry   : Ptr32 _HEAP_ENTRY<br />   +0x02c NumberOfUnCommittedPages : Uint4B<br />   +0x030 NumberOfUnCommittedRanges : Uint4B<br />   +0x034 SegmentAllocatorBackTraceIndex : Uint2B<br />   +0x036 Reserved         : Uint2B<br />   +0x038 UCRSegmentList   : _LIST_ENTRY</p>
		<p>可以看到HEAP结构中灰色部分是和HEAP_SEGMENT结构中的字段是重复的，也就是说灰色部分字段便是HEAP_SEGMENT结构。在HEAP_SEGMENT结构中，我们可以找到FirstEntry字段，这里指的便是我们的分配的内存，不过HEAP_ENTRY结构无法观察，这里便没办法枚举出所有的HEAP_ENTRY结构了，但是说一下思路：</p>
		<p>每个HEAP_ENTRY和它对应的内存我们可以称为一个内存块，计算下一个内存块需要用到现有内存块中的2个字段，Size和UnsedBytes，Size的值乘上粒度（就是0:000&gt; !heap -a 2c0000命令显示的信息中的Granularity: 8 bytes字段，这里是8字节），下一个内存块地址就是 <font style="background-color: #cccccc">本内存块地址+Size*8+UnsedBytes</font>。当然这里的粒度可以通过HEAP字段中的AlignMask 字段算出来。</p>
		<h1>
		</h1>
		<h1>HEAP的分配粒度</h1>
		<p>在HEAP结构中指明了分配粒度，这个分配粒度是说每次堆分配的时候，都以这个粒度为最小单位，这里看到粒度为8字节。所以这里就有了第二次分配内存的实验，我们让程序执行第9行，然后用!heap -a 002c0000观察分配情况</p>
		<p>Heap entries for Segment00 in Heap 002c0000<br />    002c0000: 00000 . 00588 [101] - busy (587)<br />    002c0588: 00588 . 00088 [101] - busy (80)<br />    <font color="#ff0000">002c0610: 00088 . 00088 [101] - busy (79)</font><br />    002c0698: 00088 . 00948 [100]<br />    002c0fe0: 00948 . 00020 [111] - busy (1d)<br />    002c1000:      00001000      - uncommitted bytes.</p>
		<p>
				<font color="#ff0000">这里可以看出多出了一个占用块，大小是0x79（121） bytes，但是实际分配的大小还是0x 88 （128）bytes，这是因为系统是以8 bytes为粒度分配的，所以为这块121 bytes的内存自动填充了7个字节，可见申请121 bytes和申请128 bytes所使用的空间是一样的。</font>
		</p>
		<h1>HEAP的释放和销毁</h1>
		<p>执行了11行和12行的代码后，堆中的内容分别如下：</p>
		<p>
				<strong>执行11行代码的堆情况</strong>
		</p>
		<p>FreeList[ 00 ] at 002c00c4: 002c06a0 . 002c0590  <br />    002c0588: 00588 . 00088 [100] – free   <font color="#ff0000">；空闲列表中多出了一块内存</font><br />    002c0698: 00088 . 00948 [100] – free   <font color="#ff0000">；空闲内存，空闲空间为948</font></p>Heap entries for Segment00 in Heap 002c0000<br />002c0000: 00000 . 00588 [101] - busy (587)<br />002c0588: 00588 . 00088 [100]   <font color="#ff0000">；原先的这块内存释放掉了</font><br />002c0610: 00088 . 00088 [101] - busy (79)<br />002c0698: 00088 . 00948 [100]    <font color="#ff0000">; 空闲内存</font><br />002c0fe0: 00948 . 00020 [111] - busy (1d)<br />002c1000: 00001000 - uncommitted bytes. 
<p><strong>执行12行代码的堆情况</strong></p><p>FreeList[ 00 ] at 005c00c4: 005c0590 . 005c0590  <br />    005c0588: 00588 . 00a58 [100] – free <font color="#ff0000">；回收了buffer1的内存后，由于由于空闲内存是连续的，所以直接合并成一块内存。可以看到之前内存free空间是948，现在合并了以后便是948+88+88=a58,也就是当前内存大小</font></p><p>Heap entries for Segment00 in Heap 005c0000<br />    005c0000: 00000 . 00588 [101] - busy (587)<br />    005c0588: 00588 . 00a58 [100]<br />    005c0fe0: 00a58 . 00020 [111] - busy (1d)<br />    005c1000:      00001000      - uncommitted bytes.<br /></p><p>最后执行14行代码,对堆进行释放,释放后我们通过!heap也可以看到只有4个堆了,我们申请的堆被释放了.</p>0:000&gt; !heap <br />Index Address Name Debugging options enabled<br />1: 00300000 <br />2: 00010000 <br />3: 00020000 <br />4: 002e0000 <br /><p> </p><p>至于HEAP_ENTRY结构的问题,有时间在调试看看是怎么回事吧~另外，这里说明下，new和malloc内部都会调用HeapAlloc来申请内存，但是堆句柄从哪来呢？它会检测_crtheap变量是否为空，若不为空则拿_crtheap变量来作为自己的堆句柄去调用HeapAlloc</p><p>参考：</p><p>软件调试    张奎银</p><p>MSDN    </p><p><a href="http://doxygen.reactos.org/" target="_blank">React OS</a></p><img src ="http://www.cppblog.com/ay19880703/aggbug/159364.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ay19880703/" target="_blank">__ay</a> 2011-10-30 19:05 <a href="http://www.cppblog.com/ay19880703/archive/2011/10/30/159364.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>tp-wn821n驱动 for aircrack（ubuntu）</title><link>http://www.cppblog.com/ay19880703/archive/2011/10/23/158923.html</link><dc:creator>__ay</dc:creator><author>__ay</author><pubDate>Sun, 23 Oct 2011 07:23:00 GMT</pubDate><guid>http://www.cppblog.com/ay19880703/archive/2011/10/23/158923.html</guid><wfw:comment>http://www.cppblog.com/ay19880703/comments/158923.html</wfw:comment><comments>http://www.cppblog.com/ay19880703/archive/2011/10/23/158923.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ay19880703/comments/commentRss/158923.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ay19880703/services/trackbacks/158923.html</trackback:ping><description><![CDATA[<p>@作者: ay @文章出处: <a href="http://cnss-ay.com/" target="_blank">cnss-ay的博客</a> <font color="#ff0000">@Notice: 转载请注明出处！若文章显示不完整，可以到文章出处阅读。</font></p> <p>&nbsp;</p> <p>昨天在京东上买的TP-LINK WN821N v3无线网卡，今天就到手了 发货真快~~凌晨给我发来邮件说已送货，注意查收。。。。汗~~</p> <p>这个无线网卡是我的开发网卡，花了一下午研究才翻到的一个commview for wifi和aircrack都能使用的网卡，用的atheros的芯片，具体型号可以在<a href="http://linuxwireless.org/en/users/Drivers/ath9k_htc/devices">http://linuxwireless.org/en/users/Drivers/ath9k_htc/devices</a>这个网址上查到。</p> <p>可惜我的是ubuntu 10.04的版本，检测不到这个网卡，只能自己动手装了</p> <p>我这里安装的是ath9k_htc</p> <p>多亏了下面这两篇文章，网卡成功在ubuntu上面跑起来了</p> <p><a href="http://hi.baidu.com/kids0cn/blog/item/2e304f59bd435dc49c820481.html" target="_blank">ath9k_htc：一些USB无线网卡的原生驱动</a></p> <p><a href="http://blog.sina.com.cn/s/blog_59cf67260100qqmf.html" target="_blank">ath9k_htc USB无线网卡驱动安装</a></p> <p>不过上面说貌似在UBUNTU 11.04中就可以识别出来，我也不晓得 这个待验证。</p> <p>试了下aircrack</p> <p>sudo aircmon start wlan%d （貌似网卡名解析有错。。。。竟然给我解析成wlan%d！不过也能用，将就吧~）</p> <p>打开wireshark监听mon0这个虚拟网卡，结果如下</p> <p><a href="http://www.cppblog.com/images/cppblog_com/ay19880703/Windows-Live-Writer/06eabf0f0437_B9AE/image_2.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/ay19880703/Windows-Live-Writer/06eabf0f0437_B9AE/image_thumb.png" width="592" height="284"></a></p> <p>嘿嘿&nbsp; 可以使用，那接下来可以继续wireless fundamental系列文章了，noctilio这个开源项目也开始步入开发阶段了</p><img src ="http://www.cppblog.com/ay19880703/aggbug/158923.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ay19880703/" target="_blank">__ay</a> 2011-10-23 15:23 <a href="http://www.cppblog.com/ay19880703/archive/2011/10/23/158923.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>把windows的VIM配置文件搬到ubuntu下</title><link>http://www.cppblog.com/ay19880703/archive/2011/10/23/158903.html</link><dc:creator>__ay</dc:creator><author>__ay</author><pubDate>Sat, 22 Oct 2011 16:34:00 GMT</pubDate><guid>http://www.cppblog.com/ay19880703/archive/2011/10/23/158903.html</guid><wfw:comment>http://www.cppblog.com/ay19880703/comments/158903.html</wfw:comment><comments>http://www.cppblog.com/ay19880703/archive/2011/10/23/158903.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ay19880703/comments/commentRss/158903.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ay19880703/services/trackbacks/158903.html</trackback:ping><description><![CDATA[<p>@作者: ay @文章出处: cnss-ay.com <font color="#ff0000">@Notice: 转载请注明出处</font></p> <p>之前写了一篇文章: <a href="http://cnss-ay.com/?p=43">VIM–指定自己的VIM工作目录</a></p> <p>今天准备把windows下的vim配置弄到虚拟机中的ubuntu来,于是就有了这篇文章,仅记录下期间遇到的问题</p> <p>&nbsp;</p> <p>1. 把windows文件夹映射到ubuntu下</p> <p>我这里是用了vmware tools来搞定的,先安装vmware tools,然后在edit virtual mechine setting中的options里面有个shared floders,把它设为always enable然后在下面添加自己要共享的目录就是了,我在这里面把我之前的$MY_VIM_DIR这个环境变量的目录添加进去了,我的floder name为vim.然后在虚拟机的UBUNTU下面,到/mnt/hgfs/vim目录下就可以访问我的vim配置文件了,第一步搞定,没什么大问题.</p> <p>&nbsp;</p> <p>2.然后在环境变量中添加$MY_VIM_DIR(/mnt/hgfs/vim)和$VIMINIT(source $MY_VIM_DIR/_vimrc)环境变量,我这里查了下资料,在~/.bashrc这个文件中加上这两句话：</p> <p>export MY_VIM_DIR=/mnt/hgfs/vim</p> <p>export VIMINIT="source $MY_VIM_DIR/_vimrc"</p> <p>在每次启动bash的时候会执行~/.bashrc这个文件，我们的环境变量就自动设进去了，但是这个设置只对当前用户有效，换个用户我们设置的环境变量就不起作用了. 如果你希望对全局用户都有效,那么可以去修改/etc/profile文件,在文件末尾加上这两句话.</p> <p>&nbsp;</p> <p>3.好了,一切就绪,在控制台启动VIM, 失败! 提示说检测到有^M, 无法解析.晕 windows格式编码问题....</p> <p>解决方法很简单 先跑去装个tofrodos,命令: sudo apt-get install tofrodos</p> <p>然后执行sudo fromdos -p $MY_VIM_DIR/*/*.vim</p> <p>把配置文件目录下的所以.vim文件的换行都转成unix格式的</p> <p>然后在我们的_vimrc中加入一句话: set fileformat=unix</p> <p>告诉vim我们换行符用unix格式的</p> <p>在windows下, gvim可以识别这种带有unix换行符的vimrc文件</p> <p>OK,问题解决</p> <p>&nbsp;</p> <p>4.执行sudo vim, 晕.... 竟然没有加载我的_vimrc文件,而是转而加载了默认的.vimrc文件,也就是说2中设置的环境变量不起作用</p> <p>然后想可能是sudo用的是root,不会继承我这个用户的环境变量,OK&nbsp; 我在/etc/profile中设置环境变量总可以了吧</p> <p>登出后登入,看效果</p> <p>晕... 还是不行</p> <p>然后百度了一些,原来在sudo命令出于安全性考虑,执行期间会把环境变量reset,然后保留可信的环境变量,比如$PATH这种变量</p> <p>那么问题找到就好办了,有几种方法</p> <p>a. 使用sudo -s命令，指出sudo继承shell环境变量</p> <p>b. 执行visudo命令, 把Defaults env_reset改成Defaults !env_reset。 禁止sudo对环境变量重置，因为我是在虚拟机上个人用ubuntu做开发,所以用的这种方法.若在多人共享使用的操作系统上,还是推荐使用a方法安全些</p><img src ="http://www.cppblog.com/ay19880703/aggbug/158903.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ay19880703/" target="_blank">__ay</a> 2011-10-23 00:34 <a href="http://www.cppblog.com/ay19880703/archive/2011/10/23/158903.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用vim编辑moin</title><link>http://www.cppblog.com/ay19880703/archive/2011/10/23/158901.html</link><dc:creator>__ay</dc:creator><author>__ay</author><pubDate>Sat, 22 Oct 2011 16:32:00 GMT</pubDate><guid>http://www.cppblog.com/ay19880703/archive/2011/10/23/158901.html</guid><wfw:comment>http://www.cppblog.com/ay19880703/comments/158901.html</wfw:comment><comments>http://www.cppblog.com/ay19880703/archive/2011/10/23/158901.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ay19880703/comments/commentRss/158901.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ay19880703/services/trackbacks/158901.html</trackback:ping><description><![CDATA[<p>@作者: ay @文章出处: cnss-ay.com <font color="#ff0000">@Notice: 转载请注明出处</font></p> <p>&nbsp;</p> <p>最近在尝试用moin wiki来做协同开发，直接用moin自带的编辑器太不爽了，字体难看不说，还没法着色，看着不舒服。</p> <p>然后去搜索了下vim中相关moin的插件，想不到还真有：</p> <p>&nbsp;</p> <p>首先是这个<a href="http://labix.org/editmoin#head-41b5354e1642a64b71772f5510600785d3e5813d">editmoin</a>，用python写的，可以通过这个插件来直接操作moin的页面，但是我这里是windows系统，它老读不到我的配置文件。这个方法linux下的用户可以用，我就不行咯，所以作罢，重新寻找其它方法。</p> <p>&nbsp;</p> <p>然后去moin主页上看，上面推荐了一个firefox的插件，可以使用指定编辑器来编辑页面内容！插件地址在这：<a href="https://addons.mozilla.org/zh-CN/firefox/addon/its-all-text/">It's All Text!</a>&nbsp;</p> <p>这款插件中你指定vim为编辑器就可以了，然后在编辑的时候激活该插件即可，它会帮你把内容复制到vim中，编辑完关闭后会把修改应用到网页编辑器内，很好用的说~~哈哈</p> <p>&nbsp;</p> <p>此外，在moin官网上提到的vim插件moin.vim可以对moin wiki语法进行高亮显示，但是用的时候注意，请把moin.vim中的 hi clear&nbsp; 这一句话注释掉,否则vim中配置的主题会失效</p><img src ="http://www.cppblog.com/ay19880703/aggbug/158901.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ay19880703/" target="_blank">__ay</a> 2011-10-23 00:32 <a href="http://www.cppblog.com/ay19880703/archive/2011/10/23/158901.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VIM – 指定自己的VIM工作目录</title><link>http://www.cppblog.com/ay19880703/archive/2011/10/07/157716.html</link><dc:creator>__ay</dc:creator><author>__ay</author><pubDate>Fri, 07 Oct 2011 13:03:00 GMT</pubDate><guid>http://www.cppblog.com/ay19880703/archive/2011/10/07/157716.html</guid><wfw:comment>http://www.cppblog.com/ay19880703/comments/157716.html</wfw:comment><comments>http://www.cppblog.com/ay19880703/archive/2011/10/07/157716.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ay19880703/comments/commentRss/157716.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ay19880703/services/trackbacks/157716.html</trackback:ping><description><![CDATA[<pre><p>
 </p><p>@Author：ay        @Date：2011-10-07    @文章出处：<a href="http://cnss-ay.com/">http://cnss-ay.com</a>
	</p><p><span style="color:red"><strong>@Notice：转载请注明作者信息！
</strong></span></p><p> 
 </p><p><span style="font-family:微软雅黑">在使用vim的过程中个，我发现我的vim配置要转移到windows上很麻烦，因为我的插件和vim插件放在vim安装目录下，和vim原配的插件混一起了。而且vimrc文件也被我改的很乱，放到windows上面未必能够直接使用。所以就有了这篇文章，讨论如何指定的一个自己的文件夹，vim从这文件夹中载入我的插件和载入初始化文件vimrc。
</span></p><p> 
 </p><p><span style="font-family:微软雅黑">用过vim的都晓得，vim在初始化之处会载入vimrc文件，在windows下这个文件是_vimrc，在linux下是.vimrc，我们可以修改这个文件来定制我们的初始化操作。但是我们必须得把这个vimrc文件放在指定的位置才可以被vim检测到，这些个路径分别是$VIM,$VIMRUNTIME,$HOME这三个环境变量下所指的路径。这里有两种方法：
</span></p><p><span style="font-family:微软雅黑">1 修改$HOME的路径，把这个环境变量指向我们自己定义的路径，$VIM和$VIMRUNTIME指向的路径可能vim还有其它用途，最好别改。但是修改$HOME也不是很好，特别是linux下，很多程序都是依赖于$HOME环境变量的。
</span></p><p> 
 </p><p><span style="font-family:微软雅黑">2指定$VIMINIT，我是定义了一个$MY_VIM_PATH的环境变量，这里面存了我VIM配置文件和插件的目录地址，然后$VIMINIT中的内容是：
</span></p><p><span style="font-family:微软雅黑">source $MY_VIM_PATH/_vimrc
</span></p><p><span style="font-family:微软雅黑">在vim初始化之初会查看$VIMINIT有没有被定义，若被定义了就放弃后面载入vimrc的操作，转而执行$VIMINIT变量内的操作。当然也可以设置$MYVIMRC的环境变量为$MY_VIM_PATH/_vimrc，vim同样会先载入$MYVIMRC变量中指定的vimrc文件。只有当这两个环境变量都没定义的时候vim才回去剩下的目录中找vimrc文件。
</span></p><p><span style="font-family:微软雅黑">注：在windows中没法创建.vimrc的文件，所以只能用_vimrc代替之。
</span></p><p> 
 </p><p><span style="font-family:微软雅黑">关于如何设置插件，帮助文档的查找路径
</span></p><p><span style="font-family:微软雅黑">在vimrc中加入如下语句即可
</span></p><p><span style="font-family:微软雅黑">set runtimepath=$MY_VIM_PATH,$VIMRUNTIME
</span></p><p><span style="font-family:微软雅黑">在vim运行时，会在runtimepath变量中记录的路径中去找加载插件，背景色等内容。
</span></p><p> 
 </p><p> 
 </p><p>
 </p><p>
 </p><p><span style="color:red"><strong>
			</strong></span> </p><br/><br/>文章来源：<a href=http://cnss-ay.com/?p=43>http://cnss-ay.com/?p=43</a></pre><img src ="http://www.cppblog.com/ay19880703/aggbug/157716.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ay19880703/" target="_blank">__ay</a> 2011-10-07 21:03 <a href="http://www.cppblog.com/ay19880703/archive/2011/10/07/157716.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>