您好,欢迎来到[编程问答]网站首页   源码下载   电子书籍   软件下载   专题
当前位置:首页 >> 编程问答 >> C/C++ >> 计算机软件基本知识二:可执行文件的结构和加载

计算机软件基本知识二:可执行文件的结构和加载

来源:网络整理     时间:2016/7/12 13:08:09     关键词:

关于网友提出的“ 计算机软件基本知识二:可执行文件的结构和加载”问题疑问,本网通过在网上对“ 计算机软件基本知识二:可执行文件的结构和加载”有关的相关答案进行了整理,供用户进行参考,详细问题解答如下:

问题: 计算机软件基本知识二:可执行文件的结构和加载
描述:

可执行文件的结构和加载 
          
我们先来看一个程序: 
///////////////////////////////////////////////////////////////// 
     
    int global_a = 0x5; /* 01 */ 
    int global_b; /* 02 */ 
     /* 03 */ 
    int main() /* 04 */ 
    { /* 05 */ 
     char *q = "123456789"; /* 06 */ 
     /* 07 */ 
     q[3] = 'A'; /* 08 */ 
     /* 09 */ 
     global_a = 0xaaaaaaaa; /* 10 */ 
     global_b = 0xbbbbbbbb; /* 11 */ 
     /* 12 */ 
    // strcmp(q, NULL); /* 13 */ 
     return 0x0; /* 14 */ 
    } /* 15 */ 
     
    1. 你能说出程序中出现的变量和常量在可执行程序的哪个段中么? 
    2. 程序运行的结果是什么? 
     
    ///////////////////////////////////////////////////////////////// 
     
    能正确回答上面问题者,此节可以跳过不读: 
但是:
    如果有人问笔者第一个问题,我会说:“不知道”。因为没告诉目标 CPU,编译器,链接器。 
    如果有人问笔者第二个问题,我会说:“不知道”。因为没告诉链接器,链接参数,目标操作系统。 
详细说明一下:
    比如 "123456789" 在某些编译环境下出现在 ".text" 中,某些编译环境下出现在 ".data" 中。 
    再比如,如果用 VC6.0 环境,编译时加上 /GF 选项,该程序会崩溃(第 8 行)。 
    再比如第 13 行,这种错误极为愚蠢,但是在某些操作系统下居然执行得挺顺利,至少不会崩溃。空指针为什么会崩溃下面会讲。 
    所以 C 程序严重依赖于,CPU,编译器,链接器,操作系统。正是因为这种不确定性,所以为了保证你写的程序能在各种环境下运行,或者你想能够在任何环境下 debug 你的 C 程序。你必须知道可执行文件的格式和操作系统如何加载。否则当你在介绍自己的时候,只能使用类似:“我是X86平台上,VC6.0集成开发环境下的 C 语言高手” 之类的描述。颇为尴尬。 
     
    为了说明方便我们的讨论建立在一套虚拟的环境上。当然了这仅限于宏观的讨论,一些具体的例子我会给出我调试所用的环境。我们假设虚拟环境满足下列条件: 
    1. 足够物理内存 
    2. 操作系统不允许缺页中断 
    3. 物理页面 4K 
    4. 二级页表映射 
    5. 4G 虚拟地址空间 
    6. 操作系统不支持 swap 机制 
    7. I/O 使用独立的地址空间 
    8. 有若干通用寄存器 r0,r1,r2,r3,...... 
    9. 函数的返回值放在 r0 中 
    10. 单 CPU 
     
    言归正传,过于古老的文件结构我们不提(入门的格式请参考 a.out 格式)。现在比较常用的文件格式是 ELF 和 PE/COFF。嵌入式方面 ELF 比较主流。 
     
     可执行文件基本上的结构如下图: 
     
     +----------------------------------+ 
     | 
     | 文件头
     |  
     +----------------------------------+ 
     |  
     | 段描述表  
     |  
     +----------------------------------+ 
     | 
     | 段1  
     | 
     +----------------------------------+ 
     |  
     | :  
     | 
     +----------------------------------+ 
     |  
     | 段n  
     | 
     +----------------------------------+ 
     
     其中这些段中常见的段有 .text,.rodata,.rwdata,.bss。还有一些段因为编译器和文件格式有细微差别我们不再一一说明。 
     参考:1. Executable and Linkable Format Specification 
     2. PE/COFF Sepcification 
     
     .text:正文段,也称为程序段,可执行的代码 
     .rodata:只读数据段,存放只读数据 
     .rwdata:可读写数据段, 
     .bss段:未初始化数据 (下文详述) 
     
  就上面的例子来说,我们先回答第一个问题: 
     1. a 在 .rwdata 中 
     2. b 在 .bss 中 
     3. q 程序运行的时候从 stack 中分配 
     4. 'A',0x5,0xaaaaaaaa,0xbbbbbbbb 在 .text 段。 
     5. "123456789" 在 .rodata 中 
     
     第二个问题,程序在第 8 行会崩溃。程序为什么会崩溃呢?要回答这个问题我们要知道可执行程序的加载。 
     
     可执行程序的加载 
     
     当操作系统装载一个可执行文件的时候,首先操作系统盘但该文件是否是一个合法的可执行文件。如果是操作系统将按照段表中的指示为可执行程序分配地址空间。操作系统的内存管理十分复杂,我们不在这里讨论
