龙空技术网

[Linux C/C++]Hello world程序的命令行参数和环境变量存放在哪里?

final 78

前言:

而今大家对“环境变量文件在哪”大约比较关注,朋友们都需要知道一些“环境变量文件在哪”的相关内容。那么小编也在网上搜集了一些关于“环境变量文件在哪””的相关知识,希望看官们能喜欢,咱们快快来学习一下吧!

在Linux平台上,大家都很熟悉如下经典的Hello world程序 - hello.c。

[root:~/work/v1/hello]# cat hello.c//File Name: hello.c//The hello world program#include <stdio.h>int main(){printf("hello, world\n");return 0;}[root:~/work/v1/hello]#

C/C++ main函数的原型可以如下:

int main();int main(int argc, char *argv[]); //等价于int main(int argc,char **argv)int main(int argc, char *argv[], char *envp[]);//等价于int main(int argc, char **argv, char **envp)

main函数是可以带参数的,main函数的参数是用于向程序提供信息,这些信息可以是命令行参数,也可以是环境参数。

main函数参数的介绍如下:

• int argc

程序命令行参数的数量,argc > 0,即argc >= 1

• char *argv[]

程序命令行参数的指针数组,每个参数都是以空字符(‘\0’)结尾的字符串。argv[0]是(按照惯例)程序本身的名称。argv的指针列表以一个空指针终止(即 argv[argc] 是 NULL)。

• char *envp[]

程序的环境列表指针数组,与参数列表一样,环境列表是一个字符指针数组,每个指针包含一个以NULL结尾的C字符串的地址。环境参数的最后一个元素的下一个元素为NULL。

例如: 对于一个带有命令行和环境参数的hello程序,假如其调用过程如下:

• ./hello hello world

则此hello程序的参数组成如下图所示:

问题: 在hello程序执行过程中,main函数的命令行和环境参数存放在哪里? argv参数存放的位置和envp参数存储的位置有什么关系?

对于C/C++程序员来说,这个问题大家可能很容易想到,但如果不了解的话,可能很难给出自信且准确的回答。如果在面试过程中,碰到这个问题,如果事先不了解的话,大概率会一脸懵。今天我就带大家了解一下这个问题。

我们知道,main函数是由C运行时库调用的,调用关系为:

_start -> __libc_start_main -> main

其中_start位于crt1.o或Scrt1.o中,__libc_start_main 位于libc.so中。通过另一篇文章:[Linux C/C++]Hello world程序的入口点到底是什么?我们知道,抛开ld.so不说,hello ELF可执行文件最早的执行入口是_start,我们可以通过crt1.o的源文件中准确的了解到main函数参数的存储位置。

以x86-64平台为例,crt1.o是由以下命令生成的,其主要的源码来自于glibc源码中的sysdeps/x86_64/start.S:

gcc -nostdlib -nostartfiles -r -o crt1.o start.o abi-note.o init.o

start.o对应的源文件是sysdeps/x86_64/start.Sabi-note.o对应的源文件是csu/abi-note.Sinit.o对应的源文件是csu/init.cstart.S的源码解析

这里以x86-64平台为例。我们先看start.S源码开头的注释,这对我们理解整体的实现非常有帮助。

/* This is the canonical entry point, usually the first thing in the textsegment. The SVR4/i386 ABI (pages 3-31, 3-32) says that when the entrypoint runs, most registers' values are unspecified, except for:%rdx      Contains a function pointer to be registered with `atexit'.This is how the dynamic linker arranges to have DT_FINIfunctions called for shared libraries that have been loadedbefore this code runs.%rsp      The stack contains the arguments and environment:0(%rsp)   argcLP_SIZE(%rsp)   argv[0]...(LP_SIZE*argc)(%rsp)   NULL(LP_SIZE*(argc+1))(%rsp)   envp[0]...NULL*/

其中LP_SIZE在sysdeps/x86-64/sysdep.h中的定义为:

/* Long and pointer size in bytes. */#define LP_SIZE 8

通过这段注释,我们可以清楚了解到在_start执行前堆栈的数据,可以看到:

命令行和环境参数都位于堆栈上以及它们的存储位置命令行参数和环境参数之间间隔一个NULL(在x86-64上为8个字节)

