您的位置:首页 > 编程语言

C源代码编译及加载执行过程(二)

2013-09-06 17:17 190 查看

C源代码编译及加载执行过程(二)

     在前一篇的基础上我们再稍微细致的了解一下每个过程到底发生了什么,此文就不对预处理,编译再分析啦,下面从汇编开始介绍。
   先说一个汇编代码,此代码功能是求几个数的最大数,这几个数以零做为结束标志。
#find the max number of a set of data items.
.section .data
data_items:
.long 3,67,23,0
.section .text
.globl _start
_start:
movl $0,%edi
movl data_items(,%edi,4),%eax
movl %eax,%ebx
start_loop:
cmpl $0,%eax
je loop_exit
incl %edi
movl data_items(,%edi,4),%eax
cmpl %ebx,%eax
jle start_loop
movl %eax,%ebx
jmp start_loop
loop_exit:
movl $1,%eax;
int $0x80


         下面我们介绍一下此代码:
   汇编程序中以"#"开头的是注释。
   汇编程序中以.开头的名称并不是指令的助记符,不会被翻译成机器指令,它只是给汇编器一些指示,称为汇编指示或伪操作。.section指示把程序分成若干个段,每个段被加载到内存的不同地址具有不同的读、写、执行权限。.section .data声明一个数据段,具有读写权限 
  data_items标号可把其当作数组的首地址。.long指示数组成员是32位的。
  .section .text声明一个代码段,是只读和可执行的。
  .globl _start:_start符号就如同我们C语言中的变量名和函数名,本质上代表的是一个地址,不过_start符号比较特殊,她就如同C的main函数一般,是整个程序的入口。链接器在链接时会查找目标文件的_start符号代表的地址,把它设置为整个程序的入口地址。.globl告诉汇编器_start符号会被链接器用到,所以要在目标文件中特殊标记。每个汇编程序都包含一个.globl _start语句,如果一个符号没有用.globl声明,就表示这个符号不会被链接器用到。
   程序的后续代码就不介绍啦。现在我们对这个.s文件汇编后的目标文件查看一下有啥变化:




  
    我们截取了目标文件的一部分看一下有啥变化。
    ELF Header描述了操作系统是UNIX,体系结构是80386,文件类型是可重定位的目标文件。Section Header Table 中有8个Section Header,每个Section Header有40个字节。在文件中的位置从160开始(0xA0)每个40字节共320个字节,到1DF结束,这个目标文件没有Program
Header。
    从Section Header中读出每个段的描述信息,.data和.text是我们声明的,其它是汇编器添加的。Addr是加载到内存的地址,到链接的时候才填写,所以目前是0。off和size描述了各段在目标文件中的文件位置和大小。
    根据以上信息我们就可画出目标文件的布局。ELF Header从0x0-0x33,7个有文件地址的段在文件中的地址明显啦,再加上Section Header Table从0xA0-1DF就是目标文件的布局。
    .data段会原封不动的加载到内存中。.shstrtab中保存的是各段的名字,.strtab中保存的是各符号的名字,每个名字都是以"\0"结束的字符串。.bss段保存未初始化的全局变量,它虽然占用一个Section Header但没有对应的Section,.bss段在加载时占多大的内存在Section Header描述,这个例子没有用到.bss段。.rel.text告诉链接器哪些地方需要重定位。.symtab是符号表。.text段我们可以反汇编查看一下变化,如下:
     



    
    汇编文件变成目标文件后所有的符号都变成了地址。目前所有的跳转地址和内存访问中的地址全是符号的相对地址。下一步链接的时候,链接器会把地址改成加载时的内存地址,这些指令才能正确执行。
    下面我们看一下目标文件到可执行文件发生了什么变化。



    





   
    在ELF Header中type变成了exec,Entry point address改成了0x8048074(_start符号的地址),还可以看出少了两个Section Header,多了两个Program Header。
    在Section Header Table中,.text和.data的加载地址分别改成了0x08048074和0x080490a0。bss段没有用到,所以被删掉了。.rel.text段就是用于链接过程的,链接完了就没用了,所以也删掉了。Program Header
Table 描述了两个Segment的信息。.text段和前面的ELFHeader、Program Header Table一起组成一个Segment(FileSiz指出总长度是0x9e),.data段组成另一个Segment (总长度是0x10)。VirtAddr列指出第一个Segment加载到虚拟地址0x0804 8000(注意在x86平台上后面的PhysAddr列是没有意义的),第二个Segment加载到址0x0804 90a0。Flg列指出第一个Segment的访问权限是可读、可执行,第二个Segment的访问权限是可读可写。最后一列Align值0x1000(4K)是x86平台的内存页面大小。在加载时文件也要按内存页面大小分成若干页,文件中的一页对应内存中的一页。
    这个可执行文件很小,总共也不超过一页大小,但是两个Segment必须加载到内存中两个不同的页面,因为MMU的权限保护机制是以页为单位的,一个页面只能设置一种权限。此外还规定每个Segment在文件页面内偏移多少加载到内存页面仍然偏移多少,比如第二个Segment在文

件中的偏移是0xa0,在内存页面0x0804 9000中的偏移仍然是0xa0,所以是从0x0804 90a0开始,这样规定是为了简化链接器和加载器的实现。.text段的加载地址应该是0x0804 8074,也正是_start符号的地址和程序的入口地址。原来目标文件符号表中的Value都是相对地址,现在都改成绝对地址了。此外还多了三个符号__bss_start、_edata和_end,这些是在链接过程中添进去的,加载器可以利用这些信息。
    虽然看了不少但许多地方还是一知半解,可能与自己的知识积累量达不到要求有关,暂且先记下来,以后再详细理解一下!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息