深入理解程序的结构

1静态可执行程序中的段

程序由不同的段构成(代码段、数据段),程序的静态特征就是指令和数据,动态特征就是执行指令处理数据。
源程序代码到可执行程序的对应关系:
深入理解程序的结构

代码段

  • 源代码的可执行语句编译后进入代码段,编译完成后大小固定
  • 代码段在内存管理单元的系统中具有只读属性(是一种保护,防止被改写)
  • 代码段中可以包括常量数据(如字符串常量)

    数据段(.data .bss .rodata)

    .bss段(Block Started by Symbol segment)
    存储未初始化的全局数据,不占用可执行文件的大小。
    .data段:存储具有非0初始值的变量
    .rodata段:存储const修饰的和其他只读数据
    问题:.bss和.data段同样存储的是全局数据,为什么初始化的和不初始化的保存在不同的段中?
    .bss段中的变量不用在再程序文件中保存初始值,从而减小可执程序的大小,提高程序加载的效率。(对于.bss段中的变量,在可执行文件中只需要保存其变量名和变量类型,在加载时统一将其初始化为0;而对于存储于.data段中的数据,需要保存其变量名,类型、和值,在加载其需要将变量值拷贝得到变量对应的空间)
    编程实验:可以编写简单测试程序,通过objdump -h命令查看各个段的信息,使用nm命令查看可执行文件中的符号和地址,使用objdump -s -j .data ./a.ou查看某个段中的具体信息,并将上述信息对应起来。

    6.2动态加载后生成的段

    栈(stack)

    栈时在程序被加载到内存之后才生成的,其本质时片连续的存储空间
    SP寄存器作为栈顶“指针”实现入栈操作和出栈操作
    深入理解程序的结构
    栈通常作为以下用途
    中断发生时,栈用于保存寄存器的值(现场保护)
    函数调用时,栈用于保存函数的活动记录(栈帧信息)
    并发编程时,每个线程拥有自己独立的栈

    堆(Heap)

    堆和栈一样,时程序被加载到内存后才生成的。是一片“空闲的空间”,用于提供动态内存分配。
    需要函数的支持(malloc、free)
    内存映射段(memory mapping segment)
    内核将硬盘中的文件内容直接映射到内存映射段(mmap)
    动态链接库在可执行程序加载时映射到内存映射段
    程序执行时能够创建匿名映射区存放程序数据
    内存映射文件的原理:
    将硬盘上的文件数据逻辑映射到内存中(零耗时),通过缺页中断进行文件数据的实际载入(一次数据拷贝),映射后对内存的读写就是对文件的读写(极大的提高了文件的读写效率)。
    深入理解程序的结构
    使用传统的方式通过read函数来读取文件,首先内核程序接到应用程的请求,然后内核程序去读取硬盘中的文件内核空间,然后再讲内核空间中的数据拷贝到应用空间(使用了两次数据拷贝)。
    最终各个段在内存中的布局:
    深入理解程序的结构
    这里我们看到栈、堆的起始地址都是随机的,这样做的目的是为了安全,当其实地址随机后,恶意代码修改程序的暗度变大(难以直接通过固定地址获取到程度的返回地址)。