golang程序的启动内存布局

云计算分享者 2024-02-22 08:18:45

Linux x86-64架构下的ABI规定如下。进程启动时,其初始化的栈布局如下。栈本身是从高地址向低地址生长的。

%rsp指向的地方是int argc,从%rsp+8到上面的argc个qword是argv指针数组(char* argv[]),再上面有一个全0的qword,然后是和char* argv[]类似的char* envp的值,然后又是一个全0的qword,再上面是auxiliary vector的信息,再上面就是argv,envp,auxiliary的真正的值存放的地方了。

对照这个ABI,我们通过gdb来看下

通过go build,加上-gcflags '-l -N'来编译得到没有任何优化的文件

然后用gdb来开始调试。先使用i files来得到程序的入口地址。通过 b *入口地址 在入口处打一个断点。通过set args a b c d e来设置等会执行的参数(这样才好看argc,argv的信息),然后run开始执行,会马上停在入口的地方。

ps auxf可以看到gdb下面开始启动了进程了

为了看内存布局,先使用i r看下寄存器的值,看到SP已经被操作系统准备好了。我们通过SP来看上面提到的内存布局

gdb中使用x命令来看内存的值。 x/70xg 0x7fffffffdee0

70表示重复70次,x表示输出用hex表示,g表示显示单位为8bytes的。

可以看到SP的地址处值为6,表示程序本身+5=6个参数

后面紧接着就是6个在栈上的地址argv。在一个全0的qword之后,就是envp的值,然后一个全0的qword之后是auxiliary的值

这儿需要注意,argc,argv,envp,auxp这些值,都是kernel在execve的系统调用里面准备完成的,这部分逻辑就不详细分析了。可以认为execve这个系统调用在返回给用户态的时候,已经把这些值准备好了,然后把SP,IP这些寄存器都准备好了。

再使用x/70sg 0x00007fffffffe16f看argv和envp的值,注意这儿使用的FMT是s,表示string模式,这样可以很容易看到里面的内容,这儿不使用x模式了,因为看起来太不直观了。可以看到argv的6个值和envp的所有值

要注意的是这儿的字符串都是以\000结尾的,为了看到这个,我们再用x/120cb 0x00007fffffffe16f看一次,这个c就是char的意思。

其中的0 '\000'就表示是\000结尾的。

再看下auxv的值,这部分是一个type/value形式的对,是kernel的execve系统调用,载入完成了elf文件之后,和argc,argv,envp一起准备的。根据其type,值的类型可能不同,最后以type和value都是一个为0的qword结束。这儿我们就不看具体的值了。

在man 3 getauxval里面有介绍相应的信息。这个值主要是给动态链接器使用的,对elf的,就是ld-linux.so。我们可以通过给一个动态链接的文件传入LD_SHOW_AUXV=1来触发ld-linux.so打印出这部分的值。因为我们刚才go build的是一个静态链接的文件,无法直接打印出来,但是其值都是类似的。

0 阅读:0

云计算分享者

简介:感谢大家的关注