反馈收集和回复


反馈收集和回复
===
请您在这里通过添加评论留下您对这篇文章的反馈信息,我会及时整理并且回复。如果您的反馈不想让别人看到,可以点击右边的Email链接给我发邮件:


http://blogs.msdn.com/lixiong/contact.aspx


===


Q:


问个其他的问题。最近困扰我很久了。
一般stdcall函数的标准入口是这样的:
push ebp
mov ebp, esp
但是现在我看一些windows自身的动态库,比如NTDLL.DLL,都是这样的:
mov edi, edi
push ebp
mov ebp, esp
多出来的mov edi,edi不会没有理由吧。
我从网上搜索了一下,
http://blogs.msdn.com/ishai/archive/...24/165143.aspx



还真有个说法。但是我没有看懂。他只是说这种指令能够做到“热打补丁”而不用重新启动应用程序。


A:


The hot patching tech involves both short jump and a real jump. The mov esi,esi is used to reserve the space for the short jump.And before the move instruction, there should be always 5 nop instrunctions for the real jump.


With both short jump and real jump, it balances the performance and felixibility:


http://msmvps.com/blogs/kernelmustard/archive/2005/04/25/44413.aspx


http://mirror.href.com/thestarman/asm/2bytejumps.htm


===


Q:


就比如crash这个概念。我一直以为,crash和程序异常结束没有什么区别。但是adplus一个不适用的场景是“If you need to troubleshoot a program or process that terminates unexpectedly upon startup”。
这里我就有个问题:是程序一起动就异常结束不适合用adplus,还是所有异常结束的程序都不适合用adplus?
是不是adplus适合那种弹出一个错误对话框,等待人确定后再异常结束的场景(也就是现在的xp下很多程序在异常结束的时候都会问人发送不发送错误报告)?


还有,对于hang模式,我想是不是直接用windbg绑定上去看哪个线程挂死来的更快点?


A:



首先,adplus是用来抓取dump的工具。原理是adplus调用调试器监视目标进程的状态,根据配置和参数,当异常发生,或者条件断点触发的时候,自动生成dump。同时adpus的-hang参数可以不等异常发生,立刻抓取dump


这里带来一个问题,如果目标进程刚启动就崩溃,就找不到实际来运行adplus进行监视。在当前版本的adplus中,-sc参数可以让adplus去启动目标进程然后立刻监视,这样就可以解决这个问题。所以上面提到的不适合场景通过-sc参数是可以解决的。所有异常结束的程序都可以用adplus


hang发生的时候,当然可以直接用windbg上去看。但是如果问题在远程生产机上发生,调试人员又不在,就可以使用adplus -hang生成一个dump,然后把dump发送给开发人员进行事后分析。


===


Q:



另外,对于first chance exception和second exception。我一直以为first chance exception是异常(exception)第一次被抛出(raise)时候,给你第一次处理(handle)的机会,如果此时被处理(可能编程者自己写了一些catch)了,就没有第二次机会(second chance)了.
文档上说的是,第seconde chance exception只能被debuger处理(handler),如果debuger不处理,程序就关闭(shut down)。


对于传统的c++程序员来说,理解这点可能有点问题。因为在c++中,是允许在catch程序块中再次抛出异常,这个异常可以被更外层的catch处理掉。但是这里抛出的异常已经不是楼主文档中second chance exception了。


A:


首先,frist/second chance exception是调试器专用概念,跟程序设计没有任何关系。这里的异常是说系统的SEH,对于C++异常处理的实现,是建立在SEH上的。两个在不同的层次。当异常发生的时候,如果目标进程没有调试器加载,不管发生什么异常,都跟这两个名词没有关系。如果有调试器加载,当异常发生的时候,系统在发送给程序处理(比如catch语句)以前,首先会发给调试器,这个时候调试器里面就观察到了1st chance exception。调试器可以选者用gn或者gh来让程序继续执行。


