第三章 目标文件里有什么

1261阅读 0评论2012-01-12 校长的马夹
分类:

现在PC平台流行的可执行文件格式主要是windows下的PE和linux的ELF,他们都是COFF格式的变种。
 
ELF格式的文件分为4类:
可重定位文件(Relocatable File):这类文件包含了代码和数据,可以被用来连接成可执行文件或共享文件,静态链接库也可归为这一类。实例:linux的.o和windows的.obj。
可执行文件(Executable File):这类文件包含了可以直接执行的程序。实例:/bin/bash文件和windows的.exe。
共享目标文件(Shared Object File):这类文件包含了代码和数据,可以在以下两种情况下使用。一种是连接器可以使用这种文件跟其他的可重定位文件和共享目标文件链接,产生新的目标文件。第二种是动态连接器可以将几个这种共享目标文件与可执行文件结合,作为进程映像的一部分来运行。实例:linux的.so和windows的DLL。
核心转储文件(Core Dump File):当进程意外终止时,系统可以讲该进程的地址空间的内容及终止时的一些其他信息转储到核心转储文件。实例:linux下的core dump。
 
.bss():.text.data段:
程序源代码被编译以后主要分成两种段:程序指令和程序数据。代码段属于程序指令,而数据段和.bss段属于程序数据。为什么分开放?
1)数据区域可读写,指令区域只读,防止程序的指令被有意或无意地改写。
2)对提高CPU的缓存命中率有好处
3)共享指令,节省空间(当系统中运行着多个某程序的副本是只需保存一份该程序的指令部分即可)。
 
objdump分析.o文件
.text:或称.code段,存放编译后的执行语句。
.data段:已初始化的全局变量和局部静态变量。
.bss段:Block Started by Symbol的缩写,为未初始化的全局变量和局部静态变量预留位置,并没有内容,在文件中也不占空间。
.rodata段:存放只读变量和字符串常量(也有些编译器吧字符串常量放到.data段)。
.comment段:注释
.shstrtab段:
Section Table段:
.symtab段:
.rel.text段:代码段重定位表text Relocation Table
.rel.data段:数据段重定位表data Relocation Table
.note.GNU-stack段:
.dynamic:动态链接信息
.plt:动态链接的跳转表。
.got:全局入口表
.init:程序初始化段
.fini:程序终结代码段
 
自定义段
如果要将一个二进制文件,比如图片、MP3文件、词典等一类的东西作为目标文件中的一个段,该怎么做?
可以使用objcopy工具来实现。
GCC提供一种扩展机制使得程序员可以指定变量所处的段:
在全局变量或函数之前加上“__attribute_(section("name")))”属性就可以把相应的变量或函数放到以“name”作为段名的段中。
 
 

魔数
    很多类型的文件,其起始的几个字节的内容是固定的(或是有意填充,或是本就如此)。因此这几个字节的内容也被称为魔数 (magic number),因为根据这几个字节的内容就可以确定文件类型。例如 FreeBSD 上 ELF 文件的 magic number 就是文件的前四个字节依次为"7f 45 4c 46",对应的ascii字符串即 "^?ELF"。tar 文件的 magic number 是从第257个字节起为 "ustar"。Unix 命令 "file" 应该就是利用这个原理工作的。
    在一个Project里面,避免使用魔数(Magic Number)和魔字符串(Magic String)是相当必要的。通过定义的常量去access特定的字符串和数字也已经是软件开发的standard。那么是不是所有的数字和字符串都应该定义成常量呢?或许有朋友会认为所有的数字和字符串都应该定义成常量,但是我觉得,每个字符串确实是应该定义成常量的,但是对于数字而言,如果数字本身的语义没有得到延伸,那么就不应该定义成常量。譬如数组的index就不应该定义成变量。

符号修饰与函数签名
符号修饰:解决函数重载
函数签名:用于识别不同的函数,包含了一个函数的信息,包括函数名、参数类型、所在的类和名称空间等其他信息。
符号修饰和函数签名机制不光被用在函数上,C++中的全局变量和静态变量也有同样的机制。

#ifdef __cpluscplus
extern "C" {
#endif

void *memset(void *, int, size_t);

#ifdef __cpluscplus
}
#endif
如果当前编译单元式C++代码,那么memset会在extern "C"里面被声明;
如果是C代码,就直接声明。

强符号与弱符号
对于C/C++语言来说,编译器默认函数和初始化了的全局变量为强符号;为初始化的全局变量为弱符号。
比如有下面这段程序:
extern int ext;

int weak;
int strong = 1;
__attribute__((weak)) weak2 = 2;

int main()
{
    return 0;
}
上面这段程序中,weak和weak2是弱符号,strong和main是强符号,而ext既非强符号也非弱符号,因为他是一个外部变量的引用。
针对强弱符号的概念,连接器就会按照如下规则处理与选择被多次定义的全局符号:
规则1:不允许强符号被多次定义;如果有多个强符号定义,则链接器报符号重复定义错误
规则2:如果一个符号在某个目标文件中是强符号,则在其他文件中都是弱符号,那么选强符号。
规则3:如果一个符号在所有目标文件中都是弱符号,则选择其中占用空间最大的一个。

强引用与弱引用对于库来说非常有用。

在linux中可以使用strip命令来去掉ELF文件中的调试信息。



 

 
 
 
上一篇:为什么 12306 不需要排队系统
下一篇:PowerShell格式化数字(Formatting Numbers) [