[ASM C/C++] C函数调用分析

  在执行程序时,操作系统为进程分配一块栈空间来保存函数栈帧,esp寄存器总是指向栈顶。x86平台上这个栈是从高地址向低地址增长的,每次调用一个函数都要分配一个栈帧来保存参数和局部变量,C函数参数是按从右到左的顺序入栈的。各个堆栈桢之间是通过把ebp和eip压栈,而串在一起的。参数和局部变量是以栈帧基址(即ebp)为准+内存偏移量来定位和存取的。

下面用GCC 和 GDB 做一下分析

--------------------------

C源代码

int TestB(int c, int *d)
{
    int e = c + (*d);
    *d = e;
    return e;
}

int TestA(int a, int *b)
{
    return TestB(a, b);
}

int main(void)
{
    int i = 5;
    int r = TestA(2, &i);
    return 0;
}

------------------------------

 编译

abeen@ubuntu:~/Documents/C$ gcc -g -o test test.c

启动GDB调试
abeen@ubuntu:~/Documents/C$ gdb test
GNU gdb (GDB) 7.0-ubuntu
...

显示源码
(gdb) list TestB
1    int TestB(int c, int *d)
2    {
3        int e = c + (*d);
4        *d = e;
5        return e;
6    }
7    
8    int TestA(int a, int *b)
9    {
10        return TestB(a, b);

设置断点,使其在TestB的"*d = e"处停止
(gdb) b 4
Breakpoint 1 at 0x80483c5: file test.c, line 4.

开始运行调试程序
(gdb) r
Starting program: /home/abeen/Documents/C/test

Breakpoint 1, TestB (c=2, d=0xbffff444) at test.c:4
4        *d = e;

此时已运到行断点处,查看调用堆栈帧信息
(gdb) bt
#0  TestB (c=2, d=0xbffff444) at test.c:4
#1  0x080483ea in TestA (a=2, b=0xbffff444) at test.c:10
#2  0x0804840c in main () at test.c:16
(gdb) info frame
Stack level 0, frame at 0xbffff420:
 eip = 0x80483c5 in TestB (test.c:4); saved eip 0x80483ea
 called by frame at 0xbffff430
 source language c.
 Arglist at 0xbffff418, args: c=2, d=0xbffff444
 Locals at 0xbffff418, Previous frame's sp is 0xbffff420
 Saved registers:
  ebp at 0xbffff418, eip at 0xbffff41c
(gdb) info frame 1
Stack frame at 0xbffff430:
 eip = 0x80483ea in TestA (test.c:10); saved eip 0x804840c
 called by frame at 0xbffff450, caller of frame at 0xbffff420
 source language c.
 Arglist at 0xbffff428, args: a=2, b=0xbffff444
 Locals at 0xbffff428, Previous frame's sp is 0xbffff430
 Saved registers:
  ebp at 0xbffff428, eip at 0xbffff42c
(gdb) info frame 2
Stack frame at 0xbffff450:
 eip = 0x804840c in main (test.c:16); saved eip 0x4c6b56
 caller of frame at 0xbffff430
 source language c.
 Arglist at 0xbffff448, args:
 Locals at 0xbffff448, Previous frame's sp is 0xbffff450
 Saved registers:
  ebp at 0xbffff448, eip at 0xbffff44c
(gdb) p $esp
$1 = (void *) 0xbffff408

查看分析得知堆栈帧内存布局范围
#0  ~~   0xbffff420
#1 0xbffff420 ~~ 0xbffff430
#2 0xbffff430 ~~ 0xbffff450 

查看此时内存情况
(gdb) x/20xw 0xbffff400
                                                                                        (ebp)        (eip)
0xbffff400:    0x005efff4    0x08049ff4                              0xbffff418    0x080482c4
0xbffff410:    0x00c72d20    0x00000007(TestB:e)             0xbffff428    0x080483ea
0xbffff420:    0x00000002(2再次压栈)    0xbffff444( i地址)    0xbffff448    0x0804840c
0xbffff430:    0x00000002( 2压栈)    0xbffff444( i地址)         0x0804843b    0x005efff4
0xbffff440:    0x08048430    0x00000005( i = 5)                 0xbffff4c8    0x004c6b56
              
继续执行完(*d = e)
(gdb) s
5        return e;
(gdb) x/20xw 0xbffff400
                                                                                        (ebp)        (eip)
0xbffff400:    0x005efff4    0x08049ff4                              0xbffff418    0x080482c4
0xbffff410:    0x00c72d20    0x00000007(TestB:e)             0xbffff428    0x080483ea
0xbffff420:    0x00000002(2再次压栈)    0xbffff444( i地址)    0xbffff448    0x0804840c
0xbffff430:    0x00000002( 2压栈)    0xbffff444( i地址)         0x0804843b    0x005efff4
0xbffff440:    0x08048430    0x00000005( i = 5)                 0xbffff4c8    0x004c6b56
可以看出,C函数参数是按从右到左的顺序入栈的。堆栈桢之间是通过把ebp和eip压栈,来串在一起的。

 

后面查看更多参数信息
(gdb) info local
e = 7
(gdb) s
6    }
(gdb) s
TestA (a=2, b=0xbffff444) at test.c:11
11    }
(gdb) info local
No locals.
(gdb) info arg
a = 2
b = 0xbffff444
(gdb) s
main () at test.c:17
17        return 0;
(gdb) info local
i = 7
r = 7
-----------------------------------------------

