第15课 缔造程序兼容的合约(下)

1. ABI规定桢栈结构

(1)栈帧的内存布局(以Linux为例)

(2)栈帧的形成方式

(3)栈帧的销毁方式

2. ebp寄存器

(1)ebp为当前栈帧的基准(存储上一个栈帧的ebp值

(2)通过ebp能够获取返回值地址、参数和局部变量等。

3. 函数调用发生时的细节

(1)调用者通过call指令调用函数,将返回地址压入栈中

(2)函数所需要的栈空间大小由编译器确定,表现为字面常量

(3)函数结束时,leave指令恢复上一个栈帧的esp和ebp(mov ebp, esp; pop ebp

(4)函数返回时,ret指令将返回地址恢复到eip寄存器(pop eip)

【小贴士】GDB:info frame命令输出

【编程实验】函数栈帧结构初探

//frame.c

#include <stdio.h>

#define PRINT_STACK_FRAME_INFO() do                        \
{                                                              char* ebp = NULL;                                          char* esp = NULL;                                                                                                                                                                asm volatile (                                                 "movl %%ebp, %0\n"                                         "movl %%esp, %1\n"                                         : "=r"(ebp), "=r"(esp)                                     );                                                                                                               printf("ebp = %p\n", ebp);                                 printf("previous ebp = 0x%x\n", *((int*)ebp));             printf("return address = 0x%x\n", *((int*)(ebp + 4)));     printf("previous esp = %p\n", ebp + 8);                    printf("esp = %p\n", esp);                                 printf("&ebp = %p\n", &ebp);                               printf("&esp = %p\n", &esp);                            } while(0)

void test(int a, int b)
{
    int c = 3;

    printf("test() : \n");

    PRINT_STACK_FRAME_INFO();

    printf("&a = %p\n", &a);
    printf("&b = %p\n", &b);
    printf("&c = %p\n", &c);
}

void func()
{
    int a = 1;
    int b = 2;

    printf("func() : \n");

    PRINT_STACK_FRAME_INFO();

    printf("&a = %p\n", &a);
    printf("&b = %p\n", &b);

    test(a, b);
}

int main()
{
    printf("main() : \n");

    PRINT_STACK_FRAME_INFO();

    func();

    return 0;
}

/*调试结果
[email protected]:~$ gdb
(gdb) shell gcc -g test.c -o test.out  //-g加上调试信息
(gdb) file test.out       //载入test.out文件
(gdb) start
(gdb) break test.c : 35   //在第35行下断点
(gdb) info breakpoints    //查看断点信息
(gdb) continue            //继续执行
Continuing.
main() :
ebp = 0xbffff2d8
previous ebp = 0xbffff358
return address = 0x145ce7
previous esp = 0xbffff2e0
esp = 0xbffff2b0
&ebp = 0xbffff2cc
&esp = 0xbffff2c8
func() :
ebp = 0xbffff2a8
previous ebp = 0xbffff2d8
return address = 0x80486d6
previous esp = 0xbffff2b0
esp = 0xbffff280
&ebp = 0xbffff294
&esp = 0xbffff290
&a = 0xbffff29c
&b = 0xbffff298
test() :
ebp = 0xbffff278
previous ebp = 0xbffff2a8
return address = 0x8048601
previous esp = 0xbffff280
esp = 0xbffff250
&ebp = 0xbffff268
&esp = 0xbffff264
&a = 0xbffff280
&b = 0xbffff284
&c = 0xbffff26c

(gdb) info frame  //查看当前栈桢信息
Stack level 0, frame at 0xbffff280:
 eip = 0x80484f7 in test (test.c:35); saved eip 0x8048601
 called by frame at 0xbffff2b0
 source language c.
 Arglist at 0xbffff278, args: a=1, b=2
 Locals at 0xbffff278, Previous frame‘s sp is 0xbffff280
 Saved registers:
  ebp at 0xbffff278, eip at 0xbffff27c
(gdb) x /lwx 0xbffff27c    //查看0xbffff27c内存单元的内容
0xbffff27c:    0x08048601 //保留着调用者的eip,即函数返回地址
(gdb) x /1wx 0xbffff280    //查看第1个参数(a)
0xbffff280:    0x00000001
(gdb) x /1wx 0xbffff284    //查看第2个参数
0xbffff284:    0x00000002
(gdb) shell objdump -S test.out > test.s //反汇编输出到test.s文件
(gdb)
*/

//frame.s ——反汇编结果分析

test.out:     file format elf32-i38

void test(int a, int b)
{
 80483f4:    55                       push   %ebp
 80483f5:    89 e5                    mov    %esp,%ebp
 80483f7:    83 ec 28                 sub    $0x28,%esp
    int c = 3;
 80483fa:    c7 45 f4 03 00 00 00     movl   $0x3,-0xc(%ebp)

    printf("test() : \n");
 8048401:    c7 04 24 a0 87 04 08     movl   $0x80487a0,(%esp)
 8048408:    e8 17 ff ff ff           call   8048324 <[email protected]>

    PRINT_STACK_FRAME_INFO();
 804840d:    c7 45 f0 00 00 00 00     movl   $0x0,-0x10(%ebp)
 8048414:    c7 45 ec 00 00 00 00     movl   $0x0,-0x14(%ebp)
 804841b:    89 ea                    mov    %ebp,%edx
 804841d:    89 e0                    mov    %esp,%eax
 804841f:    89 55 f0                 mov    %edx,-0x10(%ebp)
 8048422:    89 45 ec                 mov    %eax,-0x14(%ebp)
 8048425:    8b 55 f0                 mov    -0x10(%ebp),%edx
 8048428:    b8 aa 87 04 08           mov    $0x80487aa,%eax
 804842d:    89 54 24 04              mov    %edx,0x4(%esp)
 8048431:    89 04 24                 mov    %eax,(%esp)
 8048434:    e8 db fe ff ff           call   8048314 <[email protected]>
 8048439:    8b 45 f0                 mov    -0x10(%ebp),%eax
 804843c:    8b 10                    mov    (%eax),%edx
 804843e:    b8 b4 87 04 08           mov    $0x80487b4,%eax
 8048443:    89 54 24 04              mov    %edx,0x4(%esp)
 8048447:    89 04 24                 mov    %eax,(%esp)
 804844a:    e8 c5 fe ff ff           call   8048314 <[email protected]>
 804844f:    8b 45 f0                 mov    -0x10(%ebp),%eax
 8048452:    83 c0 04                 add    $0x4,%eax
 8048455:    8b 10                    mov    (%eax),%edx
 8048457:    b8 c9 87 04 08           mov    $0x80487c9,%eax
 804845c:    89 54 24 04              mov    %edx,0x4(%esp)
 8048460:    89 04 24                 mov    %eax,(%esp)
 8048463:    e8 ac fe ff ff           call   8048314 <[email protected]>
 8048468:    8b 45 f0                 mov    -0x10(%ebp),%eax
 804846b:    8d 50 08                 lea    0x8(%eax),%edx
 804846e:    b8 e0 87 04 08           mov    $0x80487e0,%eax
 8048473:    89 54 24 04              mov    %edx,0x4(%esp)
 8048477:    89 04 24                 mov    %eax,(%esp)
 804847a:    e8 95 fe ff ff           call   8048314 <[email protected]>
 804847f:    8b 55 ec                 mov    -0x14(%ebp),%edx
 8048482:    b8 f3 87 04 08           mov    $0x80487f3,%eax
 8048487:    89 54 24 04              mov    %edx,0x4(%esp)
 804848b:    89 04 24                 mov    %eax,(%esp)
 804848e:    e8 81 fe ff ff           call   8048314 <[email protected]>
 8048493:    b8 fd 87 04 08           mov    $0x80487fd,%eax
 8048498:    8d 55 f0                 lea    -0x10(%ebp),%edx
 804849b:    89 54 24 04              mov    %edx,0x4(%esp)
 804849f:    89 04 24                 mov    %eax,(%esp)
 80484a2:    e8 6d fe ff ff           call   8048314 <[email protected]>
 80484a7:    b8 08 88 04 08           mov    $0x8048808,%eax
 80484ac:    8d 55 ec                 lea    -0x14(%ebp),%edx
 80484af:    89 54 24 04              mov    %edx,0x4(%esp)
 80484b3:    89 04 24                 mov    %eax,(%esp)
 80484b6:    e8 59 fe ff ff           call   8048314 <[email protected]>

    printf("&a = %p\n", &a);
 80484bb:    b8 13 88 04 08           mov    $0x8048813,%eax
 80484c0:    8d 55 08                 lea    0x8(%ebp),%edx
 80484c3:    89 54 24 04              mov    %edx,0x4(%esp)
 80484c7:    89 04 24                 mov    %eax,(%esp)
 80484ca:    e8 45 fe ff ff           call   8048314 <[email protected]>
    printf("&b = %p\n", &b);
 80484cf:    b8 1c 88 04 08           mov    $0x804881c,%eax
 80484d4:    8d 55 0c                 lea    0xc(%ebp),%edx
 80484d7:    89 54 24 04              mov    %edx,0x4(%esp)
 80484db:    89 04 24                 mov    %eax,(%esp)
 80484de:    e8 31 fe ff ff           call   8048314 <[email protected]>
    printf("&c = %p\n", &c);
 80484e3:    b8 25 88 04 08           mov    $0x8048825,%eax
 80484e8:    8d 55 f4                 lea    -0xc(%ebp),%edx
 80484eb:    89 54 24 04              mov    %edx,0x4(%esp)
 80484ef:    89 04 24                 mov    %eax,(%esp)
 80484f2:    e8 1d fe ff ff           call   8048314 <[email protected]>
}
 80484f7:    c9                       leave
 80484f8:    c3                       ret    

080484f9 <func>:

void func()
{
 80484f9:    55                       push   %ebp
 80484fa:    89 e5                    mov    %esp,%ebp
 80484fc:    83 ec 28                 sub    $0x28,%esp
    int a = 1;
 80484ff:    c7 45 f4 01 00 00 00     movl   $0x1,-0xc(%ebp)
    int b = 2;
 8048506:    c7 45 f0 02 00 00 00     movl   $0x2,-0x10(%ebp)

    printf("func() : \n");
 804850d:    c7 04 24 2e 88 04 08     movl   $0x804882e,(%esp)
 8048514:    e8 0b fe ff ff           call   8048324 <[email protected]>

    PRINT_STACK_FRAME_INFO();
 8048519:    c7 45 ec 00 00 00 00     movl   $0x0,-0x14(%ebp)
 8048520:    c7 45 e8 00 00 00 00     movl   $0x0,-0x18(%ebp)
 8048527:    89 ea                    mov    %ebp,%edx
 8048529:    89 e0                    mov    %esp,%eax
 804852b:    89 55 ec                 mov    %edx,-0x14(%ebp)
 804852e:    89 45 e8                 mov    %eax,-0x18(%ebp)
 8048531:    8b 55 ec                 mov    -0x14(%ebp),%edx
 8048534:    b8 aa 87 04 08           mov    $0x80487aa,%eax
 8048539:    89 54 24 04              mov    %edx,0x4(%esp)
 804853d:    89 04 24                 mov    %eax,(%esp)
 8048540:    e8 cf fd ff ff           call   8048314 <[email protected]>
 8048545:    8b 45 ec                 mov    -0x14(%ebp),%eax
 8048548:    8b 10                    mov    (%eax),%edx
 804854a:    b8 b4 87 04 08           mov    $0x80487b4,%eax
 804854f:    89 54 24 04              mov    %edx,0x4(%esp)
 8048553:    89 04 24                 mov    %eax,(%esp)
 8048556:    e8 b9 fd ff ff           call   8048314 <[email protected]>
 804855b:    8b 45 ec                 mov    -0x14(%ebp),%eax
 804855e:    83 c0 04                 add    $0x4,%eax
 8048561:    8b 10                    mov    (%eax),%edx
 8048563:    b8 c9 87 04 08           mov    $0x80487c9,%eax
 8048568:    89 54 24 04              mov    %edx,0x4(%esp)
 804856c:    89 04 24                 mov    %eax,(%esp)
 804856f:    e8 a0 fd ff ff           call   8048314 <[email protected]>
 8048574:    8b 45 ec                 mov    -0x14(%ebp),%eax
 8048577:    8d 50 08                 lea    0x8(%eax),%edx
 804857a:    b8 e0 87 04 08           mov    $0x80487e0,%eax
 804857f:    89 54 24 04              mov    %edx,0x4(%esp)
 8048583:    89 04 24                 mov    %eax,(%esp)
 8048586:    e8 89 fd ff ff           call   8048314 <[email protected]>
 804858b:    8b 55 e8                 mov    -0x18(%ebp),%edx
 804858e:    b8 f3 87 04 08           mov    $0x80487f3,%eax
 8048593:    89 54 24 04              mov    %edx,0x4(%esp)
 8048597:    89 04 24                 mov    %eax,(%esp)
 804859a:    e8 75 fd ff ff           call   8048314 <[email protected]>
 804859f:    b8 fd 87 04 08           mov    $0x80487fd,%eax
 80485a4:    8d 55 ec                 lea    -0x14(%ebp),%edx
 80485a7:    89 54 24 04              mov    %edx,0x4(%esp)
 80485ab:    89 04 24                 mov    %eax,(%esp)
 80485ae:    e8 61 fd ff ff           call   8048314 <[email protected]>
 80485b3:    b8 08 88 04 08           mov    $0x8048808,%eax
 80485b8:    8d 55 e8                 lea    -0x18(%ebp),%edx
 80485bb:    89 54 24 04              mov    %edx,0x4(%esp)
 80485bf:    89 04 24                 mov    %eax,(%esp)
 80485c2:    e8 4d fd ff ff           call   8048314 <[email protected]>

    printf("&a = %p\n", &a);
 80485c7:    b8 13 88 04 08           mov    $0x8048813,%eax
 80485cc:    8d 55 f4                 lea    -0xc(%ebp),%edx
 80485cf:    89 54 24 04              mov    %edx,0x4(%esp)
 80485d3:    89 04 24                 mov    %eax,(%esp)
 80485d6:    e8 39 fd ff ff           call   8048314 <[email protected]>
    printf("&b = %p\n", &b);
 80485db:    b8 1c 88 04 08           mov    $0x804881c,%eax
 80485e0:    8d 55 f0                 lea    -0x10(%ebp),%edx
 80485e3:    89 54 24 04              mov    %edx,0x4(%esp)
 80485e7:    89 04 24                 mov    %eax,(%esp)
 80485ea:    e8 25 fd ff ff           call   8048314 <[email protected]>

    test(a, b);
 80485ef:    8b 55 f0                 mov    -0x10(%ebp),%edx
 80485f2:    8b 45 f4                 mov    -0xc(%ebp),%eax
 80485f5:    89 54 24 04              mov    %edx,0x4(%esp)
 80485f9:    89 04 24                 mov    %eax,(%esp)
 80485fc:    e8 f3 fd ff ff           call   80483f4 <test>
}
 8048601:    c9                       leave
 8048602:    c3                       ret    

08048603 <main>:

int main()
{
 8048603:    55                       push   %ebp
 8048604:    89 e5                    mov    %esp,%ebp
 8048606:    83 e4 f0                 and    $0xfffffff0,%esp
 8048609:    83 ec 20                 sub    $0x20,%esp
    printf("main() : \n");
 804860c:    c7 04 24 38 88 04 08     movl   $0x8048838,(%esp)
 8048613:    e8 0c fd ff ff           call   8048324 <[email protected]>

    PRINT_STACK_FRAME_INFO();
 8048618:    c7 44 24 1c 00 00 00     movl   $0x0,0x1c(%esp)
 804861f:    00
 8048620:    c7 44 24 18 00 00 00     movl   $0x0,0x18(%esp)
 8048627:    00
 8048628:    89 ea                    mov    %ebp,%edx
 804862a:    89 e0                    mov    %esp,%eax
 804862c:    89 54 24 1c              mov    %edx,0x1c(%esp)
 8048630:    89 44 24 18              mov    %eax,0x18(%esp)
 8048634:    8b 54 24 1c              mov    0x1c(%esp),%edx
 8048638:    b8 aa 87 04 08           mov    $0x80487aa,%eax
 804863d:    89 54 24 04              mov    %edx,0x4(%esp)
 8048641:    89 04 24                 mov    %eax,(%esp)
 8048644:    e8 cb fc ff ff           call   8048314 <[email protected]>
 8048649:    8b 44 24 1c              mov    0x1c(%esp),%eax
 804864d:    8b 10                    mov    (%eax),%edx
 804864f:    b8 b4 87 04 08           mov    $0x80487b4,%eax
 8048654:    89 54 24 04              mov    %edx,0x4(%esp)
 8048658:    89 04 24                 mov    %eax,(%esp)
 804865b:    e8 b4 fc ff ff           call   8048314 <[email protected]>
 8048660:    8b 44 24 1c              mov    0x1c(%esp),%eax
 8048664:    83 c0 04                 add    $0x4,%eax
 8048667:    8b 10                    mov    (%eax),%edx
 8048669:    b8 c9 87 04 08           mov    $0x80487c9,%eax
 804866e:    89 54 24 04              mov    %edx,0x4(%esp)
 8048672:    89 04 24                 mov    %eax,(%esp)
 8048675:    e8 9a fc ff ff           call   8048314 <[email protected]>
 804867a:    8b 44 24 1c              mov    0x1c(%esp),%eax
 804867e:    8d 50 08                 lea    0x8(%eax),%edx
 8048681:    b8 e0 87 04 08           mov    $0x80487e0,%eax
 8048686:    89 54 24 04              mov    %edx,0x4(%esp)
 804868a:    89 04 24                 mov    %eax,(%esp)
 804868d:    e8 82 fc ff ff           call   8048314 <[email protected]>
 8048692:    8b 54 24 18              mov    0x18(%esp),%edx
 8048696:    b8 f3 87 04 08           mov    $0x80487f3,%eax
 804869b:    89 54 24 04              mov    %edx,0x4(%esp)
 804869f:    89 04 24                 mov    %eax,(%esp)
 80486a2:    e8 6d fc ff ff           call   8048314 <[email protected]>
 80486a7:    b8 fd 87 04 08           mov    $0x80487fd,%eax
 80486ac:    8d 54 24 1c              lea    0x1c(%esp),%edx
 80486b0:    89 54 24 04              mov    %edx,0x4(%esp)
 80486b4:    89 04 24                 mov    %eax,(%esp)
 80486b7:    e8 58 fc ff ff           call   8048314 <[email protected]>
 80486bc:    b8 08 88 04 08           mov    $0x8048808,%eax
 80486c1:    8d 54 24 18              lea    0x18(%esp),%edx
 80486c5:    89 54 24 04              mov    %edx,0x4(%esp)
 80486c9:    89 04 24                 mov    %eax,(%esp)
 80486cc:    e8 43 fc ff ff           call   8048314 <[email protected]>

    func();
 80486d1:    e8 23 fe ff ff           call   80484f9 <func>

    return 0;
 80486d6:    b8 00 00 00 00           mov    $0x0,%eax
}
 80486db:    c9                       leave
 80486dc:    c3                       ret    

4. 常见的函数调用约定


调用约定


作用


备注


__cdecl__


①参数从右向左入栈

②函数的调用者负责将参数弹出栈

③返回值保存在eax寄存器


只有使用这种调用约定才支持可变参数定义


__stdcall__


①参数从右向左入栈

②被调函数清理栈中的参数

③返回值保存在eax寄存器


__fastcall__


①使用ecx和edx传递前两个参数,剩余的参数从右向左入栈

②被调函数清理栈中的参数

③返回值保存在eax寄存器中


__thiscall__


c++成员函数的调用约定

②参数从右至左的方式入栈

③返回值保存在eax寄存器


①如果参数确定,this指针存放于ecx寄存器,函数自身清理栈中的参数

②如果参数不确定,this指针在所有参数入栈后再入栈,同时由调用者清栈

③当参数为可变参数时,自动变为__cdecl__

【编程实验】函数调用约定

//convention.c

#include <stdio.h>

int test(int a, int b, int c)
{
    return a +  b + c;
}

//三方不同的调用约定,反汇编观察参数入栈、清栈者等信息。
void __attribute__((__cdecl__)) func_1(int a, int b)
{
}

void __attribute__((__stdcall__)) func_2(int a, int b)
{
}

void __attribute__((__fastcall__)) func_3(int a, int b)
{
}

int main()
{
    int a = 1;
    int b = 2;

    int r = test(1, 2, 3);  //反汇编,观察函数的返回值(存放于eax中)

    func_1(a, b);

    func_2(a, b);

    func_3(a, b);

    printf("r = %d\n", r);

    return 0;
}

//convention.s——反汇编结果分析

demo.out:     file format elf32-i386

080483c4 <test>:
#include <stdio.h>

int test(int a, int b, int c)
{
 80483c4:    55                       push   %ebp
 80483c5:    89 e5                    mov    %esp,%ebp
    return a +  b + c;
 80483c7:    8b 45 0c                 mov    0xc(%ebp),%eax
 80483ca:    8b 55 08                 mov    0x8(%ebp),%edx
 80483cd:    8d 04 02                 lea    (%edx,%eax,1),%eax
 80483d0:    03 45 10                 add    0x10(%ebp),%eax
}
 80483d3:    5d                       pop    %ebp
 80483d4:    c3                       ret    

080483d5 <func_1>:

//三方不同的调用约定,反汇编观察参数入栈、清栈者等信息。
void __attribute__((__cdecl__)) func_1(int a, int b)
{
 80483d5:    55                       push   %ebp
 80483d6:    89 e5                    mov    %esp,%ebp
}
 80483d8:    5d                       pop    %ebp
 80483d9:    c3                       ret    

080483da <func_2>:

void __attribute__((__stdcall__)) func_2(int a, int b)
{
 80483da:    55                       push   %ebp
 80483db:    89 e5                    mov    %esp,%ebp
}
 80483dd:    5d                       pop    %ebp
 80483de:    c2 08 00                 ret    $0x8

080483e1 <func_3>:

void __attribute__((__fastcall__)) func_3(int a, int b)
{
 80483e1:    55                       push   %ebp
 80483e2:    89 e5                    mov    %esp,%ebp
 80483e4:    83 ec 08                 sub    $0x8,%esp
 80483e7:    89 4d fc                 mov    %ecx,-0x4(%ebp)
 80483ea:    89 55 f8                 mov    %edx,-0x8(%ebp)
}
 80483ed:    c9                       leave
 80483ee:    c3                       ret    

080483ef <main>:

int main()
{
 80483ef:    8d 4c 24 04              lea    0x4(%esp),%ecx
 80483f3:    83 e4 f0                 and    $0xfffffff0,%esp
 80483f6:    ff 71 fc                 pushl  -0x4(%ecx)
 80483f9:    55                       push   %ebp
 80483fa:    89 e5                    mov    %esp,%ebp
 80483fc:    51                       push   %ecx
 80483fd:    83 ec 24                 sub    $0x24,%esp
    int a = 1;
 8048400:    c7 45 f4 01 00 00 00     movl   $0x1,-0xc(%ebp)
    int b = 2;
 8048407:    c7 45 f0 02 00 00 00     movl   $0x2,-0x10(%ebp)

    int r = test(1, 2, 3);  //反汇编,观察函数的返回值(存放于eax中)
 804840e:    c7 44 24 08 03 00 00     movl   $0x3,0x8(%esp)
 8048415:    00
 8048416:    c7 44 24 04 02 00 00     movl   $0x2,0x4(%esp)
 804841d:    00
 804841e:    c7 04 24 01 00 00 00     movl   $0x1,(%esp)
 8048425:    e8 9a ff ff ff           call   80483c4 <test>
 804842a:    89 45 ec                 mov    %eax,-0x14(%ebp)

    func_1(a, b);
 804842d:    8b 45 f0                 mov    -0x10(%ebp),%eax //参数b入栈
 8048430:    89 44 24 04              mov    %eax,0x4(%esp)
 8048434:    8b 45 f4                 mov    -0xc(%ebp),%eax  //参数a入栈
 8048437:    89 04 24                 mov    %eax,(%esp)
 804843a:    e8 96 ff ff ff           call   80483d5 <func_1>

    func_2(a, b);
 804843f:    8b 45 f0                 mov    -0x10(%ebp),%eax  //参数b入栈
 8048442:    89 44 24 04              mov    %eax,0x4(%esp)
 8048446:    8b 45 f4                 mov    -0xc(%ebp),%eax   //参数a入栈
 8048449:    89 04 24                 mov    %eax,(%esp)
 804844c:    e8 89 ff ff ff           call   80483da <func_2>
 8048451:    83 ec 08                 sub    $0x8,%esp

    func_3(a, b);
 8048454:    8b 55 f0                 mov    -0x10(%ebp),%edx  //用edx传参(b)
 8048457:    8b 45 f4                 mov    -0xc(%ebp),%eax
 804845a:    89 c1                    mov    %eax,%ecx        //用ecx传参(a)
 804845c:    e8 80 ff ff ff           call   80483e1 <func_3>

    printf("r = %d\n", r);
 8048461:    b8 50 85 04 08           mov    $0x8048550,%eax
 8048466:    8b 55 ec                 mov    -0x14(%ebp),%edx
 8048469:    89 54 24 04              mov    %edx,0x4(%esp)
 804846d:    89 04 24                 mov    %eax,(%esp)
 8048470:    e8 7f fe ff ff           call   80482f4 <[email protected]>

    return 0;
 8048475:    b8 00 00 00 00           mov    $0x0,%eax
}
 804847a:    8b 4d fc                 mov    -0x4(%ebp),%ecx
 804847d:    c9                       leave
 804847e:    8d 61 fc                 lea    -0x4(%ecx),%esp
 8048481:    c3                       ret    

5. 结构体类型的返回值

(1)函数调用时,接收返回值的变量地址需要入栈

(2)被调函数直接通过变量地址拷贝返回值

(3)函数返回值用于初始化变量时,会把变量地址传入栈中

(4)函数返回值用于赋值时,会生成临时变量来接收返回值

【编程实验】结构体函数返回值

//return.c

#include <stdio.h>

struct ST
{
    int x;
    int y;
    int z;
};

struct ST f(int x, int y, int z)
{
    struct ST st = {0};

    printf("f() : &st = %p\n", &st);

    st.x = x;
    st.y = y;
    st.z = z;

    return st;
}

//函数返回值用于赋值操作
void assign()
{
    struct ST st = {0};

    printf("assign() : &st = %p\n", &st);

    st = f(1, 2, 3);

    printf("assign() : &st = %p\n", &st);
    printf("assign() : st.x = %d\n", st.x);
    printf("assign() : st.y = %d\n", st.y);
    printf("assign() : st.z = %d\n", st.z);
}

//函数返回值用于初始化
void init()
{
    struct ST st = f(4, 5, 6);

    printf("init() : &st = %p\n", &st);
    printf("init() : st.x = %d\n", st.x);
    printf("init() : st.y = %d\n", st.y);
    printf("init() : st.z = %d\n", st.z);
}

int main()
{
    init();

    assign();

    return 0;
}
/*运行结果与分析
[email protected]:~$ gcc return.c
[email protected]:~$ ./a.out
f() : &st = 0xbfbe71a4
init() : &st = 0xbfbe71d4
init() : st.x = 4
init() : st.y = 5
init() : st.z = 6
assign() : &st = 0xbfbe71d4
f() : &st = 0xbfbe7194
assign() : &st = 0xbfbe71d4
assign() : st.x = 1
assign() : st.y = 2
assign() : st.z = 3
[email protected]:~$ gcc -g return.c -o return.out
[email protected]:~$ gdb
(gdb) file return.out
(gdb) start
(gdb) break return.c : 21  //在第21行下断点
(gdb) continue
Continuing.
f() : &st = 0xbffff284

Breakpoint 2, f (x=4, y=5, z=6) at return.c:21
21    }
(gdb) backtrace     //查看函数调用栈(main->init->f)
#0  f (x=4, y=5, z=6) at return.c:21
#1  0x08048506 in init () at return.c:41
#2  0x08048566 in main () at return.c:51
(gdb) frame 1       //回到init函数的栈帧
#1  0x08048506 in init () at return.c:41
41        struct ST st = f(4, 5, 6);
(gdb) info frame    //查看init函数的栈帧信息
Stack level 1, frame at 0xbffff2d0:
 eip = 0x8048506 in init (return.c:41); saved eip 0x8048566
 called by frame at 0xbffff2e0, caller of frame at 0xbffff2a0
 source language c.
 Arglist at 0xbffff2c8, args:
 Locals at 0xbffff2c8, Previous frame‘s sp is 0xbffff2d0
 Saved registers:
  ebp at 0xbffff2c8, eip at 0xbffff2cc
(gdb) print /x &st  //打印st变量的地址
$1 = 0xbffff2b4
(gdb) frame 0       //回到f函数栈帧
#0  f (x=4, y=5, z=6) at return.c:21
21    }
(gdb) info frame    //查看f函数栈帧
Stack level 0, frame at 0xbffff2a0:
 eip = 0x804841a in f (return.c:21); saved eip 0x8048506
 called by frame at 0xbffff2d0
 source language c.
 Arglist at 0xbffff298, args: x=4, y=5, z=6
 Locals at 0xbffff298, Previous frame‘s sp is 0xbffff2a0
 Saved registers:
  ebx at 0xbffff294, ebp at 0xbffff298, eip at 0xbffff29c
(gdb) x /1wx 0xbffff298+8  //查看第4个参数,传参时st地址最后一个入栈
0xbffff2a0:    0xbffff2b4 //st的地址为0xbffff2b4(注意,可见st地址会入栈!)
(gdb) continue
Continuing.
init() : &st = 0xbffff2b4
init() : st.x = 4
init() : st.y = 5
init() : st.z = 6
assign() : &st = 0xbffff2b4
f() : &st = 0xbffff274

Breakpoint 2, f (x=1, y=2, z=3) at return.c:21
21    }
(gdb) backtrace  //查看函数调用栈信息(main->assign->f)
#0  f (x=1, y=2, z=3) at return.c:21
#1  0x08048476 in assign () at return.c:30
#2  0x0804856b in main () at return.c:53
(gdb) info frame  //查看f函数的栈帧信息
Stack level 0, frame at 0xbffff290:
 eip = 0x804841a in f (return.c:21); saved eip 0x8048476
 called by frame at 0xbffff2d0
 source language c.
 Arglist at 0xbffff288, args: x=1, y=2, z=3
 Locals at 0xbffff288, Previous frame‘s sp is 0xbffff290
 Saved registers:
  ebx at 0xbffff284, ebp at 0xbffff288, eip at 0xbffff28c
(gdb) x /1wx 0xbffff288+8  //查看第4个参数,是临时变量的地址
0xbffff290:    0xbffff2a0 //临时变量的地址,而不是st的地址(0xbffff274)
(gdb)
*/

//return.s——反汇编结果分析

return.out:     file format elf32-i386

struct ST f(int x, int y, int z)
{
 80483c4:    55                       push   %ebp
 80483c5:    89 e5                    mov    %esp,%ebp
 80483c7:    53                       push   %ebx
 80483c8:    83 ec 24                 sub    $0x24,%esp
 80483cb:    8b 5d 08                 mov    0x8(%ebp),%ebx
    struct ST st = {0};
 80483ce:    c7 45 ec 00 00 00 00     movl   $0x0,-0x14(%ebp)
 80483d5:    c7 45 f0 00 00 00 00     movl   $0x0,-0x10(%ebp)
 80483dc:    c7 45 f4 00 00 00 00     movl   $0x0,-0xc(%ebp)

    printf("f() : &st = %p\n", &st);
 80483e3:    b8 40 86 04 08           mov    $0x8048640,%eax
 80483e8:    8d 55 ec                 lea    -0x14(%ebp),%edx
 80483eb:    89 54 24 04              mov    %edx,0x4(%esp)
 80483ef:    89 04 24                 mov    %eax,(%esp)
 80483f2:    e8 fd fe ff ff           call   80482f4 <[email protected]>

    st.x = x;
 80483f7:    8b 45 0c                 mov    0xc(%ebp),%eax
 80483fa:    89 45 ec                 mov    %eax,-0x14(%ebp)
    st.y = y;
 80483fd:    8b 45 10                 mov    0x10(%ebp),%eax
 8048400:    89 45 f0                 mov    %eax,-0x10(%ebp)
    st.z = z;
 8048403:    8b 45 14                 mov    0x14(%ebp),%eax
 8048406:    89 45 f4                 mov    %eax,-0xc(%ebp)

    return st;
 8048409:    8b 45 ec                 mov    -0x14(%ebp),%eax
 804840c:    89 03                    mov    %eax,(%ebx)
 804840e:    8b 45 f0                 mov    -0x10(%ebp),%eax
 8048411:    89 43 04                 mov    %eax,0x4(%ebx)
 8048414:    8b 45 f4                 mov    -0xc(%ebp),%eax
 8048417:    89 43 08                 mov    %eax,0x8(%ebx)
}
 804841a:    89 d8                    mov    %ebx,%eax
 804841c:    83 c4 24                 add    $0x24,%esp
 804841f:    5b                       pop    %ebx
 8048420:    5d                       pop    %ebp
 8048421:    c2 04 00                 ret    $0x4

08048424 <assign>:

//函数返回值用于赋值操作
void assign()
{
 8048424:    55                       push   %ebp
 8048425:    89 e5                    mov    %esp,%ebp
 8048427:    83 ec 38                 sub    $0x38,%esp
    struct ST st = {0};
 804842a:    c7 45 ec 00 00 00 00     movl   $0x0,-0x14(%ebp) //-0x14(%ebp),也是st的地址
 8048431:    c7 45 f0 00 00 00 00     movl   $0x0,-0x10(%ebp)
 8048438:    c7 45 f4 00 00 00 00     movl   $0x0,-0xc(%ebp)

    printf("assign() : &st = %p\n", &st);
 804843f:    b8 50 86 04 08           mov    $0x8048650,%eax
 8048444:    8d 55 ec                 lea    -0x14(%ebp),%edx //-0x14(%ebp),也是st的地址
 8048447:    89 54 24 04              mov    %edx,0x4(%esp)
 804844b:    89 04 24                 mov    %eax,(%esp)
 804844e:    e8 a1 fe ff ff           call   80482f4 <[email protected]>

    st = f(1, 2, 3);
 8048453:    8d 45 d8                 lea    -0x28(%ebp),%eax //-0x28(%ebp),临时变量(栈上)的地址
 8048456:    c7 44 24 0c 03 00 00     movl   $0x3,0xc(%esp)   //参数3入栈
 804845d:    00
 804845e:    c7 44 24 08 02 00 00     movl   $0x2,0x8(%esp)  //参数2入栈
 8048465:    00
 8048466:    c7 44 24 04 01 00 00     movl   $0x1,0x4(%esp)  //参数1入栈
 804846d:    00
 804846e:    89 04 24                 mov    %eax,(%esp)    //临时变量入栈
 8048471:    e8 4e ff ff ff           call   80483c4 <f>    //调用f函数!
 8048476:    83 ec 04                 sub    $0x4,%esp
 8048479:    8b 45 d8                 mov    -0x28(%ebp),%eax //将临时变量地址保存在eax
 804847c:    89 45 ec                 mov    %eax,-0x14(%ebp)  //拷贝x
 804847f:    8b 45 dc                 mov    -0x24(%ebp),%eax
 8048482:    89 45 f0                 mov    %eax,-0x10(%ebp)  //拷贝y
 8048485:    8b 45 e0                 mov    -0x20(%ebp),%eax
 8048488:    89 45 f4                 mov    %eax,-0xc(%ebp)   //拷贝z

    printf("assign() : &st = %p\n", &st);
 804848b:    b8 50 86 04 08           mov    $0x8048650,%eax
 8048490:    8d 55 ec                 lea    -0x14(%ebp),%edx
 8048493:    89 54 24 04              mov    %edx,0x4(%esp)
 8048497:    89 04 24                 mov    %eax,(%esp)
 804849a:    e8 55 fe ff ff           call   80482f4 <[email protected]>
    printf("assign() : st.x = %d\n", st.x);
 804849f:    8b 55 ec                 mov    -0x14(%ebp),%edx
 80484a2:    b8 65 86 04 08           mov    $0x8048665,%eax
 80484a7:    89 54 24 04              mov    %edx,0x4(%esp)
 80484ab:    89 04 24                 mov    %eax,(%esp)
 80484ae:    e8 41 fe ff ff           call   80482f4 <[email protected]>
    printf("assign() : st.y = %d\n", st.y);
 80484b3:    8b 55 f0                 mov    -0x10(%ebp),%edx
 80484b6:    b8 7b 86 04 08           mov    $0x804867b,%eax
 80484bb:    89 54 24 04              mov    %edx,0x4(%esp)
 80484bf:    89 04 24                 mov    %eax,(%esp)
 80484c2:    e8 2d fe ff ff           call   80482f4 <[email protected]>
    printf("assign() : st.z = %d\n", st.z);
 80484c7:    8b 55 f4                 mov    -0xc(%ebp),%edx
 80484ca:    b8 91 86 04 08           mov    $0x8048691,%eax
 80484cf:    89 54 24 04              mov    %edx,0x4(%esp)
 80484d3:    89 04 24                 mov    %eax,(%esp)
 80484d6:    e8 19 fe ff ff           call   80482f4 <[email protected]>
}
 80484db:    c9                       leave
 80484dc:    c3                       ret    

080484dd <init>:

//函数返回值用于初始化
void init()
{
 80484dd:    55                       push   %ebp
 80484de:    89 e5                    mov    %esp,%ebp
 80484e0:    83 ec 28                 sub    $0x28,%esp
    struct ST st = f(4, 5, 6);
 80484e3:    8d 45 ec                 lea    -0x14(%ebp),%eax  //取st的地址
 80484e6:    c7 44 24 0c 06 00 00     movl   $0x6,0xc(%esp)    //参数6入栈
 80484ed:    00
 80484ee:    c7 44 24 08 05 00 00     movl   $0x5,0x8(%esp)    //参数5入栈
 80484f5:    00
 80484f6:    c7 44 24 04 04 00 00     movl   $0x4,0x4(%esp)    //参数4入栈
 80484fd:    00
 80484fe:    89 04 24                 mov    %eax,(%esp)       //st地址入栈
 8048501:    e8 be fe ff ff           call   80483c4 <f>       //调用f函数!
 8048506:    83 ec 04                 sub    $0x4,%esp

    printf("init() : &st = %p\n", &st);
 8048509:    b8 a7 86 04 08           mov    $0x80486a7,%eax
 804850e:    8d 55 ec                 lea    -0x14(%ebp),%edx
 8048511:    89 54 24 04              mov    %edx,0x4(%esp)
 8048515:    89 04 24                 mov    %eax,(%esp)
 8048518:    e8 d7 fd ff ff           call   80482f4 <[email protected]>
    printf("init() : st.x = %d\n", st.x);
 804851d:    8b 55 ec                 mov    -0x14(%ebp),%edx
 8048520:    b8 ba 86 04 08           mov    $0x80486ba,%eax
 8048525:    89 54 24 04              mov    %edx,0x4(%esp)
 8048529:    89 04 24                 mov    %eax,(%esp)
 804852c:    e8 c3 fd ff ff           call   80482f4 <[email protected]>
    printf("init() : st.y = %d\n", st.y);
 8048531:    8b 55 f0                 mov    -0x10(%ebp),%edx
 8048534:    b8 ce 86 04 08           mov    $0x80486ce,%eax
 8048539:    89 54 24 04              mov    %edx,0x4(%esp)
 804853d:    89 04 24                 mov    %eax,(%esp)
 8048540:    e8 af fd ff ff           call   80482f4 <[email protected]>
    printf("init() : st.z = %d\n", st.z);
 8048545:    8b 55 f4                 mov    -0xc(%ebp),%edx
 8048548:    b8 e2 86 04 08           mov    $0x80486e2,%eax
 804854d:    89 54 24 04              mov    %edx,0x4(%esp)
 8048551:    89 04 24                 mov    %eax,(%esp)
 8048554:    e8 9b fd ff ff           call   80482f4 <[email protected]>
}
 8048559:    c9                       leave
 804855a:    c3                       ret    

0804855b <main>:

int main()
{
 804855b:    55                       push   %ebp
 804855c:    89 e5                    mov    %esp,%ebp
 804855e:    83 e4 f0                 and    $0xfffffff0,%esp
    init();
 8048561:    e8 77 ff ff ff           call   80484dd <init>

    assign();
 8048566:    e8 b9 fe ff ff           call   8048424 <assign>

    return 0;
 804856b:    b8 00 00 00 00           mov    $0x0,%eax
}
 8048570:    89 ec                    mov    %ebp,%esp
 8048572:    5d                       pop    %ebp
 8048573:    c3                       ret    

6. 小结

(1)栈帧是函数调用时形成的链式内存结构

(2)ebp是构成栈帧的核心基准寄存器

(3)调用约定决定了函数调用的细节行为,同时也定义了函数被编译后对应最终符号名

(4)基础数据类型的返回值通过eax传递

(5)结构体类型的返回值通过内存拷贝完成。

时间: 2024-11-10 08:08:35

第15课 缔造程序兼容的合约(下)的相关文章

缔造程序兼容的契约(ABI)

缔造程序兼容的契约(ABI) 1. API是什么? ABI(application binary interface),指的时应用程序二进制接口.EABI(embeded application binary interface),指的是嵌入式应用程序二进制接口,和ABI的区别在于应用程序中允许使用特权指令.ABI广义上的概率指的是应用程序在二进制层面的规范,狭义上指的是某个硬件/操作系统/虚拟机平台的规范文档. 2.ABI和 API的区别: ABI是二进制层面的规范,而API是源码层面的规范.

Spark3000门徒第15课RDD创建内幕彻底解密总结

今晚听了王家林老师的第15课RDD创建内幕彻底解密,课堂笔记如下: Spark driver中第一个RDD:代表了Spark应用程序输入数据的来源.后续通过Transformation来对RDD进行各种算子的转换实现算法 创建RDD的方法:1,使用程序中的集合创建RDD;2,使用本地文件系统创建RDD:3,使用HDFS创建RDD 4,基于DB创建RDD5,基于NoSQL,例如HBase 6,基于S3创建RDD 7,基于数据流创建RDD 不指定并行度,有多少core就用多少core,所以需要资源管

大数据Spark蘑菇云前传第15课:Scala类型参数编程实战及Spark源码鉴赏(学习笔记)

前传第15课:Scala类型参数编程实战及Spark源码鉴赏 本課課程: Spark源码中的Scala类型系統的使用 Scala类型系統编程操作实战 Spark源码中的Scala类型系統的使用 classOf[RDD[_]] 這個也是类型系統 這里的意思是說 B 這種類型必需至少是 A 這樣類型 Ordering Scala类型系統编程操作实战 作為類型系統最大的就可以對類型進行限制,在Scala 中的類型系統,他本身也作為對象.e.g. 我們可以建立 Person 這個類,現在可以建立一個什麼

C语言-第15课 - 编译过程简介

第15课  - 编译过程简介 1. 编译器做了什么 (1)预编译: l 处理所有的注释,以空格代替. l 将所有的#define删除,并且展开所有的宏定义. l 处理条件编译指令#if,#ifdef,#elif,#else,#endif. l 处理#include,展开被包含的文件. l 保留编译器需要使用的#pragma指令. 预处理指令:gcc -E file.c -o hello.i (2)编译 l 对预处理的文件进行一系列的词法分析,语法分析和语义分析: 词法分析主要分析关键字,标示符,

C++--第15课 - 惊艳的继承

第15课 - 惊艳的继承 1. 继承的概念 面向对象中的继承指类之间的父子关系,子类拥有父类的所有成员变量和成员函数.子类就是一种特殊的父类,子类对象可以当作父类对象使用,子类可以拥有父类没有的方法和属性. 如下面的程序: #include <cstdlib> #include <iostream> using namespace std; class Parent { private: int a; public: Parent() { a = 1000; } void prin

第三季-第15课-信号通讯编程

第15课-信号通讯编程 15.1 核心理论 1. 信号 在古老的战场上,信号是最有效,最直接的通讯方式:在linux系统中,信号(signal)同样也是最古老的进程间通讯机制. 2. 信号处理流程 进程A/内核---(1)选择信号-----(2)发送信号-----(3)处理信号----进程B. 3. 信号类型 Linux系统支持的所有信号均定义在/usr/include/asm/signal.h(展示),其中常见的信号有: SIGKILL:杀死进程 SIGSTOP:暂停进程 SIGCHLD:子进

masm 6.15编写的程序兼性问题

在windows7 64位系统里运行masm 6.15编写的程序出现 我们可以用Dosbox0.74 模拟器去执行此程序 首先把将f:\try挂载为c盘 然后就可以运行程序 程序已经是执行完毕了~~~~我们可以通过debug来进行分析此程序的执行状况 源文件:

windows10下安装Microsoft Visual Studio 2013.4 Community enu中文语言包出错,提示“”程序兼容模式已打开.请将其关闭”的解决方法

原来win8系统用着有点慢,就选择重新安装系统,安装了win10预览版,感觉确实很不一样,呵呵.在安装了vs2013社区版后,默认是英文版的,因为英文不太好,需要安装简体中文语言包,但是安装时一直报“程序兼容模式已打开,请将其关闭然后重新运行安装程序”, 在使用兼容win8模式安装后仍然提示这个,郁闷了好几天,后来在网上找到一个解决办法,记录如下: 使用命令提示符打开vs_langpack.exe,在后面添加 /Uninstall,类似这样D:\tools\vslang>vs_langpack.

怎样让ABBYY FineReader与其它应用程序兼容使用

ABBYY FineReader 12支持与 Microsoft Office 应用程序和 Windows 资源管理器进行集成.可以帮助大家在使用 Microsoft Outlook.Microsoft Word.Microsoft Excel 和 Windows 资源管理器时识别文档,那么ABBYY FineReader12到底是怎么和这些应用程序兼容使用的呢? 若有疑问可直接访问:http://www.abbyychina.com/FRshiyongjiqiao/fr-chengxu-jic