深入理解程序的结构

程序的构成:从操作系统的角度来看

文件头记录了与可执行文件相关的一些基本信息。

静态分析工具大多根据程序的文件头获得相关信息。

初始化的变量进入.data段,没有初始化的全局变量进入.bss段。

未初始化的变量默认为0,为了存储效率和加载效率。

没有初始化的默认为0,在程序文件中就不用存储初始值了,只需在程序文件中记录:有几个全局变量,以及它们的类型。

而data段要保存初始值。

实验分析:

代码如下:

做如下编译:

-e指明程序入口,-nostartfiles说明不使用程序自带的那些启动文件。

使用objdump可以查看程序有哪些段:

1、2这两个段是gnu系统特有的辅助段,7这个段也是可以去掉的。 最重要的就是.text段。我们这个程序中没有数据段。

改写程序:

objdump结果如下:

.data和.bss段都有了。

改编程序:

objdump结果如下:

编译器默认按4字节对齐,因此bss段还是占用8字节。

将程序改成如下形式就占用4个字节了:

使用nm查看符号:

 

dt_main和text段的初始地址正好对应上。

g_no_value在bss段,其中的B代表bss。g_value在data段,D代表data。

c_no_value和bss的起始地址是一致的。

c_no_value之后就是g_no_value。

用objdump查看data段中保存的初始值信息:

上图表示从8049ff4开始存储了01000000  02000000

改造程序:

 编译之后objdump结果如下:

data段的字节数是21,4字节对齐之后data段的大小应该是24,图中显示的18是16进制。正好等于十进制的24。

nm查看符号:

可以看到多了一个符号g_str。

再次改写程序:

objdump结果和nm结果:

g_c的内存地址正是rodata段的起始地址。

查看rodata段的内容:

在内存中,程序还需要栈段,这是在内存中才存在的段:

局部变量不会映射到可执行文件,因为可执行文件中没有栈段。局部变量相当于被编译到了程序的text段中。

main函数隶属于主线程。有自己的栈。如果有子线程,每个子线程也有自己的栈。

堆对应的段也不会存在于可执行文件中。

 

内存映射段也是可执行程序被加载之后被建立的一片存储空间。

常规的用法是做文件的内存映射。

例如,读文件,通常是用read。

如果使用内存映射就是将文件映射到内存映射段,然后读取内存就相当于读取文件了。

 如果我们的程序需要使用动态库,原理上也是将动态库的内容映射到内存映射段。

我们也可以在内存映射段创建匿名映射区存放程序数据。

将文件内容映射到内存,读文件内容时就直接读内存,第一次读会发生缺页中断,在中断中将文件的实际数据载入到内存中。

这样的文件读写方式更加高效。

 示例:

read读文件需要经过内核,文件内容先拷贝到内核空间,然后拷贝到用户空间。经历了两次拷贝。

mmap:

文件内容拷贝到物理内存也是在中断中完成的,而且只经历了一次拷贝。

调用mmap时,将文件和虚拟内存建立映射关系,这时并没有物理内存什么事。当使用ptr指针真正访问这片虚拟内存时,如果是第一次访问则产生缺页中断,在中断中分配物理内存,将虚拟内存和物理内存对应起来,并且将文件的内容拷贝到物理内存,可以看到mmap只经历了一次拷贝。

 使用read读文件需要经过两次拷贝,mmap方式只需文件内容的一次拷贝。

小结:

 堆的起始地址是随机的,内存映射段的起始地址也是随机的,栈的起始地址也是随机的,这些随机性都是为了安全考虑。

栈的地址如果不是随机的,栈帧信息很容易暴露,因此被恶意程序修改返回地址。

原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9823345.html