我们简单的画出在_start执行之前hello程序堆栈上的命令行和环境参数的存储情况:

实验: 验证hello程序的参数存储位置

Ÿ 编译和链接:

[root:~/work/v1/hello]# gcc -g hello.c -o hello

[root:~/work/v1/hello]#

Ÿ 通过gdb启动hello程序。

[root:~/work/v1/hello]# gdb ./hello

Reading symbols from ./hello...

(gdb) b _start

Breakpoint 1 at 0x1060

(gdb)

Ÿ 添加断点,并让hello程序启动后停止在_start,从而进行堆栈分析。

(gdb) b _start

Breakpoint 1 at 0x1060

(gdb) run

Starting program: /root/work/v1/hello/hello

Breakpoint 1, 0x0000555555555060 in _start ()

(gdb)

Ÿ 查看栈指针寄存器rsp的值

(gdb) info registers rsp

rsp 0x7fffffffe390 0x7fffffffe390

(gdb)

Ÿ 通过x指令,按照每8字节(g参数)进行十六进制(x参数)打印,最多打印20组数据。

(gdb) x/20gx 0x7fffffffe390

0x7fffffffe390: 0x0000000000000001 0x00007fffffffe637

0x7fffffffe3a0: 0x0000000000000000 0x00007fffffffe651

0x7fffffffe3b0: 0x00007fffffffe661 0x00007fffffffe678

0x7fffffffe3c0: 0x00007fffffffe68c 0x00007fffffffe6a4

0x7fffffffe3d0: 0x00007fffffffe6bc 0x00007fffffffe6ca

0x7fffffffe3e0: 0x00007fffffffe6eb 0x00007fffffffe700

0x7fffffffe3f0: 0x00007fffffffe70f 0x00007fffffffe71e

0x7fffffffe400: 0x00007fffffffe727 0x00007fffffffe732

0x7fffffffe410: 0x00007fffffffe747 0x00007fffffffe758

0x7fffffffe420: 0x00007fffffffed3a 0x00007fffffffed46

(gdb)

Ÿ 分析gdb输出的20组数据。

根据前面的注释,

0x7fffffffe390: 为栈顶,存放的是argc。

可以看到在0x7fffffffe390处存储的值为0x0000000000000001,我们启动的hello程序的argc确实为1。

0x7fffffffe398:存放的是argv[0],其值为0x00007fffffffe637,我们检查此地址处的内容。

(gdb) x/s 0x00007fffffffe637

0x7fffffffe637: "/root/work/v1/hello/hello"

(gdb)

可以看到argv[0]指针存储的正是程序本身的名称。

0x7fffffffe3a0: 也就是argv[1]处存放的0(NULL),与预期相符,因为argv[argc]的指针指向NULL。0x7fffffffe3a8: 存放的是环境参数envp[0]指向的地址,其值为0x00007fffffffe651。

(gdb) x/s 0x00007fffffffe651

0x7fffffffe651: "SHELL=/bin/bash"

(gdb)

0x7fffffffe3a8及之后的地址上存放的都是环境参数的指针。

(gdb) x/s 0x00007fffffffe661

0x7fffffffe661: "LC_ADDRESS=zh_CN.UTF-8"

(gdb) x/s 0x00007fffffffe678

0x7fffffffe678: "LC_NAME=zh_CN.UTF-8"

(gdb)

通过上面的检查,可以看清楚hello程序的参数及环境变量的存储位置。

另外,我们知道,环境参数的最后由NULL结尾,是这样吗?从当前的20组输出中,我们可以看到确实没有看到0x0000000000000000的出现,不过我们再多打印一些就可以看到了。

存储0x0000000000000000的栈地址为0x7fffffffe4d0,其前一个栈元素的地址为0x7fffffffe4c8,这个位置存储应该还是环境参数的指针,我们可以查看一下这个地址存放的指针(0x00007fffffffefc9)的内容:

(gdb) x/s 0x00007fffffffefc9

0x7fffffffefc9: "OLDPWD=/root/work/v1"

(gdb)

通过上面的实验,我们完整了验证了hello参数的存储情况,与预期完全符合。

附录下载Glibc源码

标签: #环境变量文件在哪