现在PC平台流行的可执行程序格式,主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),这里以Linux下的ELF格式可执行文件为例说明。
一般情况下,一个可执行二进制程序(更确切的说,在Linux操作系统下为一个进程单元)在存储(没有调入到内存运行)时拥有3个部分,分别是代码段(text)、数据段(data)和BSS段。这3个部分一起组成了该可执行程序的文件。而堆栈段是在程序装入内存后由系统分配的。
C程序各个段的意义:
段名 | 内容 |
---|---|
代码段 | 可执行代码、字符串常量、常量数据 |
数据段 | 已初始化全局变量、已初始化全局静态变量、局部静态变量 |
BSS段 | 未初始化全局变量,未初始化全局静态变量 |
栈 | 局部变量 函数参数 |
堆 | 动态内存分配 |
下面给出示例代码,并通过objdump来查看程序在的分段情况
//test.c
#include
#include
const int g_A =1; //代码段, 常量
int g_B =2; //数据段, 全局变量
static int g_C =3; //数据段,全局静态变量
static int g_D; //BSS段, 未初始化全局静态变量
int g_E; //BSS段, 未初始化全局变量
char * g_p1; //BSS段, 未初始化全局变量
int main(int argc, char const * argv[]) //栈,函数参数
{
int local_A; //栈,局部变量
static int local_B = 4; //数据段,局部静态变量
static int local_C; //BSS段,未初始化局部静态变量
char *p2 ="123456"; //“123456”在代码段,
//字符串常量, P2在栈上
g_p1 =(char*)malloc(10); //申请的10字节空间在堆上
return 0;
}
crab@ubuntu:~/Example/Ctest$ gcc -c test.c
crab@ubuntu:~/Example/Ctest$ size test.o
text data bss dec hex filename
74 12 8 94 5e test.o
前三部分的内容是代码段、数据段和 bss 段及其相应的大小。然后是十进制格式和十六进制格式的总大小。最后是文件名。
crab@ubuntu:~/Example/Ctest$ objdump -h test.o
test.o: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000035 00000000 00000000 00000034 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 0000000c 00000000 00000000 0000006c 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000008 00000000 00000000 00000078 2**2
ALLOC
3 .rodata 00000015 00000000 00000000 00000078 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002c 00000000 00000000 0000008d 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 00000000 00000000 000000b9 2**0
CONTENTS, READONLY
其中,Size表示段的长度,File off(File Offset)表示段所在的位置,
每个段第二行表示段的属性,例如,CONTENTS表示该段在文件中存在
crab@ubuntu:~/Example/Ctest$ objdump -s -d test.o
test.o: file format elf32-i386
Contents of section .text:
0000 5589e583 e4f083ec 20c74424 18040000 U....... .D$....
0010 00c70424 0a000000 e8fcffff ffa30000 ...$............
0020 0000c704 240b0000 00e8fcff ffffb800 ....$...........
0030 000000c9 c3 .....
Contents of section .data:
0000 02000000 03000000 04000000 ............
Contents of section .rodata:
0000 01000000 31323334 35360074 65737420 ....123456.test
0010 636f6465 00 code.
Contents of section .comment:
0000 00474343 3a202855 62756e74 752f4c69 .GCC: (Ubuntu/Li
0010 6e61726f 20342e34 2e342d31 34756275 naro 4.4.4-14ubu
0020 6e747535 2920342e 342e3500 ntu5) 4.4.5.
Disassembly of section .text:
00000000 <main>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 e4 f0 and $0xfffffff0,%esp
6: 83 ec 20 sub $0x20,%esp
9: c7 44 24 18 04 00 00 movl $0x4,0x18(%esp)
10: 00
11: c7 04 24 0a 00 00 00 movl $0xa,(%esp)
18: e8 fc ff ff ff call 19 <main+0x19>
1d: a3 00 00 00 00 mov %eax,0x0
22: c7 04 24 0b 00 00 00 movl $0xb,(%esp)
29: e8 fc ff ff ff call 2a <main+0x2a>
2e: b8 00 00 00 00 mov $0x0,%eax
33: c9 leave
34: c3 ret
最左边一列是偏移量,中间4列是16进制内容,最右边一列是段的ASCII码形式。
Contents of section .data:
0000 02000000 03000000 04000000 ............
其中02000000(0x00000002), 03000000(0x00000003), 04000000(0x00000004)正是对应的代码中定义的全局变量g_B,全局静态变量g_C和局部静态变量local_B的值。
2 .bss 00000008 00000000 00000000 00000078 2**2
ALLOC
虽然通过size查看BSS段大小是8字节,但是.bss段的数据为空,因此不占用目标文件的空间。从基本信息里可以看到BSS段和.rodata段的起始地址(00000078)是相同的。
Contents of section .rodata:
0000 01000000 31323334 35360074 65737420 ....123456.test
0010 636f6465 00 code.
从内容可以看到定义的const常量g_A和字符串常量“123456”都在这个段里面。
在上面分的段中没有.rodata段,所以应该讲这段归入哪个段中哪?
如果从基本信息中看分配的地址.rodata段地址是在.data段后面。为什么会说字符串常量是属于代码段哪?
我这里对代码做了下修改将字符串常量从“123456”修改为“123456789123”,然后再编译通过size查看哪个段的大小发生了变化,这样就能确定字符串常量是否真的属于代码段。
执行后结果
crab@ubuntu:~/Example/Ctest$ size test.o
text data bss dec hex filename
80 12 8 100 64 test.o
发现代码段(text)大小发生了变化,变大了,这样就可以确认字符串常量是属于代码段,也就能确认.rodata是划分属于代码段的。
为什么会这么划分,我查了些资料,const 和字符串字面值都在.rodata段。程序加载时.rodata和.text合并到一个segment中。所以,个人理解是.rodata段也认为是代码段的一部分。这也就涉及到文章开始时提到到静态代码和进程启动时的动态加载会合并一些段并再系统分配堆栈。