[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寄存器返回值。