文件格式

现在PC平台流行的可执行程序格式,主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),这里以Linux下的ELF格式可执行文件为例说明。
一般情况下,一个可执行二进制程序(更确切的说,在Linux操作系统下为一个进程单元)在存储(没有调入到内存运行)时拥有3个部分,分别是代码段(text)、数据段(data)和BSS段。这3个部分一起组成了该可执行程序的文件。而堆栈段是在程序装入内存后由系统分配的。

C程序各段

C程序各个段的意义:

段名内容
代码段可执行代码、字符串常量、常量数据
数据段已初始化全局变量、已初始化全局静态变量、局部静态变量
BSS段未初始化全局变量,未初始化全局静态变量
局部变量 函数参数
动态内存分配

示例代码

下面给出示例代码,并通过objdump来查看程序在的分段情况

源码
//test.c
#include <stdio.h>
#include <stdlib.h>
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;
}

详细分析

  1. 编译生成目标文件
crab@ubuntu:~/Example/Ctest$ gcc -c test.c
  1. 通过size查看内存分布
crab@ubuntu:~/Example/Ctest$ size test.o
  text	   data	    bss	    dec	    hex	filename
    74	     12	      8	     94	     5e	test.o

前三部分的内容是代码段、数据段和 bss 段及其相应的大小。然后是十进制格式和十六进制格式的总大小。最后是文件名。

  1. 通过objdump查看内部结构,-h显示各个段的基本信息
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表示该段在文件中存在

  1. 通过objdump查看所有段内容
    -s用于将所有段的内容以16进制的方式打印出来,
    -d用于将所有包含指令的段反汇编。
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的值。

  • BSS段
  2 .bss          00000008  00000000  00000000  00000078  2**2
                 ALLOC

虽然通过size查看BSS段大小是8字节,但是.bss段的数据为空,因此不占用目标文件的空间。从基本信息里可以看到BSS段和.rodata段的起始地址(00000078)是相同的。

  • rodata段
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段也认为是代码段的一部分。这也就涉及到文章开始时提到到静态代码和进程启动时的动态加载会合并一些段并再系统分配堆栈。

Logo

更多推荐