如果是gn,表示调试器没有把这个异常设为未处理,接下来就要看程序是否处理这个异常。如果程序处理掉了,那继续运行。如果程序没有处理,这个异常就成为了unhandled exception,这个时候系统会再次通知调试器,这时调试器观察到的就是2nd chance exception了。


如果是gh,表示调试器把这个异常设定为了处理,剩下的事情就要具体的语言和编译器是怎么处理了。由于改变了异常处理状态,代码一般都回出错。


所以,如果观察到2nd chance exception,程序只有crash。调试器能够处理的,只有1st chance exception。C++中的throw, catch, rethrow跟着两个名词没有关系。catch里面的throw,不过是触发了另外一个异常而已。如果在调试器中观察,看到的还是1st chance exception。具体的信息,可以参考文章中提到的文章,这些问题都讲得很清楚的。


===


Q:


什么时候会有GDI方面的调试文章?


本来,我以为windows程序所有的handle都是针对内核对象的。但是最近看一些书的时候,才知道windows对所有的图形元素都是用另外的管理机制。

这两天,我在观察一个客户端程序,刚跑起来,用Process Explorer观察performance,GDI handles的数值是215。过一个晚上,我早上来看,就变成1295了。

如果用windbg的!handle来查看,是不会显示GDI handles的。
所以,请教有什么方法可以看看这些泄露的GDI handle都是些什么对象吗?


A:


我对GDI Debugging没有太多研究。微软内部有专门的windbg extention,结合private symbol可以打印出所有GDI handle的详细信息。

如果你需要自己检查,可以用下面文章的方法,原理都一样的:

http://msdn.microsoft.com/msdnmag/issues/03/01/GDILeaks/


 

