Chinaunix首页 | 论坛 | 博客
  • 博客访问: 59360
  • 博文数量: 27
  • 博客积分: 2000
  • 博客等级: 大尉
  • 技术积分: 300
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-24 17:31
文章分类
文章存档

2011年(1)

2010年(8)

2009年(18)

我的朋友

分类: LINUX

2009-06-26 13:22:42

main()的堆栈内容演示。===========================================================
预备知识:

1.
常用命令

(gdb) b * 0x80483d8         # 在地址处设置断点
(gdb) b main
                     # 在标号处设置断点
(gdb) disass 0x80483d8
    # 反汇编
(gdb) si执行1条汇编指令,ni执行整个汇编函数;s执行1C语句,n执行整个C函数

(gdb) i reg              #
查看所有寄存器内容

(gdb) x/20i 0x12345678   # 按汇编代码查看内容
(gdb) x/20i $esp         #
按汇编代码查看内容
(gdb) x/5x $esp          #
16进制查看寄存器esp内容
(gdb) x/5x 0x12345678    #
16进制查看内容
(gdb) x/20strings 0x12345678   #
按字符串查看内容


2.
父子程序之间的关系:

  |---------------|   <-----  父进程堆栈
  | param_1       |  
  |---------------|  
  | param_2       |  
  |---------------|  
  | param_3       |   <-----  call
执行之前的esp
  |---------------|  
  |
调用者eip      |   <-----  call执行之后的esp
  |---------------|   
  |               |   <-----  
以下为子进程堆栈
  |---------------|  

通常父程序把子程序所需要的参数压入父程序的堆栈,"call 子程序地址" 把紧接call指令的下一条指令地址压入父程序的堆栈,作为子程序的返回地址。刚进入子程序时(尚未执行自程序的第1条汇编指令)ESP = 父亲的EIP

我们根据以上的结论,把main当作子进程,向上反推,看看main的老爸是谁,以及它老爸的老爸是谁 


注意:

(1)如果用jump指令,则上述不成立。比如从动态连接器进入_start时是用jmp指令而非call指令。动态连接器的开头也定义了一个全局符号:也叫_start。这样从动态连接器里jmp到后面的_start(start.s)可以保持名字统一连贯。

(2)Linux系统中,应用程序和解释器的装入/启动是在Linux内核中由系统调用execve()完成,2者都被映射装入用户地址空间。execve()系统调用结束,返回用户空间时进入动态连接器入口开始执行。动态连接的实现由动态连接器在用户空间完成(参考:漫谈兼容内核之八)。所以,从进入动态连接器直到main()程序启动,全都是在用户态的事情,使用用户态堆栈,多个函数之间的call调用形成堆栈侦(push   %ebp; mov    %esp,%ebp)也都在同一用户堆栈上,不能从用户空间向上一直反推到内核堆栈。

3.
参见before main()分析:
_start( )
的堆栈内容如下:

%esp The stack contains the arguments and environment:

0(%esp)                   argc                 
堆栈顶(低地址)
4(%esp)                   argv[0]
8(%esp)                   argv[1]
...
...
(4*argc)(%esp)            NULL
(4*(argc+1))(%esp)        envp[0]
(4*(argc+2))(%esp)        envp[1]               (
高地址
)
...
...
                          NULL


_start_main经过多级调用,后面我们看到,到_main时,堆栈实际上并不是这样排列的,可能是main之前的例程太多,堆栈塞了一堆其他东西。但是只要给出**envp**argv,就可以找到那些在堆栈里连续存放的字符串。

4.
Linux上面的虚拟地址空间:
(1) user area: 0x0 - 0x0bffffff (3GB
内存空间)
0x0 - 0x08048000         Thread stack
0x08048000 - 0x40000000 
用户程序 Text, Data
0x40000000 - 0xbfffffff  shared LIB(ld-linux.so,libm.so,libc.so...)
、用户程序
Stack

(2) kernel area: 0x0c000000 - 0xffffffff  (1GB
内存空间)


()首先上查 main的前三代
=====================================================================