分析参数和局部变量定位

abeen@ubuntu:~/Documents/C$ gdb a.out

(gdb) set disassembly-flavor intel //设置反汇编指令格式
(gdb) disass main
Dump of assembler code for function main:
0x080483ec <main+0>:    push   ebp
0x080483ed <main+1>:    mov    ebp,esp
0x080483ef <main+3>:    sub    esp,0x18
0x080483f2 <main+6>:    mov    DWORD PTR [ebp-0x4],0x5
0x080483f9 <main+13>:    lea    eax,[ebp-0x4]
0x080483fc <main+16>:    mov    DWORD PTR [esp+0x4],eax
0x08048400 <main+20>:    mov    DWORD PTR [esp],0x2
0x08048407 <main+27>:    call   0x80483d2 <TestA>
0x0804840c <main+32>:    mov    DWORD PTR [ebp-0x8],eax
0x0804840f <main+35>:    mov    eax,0x0
0x08048414 <main+40>:    leave  
0x08048415 <main+41>:    ret    
End of assembler dump.
(gdb) disass TestA
Dump of assembler code for function TestA:
0x080483d2 <TestA+0>:    push   ebp
0x080483d3 <TestA+1>:    mov    ebp,esp
0x080483d5 <TestA+3>:    sub    esp,0x8
0x080483d8 <TestA+6>:    mov    eax,DWORD PTR [ebp+0xc]
0x080483db <TestA+9>:    mov    DWORD PTR [esp+0x4],eax
0x080483df <TestA+13>:    mov    eax,DWORD PTR [ebp+0x8]
0x080483e2 <TestA+16>:    mov    DWORD PTR [esp],eax
0x080483e5 <TestA+19>:    call   0x80483b4 <TestB>
0x080483ea <TestA+24>:    leave  
0x080483eb <TestA+25>:    ret    
End of assembler dump.
(gdb) disass TestB
Dump of assembler code for function TestB:
0x080483b4 <TestB+0>:    push   ebp
0x080483b5 <TestB+1>:    mov    ebp,esp
0x080483b7 <TestB+3>:    sub    esp,0x10
0x080483ba <TestB+6>:    mov    eax,DWORD PTR [ebp+0xc]
0x080483bd <TestB+9>:    mov    eax,DWORD PTR [eax]
0x080483bf <TestB+11>:    add    eax,DWORD PTR [ebp+0x8]
0x080483c2 <TestB+14>:    mov    DWORD PTR [ebp-0x4],eax
0x080483c5 <TestB+17>:    mov    eax,DWORD PTR [ebp+0xc]
0x080483c8 <TestB+20>:    mov    edx,DWORD PTR [ebp-0x4]
0x080483cb <TestB+23>:    mov    DWORD PTR [eax],edx
0x080483cd <TestB+25>:    mov    eax,DWORD PTR [ebp-0x4]
0x080483d0 <TestB+28>:    leave  
0x080483d1 <TestB+29>:    ret    
End of assembler dump.
(gdb)
看汇编指令,参数和局部变量是以ebp为基准+偏移量来定位的。最后用eax寄存器返回值。

posted @ 2009-11-24 17:55  ABeen  阅读(922)  评论(0编辑  收藏  举报