作者:高玉涵
时间:2021.10.9 16:59
博客:blog.csdn.net/cg_i
环境:Linux 7e142849497c 5.10.47-linuxkit #1 SMP Sat Jul 3 21:51:47 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
除了他的勇气、坚持和热爱之外,更重要的是上千次的练习和尝试,才成绩了一次的完美绽放。
——《徒手攀岩》观后感
不同的操作系统使用不同的方法把命令行参数传递给程序。在试图解释 Linux 中如何把命令行参数传递给程序之前,最好首先解释 Linux 如何从命令行执行程序。
从 Linux 的 Shell 提示符运行程序时,Linux 系统为要执行的程序在内存中创建一个区域。分配给程序的内存区域可以位于系统物理内存的任何位置。为了使这一过程简化,每个程序都被分配相同的虚拟内存位置。虚拟内存地址由操作系统映射到物理内存地址。
在 Linux 中,分配给程序运行的虚拟内存地址 0x80480000 开始,到地址 0xbfffffff 结束。Linux 操作系统按照专门的格式把程序存放在虚拟内存地址中。如下所示:
程序虚拟内存区域
------------- 0xbfffffff
堆栈数据
-------------
??????????
-------------
程序代码和数据
------------- 0x80480000
内存区域中的第一块区域包含汇编程序的所有指令和数据(来自 .bss 和 .data 段)。指令不仅包含汇编程序的指令代码,而且包含 Linux 运行程序的连接过程所需的指令信息。
内存区域中的第二块区域是程序堆栈。就像先前讲过的,堆栈从内存区域的底部向下增长。鉴于此,您可能会认为程序每次启动时,堆栈指针会被设置为 0xbfffffff,但是情况并非如此。在加载程序之前,Linux 把一些内容存放到堆栈中,命令行参数就在这里。
程序启动时,Linux 把 4 种类型的信息存放到程序堆栈中:
命令行参数(包括程序名称)的数目
从 shell 提示符执行的程序的名称
命令行中包含的任何命令行参数
在程序启动时的所有当前 Linux 环境变量
程序名称、命令行参数和环境变量是以空结尾的长度可变的字符串。为了使工作更加简单,Linux 不仅把字符串加载到堆栈中,它还把指向每个这些元素的指针加载到堆栈中,所以可以容易地在程序中定位它们。
程序启动时,堆栈的一般布局如下所示:
程序堆栈
----------------
环境变量
命令行参数
----------------
0x00000000
----------------
指向命令行参数3的指针
----------------
指向命令行参数2的指针
----------------
指向命令行参数1的指针
----------------
程序名称
----------------
ESP --> 参数数目
----------------
从堆栈指针( ESP )开始,启动程序所使用的命令行参数的数目为 4 字节的无符号整数值被指定。在这之后,指向程序名称位置的 4 字节指针被存放在堆栈中的下一个位置。再之后,指向每个命令行参数的指针存放在堆栈中(同样,每个指针的长度是 4 字节)。
可以使用调试器查看这种情况。在调试器中运行 toqupper 程序(程序将输入文件所有字母转换为大写字母然后输出到输出文件,有时间的话会再出一文详细讲解),并且在程序启动时监视堆栈。首先,启动程序并且给它提供一个命令行参数(这里在调试器中的运行命令中完成的):
root@7e142849497c:~/html# gdb -q toupper
Reading symbols from toupper...
(gdb) b _start
Breakpoint 1 at 0x8049000: file toupper.s, line 65.
(gdb) run toupper.s toupper.up
Starting program: /var/www/html/toupper toupper.s toupper.up
warning: Error disabling address space randomization: 不允许的操作
Breakpoint 1, _start () at toupper.s:65
65 movl %esp, %ebp
(gdb) print $esp
$1 = (void *) 0xff9a3200
注意:Starting program 这一行表明运行命令中指定的命令行参数。ESP 指针显示它指向内存位置 0xff9a3200 。这表示堆栈的顶部。可以使用 x 命令查看这里的值:
(gdb) x/20x 0xff9a3200
0xff9a3200: 0x00000003 0xff9a3876 0xff9a388c 0xff9a3896
0xff9a3210: 0x00000000 0xff9a38a1 0xff9a38b1 0xff9a38c0
0xff9a3220: 0xff9a38cd 0xff9a38dc 0xff9a38e5 0xff9a38f0
0xff9a3230: 0xff9a3ed2 0xff9a3edd 0xff9a3eff 0xff9a3f0a
0xff9a3240: 0xff9a3f2a 0xff9a3f34 0xff9a3f3c 0xff9a3f4f
x/20x 命令显示从指定内存位置开始的前 20 个字节,格式是十六进制。第一个值表示命令行参数的数目(包括程序名称)。下两个内存位置包含指向稍后存储在堆栈中的程序名称和命令行参数字符串的指针。可以使用 x 命令和指针地址查看字符串值:
(gdb) x/s 0xff9a3876
0xff9a3876: "/var/www/html/toupper"
(gdb) x/s 0xff9a388c
0xff9a388c: "toupper.s"
(gdb) x/s 0xff9a3896
0xff9a3896: "toupper.up"
就像我们期望的,第一个指针指向存储在堆栈区域中的程序名称字符串。第二、三个指针指向命令行参数的字符串。
小心:记住,所有命令行参数都被指定为字符串,即使它们看上去像是数字,这一点很重要。
在命令行参数之后,4 字节的空值被存放在堆栈中,用于把参数和指向环境变量的指针的开始位置分隔开来。同样,可以使用 x 命令查看存储在堆栈中的一些环境变量:
(gdb) x/s 0xff9a38a1
0xff9a38a1: "SHELL=/bin/bash"
(gdb) x/s 0xff9a38b1
0xff9a38b1: "PWD=/root/html"
(gdb) x/s 0xff9a38c0
0xff9a38c0: "LOGNAME=root"
(gdb) x/s 0xff9a38cd
0xff9a38cd: "_=/usr/bin/gdb"
(gdb) x/s 0xff9a38dc
0xff9a38dc: "LINES=24"
(gdb) x/s 0xff9a38e5
0xff9a38e5: "HOME=/root"
(gdb) x/s 0xff9a38f0
0xff9a38f0: "LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc"...
(gdb) x/s 0xff9a3ed2
0xff9a3ed2: "COLUMNS=80"
(gdb) x/s 0xff9a3edd
0xff9a3edd: "LESSCLOSE=/usr/bin/lesspipe %s %s"
(gdb) x/s 0xff9a3eff
0xff9a3eff: "TERM=xterm"
(gdb) x/s 0xff9a3f0a
0xff9a3f0a: "LESSOPEN=| /usr/bin/lesspipe %s"
(gdb) x/s 0xff9a3f2a
0xff9a3f2a: "USER=root"
(gdb) x/s 0xff9a3f34
0xff9a3f34: "SHLVL=1"
(gdb) x/s 0xff9a3f3c
0xff9a3f3c: "LC_ALL=zh_CN.UTF-8"
(gdb) x/s 0xff9a3f4f
0xff9a3f4f: "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/root/bin"
取决于在 Linux 环境中加载的是什么应用程序,这里可能加载很多环境变量。