转自: http://www.groad.net/bbs/simple/?t2609.html
1. Linux 如何从命令行执行程序 从 shell 中运行程序时,系统会为要执行的程序在内存中创建一个区域。分配给程序的内存区域可以位于物理内存的任何位置。为了使这一过程简化,每个程序都被分配相同的虚拟内存地址。虚拟内存地址由操作系统映射到物理内存地址。
在 Linux 中,分配给程序的虚拟地址从地址 0x80480000 开始,到 0xbfffffff 结束。Linux 操作系统按照专门的格式把程序存放在虚拟内存地址中,如下图所示:
内存区域中的第 1 块区域包含会变代码的所有指令和数据(来自 .bss 和 .data 段)。指令步进包含汇编程序的指令代码,还包含 Linux 运行程序的连接过程所需要的指令信息。
内存中的第 2 块区域时堆栈区,它向下增长。但不能就认为,堆栈指针就是从 0xbfffffff 开始的。因为在加载程序之前,Linux 会把一些内容放到堆栈中,其中命令行参数就在这里。
2. 分析堆栈 程序启动时,Linux 会将 4 种类型的信息存放到程序堆栈中:
- 命令行参数(包括程序名称)的数目
- 从 shell 里执行的程序名称
- 命令行中包含的任何参数
- 在程序启动时所有当前的 Linux 环境变量
程序名称、命令行参数和环境变量都是均已 '/' 结尾的长度可变的字符串。为了让工作变得更简单,Linux 不仅把字符串加载到堆栈中,还把执行每个这些元素的指针加载到堆栈中,所以可以很容易的在程序中定位它们。
程序启动时,堆栈的一般布局如下图所示:
下面通过调试 http://www.groad.net/bbs/read.php?tid-2600.html 中的程序观察堆栈的情况。
引用
(gdb) b 13 Breakpoint 1 at 0x8048075: file area.s, line 13. (gdb) run 10 20 30 Starting program: /home/beyes/Program/Assembly/area 10 20 30
Breakpoint 1, _start () at area.s:13 13 finit (gdb) print $esp $1 = (void *) 0xbffff430
上面,Starting program 表示运行命令行中指定的命令行参数。这里我们使用了 10, 20, 30 这 3 个数字作为命令行参数。地址 0xbffff430 是栈顶。现在看一下堆栈里都如何存放上面所说的数据,使用 x 命令看内存中的值:
引用
(gdb) x/20x 0xbffff430 0xbffff430: 0x00000004 0xbffff5c9 0xbffff5eb 0xbffff5ee 0xbffff440: 0xbffff5f1 0x00000000 0xbffff5f4 0xbffff615 0xbffff450: 0xbffff628 0xbffff633 0xbffff643 0xbffff693 0xbffff460: 0xbffff6a5 0xbffff6cf 0xbffff6ef 0xbffff6fa 0xbffff470: 0xbffff71a 0xbffffbbb 0xbffffbe1 0xbffffc13
对照上图: 第 1 个值 0x00000004 正是命令行的参数数目(包含全路径的的程序名,10,20,30),共 4 个。 第 2 个值 0xbffff5c9 是包含全路经的程序名:
引用
(gdb) x/s 0xbffff5c9 0xbffff5c9: "/home/beyes/Program/Assembly/area"
全路径名+最后一个'/'字符共 0x22 个字节, 0xbffff5c9 + 0x21 = 0xbffff5ea 。那么从 0xbffff5eb 开始就存放命令行参数了。 第 3,4,5 个值分别是参数 10, 20, 30 的地址:
引用
(gdb) x/s 0xbffff5eb 0xbffff5eb: "10" (gdb) x/s 0xbffff5ee 0xbffff5ee: "20" (gdb) x/s 0xbffff5f1 0xbffff5f1: "30"
注意,所有命令行参数都是以字符串形式存储的!如参数 "10" 的起始地址是 0xbffff5eb,结束地址为 0xbffffe3d,这里总共 3 个字节,其中包括 '/' 结尾这个字节。这也就是为什么查看这个内存是是用 x/s 来显示(s 表示显示字符串)。
在命令行参数之后,4 字节的空值被放到堆栈中,作为参数和指向环境变量的指针的分界点。在 0x00000000 往上,是一些环境变量:
引用
(gdb) x/s 0xbffff5f4 0xbffff5f4: "ORBIT_SOCKETDIR=/tmp/orbit-beyes" (gdb) x/s 0xbffff615 0xbffff615: "SSH_AGENT_PID=1359" (gdb) x/s 0xbffff628 0xbffff628: "TERM=xterm" ... ...
查看命令行参数:
引用
.section .data output1: .asciz "There are %d parameters:/n" output2: .asciz "%s/n"
.section .text .global _start _start: movl (%esp), %ecx #读取"参数数目" pushl %ecx pushl $output1 call printf #C函数的参数入栈从右到左入栈 addl $4, %esp popl %ecx movl %esp, %ebp addl $4, %ebp #EBP指向第一个命令行参数(即函数名./read) loop1: pushl %ecx #printf函数会改变ECX的值,这里要入栈保存起来 pushl (%ebp) pushl $output2 call printf addl $8, %esp popl %ecx #弹出以递减 addl $4, %ebp loop loop1
pushl $0 call exit
运行与输出:
引用
$ ./read 10 20 30 There are 4 parameters: ./read 10 20 30
查看环境变量:
引用
.section .data output: .asciz "%s/n"
.section .text .global _start _start: movl %esp, %ebp addl $12, %ebp #指向环境变量(不加其他命令行参数运行程序) loop1: cmpl $0, (%ebp) je endit pushl (%ebp) pushl $output call printf addl $12, %esp addl $4, %ebp loop loop1 endit: pushl $0 call exit
运行与输出:
引用
$ ./read2 ORBIT_SOCKETDIR=/tmp/orbit-beyes SSH_AGENT_PID=1364 SHELL=/bin/bash TERM=xterm ... ...
|