Windbg查看函数调用过程中的内存布局

     我们在分析问题的时候经常会需要查看进程的栈和帧中的值,下面我们就用一个简单的例子来分析一下这个过程。

源代码:

#include <iostream>
int add(int a, int b)
{
   return a + b;
}


int main()
{
    int a, b;
    a = 3;
    b = 4;
    int ret = add(a, b);
    std::cout << "Result:"<<ret<<"\t\n";
}

生成的汇编代码:

main 函数的汇编代码

 10 004c1000 55              push    ebp
   10 004c1001 8bec            mov     ebp,esp
   10 004c1003 83ec0c          sub     esp,0Ch
   12 004c1006 c745f803000000  mov     dword ptr [ebp-8],3
   13 004c100d c745fc04000000  mov     dword ptr [ebp-4],4
   14 004c1014 8b45fc          mov     eax,dword ptr [ebp-4]
   14 004c1017 50              push    eax
   14 004c1018 8b4df8          mov     ecx,dword ptr [ebp-8]
   14 004c101b 51              push    ecx
   14 004c101c e83f000000      call    statckTest!add (004c1060)
   14 004c1021 83c408          add     esp,8
   14 004c1024 8945f4          mov     dword ptr [ebp-0Ch],eax
   15 004c1027 6840314c00      push    offset statckTest!GS_ExceptionPointers+0x8 (004c3140)
   15 004c102c 8b55f4          mov     edx,dword ptr [ebp-0Ch]
   15 004c102f 52              push    edx
   15 004c1030 6844314c00      push    offset statckTest!std::_Fake_alloc+0x1 (004c3144)
   15 004c1035 a16c304c00      mov     eax,dword ptr [statckTest!_imp_?coutstd (004c306c)]
   15 004c103a 50              push    eax
   15 004c103b e8e0020000      call    statckTest!std::operator<<<std::char_traits<char> > (004c1320)
   15 004c1040 83c408          add     esp,8
   15 004c1043 8bc8            mov     ecx,eax
   15 004c1045 ff1540304c00    call    dword ptr [statckTest!_imp_??6?$basic_ostreamDU?$char_traitsDstdstdQAEAAV01HZ (004c3040)]
   15 004c104b 50              push    eax
   15 004c104c e8cf020000      call    statckTest!std::operator<<<std::char_traits<char> > (004c1320)
   15 004c1051 83c408          add     esp,8
   16 004c1054 33c0            xor     eax,eax
   16 004c1056 8be5            mov     esp,ebp
   16 004c1058 5d              pop     ebp
   16 004c1059 c3              ret

这段汇编代码是一个函数的一部分,它执行了以下操作:

  1. 函数开始

    • push ebp 和 mov ebp,esp:保存当前的栈指针(esp)到基指针(ebp),并设置ebp为当前栈指针的值。这是函数调用的标准开头,用于设置函数的局部变量和参数的基础地址。
  2. 局部变量分配

    • sub esp,0Ch:从栈指针中减去12(0Ch是十六进制的12),为局部变量分配空间。
  3. 初始化局部变量

    • mov dword ptr [ebp-8],3:将整数值3存储到ebp-8指向的局部变量中。
    • mov dword ptr [ebp-4],4:将整数值4存储到ebp-4指向的局部变量中。
  4. 函数调用准备

    • mov eax,dword ptr [ebp-4] 和 mov ecx,dword ptr [ebp-8]:将两个局部变量的值分别加载到eaxecx寄存器中。
    • push eax 和 push ecx:将这两个值压入栈中,作为add函数的参数。
  5. 函数调用

    • call statckTest!add (004c1060):调用名为add的函数,该函数可能位于004c1060地址处。
  6. 处理函数返回值

    • mov dword ptr [ebp-0Ch],eax:将add函数的返回值存储在ebp-0Ch指向的局部变量中。
  7. 异常处理准备

    • push offset statckTest!GS_ExceptionPointers+0x8:将GS_ExceptionPointers+0x8的地址压入栈中,可能是为了异常处理。
    • push dword ptr [ebp-0Ch]:将先前存储的add函数的返回值压入栈中。
    • push offset statckTest!std::_Fake_alloc+0x1:将std::_Fake_alloc+0x1的地址压入栈中,这可能与异常处理相关。
  8. 输出到控制台

    • mov eax,dword ptr [statckTest!_imp_?coutstd (004c306c)]:获取std::cout的地址,并将其存储在eax寄存器中。
    • push eax:将std::cout的地址压入栈中。
    • call statckTest!std::operator<<<std::char_traits<char> > (004c1320):调用std::operator<<来输出一个值到std::cout
    • 同样的操作重复了一次,可能是为了输出两个不同的值。
  9. 清理栈

    • add esp,8:调整栈指针,清理之前压入栈的参数。
  10. 函数结束

    • xor eax,eax:将eax寄存器清零,这通常是函数返回前的一个操作。
    • mov esp,ebp 和 pop ebp:恢复栈指针和基指针。
    • ret:从函数返回。

add 函数的汇编代码 

   18 004c1060 55              push    ebp
   18 004c1061 8bec            mov     ebp,esp
   19 004c1063 8b4508          mov     eax,dword ptr [ebp+8]
   19 004c1066 03450c          add     eax,dword ptr [ebp+0Ch]
   20 004c1069 5d              pop     ebp
   20 004c106a c3              ret

