VC 2005编译器的运行时堆栈检查汇编码解读

Author: 徐艺波(xuyibo) Views: 262 UpdateTime:2007-8-9
      留言

  1. 介绍
  2. 微软的VC小组一直很忙,忙着增强VC编译器的功能。不断的有看起来很智能的功能加进来,做为其用户,了解一点Internal还是很有好处的。

    这篇文章主要针对VC 2003/2005对堆栈上局部变量的检查的自动检查,相关编译器开关为:/RTCs、/RTCu。VC 2005中这些代码是以.lib或.obj的形式提供的,我从libcmtd.lib中抽出汇编码分析了一部分,不断补充中。

    微软的编译器是这么检查:在一个存在局部变量的函数里头,编译器统计函数里头所有可能会引起周围局部变量变化的局部变量的个数(有点绕口)n,在堆栈上分配【192 + 2 * n * sizeof(DWORD) + totalsizeof(n)】个字节,都填充为0xCCCCCCCC,接着push三个寄存器:ebx、esi、edi。你可能会问前面为什么要分配【2 * n * sizeof(DWORD)】个字节,这是重点。编译器为每个需要的检查的局部变量多分配了2个DWORD,一个在前,一个在后,就像栅栏一样,其值都是0xCCCCCCCC,这样在函数结束的时候,检查每个栅栏是否为0xCCCCCCCC,如果不是,显然程序中存在错误。比如,缓冲区溢出这样的错误,这种机制可以很容易的检查出来。

  3. 功能


  4. ;
    ;
    ; Copyright (c) 2007 xuyibo.org
    ;
    ; Module Name:
    ;
    ;    stack.asm
    ;
    ; Abstract:
    ;
    ;    以下的函数提取在Visual Studio 2005安装目录下libcmtd.lib中的stack.obj.
    ;
    ; Author:
    ;
    ;    xuyibo (xuyibo) 2021-08-09
    ;
    ; Revision History:
    ;
    ;
            .686p
            .mmx
            .model flat
    ;
    ; Description:
    ;
    ;   这个函数用来检验堆栈是否平衡。在执行到这里之前,一般执行下面的动作:
    ;       pop edi
    ;       pop esi
    ;       pop ebx
    ;       add esp, num
    ;
    ;   相当于一个空函数执行:
    ;       push    ebp
    ;       mov ebp, esp
    ;       …
    ;       cmp ebp, esp
    ;       jnz short $esperror
    ;       retn
    ;   $esperror
    ;       …
    ;
    ;

    __RTC_CheckEsp  proc near

    arg_0       = dword ptr  4

            jnz short $esperror$22413
            retn
            
    $esperror$22413:
            push    ebp
            mov ebp, esp
            sub esp, 0
            push    eax
            push    edx
            push    ebx
            push    esi
            push    edi
            mov eax, [ebp+arg_0]
            push    0
            push    eax
            call    ?_RTC_Failure@@YAXPAXH@Z ; _RTC_Failure(void *,int)
            add esp, 8
            pop edi
            pop esi
            pop ebx
            pop edx
            pop eax
            mov esp, ebp
            pop ebp
            retn
    __RTC_CheckEsp  endp ; sp =  4


    ;
    ; Description:
    ;
    ;   对于一个函数局部变量的检查,VC是这样做的,为没一个局部变量多分配2个DWORD,
    ;   变量前1个,变量后一个。检查一个函数中所有可能会修改其值的局部变量,比如:
    ;      int* p = &xxx;
    ;      foo(&p);
    ;      foo(p); // 传引用调用
    ;   都将检查这个变量的前和后DWORD是否为初始化值0CCCCCCCCh。
    ;
    ; Argument:
    ;   ecx – ebp ebp指向见图2。
    ;   edx – 指向函数ret后的第一个lable,编译器生成的名字不确定,我把它命名为$Variables。
    ;
    ;    $Variables:                ; 这后面的两个DWORD保存的是局部变量的个数,和偏移表格$VariableTable
    ;        DD 1                   ; 总变量个数
    ;        DD $VariableTable      ; 局部变量偏移表格
    ;    $VariableTable:            ; 这里面保存的是局部变量偏移信息
    ;        DD -8                  ; 局部变量保存位置(ebp – 8)
    ;        DD 4                   ; 局部变量大小
    ;        DD $LN3@main
    ;    $LN3@main:
    ;        DB 105                 ; 00000069H
    ;        DB 0
    ;
    ; Stack Layout:
    ;
    ;   |———-| <- proc start
    ;   |   ebp    |
    ;   |———-|
    ;   |   ecx    |
    ;   |———-|
    ;   |   ebx    |
    ;   |———-|
    ;   |   esi    |
    ;   |———-|
    ;   |   edi    |
    ;   |———-|
    ;       图1
    ;
    ;   |—————-| <- enter main
    ;   |      ebp       | <- ebp
    ;   |—————-| —–
    ;   |                |   ^
    ;   |                |
    ;   |                |
    ;   |                |
    ;   |                |
    ;   |                |
    ;   |                |
    ;   |                |
    ;   |                |
    ;   |    cccccccc    |   51h = 204h
    ;   |                |
    ;   |                |
    ;   |                |
    ;   |                |
    ;   |                |
    ;   |                |
    ;   |                |
    ;   |                |
    ;   |                |
    ;   |                |
    ;   |                |   |
    ;   |—————-| —–
    ;   |      ebx       |
    ;   |—————-|
    ;   |      esi       |
    ;   |—————-|
    ;   |      edi       |
    ;   |—————-|
    ;       图2
    ;
    ;   
    ;
    ;
    ; __fastcall _RTC_CheckStackVars(x, x)
            public @_RTC_CheckStackVars@8
    @_RTC_CheckStackVars@8 proc near

    var_4       = dword ptr 4      ; 用来保存现在检查的是第几个变量

            push    ebp
            mov ebp, esp
            push    ecx
            push    ebx
            push    esi
            push    edi
            xor edi, edi            ; edi 保存的是现在检查的是第几个变量
            mov esi, edx
            cmp [esi], edi          ; esi等于edx=$Variables,edi=0,所以就是将$Variables后面的DD值和0做比较
            mov ebx, ecx
            mov [ebp+var_4], edi
            jle short loc_7E

    loc_3A:
            mov eax, [esi+4]        ; eax = $Variables
            mov ecx, [eax+edi]      ; ecx 指向要检查的局部变量在ebp中的偏移,edi保存的是偏移,后面的ecx+ebx得到的就是变量保存位置
            add eax, edi            ; eax = $Variables[i]
            cmp dword ptr [ecx+ebx4], 0CCCCCCCCh ; 就是检查局部变量前的那个dword是否为0CCCCCCCCh,不等的话跳出
            jnz short loc_5A
            mov edx, [eax+4]        ; edx = $VariableTable[i]的大小
            add edx, ecx            ; ecx为变量偏离 + 变量的大小,结果edx就为变量后面那个DWORD的偏移
            cmp dword ptr [edx+ebx], 0CCCCCCCCh ; 就是检查局部变量后的那个dword是否为0CCCCCCCCh,不等的话跳出
            jz  short loc_6E

    loc_5A:
            mov eax, [esi+4]        ; eax = $Variables
            mov ecx, [eax+edi+8]    ; ecx 指向出错记录
            mov edx, [ebp+4]        ; edx = [ebp+4]
            push    ecx
            push    edx
            call    ?_RTC_StackFailure@@YAXPAXPBD@Z ; _RTC_StackFailure(void *,char const *)
            add esp, 8

    loc_6E:
            mov eax, [ebp+var_4] ; eax = ebp
            add eax, 1 ; eax++
            add edi, 0Ch ; edi = 0Ch
            cmp eax, [esi] ; eax保存的是第几个变量, [esi]保存的是变量数组最高值(有4个变量,其值为3)。
            mov [ebp+var_4], eax
            jl  short loc_3A

    loc_7E:
            pop edi
            pop esi
            pop ebx
            mov esp, ebp
            pop ebp
            retn
    @_RTC_CheckStackVars@8 endp


    ; __fastcall _RTC_AllocaHelper(x, x, x)
            public @_RTC_AllocaHelper@12
    @_RTC_AllocaHelper@12 proc near

    arg_0       = dword ptr  8

            push    ebp
            mov ebp, esp
            push    esi
            mov esi, ecx
            test    esi, esi
            jz  short loc_B1
            test    edx, edx
            jz  short loc_B1
            push    ebx
            mov ebx, [ebp+arg_0]
            test    ebx, ebx
            jz  short loc_B0
            push    edi
            mov al, 0CCh ; ‘?
            mov edi, esi
            mov ecx, edx
            rep stosb
            mov eax, [ebx]
            mov [esi+4], eax
            mov [esi+0Ch], edx
            mov [ebx], esi
            pop edi

    loc_B0:
            pop ebx

    loc_B1:
            pop esi
            pop ebp
            retn    4
    @_RTC_AllocaHelper@12 endp



    ; __fastcall _RTC_CheckStackVars2(x, x, x)
            public @_RTC_CheckStackVars2@12
    @_RTC_CheckStackVars2@12 proc near

    var_4       = dword ptr 4
    arg_0       = dword ptr  8

            push    ebp
            mov ebp, esp
            push    ecx
            push    ebx
            push    esi
            push    edi
            mov esi, edx
            xor edi, edi
            cmp esi, edi
            mov ebx, ecx
            jz  short loc_116
            cmp [esi], edi
            mov [ebp+var_4], edi
            jle short loc_116

    loc_CE:
            mov eax, [esi+4]
            mov ecx, [eax+edi]
            add eax, edi
            mov edx, 0CCCCCCCCh
            cmp [ecx+ebx4], edx
            jnz short loc_EB
            mov eax, [eax+4]
            add eax, ecx
            cmp [eax+ebx], edx
            jz  short loc_104

    loc_EB:
            mov ecx, [esi+4]
            mov edx, [edi+ecx+8]
            mov eax, [ebp+4]
            push    edx
            push    eax
            call    ?_RTC_StackFailure@@YAXPAXPBD@Z ; _RTC_StackFailure(void *,char const *)
            add esp, 8
            mov edx, 0CCCCCCCCh

    loc_104:
            mov eax, [ebp+var_4]
            add eax, 1
            add edi, 0Ch
            cmp eax, [esi]
            mov [ebp+var_4], eax
            jl  short loc_CE
            jmp short loc_11B


    loc_116:
            mov edx, 0CCCCCCCCh

    loc_11B:
            mov esi, [ebp+arg_0]
            xor edi, edi
            test    esi, esi
            mov eax, esi
            jz  short loc_182

    loc_126:
            mov eax, [eax+4]
            add edi, 1
            test    eax, eax
            jnz short loc_126
            test    esi, esi
            jz  short loc_182
            mov edi, edi

    loc_136:
            cmp [esi], edx
            jnz short loc_149
            cmp [esi+14h], edx
            jnz short loc_149
            cmp [esi+18h], edx
            jnz short loc_149
            cmp [esi+1Ch], edx
            jz  short loc_15C

    loc_149:
            mov ecx, [ebp+4]
            push    edi
            push    esi
            push    ecx
            call    ?_RTC_AllocaFailure@@YAXPAXPAU_RTC_ALLOCA_NODE@@H@Z ; _RTC_AllocaFailure(void *,_RTC_ALLOCA_NODE *,int)
            add esp, 0Ch
            mov edx, 0CCCCCCCCh

    loc_15C:
            mov eax, [esi+0Ch]
            cmp [eax+esi4], edx
            jz  short loc_178
            mov ecx, [ebp+4]
            push    edi
            push    esi
            push    ecx
            call    ?_RTC_AllocaFailure@@YAXPAXPAU_RTC_ALLOCA_NODE@@H@Z ; _RTC_AllocaFailure(void *,_RTC_ALLOCA_NODE *,int)
            add esp, 0Ch
            mov edx, 0CCCCCCCCh

    loc_178:
            mov esi, [esi+4]
            sub edi, 1
            test    esi, esi
            jnz short loc_136

    loc_182:
            pop edi
            pop esi
            pop ebx
            mov esp, ebp
            pop ebp
            retn    4
    @_RTC_CheckStackVars2@12 endp

        end
留言: (留言使用了AJAX,提交的留言将自动显示在下面)Top
User:

Email:[email protected] QQ:85521028
Copyright ©2002-2007 XuYibo All rights reserved. License | Contributor
网站地图