在Linux 64位下分析了栈分布情况,在函数参数,局部变量等上面和32位有一些差别。现记录下来,以供参考。
首先在64位下,寄存器esp变成了rsp,ebp变成了rbp,ip变成了rip。
环境:
1.Linux内核版本:
>cat /proc/version
Linux version 2.6.18-128.7.1.el5 (brewbuilder@norob.fnal.gov) (gcc version 4.1.2 20080704 (Red Hat 4.1.2-44)) #1 SMP Mon Aug 24 08:12:52 EDT 2009
2.Linux版本:
>lsb_release -a
LSB Version::core-3.1-amd64:core-3.1-ia32:core-3.1-noarch:graphics-3.1-amd64:graphics-3.1-ia32:graphics-3.1-noarch
Distributor ID: ScientificSL
Description:Scientific Linux SL release 5.3 (Boron)
Release:5.3
Codename: Boron
3.G++版本:
>g++ -v
Using built-in specs.
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-libgcj-multifile --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --enable-plugin --with-java-home=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre --with-cpu=generic --host=x86_64-redhat-linux
Thread model: posix
gcc version 4.1.2 20080704 (Red Hat 4.1.2-44)
参数传递:
原程序:
int func( int c, char* s, int off )
{
int a = 0x12345678;
int *p = &a;
int res = c + *( s + off );
return *p + res;
}
int main()
{
int b = 0x87654321;
return b + func( 0x100, "hello", 3 );
}
在《coredump问题研究》Linux X86版3.4节栈布局之函数参数篇中指出,esp, esp+4 esp+8 存放着0x100,"hello"的地址,以及3.
然而,实际上在64位下,这三个参数是通过寄存器来传递的,请看main的反汇编程序:
(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000400686 <main+0>:push %rbp
0x0000000000400687 <main+1>:mov%rsp,%rbp
0x000000000040068a <main+4>:sub$0x10,%rsp
0x000000000040068e <main+8>:movl $0x87654321,-0x4(%rbp)
0x0000000000400695 <main+15>: mov$0x3,%edx
0x000000000040069a <main+20>: mov$0x400818,%esi
0x000000000040069f <main+25>: mov$0x100,%edi
0x00000000004006a4 <main+30>: callq 0x400648 <_Z4funciPci>
0x00000000004006a9 <main+35>: add-0x4(%rbp),%eax
0x00000000004006ac <main+38>: leaveq
0x00000000004006ad <main+39>: retq
End of assembler dump
可以看到100, “hello”的地址以及3由edi,esi以及edx来传递。我们可以看Func是怎么取得这三个参数的。
disassemble func
Dump of assembler code for function _Z4funciPci:
0x0000000000400648 <_Z4funciPci+0>: push %rbp
0x0000000000400649 <_Z4funciPci+1>: mov%rsp,%rbp
0x000000000040064c <_Z4funciPci+4>: mov%edi,-0x24(%rbp)
0x000000000040064f <_Z4funciPci+7>: mov%rsi,-0x30(%rbp)
0x0000000000400653 <_Z4funciPci+11>:mov%edx,-0x34(%rbp)
0x0000000000400656 <_Z4funciPci+14>:movl $0x12345678,-0x14(%rbp)
0x000000000040065d <_Z4funciPci+21>:lea-0x14(%rbp),%rax
0x0000000000400661 <_Z4funciPci+25>:mov%rax,-0x10(%rbp)
0x0000000000400665 <_Z4funciPci+29>:mov-0x34(%rbp),%eax
一进入func()函数,把main函数的帧地址压栈后,直接从edi,esi和edx中取出参数放到func帧地址的下面(也是func的栈上)。
同时我们可以在main函数开始处同过edi,esi和edx来得到main函数的参数,argc,argv和envp。
>tbreak *0x000000000040068a
>r
(gdb) info r
rax0x3202152a20 214783306272
rbx0x3201c1bbc0 214777838528
rcx0x4 4
rdx0x7fffb28031c8 140736188133832
rsi0x7fffb28031b8 140736188133816
rdi0x1 1
rbp0x7fffb28030d0 0x7fffb28030d0
rsp0x7fffb28030d0 0x7fffb28030d0
r8 0x3202151370 214783300464
r9 0x3201a0d640 214775682624
r100x0 0
r110x3201e33510 214780032272
r120x0 0
r130x7fffb28031b0 140736188133808
rdi的值是1,我们不好确定是不是argc的值,那我们看看rsi的值是什么。
x /8x 0x7fffb28031b8
0x7fffb28031b8: 0x00007fffb280485b 0x0000000000000000
0x7fffb28031c8: 0x00007fffb2804884 0x00007fffb2804897
0x7fffb28031d8: 0x00007fffb28048ad 0x00007fffb28048c6
0x7fffb28031e8: 0x00007fffb280498e 0x00007fffb28049b1
(gdb) x /s 0x00007fffb280485b
0x7fffb280485b: "/local/work/coredump/c3_s4"
确实是我们这个程序的名字。
同样,我们可以从rdx中得到envp的值。