这段汇编代码是一个简单的函数,它执行以下操作:

  1. push ebp 和 mov ebp,esp:保存当前的栈指针(esp)到基指针(ebp),并设置ebp为当前栈指针的值。这是函数调用的标准开头,用于设置函数的局部变量和参数的基础地址。

  2. mov eax,dword ptr [ebp+8]:从ebp+8的地址处取出一个双字(32位)值,并将其存储到eax寄存器中。这通常意味着函数接收一个整数参数,该参数在栈上的位置是ebp+8

  3. add eax,dword ptr [ebp+0Ch]:从ebp+0Ch的地址处取出另一个双字值,并将其加到eax寄存器中的当前值上。这通常意味着函数接收另一个整数参数,该参数在栈上的位置是ebp+0Ch

  4. pop ebp:恢复基指针ebp的原始值。

  5. ret:从函数返回。由于eax寄存器现在包含两个参数的和,这通常意味着函数返回这两个参数的和作为结果。

函数调用过程的栈帧分布:

从main函数的汇编中我们可以看到调用函数add 的过程, 下面两句给局部变量在栈中分配内存

   12 004c1006 c745f803000000  mov     dword ptr [ebp-8],3
   13 004c100d c745fc04000000  mov     dword ptr [ebp-4],4

从中可以看到a 放在ebp-8 和b 放在ebp -4 这个地方。 


eax=765712f0 ebx=00c9d000 ecx=00000000 edx=00000000 esi=011fa0c8 edi=011fea68
eip=004c1006 esp=00f6fd70 ebp=00f6fd7c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202

从中可以看到ebp=00f6fd7c,esp=00f6fd70,分配了12个字节的空间。

0:000> t
eax=00000004 ebx=00c9d000 ecx=00000000 edx=00000000 esi=011fa0c8 edi=011fea68
eip=004c1017 esp=00f6fd70 ebp=00f6fd7c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
statckTest!main+0x17:
004c1017 50              push    eax
0:000> t
eax=00000004 ebx=00c9d000 ecx=00000000 edx=00000000 esi=011fa0c8 edi=011fea68
eip=004c1018 esp=00f6fd6c ebp=00f6fd7c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
statckTest!main+0x18:
004c1018 8b4df8          mov     ecx,dword ptr [ebp-8] ss:002b:00f6fd74=00000003
0:000> t
eax=00000004 ebx=00c9d000 ecx=00000003 edx=00000000 esi=011fa0c8 edi=011fea68
eip=004c101b esp=00f6fd6c ebp=00f6fd7c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
statckTest!main+0x1b:
004c101b 51              push    ecx
0:000> t
eax=00000004 ebx=00c9d000 ecx=00000003 edx=00000000 esi=011fa0c8 edi=011fea68
eip=004c101c esp=00f6fd68 ebp=00f6fd7c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
statckTest!main+0x1c:
004c101c e83f000000      call    statckTest!add (004c1060)
0:000> dd 002b:00f6fd40
002b:00f6fd40  00f6fd60 7648abda 00000000 004c310c
002b:00f6fd50  00000000 004c1716 764a5406 00000001
002b:00f6fd60  00f6fd80 004c1726 00000003 00000004
002b:00f6fd70  00c9d000 00000003 00000004 00f6fdc4
002b:00f6fd80  004c1822 00000001 011fa0c8 011fea68
002b:00f6fd90  a7b8e129 004c18aa 004c18aa 00c9d000
002b:00f6fda0  00000000 00000000 00000000 00f6fd90
002b:00f6fdb0  00000000 00f6fe20 004c1fb5 a7022ab5

调用add 函数之前会把参数压入栈中

   14 004c1014 8b45fc          mov     eax,dword ptr [ebp-4]
   14 004c1017 50              push    eax
   14 004c1018 8b4df8          mov     ecx,dword ptr [ebp-8]
   14 004c101b 51              push    ecx
   14 004c101c e83f000000      call    statckTest!add (004c1060)

004c101c e83f000000      call    statckTest!add (004c1060)这条指令调用之前的main 函数堆栈

 进入add 函数之后的内存布局

002b:00f6fd40  00f6fd60 7648abda 00000000 004c310c
002b:00f6fd50  00000000 004c1716 764a5406 00000001
002b:00f6fd60  00f6fd7c 004c1021 00000003 00000004
002b:00f6fd70  00c9d000 00000003 00000004 00f6fdc4
002b:00f6fd80  004c1822 00000001 011fa0c8 011fea68
002b:00f6fd90  a7b8e129 004c18aa 004c18aa 00c9d000
002b:00f6fda0  00000000 00000000 00000000 00f6fd90
002b:00f6fdb0  00000000 00f6fe20 004c1fb5 a7022ab5

 

 函数的返回地址是 14 004c1021 83c408          add     esp,8 正好是add 函数调用完成之后下一条汇编的地址。

   14 004c1021 83c408          add     esp,8
   14 004c1024 8945f4          mov     dword ptr [ebp-0Ch],eax

上面两条是内存回收和将add 函数的返回值存贮在002b:00f6fd70 这个地址上面。

002b:00f6fd40  00f6fd60 7648abda 00000000 004c310c
002b:00f6fd50  00000000 004c1716 764a5406 00000001
002b:00f6fd60  00f6fd7c 004c1021 00000003 00000004
002b:00f6fd70  00000007 00000003 00000004 00f6fdc4
002b:00f6fd80  004c1822 00000001 011fa0c8 011fea68
002b:00f6fd90  a7b8e129 004c18aa 004c18aa 00c9d000
002b:00f6fda0  00000000 00000000 00000000 00f6fd90
002b:00f6fdb0  00000000 00f6fe20 004c1fb5 a7022ab5

完成之后函数的ebp 和esp 恢复到之前的状态。

本文简单介绍了c 语言函数调用过程中的内存布局,在我们分析dump文件或者实时查看内存布局的时候可以使用本文的方法。

备注:如果生成的exe 无法添加函数断点请在工程配置中设置如下(vistual studio)

  • 30
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值