Comments (23)

  1. Newer2k says:

    问一个题外话,你回帖的时间居然比我问问题的时间早了2天多。

    时间穿梭?

  2. Li Xiong says:

    时间是帖子创建时间,我后来编辑的 :)

  3. Newer2k says:

    敢怀疑intel的芯片有问题?应该是一个很牛的牛人吧。

    我们公司很多人都怀疑,就是不自信有这个水平。:D

  4. Li Xiong says:

    用调试器一步一步跟踪下来,当看到某一句CPU指令执行后,寄存器的结果跟预期不一样,就可以怀疑CPU了。何况这样的现象只在一台机器上有,对等的另外一台却没有

  5. Newer2k says:

    本来,我以为windows程序所有的handle都是针对内核对象的。但是最近看一些书的时候,才知道windows对所有的图形元素都是用另外的管理机制。

    这两天,我在观察一个客户端程序,刚跑起来,用Process Explorer观察performance,GDI handles的数值是215。过一个晚上,我早上来看,就变成1295了。

    如果用windbg的!handle来查看,是不会显示GDI handles的。

    所以,请教有什么方法可以看看这些泄露的GDI handle都是些什么对象吗?

  6. Li Xiong says:

    我对GDI Debugging没有太多研究。微软内部有专门的windbg extention,结合private symbol可以打印出所有GDI handle的详细信息。

    如果你需要自己检查,可以用下面文章的方法,原理都一样的:

    http://msdn.microsoft.com/msdnmag/issues/03/01/GDILeaks/

  7. Newer2k says:

    多谢提供的信息。

    我最近正好在测试一个cs结构的程序。本来以为用些自动化测试工具(比如purify,boundscheck)就很快能够测试完,但是测试中只能发现问题,不能很好的定位问题,而且,局限也比较大,对机器的要求也很高,我的机器已经是公司里面配置最好的了,装完相关开发工具和自动化测试软件之后,已经慢的难以忍受。

    在codeproject看到一些调试分析文章之后就开始用windbg分析server端程序,再结合你写的相关部分,比较快和准确的发现问题定位问题,对开发人员帮助很大。

    昨天在测试客户端程序的时候,发现GDI资源泄漏情况,今天仔细查阅你推荐的文章(文章中很多链接,我来回参照花了一些时间),又很快的查出泄露的资源都是bitmap文件。把结果反馈给项目开发人员,让他们有针对性的去改动代码。

    如果说谁短时间内从文章中受益最多,我想可能就是我了。:D

    **********

    最近才知道Matt Pietrek 早已经加入microsoft了。见几天看他的blog,还推出一个视频(可惜我英文比较差,没台听懂他说了什么有趣的事情),还有一张老照片,上面好像有几个牛人。

  8. Li Xiong says:

    有用就好。帮忙去各大论坛多推荐推荐,让跟多人看到 😀

  9. Newer2k says:

    这两天再复习heap一章,装上vs2005严格按照要求做实验。

    对文章中48页的例子做分析。

    就是对于dcba前面的数值,在例子中,你说该值就是保存对应的callstack。所以用dds 00412920就能够看到该heap曾经发生的调用。

    接着在你描述的客户案例分析中,我看到的确是

    dds poi(19b92fe6 -6).

    这两处为什么会有这么大的差异?

  10. Li Xiong says:

    两处对应的是不同的heap pointer阿。

    注意看这里的00412920是如何拿到的。

    首先是

    0:000> dd 0x3f5848

    003f5848  7c88c580 0025a5f0 00412920 dcbaaaa9

    003f5858  f0f0f0f0 f0f0f0f0 f0f0f0f0 f0f0f0f0

    然后看到dcbaaaa9前面的是00412920。也就是说callstack保存在00412920上的,用dds就可以直接显示了

    再来看看19b92fe6

    19b92fe6  dcba cfe8 1a52 8fe8 1dff cfe8 1af6 f44f

    这里表示19b92fe6  地址上保存的是dcba,而不表示19b92fe6  在dcba前面。

    既然19b92fe6  地址上保存的是dcba,所以要找到保存callstack的地址的话,需要对19b92fe6前面6个字节的地址做读操作,找到对应的保存callstack的地址。这里的poi就是做这个地址转换的。

    第一种情况通过dcba直接看到了前面的地址,于是直接显示。第二种情况是首先找到保存了dcba的地址,然后从保存dcba的地址上在去找保存callstack的地址,相当于做了一次两重指针的地址转换。

  11. Newer2k says:

    我还以为19b92fe6 就是dcba前面的内容,所以才有这么一问。

    *********

    如果想扩大这个blog的影响,我建议在这里做一些关键字。好让搜索引擎可以搜索到。

  12. Li Xiong says:

    msdn blog国内好多访问不到。所以我放到cnblog上。顺便让一个csdn的朋友帮我推广推广

  13. Newer2k says:

    文章中有一个地方比较了5000000次strcpy函数的执行效果。release版本比debug版本还慢。在我机器上的执行效果还真的是这样,:D。

    对release我反汇编,看得比较清楚,就是一个字节一个字节的拷贝。

    debug版本是用4个字节,所以速度快。

    但是我对debug中的实现技巧有些不明白:

    ***********

    main_loop_entrance:

           mov     edx,7efefeffh

    7818027F  mov         edx,7EFEFEFFh

           mov     eax,dword ptr [esi] ; read 4 bytes (dword)

    78180284  mov         eax,dword ptr [esi]

           add     edx,eax

    78180286  add         edx,eax

           xor     eax,-1

    78180288  xor         eax,0FFFFFFFFh

           xor     eax,edx

    7818028B  xor         eax,edx

           mov     edx,[esi]           ; it’s in cache now

    7818028D  mov         edx,dword ptr [esi]

           add     esi,4               ; kick dest pointer

    7818028F  add         esi,4

           test    eax,81010100h

    78180292  test        eax,81010100h

           je      short main_loop

    78180297  je          main_loop (78180275h)

    ****************

    象mov  edx,7efefeffh这种语句,我看不出跟字符串拷贝有什么关系。

    ****************

    我把strcpy改成 strncpy实现,两个的效率就差不多了,strncpy的实现是4字节拷贝方式。

  14. Li Xiong says:

    嘿嘿,在vc里面用f11单步跟踪一下不就知道了

  15. Newer2k says:

    就我的能力,还不能理解这么深奥的实现,所以我搜索了一圈,找到了一个标准的答案。算是补全这里的问题:

    mov     eax,dword ptr [ecx]     ; read 4 bytes

    mov     edx,7efefeffh ; 就是 -0x81010101H

    add     edx,eax ; eax – 1000 0001 0000 0001 0000 0001 0000 0001B

    ; 使用减法取反,如果为0000 0000就是NULL,那么取反的时候会向前一字节借位

    xor     eax,-1 ; 直接取反,不借位

    xor     eax,edx

    add     ecx,4

    test    eax,81010100h ; 1000 0001 0000 0001 0000 0001 0000 0000B

    ; 看看直接取反和减法取反,可能存在借位的那些位是否相同。相同则不存在借位,继续循环

    ; 否则就扫描到NULL了,字符串结束

    ********

    巧妙。

  16. Li Xiong says:

    Cool!

    完整的方法是:

    mov     edi,[esp+8]         ; edi points to dest string

    copy_start::

           mov     ecx,[esp+0ch]       ; ecx -> sorc string

           test    ecx,3               ; test if string is aligned on 32 bits

           je      short main_loop_entrance

    src_misaligned:                     ; simple byte loop until string is aligned

           mov     dl,byte ptr [ecx]

           add     ecx,1

           test    dl,dl

           je      short byte_0

           mov     [edi],dl

           add     edi,1

           test    ecx,3

           jne     short src_misaligned

           jmp     short main_loop_entrance

    main_loop:                          ; edx contains first dword of sorc string

           mov     [edi],edx           ; store one more dword

           add     edi,4               ; kick dest pointer

    main_loop_entrance:

           mov     edx,7efefeffh

           mov     eax,dword ptr [ecx] ; read 4 bytes

           add     edx,eax                                  7e fe fe ff

           xor     eax,-1

           xor     eax,edx

           mov     edx,[ecx]           ; it’s in cache now

           add     ecx,4               ; kick dest pointer

           test    eax,81010100h

           je      short main_loop

           ; found zero byte in the loop

    ; main_loop_end:

           test    dl,dl               ; is it byte 0

           je      short byte_0

           test    dh,dh               ; is it byte 1

           je      short byte_1

           test    edx,00ff0000h       ; is it byte 2

           je      short byte_2

           test    edx,0ff000000h      ; is it byte 3

           je      short byte_3

           jmp     short main_loop     ; taken if bits 24-30 are clear and bit

                                       ; 31 is set

    byte_3:

           mov     [edi],edx

           mov     eax,[esp+8]         ; return in eax pointer to dest string

           pop     edi

           ret

    byte_2:

           mov     [edi],dx

           mov     eax,[esp+8]         ; return in eax pointer to dest string

           mov     byte ptr [edi+2],0

           pop     edi

           ret

    byte_1:

           mov     [edi],dx

           mov     eax,[esp+8]         ; return in eax pointer to dest string

           pop     edi

           ret

    byte_0:

           mov     [edi],dl

           mov     eax,[esp+8]         ; return in eax pointer to dest string

           pop     edi

           ret

  17. Newer2k says:

    我是在搜索mov edx,7efefeffh 过程中找到http://www.programfan.com/club/showtxt.asp?id=141040链接

    虽然是strlen的实现,但是实现的精华部分和这里是一样的。看了之后,除了感叹作者的匠心之外,没有别的。

    **********

    另外,这种实现应该指示在intel这种little endian平台上的。如果在powerpc上,相应的数值应该变了。

  18. Li Xiong says:

    最开始考虑strcpy的性能的时候,只考虑了要4bytes一起拷贝。但是忽略了一个关键问题,就是如何判断字符串的结束。由于字符串可以从4bytes中任意一个byte结束,所以需要判断是否有任何一个byte为0。

    如果对一个4bytes拆分成4个byte分别判断,那4bytes一起拷贝节省下拉性能立刻浪费掉了。今天看到Newer2k的回复,才重新开始考虑这个问题。

    这里对一个DWORD (EAX)的判断方法是:

    1. 对EAX+0x7efefeff

    2. 对EAX取反

    3. 把1和2的结果作XOR,然后跟0x81010100h作test运算

    研究了好久,理解如下:

    问题的关键点在于,当且仅当EAX四个byte都不为0的时候,运算结果会是下面的pattern:

    0??? ???0 ???? ???0 ???? ???0 ???? ????

    分别解释如下:

    如果第一个byte为0, 考虑第二个byte的最后一个bit。不管这个bit是0还是1,计算公式是:

    (x+0) XOR (!x) =x xor !x=0

    如果第一个byte不为0,肯定产生进位,考虑第二个byte的最后一个bit。不管这个bit是0还是1,计算公式是:

    (x+1)XOR(!x)=!x xor !x=1

    这就是上面0??? ???0 ???? ???0 ???? ???0 ???? ????第二个byte的第一个bit是0的来历

    同理,第二,三,四个byte中的的第一个bit的0也是在前面所有的byte都不为0的时候才会出现,否则就会出现至少一个1

    最后再来看最一个byte的最高位。根据前面的分析,我们已经可以确保前面三个byte为0的pattern。所以我们只需要考虑前面三个byte都不是0,然后检测最后一个byte是否为0的情况。这里分成三种情况来考虑最高一个bit的情况:

    1) 最高byte为0

    由于前面三个byte都不是0,所以相加后最高byte肯定拿到一个进位,所以最高byte的加法变成了跟7f相加。所以公式:

    (0+0) XOR (!0) =1

    2) 最高byte不为0,最高bit为0。这种情况下跟7f相加,相加结果最高bit肯定会变成1:

    1 XOR (!0) =0

    2) 最高byte不为0,最高bit为1。这种情况下跟7f相加,不管相加后最高为是0还是1,都有:

    1 XOR (!1) =0

    0 XOR (!1) =0

    所以,如果EAX最高byte为0,结果的最高bit为1. 如果EAX最高byte不为0,结果最高的bit为0

    综上,如果所有四个byte都不为0,最后得到的pattern是:

    0??? ???0 ???? ???0 ???? ???0 ???? ????

    于是我们可以拿结果跟0x81010100 作TEST运算,当且仅当四个byte都不为0的时候,ZR寄存器的值为0。

  19. Newer2k says:

    1 XOR (!1) =0

    0 XOR (!1) =0

    这两个等式成立吗?

  20. Li Xiong says:

    The TEST instruction does a bitwise logical AND of the operands and sets the PL, ZR, and PE (SF, ZF, and PF for the Intel manuals) flags accordingly. The TEST instruction checks whether a bit value was set.

  21. Li Xiong says:

    1 XOR (!1) =0

    错了,这里应该是1

    换句话说,上面的代码无法区分最高一个byte最高bit为0,其他bit为1的情况。这是这种算法的一个死穴。当出现比如0x80112233这样的DWORD的时候,test    eax,81010100h 计算的结果跟0x00112233一样。当然最后的结果不会有问题,因为byte_3 — byte_0里面会再次作判断。所以,如果用一连串的0x80112233作为字符串内容,strcpy的效率会大大下降

    谢谢newer2k帮我找到我分析中的错误!

  22. Newer2k says:

    昨天我在忙别的事情,也开始怀疑80??????模式下的效率问题。

    我试验了一下。如果是80开始的,效率会和release模式下的一样,甚至更慢点。

    看来这个实现加入一点赌的成分,:D

  23. Li Xiong says:

    对于一个DWORD,导致这个因素的可能是

    2^24/2^32=1/2^8=1/256

    算是比较罕见了

    从逻辑上说,最高byte是无法区分本身为1,或者是低byte进位的情况。所以单独的DWORD是无法判断出所有情况的,当前的做法已经算很有想法的了

Skip to main content