[test@radhat]$ more t1.c
#include <stdio.h>
Foo(char* s)
{
    char buf[16]="";
    strcpy(buf, s);
#   printf("The input String is %s\n", buf);   #
注释掉printf函数,容易跟踪,不至于眼花。
}

main(int argc, char* argv[])
{
    if(argc == 2)
    {
        Foo(argv[1]);
    }
    else
    {
        printf("Usage: %s \n", argv[0]);
    }
}



[test@radhat]$ gcc -g -o t1 t1.c 

[test@radhat]$ gdb q t1

(gdb) b main

(gdb) r

(gdb) disass main

Dump of assembler code for function main:

0x080483c7 :    lea    0x4(%esp),%ecx

0x080483cb :    and    $0xfffffff0,%esp

0x080483ce :    pushl  0xfffffffc(%ecx)

0x080483d1 :   push   %ebp

0x080483d2 :   mov    %esp,%ebp
。。。
。。。

(gdb) info reg esp

esp            0xbfc93ea0

(gdb) x/1x 0xbfc93ea0

0xbfc93ea0:     0x4f969875

(gdb) disass 0x4f969875

Dump of assembler code for function __new_exitfn:

0x4f969860 <__new_exitfn+0>:    push   %ebp

0x4f969861 <__new_exitfn+1>:    xor    %eax,%eax

0x4f969863 <__new_exitfn+3>:    mov    %esp,%ebp

0x4f969865 <__new_exitfn+5>:    mov    $0x1,%ecx

0x4f96986a <__new_exitfn+10>:   push   %edi

0x4f96986b <__new_exitfn+11>:   push   %esi

0x4f96986c <__new_exitfn+12>:   push   %ebx

0x4f96986d <__new_exitfn+13>:   sub    $0x8,%esp

0x4f969870 <__new_exitfn+16>:   call   0x4f953c30 <__i686.get_pc_thunk.bx>

0x4f969875 <__new_exitfn+21>:   add    $0x10d77f,%ebx


# main
的上级是__new_exitfn

#####################################################################

(gdb) b * 0x4f969860    # __new_exitfn开头设断点

(gdb) r

(gdb) i reg esp

esp            0xbfaf351c

(gdb) x/1x 0xbfaf351c

0xbfaf351c:     0x4f9699a0

(gdb) disass 0x4f9699a0

Dump of assembler code for function __cxa_atexit_internal:

0x4f969980 <__cxa_atexit_internal+0>:   push   %ebp

0x4f969981 <__cxa_atexit_internal+1>:   mov    %esp,%ebp

0x4f969983 <__cxa_atexit_internal+3>:   sub    $0x8,%esp

0x4f969986 <__cxa_atexit_internal+6>:   mov    %ebx,(%esp)

0x4f969989 <__cxa_atexit_internal+9>:   call   0x4f953c30 <__i686.get_pc_thunk.bx>

0x4f96998e <__cxa_atexit_internal+14>:  add    $0x10d666,%ebx

0x4f969994 <__cxa_atexit_internal+20>:  mov    %esi,0x4(%esp)

0x4f969998 <__cxa_atexit_internal+24>:  mov    0x8(%ebp),%esi

0x4f96999b <__cxa_atexit_internal+27>:  call   0x4f969860 <__new_exitfn>

0x4f9699a0 <__cxa_atexit_internal+32>:  mov    %eax,%edx


# __new_exitfn()
的上级是__cxa_atexit_internal()

#####################################################################

(gdb) b * 0x4f969980    # __cxa_atexit_internal开头设断点

(gdb) r

(gdb) i reg esp

esp            0xbf80ddcc

(gdb) x/1x 0xbf80ddcc

0xbf80ddcc:     0x4f953d5e

(gdb) disass 0x4f953d5e

Dump of assembler code for function __libc_start_main:

0x4f953d10 <__libc_start_main+0>:       push   %ebp

0x4f953d11 <__libc_start_main+1>:       xor    %edx,%edx

0x4f953d13 <__libc_start_main+3>:       mov    %esp,%ebp

