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]$
转:Before main() 分析 实例2
最新推荐文章于 2025-08-13 15:45:37 发布