程序实例介绍各个段内容、分析段(segment)分布
先举个小例子说明,让大家有个表面的印象。
下面的程序定义了一些不同类型的变量,主要包括初始化的全局变量(globle_var1 )和静态全局变量(globle_static_var1 )、未初始化的全局变量(globle_var2)和静态全局变量(globle_static_var2)、常量(globle_const_var 、const_var )和局部变量(var1 、var2)。并通过函数func进行打印输出求和结果,非常简单的一个程序。
//hello.c
#include<stdio.h>
int globle_var1 = 10;
int globle_var2;
static int globle_static_var1 = 20;
static int globle_static_var2;
const int globle_const_var = 30;
void func(int i)
{
printf("%d\n",i);
}
int main()
{
static int static_var1 = 70;
static int static_var2;
const int const_var = 40;
int var1 = 30;
int var2;
var2 = globle_var1+globle_static_var1+globle_const_var+static_var1+const_var+var1;
func(var2);
return 0;
}
在 目标文件中的内容至少包括编译后的机器指令代码、数据,还包括了链接时所需要的的一些信息,比如符号表、调试信息、字符串等。目标文件将这些信息按不同的属性以“ 段 ”(segment)的形式存储。以下就是一些常见的段。
并且还根据一个简单程序说明编译后的目标文件的结构。主要分析表格中常见的段信息,其实还有其他段例如注释信息段( .comment ),但这不是我们关注的重点。
下面的图是一个目标文件的结构分析,主要分析代码和数据存放在那些段中,图中没有给出堆栈段信息,因为堆栈需要在程序被装载准备运行才会分配,因此在目标文件这个阶段还没有分配栈段的内存。并且堆栈相信大家非常熟悉,栈就是存放函数局部变量,malloc()内存分配就是从堆分配的。
分析代码段(.text)、数据段(.data)、bss段、只读数据段(.rodata)划分依据
以前学习的时候我也只背住各个段分别存放什么内容,根本不知道为什么需要进行不同段的划分。下面分析一下划分的依据,以及这样划分有什么好处。
首先,可以先区分代码段和数据段。程序源代码编译后的机器指令就会放在代码段里;然后这里的数据段包括" .data “、” .bss “、” .rodata ",将程序中定义的全局变量和局部变量都称先称为数据段。
为什么把程序的“ 代码段 ”和“ 数据段 ”分开存放?
- 当程序被装载后,数据和指令分别被映射到两个虚拟内存区域。数据段对进程来讲是可读写的,而代码段对进程来说是只读的,所以这两个虚拟内存区域的权限可以被分别设置为可读写和只读,防止程序的指令被有意和无意地改写。
- 现代CPU的缓存一般被设计成数据缓存和指令缓存分离,程序的指令和数据被分开存放对CPU的缓存命中率提高有好处。
- 当系统中运行着多个该程序的副本时,例如多个线程同时都运行同一个程序,它们的代码段指令都是一样的,所以内存中只需要保存一份该程序的代码段,然后将每个副本进程的数据段区域分来,这样可以节省大量空间。
为什么数据段还需要分" .data “、” .bss “、” .rodata "这么麻烦?有什么区别?
主要根据两个维度进行区分,是否占内存空间、读写权限。
以初始化的全局变量和局部静态变量都保存在" .data “段。未初始化的全局变量和局部静态变量一般都放在” .bss "段,因为未初始化的变量默认值为0,本来它们也可以放在.data段,但是因为它们都是0,所以为它们在.data段分配空间并且存放数据0是没有必要的。
" .data"段和" .bss “段都是可读写的数据段,而” .rodata “存放的是只读数据,主要是一些const变量和字符串常量。单独设立” .radata “段的好处是,在程序加载的时候可以将” .rodata “段的属性映射成只读,这样对这个段的任何修改操作都作为非法操作处理。另外在某些平台还可以将” .rodata "段存放在只读存储器,例如ROM,通过硬件保证只读。
所以如果通过命令objdump查看各个段的大小发现,.data段大小为12字节,正好是变量globle_var1、globle_static_var1、static_var1的大小。但是.bss段的大小为8个字节,并不是变量globle_var2、globle_static_var2、static_var2变量的大小,可以看出.bss并没有为变量分配内存。
zhouyuanlin@zhouyuanlin-virtual-machine:/mnt/hgfs/share$ ls
a.out hello.c hello.o
zhouyuanlin@zhouyuanlin-virtual-machine:/mnt/hgfs/share$ objdump -x -s -d hello.o
hello.o: 文件格式 elf64-x86-64
hello.o
体系结构:i386:x86-64, 标志 0x00000011:
HAS_RELOC, HAS_SYMS
起始地址 0x0000000000000000
节:
Idx Name Size VMA LMA File off Algn
0 .text 00000073 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 0000000c 0000000000000000 0000000000000000 000000b4 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000008 0000000000000000 0000000000000000 000000c0 2**2
ALLOC
3 .rodata 00000008 0000000000000000 0000000000000000 000000c0 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 00000036 0000000000000000 0000000000000000 000000c8 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 000000fe 2**0
CONTENTS, READONLY
6 .eh_frame 00000058 0000000000000000 0000000000000000 00000100 2**3
参考文献
[1] 俞甲子,石凡,潘爱民.程序员的自我修养——链接、装载与库[M].北京:电子工业出版社,2009.4