0x4f953d15 <__libc_start_main+5>:       push   %edi

0x4f953d16 <__libc_start_main+6>:       push   %esi

0x4f953d17 <__libc_start_main+7>:       push   %ebx

0x4f953d18 <__libc_start_main+8>:       call   0x4f953c30 <__i686.get_pc_thunk.bx>

0x4f953d1d <__libc_start_main+13>:      add    $0x1232d7,%ebx

0x4f953d23 <__libc_start_main+19>:      sub    $0x4c,%esp

0x4f953d26 <__libc_start_main+22>:      mov    0x14(%ebp),%edi

0x4f953d29 <__libc_start_main+25>:      mov    0x1c(%ebp),%ecx

0x4f953d2c <__libc_start_main+28>:      mov    0xffffff14(%ebx),%eax

0x4f953d32 <__libc_start_main+34>:      test   %eax,%eax

0x4f953d34 <__libc_start_main+36>:      jne    0x4f953df4 <__libc_start_main+228>

0x4f953d3a <__libc_start_main+42>:      mov    0xffffffd0(%ebx),%eax

0x4f953d40 <__libc_start_main+48>:      test   %ecx,%ecx

0x4f953d42 <__libc_start_main+50>:      mov    %edx,(%eax)

0x4f953d44 <__libc_start_main+52>:      je     0x4f953d5e <__libc_start_main+78>

0x4f953d46 <__libc_start_main+54>:      movl   $0x0,0x8(%esp)

0x4f953d4e <__libc_start_main+62>:      movl   $0x0,0x4(%esp)

0x4f953d56 <__libc_start_main+70>:      mov    %ecx,(%esp)

0x4f953d59 <__libc_start_main+73>:      call   0x4f969980 <__cxa_atexit_internal>

0x4f953d5e <__libc_start_main+78>:      mov    0xffffff50(%ebx),%esi

# __cxa_atexit_internal()的上级是 __libc_start_main()

######################################################################

(gdb) b * 0x4f953d10   # __libc_start_main开头设断点

Breakpoint 4 at 0x4f953d10

(gdb) r

(gdb) i reg esp

esp            0xbfe498dc

(gdb) x/1x 0xbfe498dc

0xbfe498dc:     0x08048301

(gdb) disass 0x08048301

Dump of assembler code for function _start:

0x080482e0 <_start+0>:  xor    %ebp,%ebp

0x080482e2 <_start+2>:  pop    %esi

0x080482e3 <_start+3>:  mov    %esp,%ecx

0x080482e5 <_start+5>:  and    $0xfffffff0,%esp

0x080482e8 <_start+8>:  push   %eax

0x080482e9 <_start+9>:  push   %esp

0x080482ea <_start+10>: push   %edx

0x080482eb <_start+11>: push   $0x8048420

0x080482f0 <_start+16>: push   $0x8048430

0x080482f5 <_start+21>: push   %ecx

0x080482f6 <_start+22>: push   %esi

0x080482f7 <_start+23>: push   $0x80483c7

0x080482fc <_start+28>: call   0x80482a8 <__libc_start_main@plt>

0x08048301 <_start+33>: hlt   

0x08048302 <_start+34>: nop   

0x08048303 <_start+35>: nop   

End of assembler dump.

# __libc_start_main()的上级是 _start()

######################################################################

(gdb) b * 0x080482e0    # _start开头设断点

(gdb) r bbbbbbbbbbb     # 传递1个参数“bbbbbbbbbbb”给 t1程序

(gdb) i r esp

esp            0xbfba5660

(gdb) x/1x 0xbfba5660

0xbfba5660:     0x00000002   # 0x00000002 不是一个地址,没办法继续向上追查了。实际上它是argc,即参数个数(1个参数 + 程序本身=2)。

(gdb) x/40 0xbfba5660      # 看看_start堆栈的内容。基本上都是一些地址(即:字符串指针)

0xbfb54600:     0x00000002      0xbfb55bd4  0xbfb55bdd 0x00000000

