zoukankan      html  css  js  c++  java
  • 汇编看C函数调用

    http://blog.csdn.net/wishfly/article/details/5022008

     
    简单的函数调用,通过简单的函数调用反汇编可以清楚了解如下

    1.栈到底是什么,如何操纵栈的?

    2.参数和临时变量是以什么形式在哪存放?

    3.如何传递返回值?

    测试代码如下(这是一个简单的通过调用函数计算两数之和的程序):

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    #include <stdio.h>

    int add(int a, int b)
    {
        int c = 0;
        c = a + b;
        return c;
    }

    int main(void)
    {
        int x = 0;
        int y = 3;
        int z = 4;
        x = add(y, z);
        return 0;
    }
     
     
    解释如下
     
     ASM Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
     
    add函数
    {
    ;ebp=1000,esp=900
        0040D750   push        ebp

    ;把main函数的ebp压栈,ebp=1000,esp=896
        0040D751   mov         ebpesp

    ;得到“新”栈基址,这里的新的意思是每个函数访问属于自己的一块栈区域,其实是相邻的内存区域,或者说栈只有一个
        ;ebp=896,esp=896
        0040D753   sub         esp, 44h

    ;ebp=896,esp=828
        0040D756   push        ebx
        0040D757   
    push        esi
        0040D758   
    push        edi

    ;ebp=896,esp=816
        0040D759   lea         edi, [ebp - 44h]
        0040D75C   
    mov         ecx, 11h
        0040D761   
    mov         eax, 0CCCCCCCCh
        0040D766   
    rep stos    dword ptr [edi]

    ;初始化内部变量区
    5:  int c = 0;
        0040D768   mov         dword ptr [ebp - 4], 0

    ;c放入“新”栈基址
        ;因为“新”栈基地址就是“旧”栈顶地址,所以通过ebp访问传过来的参数
        ;ebp+8到ebp+c是因为ebp上方的8字节用于存储调用函数的调用地址和“旧”堆栈基地址了
        ;ebp-4则位于新栈中
    6:  c = a + b;
        0040D76F   mov         eaxdword ptr [ebp + 8]
        0040D772   
    add         eaxdword ptr [ebp + 0Ch]

    ;运算结果放入c中
        0040D775   mov         dword ptr [ebp - 4], eax

    ;用寄存器eax返回结果
    7:  return c;
        0040D778   mov         eaxdword ptr [ebp - 4]

    ;恢复寄存器的值,ebp=896,esp=828
    8:  }
    0040D77B   
    pop         edi
    0040D77C   
    pop         esi
    0040D77D   
    pop         ebx

    ;恢复“旧”栈顶地址,ebp=896,esp=896,此函数堆栈被释放!
    0040D77E   mov         espebp

    ;恢复“旧”栈基地址,ebp=1000,esp=900,此时恢复到调用前的栈基地址和顶地址
    0040D780   pop         ebp

    ;返回调用点,ebp=1000,esp=904,调用点地址被弹出,返回到调用点
    0040D781   ret



    main函数

    {
    ;用栈顶地址作为栈基地址,目的是不和调用前栈空间冲突
        ;为了叙述方便假设原 ebp=1004,esp=1004
        ;执行完下面两行汇编语句后 ebp=1000,esp=1000
        0040D790   push        ebp
        0040D791   
    mov         ebpesp

    ;esp下移,开辟出0x4C字节的空间,这个空间是留给内部参数用的,这个空间的大小随内部变量多少由编译器决定。
        ;ebp=1000,esp=1000-0x4C=924
        0040D793   sub         esp, 4Ch

    ;保存 ebx,esi,edi的值,ebp=1000,esp=924-12=912
        0040D796   push        ebx
        0040D797   
    push        esi
        0040D798   
    push        edi

    ;把内部参数占用的空间每个字节都初始化为0xCC,这个是为了在DUBUG程序的方便,编译器加入的
        ;如果不在DEBUG状态下,这个区域是没有被初始化的,也就是说是随机值。
        0040D799   lea         edi, [ebp - 4Ch]
        0040D79C   
    mov         ecx, 13h
        0040D7A1   
    mov         eax, 0CCCCCCCCh

        0040D7A6   
    rep stos    dword ptr [edi]

    ;内部变量放入刚才被初始化为0xCC的内部变量区,x占用四字节在地址9996-9999,y,z一次类推
    12int x = 0;
        0040D7A8   mov         dword ptr [ebp - 4], 0
    13int y = 3;
        0040D7AF   mov         dword ptr [ebp - 8], 3
    14int z = 4;
        0040D7B6   mov         dword ptr [ebp - 0Ch], 4

    ;把参数按照stdcall方式从右到左压栈,ebp=1000,esp=912-8=904,z首先入栈在908-911,y在904-907
    15: x = add(y, z);
        0040D7BD   mov         eaxdword ptr [ebp - 0Ch]
        0040D7C0   
    push        eax
        0040D7C1   
    mov         ecxdword ptr [ebp - 8]
        0040D7C4   
    push        ecx

    ;把返回下一行代码即 0040D7CA [add esp,8] 的地址压栈
        ;转向add函数,ebp=1000,esp=900,看add函数
        0040D7C5   call        @ILT + 15(_add) (00401014)

    ;ebp=1000,esp=912,恢复到压入参数前栈基地址和顶地址,这个步骤叫做堆栈修正
        0040D7CA   add         esp8

    ;返回的变量放到x中
        0040D7CD   mov         dword ptr [ebp - 4], eax

    16: return 0;
    17: }
     
    main函数堆栈变化示意图:
    add函数堆栈变化示意图:

    现在来总结开始提出的三个问题

    1.栈到底是什么,如何操纵栈的?

       栈是操作系统分配给程序运行的一块内存区域,有以下特点:

       1.1、改变堆栈用push, pop,用的esp栈顶指针,而读指针则用ebp栈基指针灵活访问

       1.2、每当一个函数跳转到另一个函数时,会在上一个函数用到的栈空间下方开辟空间

    2.参数和临时变量是以什么形式在哪存放?

       2.1、参数放在旧栈的返回地址和旧栈基地址的上方而临时变量则在新栈的最上方处,变量名会被编译器连接一个地址,程序在被编译成汇编以后,变量名就是虚无了。

    3.如何传递返回值?

       3.1、传递一个值的情况下,通过eax传递

    可以看出,栈溢出是由于编译器没有检查栈是否还有空间。

     





    附件列表

  • 相关阅读:
    python爬虫专栏学习
    Scrapy学习笔记
    《The Python Standard Library》——http模块阅读笔记2
    《The Python Standard Library》——http模块阅读笔记1
    《The Python Tutorial》——Errors and Exceptions 阅读笔记
    web服务器架构演化及所其技术知识体系(分布式的由来)
    django学习笔记——搭建博客网站
    《计算机系统要素》学习笔记
    python查看模块版本及所在文件夹
    网络编程---笔记2
  • 原文地址:https://www.cnblogs.com/fengkang1008/p/4732284.html
Copyright © 2011-2022 走看看