转:Before main() 分析 实例2

本文介绍如何使用GDB调试工具逐步追踪程序的运行流程,并详细解析了main函数的堆栈内容,包括参数传递过程及环境变量的布局。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


main程序的堆栈内容演示:
===========================================================
预备知识:
1. 常用命令
(gdb) b * 0x80483d8         # 在地址处设置断点
(gdb) b main                   # 在标号处设置断点
(gdb) disass 0x80483d8    # 反汇编
(gdb) si是执行1条汇编指令,ni是执行整个汇编函数;s是执行1条C语句,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       |   执行之前的esp
  |---------------|   
  |调用者eip      |   执行之后的esp
  |---------------|   
  |               |   以下为子进程堆栈
  |---------------|   
通常父程序把子程序所需要的参数压入父程序的堆栈,"call 子程序地址" 把紧接call指令的下一条指令地址压入父程序的堆栈,作为子程序的返回地址。刚进入子程序时(尚未执行自程序的第1条汇编指令),ESP = 父亲的EIP。
我们根据以上的结论,把main当作子进程,向上反推,看看main的老爸是谁,以及它老爸的老爸是谁
注意:
(1)如果用jump指令,则上述不成立。比如从动态连接器进入_start时是用jmp指令而非call指令。动态连接器的开头也定义了一个全局符号:也叫_start。这样从动态连接器里jmp到后面的_start(start.s里)可以保持名字统一连贯。
(2)在Linux系统中,应用程序和解释器的装入/启动是在Linux内核中由系统调用execve()完成,2者都被映射装入用户地址空间。 execve()系统调用结束,返回用户空间时进入动态连接器入口开始执行。动态连接的实现由动态连接器在用户空间完成。(参考:
[url=file:///H:/×ÀÃæ/linux²¡¶¾±àд%20howto/elf/00/001_1%20Âþ̸¼æÈÝÄÚºËÖ®°Ë_ELFÓ³ÏñµÄ×°Èë(Ò]漫谈兼容内核之八 [/url]
)。所以,从进入动态连接器直到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的8辈祖宗
===========================================================
[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 :    push   %ebp
0x4f969861 :    xor    %eax,%eax
0x4f969863 :    mov    %esp,%ebp
0x4f969865 :    mov    $0x1,%ecx
0x4f96986a :   push   %edi
0x4f96986b :   push   %esi
0x4f96986c :   push   %ebx
0x4f96986d :   sub    $0x8,%esp
0x4f969870 :   call   0x4f953c30
0x4f969875 :   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 :   push   %ebp
0x4f969981 :   mov    %esp,%ebp
0x4f969983 :   sub    $0x8,%esp
0x4f969986 :   mov    %ebx,(%esp)
0x4f969989 :   call   0x4f953c30
0x4f96998e :  add    $0x10d666,%ebx
0x4f969994 :  mov    %esi,0x4(%esp)
0x4f969998 :  mov    0x8(%ebp),%esi
0x4f96999b :  call   0x4f969860 new_exitfn>
0x4f9699a0 :  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 :       push   %ebp
0x4f953d11 :       xor    %edx,%edx
0x4f953d13 :       mov    %esp,%ebp
0x4f953d15 :       push   %edi
0x4f953d16 :       push   %esi
0x4f953d17 :       push   %ebx
0x4f953d18 :       call   0x4f953c30
0x4f953d1d :      add    $0x1232d7,%ebx
0x4f953d23 :      sub    $0x4c,%esp
0x4f953d26 :      mov    0x14(%ebp),%edi
0x4f953d29 :      mov    0x1c(%ebp),%ecx
0x4f953d2c :      mov    0xffffff14(%ebx),%eax
0x4f953d32 :      test   %eax,%eax
0x4f953d34 :      jne    0x4f953df4
0x4f953d3a :      mov    0xffffffd0(%ebx),%eax
0x4f953d40 :      test   %ecx,%ecx
0x4f953d42 :      mov    %edx,(%eax)
0x4f953d44 :      je     0x4f953d5e
0x4f953d46 :      movl   $0x0,0x8(%esp)
0x4f953d4e :      movl   $0x0,0x4(%esp)
0x4f953d56 :      mov    %ecx,(%esp)
0x4f953d59 :      call   0x4f969980 cxa_atexit_internal>
0x4f953d5e :      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 :  xor    %ebp,%ebp
0x080482e2 :  pop    %esi
0x080482e3 :  mov    %esp,%ecx
0x080482e5 :  and    $0xfffffff0,%esp
0x080482e8 :  push   %eax
0x080482e9 :  push   %esp
0x080482ea : push   %edx
0x080482eb : push   $0x8048420
0x080482f0 : push   $0x8048430
0x080482f5 : push   %ecx
0x080482f6 : push   %esi
0x080482f7 : push   $0x80483c7
0x080482fc : call   0x80482a8 libc_start_main@plt>
0x08048301 : hlt   
0x08048302 : nop   
0x08048303 : 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   # 不是一个地址,没办法继续向上追查了。实际上是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
# 看起来很乱,我们1个1个来看。用x指令查看某个地址的内存里面放了什么内容:
大概存放4种数据:1。一个地址  2。常数  3。字符串  4。汇编指令
(gdb) x/1x 0x4f969875   # 第1个:0x4f969875内存地址里存放 汇编指令。
0x4f969875 :   0xd77fc381
(gdb) x/20i 0x4f969875   # 可以用x/ni显示汇编指令。
0x4f969875 :   add    $0x10d77f,%ebx
0x4f96987b :   cmpl   $0x0,%gs:0xc
0x4f969883 :   je     0x4f969886
0x4f969885 :   lock cmpxchg %ecx,0xc6c(%ebx)
0x4f96988d :   jne    0x4f9699d4
0x4f969893 :   mov    0x310(%ebx),%esi


(gdb) x/1x 0xbfe490fc     # 第2个:0xbfe490fc里存放1个地址:0xbfe4abe9
0xbfe490fc:     0xbfe4abe9
(gdb) x/1strings 0xbfe4abe9   # 继续看看0xbfe4abe9里存放字符串"HOSTNAME=radhat"
0xbfe4abe9:      "HOSTNAME=radhat"
(gdb) x/1x 0xbfe49058    # 第3个:0xbfe49058里存放1个地址:0xbfe490b8
0xbfe49058:     0xbfe490b8
(gdb) x/1x 0xbfe490b8    # 继续看看0xbfe490b8里存放1个常量0
0xbfe490b8:     0x00000000
(gdb) x/1x 0x4fa76ff4    # 第4个:0x4fa76ff4里存放1个地址:0x4fa76d9c
0x4fa76ff4:     0x4fa76d9c
(gdb) x/1x 0x4fa76d9c    # 继续看看0x4fa76d9c里存放1个常量1
0x4fa76d9c:     0x00000001
(gdb) x/1x 0xbfe49060    # 第5,6个:0xbfe49060里存放1个常量5
0xbfe49060:     0x00000005
(gdb) x/1x 0xbfe490b8    # 第7个:0xbfe490b8里存放1个常量0
0xbfe490b8:     0x00000000
(gdb) x/1x 0x4f953dec    # 第8个:0xbfe490b8里存放汇编指令
0x4f953dec :     0xe8240489
(gdb) x/1x 0x4f93aca0    # 第9个:0x4f93aca0里存放汇编指令
0x4f93aca0 rtld_local_ro>:    0x00000000
(gdb) x/1x 0x08048430    # 第10个:0x08048430里存放汇编指令
0x8048430 :    0x57e58955
(gdb) x/1x 0xbfe490b8    # 第11个:0xbfe490b8里存放1个常量0
0xbfe490b8:     0x00000000
(gdb) x/1x 0x4f953dec    # 第12个:0x4f953dec里存放汇编指令
0x4f953dec :     0xe8240489
# 第13个:0x00000005 是 argc 即是:参数个数
(gdb) x/1x 0xbfe490e4    # 第14个:0xbfe490e4是 **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   # 第15个:0xbfe490fc是 **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    # 第16个:0x4f92ea20里存放汇编指令
0x4f92ea20 :     0x83f0558b
(gdb) quit
[test@radhat]$

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值