0xbfb54610:     0xbfb55be9      0xbfb55bf9  0xbfb55c04 0xbfb55c14

0xbfb54620:     0xbfb55c22      0xbfb55c30  0xbfb55c46 0xbfb55c64

0xbfb54630:     0xbfb55c6e      0xbfb55e31  0xbfb55e3d 0xbfb55e49

(gdb) x/40strings 0xbfb55bd4   # 看看这些指针里都放了什么内容。参考前面对_start的堆栈的说明。

0xbfb55bd4:      "/foot/t1"            # *argv[0]

0xbfb55bdd:      'bbbbbbbbbbb'         # *argv[1]

0xbfb55be9:      "HOSTNAME=radhat"     # *envp[0]

0xbfb55bf9:      "TERM=vt100"          # *envp[1]

0xbfb55c04:      "SHELL=/bin/bash"     # *envp[2]

0xbfb55c14:      "HISTSIZE=1000"

0xbfb55c22:      "KDE_NO_IPV6=1"

0xbfb55c30:      "QTDIR=/usr/lib/qt-3.3"

0xbfb55c46:      "QTINC=/usr/lib/qt-3.3/include"

0xbfb55c64:      "USER=test"

...

...

(gdb)

# 这样的方法有局限,如果是jmp指令进入子程序,就没办法由子程序向上反查了。

==========================================================
(
)我们看看程序执行到main()时,堆栈上大致都存放了哪些内容

[test@radhat]$ gdb q t1

(gdb) b main

(gdb) r aaa bbb cccc dddddd   # 传递4个参数

Starting program: /foot/t1 aaa bbb cccc dddddd

Breakpoint 1, main (argc=5, argv=0xbfe490e4) at t1.c:9

9           if(argc == 2)

(gdb) x/40x $esp     # 看看main的堆栈。4字节1列。

0xbfe49030:     0x4f969875      0xbfe490fc      0xbfe49058      0x4fa76ff4

0xbfe49040:     0xbfe49060      0xbfe49060      0xbfe490b8      0x4f953dec

0xbfe49050:     0x4f93aca0      0x08048430      0xbfe490b8      0x4f953dec

0xbfe49060:     0x00000005      0xbfe490e4      0xbfe490fc      0x4f92ea20

0xbfe49070:     0x00000000      0xb7fa26a0      0x00000001      0x00000001

0xbfe49080:     0x4fa76ff4      0x4f93aca0      0x00000000      0xbfe490b8

0xbfe49090:     0x00fa6a06      0xf08bc7d7      0x00000000      0x00000000

# 我们11来看。x指令查看1个内存地址里面放了什么内容:一般存放4种数据:1。一个地址  2。一个常数  3。一个字符串  4。汇编指令

(gdb) x/1x 0x4f969875   # 10x4f969875内存地址里存放 汇编指令。

0x4f969875 <__new_exitfn+21>:   0xd77fc381

(gdb) x/20i 0x4f969875   # 可以用x/ni显示汇编指令。

0x4f969875 <__new_exitfn+21>:   add    $0x10d77f,%ebx

0x4f96987b <__new_exitfn+27>:   cmpl   $0x0,%gs:0xc

0x4f969883 <__new_exitfn+35>:   je     0x4f969886 <__new_exitfn+38>

0x4f969885 <__new_exitfn+37>:   lock cmpxchg %ecx,0xc6c(%ebx)

0x4f96988d <__new_exitfn+45>:   jne    0x4f9699d4 <_L_lock_23>

0x4f969893 <__new_exitfn+51>:   mov    0x310(%ebx),%esi

(gdb) x/1x 0xbfe490fc     # 20xbfe490fc里存放1个地址:0xbfe4abe9

0xbfe490fc:     0xbfe4abe9

(gdb) x/1strings 0xbfe4abe9   # 继续看看0xbfe4abe9里存放字符串"HOSTNAME=radhat"

0xbfe4abe9:      "HOSTNAME=radhat"

(gdb) x/1x 0xbfe49058    # 30xbfe49058里存放1个地址:0xbfe490b8

0xbfe49058:     0xbfe490b8