就上面的例子来说可执行文件在磁盘中的 layout 如下:(假设程序的虚拟地址从 0x00400000 开始,该平台的页面大小是 4K) 
     
     +----------------------------------+ 
     | 
     | 文件头  
     | 
     +----------------------------------+------------------ 
     | .text 描述  
     | 虚拟地址起始位置 : 0x00400000  
     | 占用虚拟空间大小 : 0x00001000 
     | 实际大小 : 0x00000130 
     | 属性 :执行/只读 
     +----------------------------------+  
     | .rwdata 描述 
     | 虚拟地址起始位置 : 0x00401000  
     | 占用虚拟空间大小 : 0x00001000  
     | 实际大小 : 0x00000004  
     | 属性 :读写 
     +----------------------------------+ 
     | .rodata 描述  
     | 虚拟地址起始位置 : 0x00402000  
     | 占用虚拟空间大小 : 0x00001000  
     | 实际大小 : 0x0000000A 
     | 属性 :只读 
     +----------------------------------+  
     | .bss 描述 
     | 虚拟地址起始位置 : 0x00403000 
     | 占用虚拟空间大小 : 0x00001000  
     | 实际大小 : 0x00000000 
     | 属性 :读写 
     +----------------------------------+----------------- 
     | 
     | .text 段                         | <- 4K对齐,不满补 0 
     |
     +----------------------------------+----------------- 
     |0x5  
     | .rwdata 段                       | <- 4K对齐,不满补 0 
     | 
     +----------------------------------+----------------- 
     |123456789                         
     | .rodata 段                       | <- 4K对齐,不满补 0 
     | 
     +----------------------------------+----------------- 
     
    请注意,.bss 段仅仅有描述,在文件中并不存在。为什么呢?.bss 专用于存放未初始化的数据。因为未初始化的数据缺省是 0,所以只需要标记出长度就可以了。操作系统会在加载的时候为它分配清 0 的页面。这种技术叫做 ZFOD (Zero Filled On Demand)。 
     
    操作系统首先将文件读入物理页面中(物理页面的管理比较复杂,不属于本文讨论的范围),反正大家就认为操作系统找到了一批空闲的物理页面,将可执行文件全部装载。如图: 
     
     : 
     +----------------------------------+ <---- 物理页面对齐 
     | 
     | .text 段 
     | 
     +----------------------------------+ 
     : 
     : 
     +----------------------------------+ <---- 物理页面对齐 
     |0x5 
     | .rwdata 段  
     | 
     +----------------------------------+ 
     : 
     : 
     +----------------------------------+ <---- 物理页面对齐 
     |123456789  
     | .rodata 段  
     | 
     +----------------------------------+ 
     : 
     : 
     
    在物理地址中,这几个段并不连续,顺序也不能保证,甚至如果一个段占用几个页面的时候,段内的连续性和顺序都不能保证。实际上我们也不程序关心在物理内存中的 layout。只需要页面对齐即可。 
     
    最后操作系统为程序创建虚拟地址空间,并建立虚拟地址-物理地址映射(虚拟地址的管理十分复杂,反正大家就认为映射建好了。另外:注意我们的假设,系统不支持缺页机制和 swap 机制,否则没有这么简单)。然后我们从虚拟地址空间看来,程序的 layout 如下图: 
     
     +----------------------------------+ 0x00400000 
     |  
     | .text 段 
     | 
     +----------------------------------+ 0x00401000 
     |0x5  
     | .rwdata 段  
     | 
     +----------------------------------+ 0x00402000 
     |123456789  
     | .rodata 段 
     | 
     +----------------------------------+ 0x00403000 
     |  
     | .bss 段  
     | 
     +----------------------------------+ 
     
    同时操作系统会根据段的属性设置页面的属性,这就是为什么通常程序的段是页面对齐的,因为机器只能以页面为单位设置属性。 
     
    所以第二个问题自然就有了答案。程序会 crash。因为 .rodata 段所属的页面是只读的。其实有些编译器会将常量 "123456789" 放在 ".text" 中,其实是一样的,两个段都是只读的,写操作都会导致非法访问,甚至同一种编译器,不同的编译参数,这个常量也会出现在不同的位置。实际上这个保护由编译器,链接器,操作系统,CPU串通好了,共同完成的。 
     
    所以说计算机有些具体问题并没有一定之规,但是他们基本的原理是一样的。我们掌握了基本原理,具体问题可以具体分析。 
至于空指针为什么会造成程序崩溃,是因为操作系统没有给你的小地址建立页表映射,也就是说,你访问没有映射的地址时候就会引起CPU不快,操作系统发现CPU怒了,你的程序就被干掉了。如果一级页面没有映射,小于4M的地址全部非法;如果二级页表没有建立映射,小于4K的地址全部非法。至于之前说的访问0地址不出错,是因为某些操作系统把小地址建立映射了,但是属性是只读的,不信你向随便一个操作系统的0地址写点什么试试。
另外不少人比较喜欢考 const 这个关键字,什么常量,指向常量的指针,常量指针指向常量等等,我就想知道,你的程序质量真的是由于没用const造成的么?考官恐怕也不知道到底 const 导致的错误是由于编译器保证的,链接器保证的还是操作系统本身保证的。所以说我们在无聊的事情上总是很专业。


以上介绍了“ 计算机软件基本知识二:可执行文件的结构和加载”的问题解答,希望对有需要的网友有所帮助。
本文网址链接:http://www.codes51.com/itwd/2550567.html

相关图片

相关文章