源码 SimpleSection.c
示例程序如下,该示例程序在ubuntu环境下编译和查看:
/*
* SimpleSection.c
*
* Linux:
* gcc -c SimpleSection.c
*
* Windows:
* cl SimpleSection.c /c /Za
*/
int printf( const char * format, ...);
int global_init_var = 84;
int global_uninit_var;
void func1(int i)
{
printf("%d\n", i);
}
int main(void)
{
static int static_var = 85;
static int static_var2;
int a = 1;
int b;
func1(static_var + static_var2 + a + b);
return a;
}
目标文件 SimpleSection.o 详细解析
1. 编译和dump
首先我们将.c文件编译成目标文件然后objdump查看。
编译命令 gcc -c SimpleSection.c -o SimpleSection.o,得到目标文件SimpleSection .o。使用objdump – 分析ELF文件结构的利器来查看细节:
objdump -s -d SimpleSection.o
其中:-s 参数用来将ELF文件所有段的内容以十六进制输出;-d 参数将包含指令的段反汇编(一般是.text代码段)。
2. 段分析
-
.text(代码段)
代码段的详细说明见上图,包含的正式fun1()和main()这两个函数的机器指令。
这里有个细节要注意,代码段(其他段也一样)中间黄色方框部分形如 554889e5 总共占4个字节,这四个字节内部字节序按大小端排序(与机器有关),这里每4个字节组成的字节序单元从左往右地址递增排序。 -
.data(数据段)
保存的是初始化了的全局变量和局部静态变量。
global_init_var 和 static_var。 -
.rodata(只读数据段)
存放只读数据。
只读数据有:
(1) const修饰的变量;
(2) 字符串常量。上例中的打印语句 printf 中的%d\n
;
单独设立 .rodata 段的好处- 支持关键字 const
- 操作系统加载时可将.rodata段映射成只读属性,以后对该段的修改会判定为非法操作,增加了程序的安全性
- 某些嵌入式平台中经常会采用只读存储器(ROM),将.rodata段放在这里可保证程序访问存储器的正确性
说明:有些编译器会把字符串常量放到.data段(例如MSVC编译器)
- .bss(block started by symbol)
前面有说过,.bss段只是为未初始化的全局变量和局部静态变量预留位置,实际并不占空间。更多细节如下:- 首先ELF文件存放在磁盘上未被执行时.bss段是不占空间的。这一点可以通过objdump -s xxx.o 来查看具体ELF文件的所有段的内容,可以发现并没有.bss段的内容。
- 其次ELF文件被链接成可执行文件执行时才会为.bss段中的内容分配内存空间。这里面又有四个细节:
1)未初始化的局部静态变量在ELF文件中是被标记在真正的 .bss 段中的(见下图 static_var2)
2)未初始化的全局变量在EFL文件中是被用符号 “*COM*” 标记的(见下图 global_uninit_var),也就是说并不是标记在.bss段的。只是待到被链接成可执行文件执行时,才会在.bss段分配空间给该变量。这与编译器的实现有关。
3)未初始化的全局变量如果添加static修饰(未初始化的全局静态变量),在ELF文件中也会被标记在.bss段,而不是用 “*COM*” 标记。这点可以修改后再通过 objdump -x来确认。
4)初始化为0的静态变量也是被标记在EFL文件的.bss段的,而不是放在 .data 段。这是因为未初始化的都是0,编译器优化将初始化为0等同于未初始化,一同放到了.bss 段,来节省磁盘空间。
扫描二维码关注公众号,回复:
10183822 查看本文章
![](/qrcode.jpg)
思考
从实际工作经验来看,目标文件的反汇编部分经常会用来分析一些复杂问题。
上面介绍的分析方法除了可以分析.o文件,同样也可以用于分析死机 sysdump 文件,因为sysdump文件也是符合COFF格式规范的,同理动态和静态链接文件也可以如此分析(可参考3.1+3.2 目标文件格式和内容)。