(gdb) x/1x 0xbfe490b8    # 继续看看0xbfe490b8里存放1个常量0

0xbfe490b8:     0x00000000

(gdb) x/1x 0x4fa76ff4    # 40x4fa76ff4里存放1个地址:0x4fa76d9c

0x4fa76ff4:     0x4fa76d9c

(gdb) x/1x 0x4fa76d9c    # 继续看看0x4fa76d9c里存放1个常量1

0x4fa76d9c:     0x00000001

(gdb) x/1x 0xbfe49060    # 560xbfe49060里存放1个常量5

0xbfe49060:     0x00000005

(gdb) x/1x 0xbfe490b8    # 70xbfe490b8里存放1个常量0

0xbfe490b8:     0x00000000

(gdb) x/1x 0x4f953dec    # 80x4f953dec里存放汇编指令

0x4f953dec <__libc_start_main+220>:     0xe8240489

(gdb) x/1x 0x4f93aca0    # 90x4f93aca0里存放汇编指令

0x4f93aca0 <_rtld_local_ro>:    0x00000000

(gdb) x/1x 0x08048430    # 100x08048430里存放汇编指令

0x8048430 <__libc_csu_init>:    0x57e58955

(gdb) x/1x 0xbfe490b8    # 110xbfe490b8里存放1个常量0

0xbfe490b8:     0x00000000

(gdb) x/1x 0x4f953dec    # 120x4f953dec里存放汇编指令

0x4f953dec <__libc_start_main+220>:     0xe8240489

# 130x00000005 argc 即:参数个数

(gdb) x/1x 0xbfe490e4    # 140xbfe490e4 **argv

0xbfe490e4:     0xbfe4abcc    # 0xbfe490e4里存放一个地址0xbfe4abcc

(gdb) x/8x 0xbfffe234  # 查看指针数组*argv[]的内容,包含了1组地址,以0结尾
0xbfe490e4: 0xbfe4abcc 0xbfe4abd5 0xbfe4abd9 0xbfe4abdd
0xbfe490f4: 0xbfe4abe2 0x00000000 0xbfe4abe9 0xbfe4abf9

(gdb) x/10strings 0xbfe4abcc  # 按字符形式查看指针数组*argv[]的内容

0xbfe4abcc:      "/foot/t1"

0xbfe4abd5:      "aaa"

0xbfe4abd9:      "bbb"

0xbfe4abdd:      "cccc"

0xbfe4abe2:      "dddddd"

0xbfe4abe9:      "HOSTNAME=radhat"

0xbfe4abf9:      "TERM=vt100"

0xbfe4ac04:      "SHELL=/bin/bash"

0xbfe4ac14:      "HISTSIZE=1000"

0xbfe4ac22:      "KDE_NO_IPV6=1"

(gdb) x/1x 0xbfe490fc   # 150xbfe490fc **envp

0xbfe490fc:     0xbfe4abe9

(gdb) x/1strings 0xbfe4abe9

0xbfe4abe9:      "HOSTNAME=radhat"  # *envp[0] = "HOSTNAME=radhat"

(gdb) x/20strings  0xbfe4abe9       # 按字符串查看指针数组*envp[]的内容

0xbfe4abe9:      "HOSTNAME=radhat"

0xbfe4abf9:      "TERM=vt100"

0xbfe4ac04:      "SHELL=/bin/bash"

0xbfe4ac14:      "HISTSIZE=1000"

0xbfe4ac22:      "KDE_NO_IPV6=1"

0xbfe4ac30:      "QTDIR=/usr/lib/qt-3.3"

0xbfe4ac46:      "QTINC=/usr/lib/qt-3.3/include"

0xbfe4ac64:      "USER=test"

...

...

(gdb) x/1x 0x4f92ea20    # 160x4f92ea20里存放汇编指令

0x4f92ea20 <_dl_fixup+176>:     0x83f0558b

(gdb) quit        #用户堆栈的最顶端是0xbfffffff,有兴趣的继续看看。

[test@radhat]$

 
 
阅读(795) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~