(转载)GCC与Obj文件,动态链接文件和ELF文件

本文详细介绍了GCC编译过程中的Obj文件、动态链接文件和ELF(Executable and Linkable Format)文件。Obj文件是编译过程中产生的二进制文件,包含代码和数据,用于链接器做地址重定位。动态链接不复制函数库代码,而是提供函数库名称,由动态链接器在运行时加载所需函数。ELF文件是最常见的执行文件格式,包括可执行文件、共享对象文件等,其结构包含ELF头、程序头表、节头表等,提供了灵活的动态链接和执行需求。在内存中,程序由ELF文件和共享对象文件组合,通过动态链接器加载到特定地址执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(转载)GCC与Obj文件,动态链接文件和ELF文件     

--------------------------------------------------------------------------------
 
1、Obj文件
     程序员编写程序,其实就是编写出一个2进制(binary)文件。假如我们声明一个变量char c,也就是声明需要一个8bit的空间,那么就需要向系统声明豫留8bit的空间,怎么做到这一点呢?就是编译一个特殊的2进制文件--obj文件,用gcc编译的C语言得到的执行文件,里面不仅包含CPU指令,还有很多别的信息在里面,它有很多格式COFF、ELF……等等,在最后一道编译过程中,链接器(linker)ld会加载一堆信息进入可执行文件。例如,当有多个编译后等待链接的.o这种可重定位(relocatable)文件,既然这些文件里面参数或者函数名的相对位置只是本身所在.o文件的相对位置,就有一些信息要告诉链接编辑器(link editor)怎么修改section的内容,来做relocate,也就是做地址的重新参照以便合成一个新的可执行文件。

     一个obj文件有两个重要时期,一个是正在链接(link)的时候,也就是处在
硬盘(disk)里的时候;一个是正在执行的时候,当然这时它位于内存里。我们平时说的ld linker其实叫link editor,最后编译的步骤ld把该有的信息写进可执行文件。如果是static link就会去找libxxx.a的函数库文件,把想要的程序代码片段拷贝一份进可执行文件,并且做成relocation后,把跳来跳去的参照写进可执行文件,这个文件就可以执行。

2、动态链接文件
     相对于静态链接(static link)拷贝原有的程序代码进可执行文件,动态链接不那样做,link editor把一些信息写进可执行文件而已。例如,需要的程序库名、函数名等,最后执行的时候,必须呼叫dynamic linker來做program intepreter,dynamic linker会根据需要的函数库名称,把想要的函数名字创造一个可执行的image放到内存,所以执行有动态链接的执行文件,最后通常都是由OS的exec系列的system call与dynamic linker如ld.so联合完成。

dynamic linker通常会做如下工作:
(1)把可执行文件的内容加载到process image
(2)把shared obj需要的东西加载到process image
(3)完成relocation

      本来这些obj文件里面的虚拟地址应该和文件的地址有相对应的偏移(offset),而文件首地址通常是0x08040800,这是绝对虚拟地址,但它只适合可执行文件,例如Linux extuable file通常是:
         file offset         virtual  address
         -----------         ----------------
         0x0                   0x08048000
         0x100                 0x08048100

      shared obj函数库里的程序代码必须为位置无关代码Position Independent Code (PIC),也就是说它的地址可能会随不同process而有不同,例如,一个程序只用了libc.so、ld-linux.so,通常这时候lib.so是从0x40017000开始的,但如果另一个程序多用一个libm.so,那么libc.so从0x40034000开始两个的printf参照(reference),就会有不同的地址,所以这种动态函数库的内部资料就要说明这些code是PIC。

3、ELF文件
(1)简介
     现在最常用的是一种叫ELF格式(executable and linkable format)的执行文件,ELF定义了一些变量与信息使得动态链接更有弹性,一个ELF的2进制文件按照spec 1.1版的说法有6种,下列是较常见的:

relocatable:它就是编译时产生的.o文件,包含了代码和数据(这些数据是和其 他 重定位文件和共享的object文件一起连接时使用的)

executable:它就是最后的可执行文件,包含了代码和数据shared obj:它就是在/lib /usr/lib下那些可动态链接的函数库文件,包含了代码和数据(这些数据是在连接时候被连接器ld和运行时动态连接器使用的)

core:Core Dump时产生的文件,包含了一堆garbage数据

     注意:这些ELF文件已经是广义的2进制文件,不单指可执行文件。
   
(2)ELF组成
     一个ELF obj文件随它存在的时期有不一样的需求和组成名字,在要链接linking时期位于硬盘,包含了:
ELF header
program header table (可以不要)
section 0
section 1
section 2
section 3
section ...
section n
section header table

     ELF header放了ELF定义的一些ELF格式识别字串(俗称magic number),还有obj文件(shared obj,relocatable或者executable)这些一般(general)的信息;program header table是描述了段(segment)信息的结构数组和一些为程序运行准备的信息。segement和section不大一样,是属于程序执行时期的元素,所以在程序执行时期是必要的,在链接时期是不必要的,所以如果程序不做链接动作,只要有program header table就可以;section header table就是一个索引表,来记录各个section的索引,sections就是把需要的资料根据属性用途分门别类后的小集合,有.bss .data .init .debug .dynamic .fini .text………,其中比较重要的有:

.text
      里面保存真的CPU指令
.bss
     保存没有initialize的data
     主要是声明的global与static变量
.data
     保存initialize的data

     写程序用到的函数名,变量名分布在多个source code目录里时,需要一个
参照(reference)的信息做连接这些名字,symbol是着被给linker来做连接用的,因为obj文件分散存在,要把这些obj文件的代码集合起来,就要靠symbol来辨别,string table存有很多行字串,每行字串用NULL来分开,每行字串就是symbol和section的名字。symbol table是一张表,存有将来要定址或重新定址所要的symbol定义和参照信息。shared lib的obj文件还有.dynsym这个section,里面存有dynamic symbol table,动态链接的时候使用。另外,如果将来的程序要用debug工具调试,编译时要加-g这个选项,它会根据sumbol和string table放进debug多需要的信息给obj文件,这样的信息现在大都用一种叫stab的格式存放,这同时也会让执行文件大小增加到将近3倍。

     在ELF不同的文件型态里,ELF定义的信息该有的都有,header section……只是里面的值或有不同而已。

     Unix/Linux通常从一个_start函数开始而不是从main开始,_start后来会调用main,所以如果要精简程序,就不要用gcc编译,直接汇编用_start就可以了(^_^)。另外像section header table如果不需要做链接也可以不要,还有可执行文件的symbol table等,其实这些可以全部不要,不过要用汇编并同GAS来生成可执行文件。其实还有很多东西,这就是为什么即使根本没有调用任何函数,做成的动态文件,用ldd看一定有ld-linux.so libc.so了。

     而一个存在内存中的process image,如下所示:
ELF header
program header table
segment 0
segment 1
segment 2
segment ...
segment n
section header table (可以不要)

     Segment有Text,Data等,根据OS定义不同,Text根据存在硬盘文件里的.txt .fini等section来的,Data段根据.data .bss等section来的,一个segment通常包含了 一个或一个以上的section,这些section在程序员角度来看更显的重要。

     在支持ELF的系统上,一个程序是由可执行文件或者加上一些shared obj文件组成。为了执行这样的程序,系统使用那些文件创建进程的内存映像。为了使一个ELF文件装载到内存,必须有一个program header table(该program header table 是一个描述段信息的结构数组和一些为程序运行准备的信息)。这里有几个在ELF文档中定义的比较特别的sections。以下这些是对程序特别有用的:

.fini
    保存进程终止代码指令
    因此,当一个程序正常退出时,系统安排执行这个section中的代码
.init
    保存可执行指令,它构成了进程的初始化代码
    因此,当一个程序开始运行时,在main函数被调用前(C语言称为main),
系统安排执行这个section中的代码

     .init和.fini sections的存在有着特别的目的。假如一个函数放到.init section,在main函数执行前系统就会执行它。同理,假如一个函数放到.fini section,在main函数返回后该函数就会执行。该特性被C++编译器使用,完成全局的构造和析构函数功能。

     当ELF可执行文件被执行,系统将在把控制权交给可执行文件前装载所以相关的共享object文件。构造正确的.init和.fini sections,构造函数和析构函数将以正确的次序被调用。

     Unix/Linux的虚拟内存使用有这样的范围:

user area

0x0 ~ 0x0bffffff  -> 3GB

kernel area

0x0c000000 ~ 0xffffffff  -> 1GB

以下面程序代码为例:
int global;

static int func1 (void)
{
               static int b;
               int *c;
               int d;
               func2();
               return 1;
}
int func2 (void)
{
               int c;
               static int d;
               return 2;
}
int main(void)
{
               int a;
               static int b;
               int init = 3;
               func1();
               return 3;
}

那么从一个Linux执行文件在内存中看起来是这个样子:

i386 Linux的执行image

           Virtual   Address  Allocation

             |----------------------------------|0x0  
             | |-----------------------------|  |
             | |                                     |  |
             | |  Thread  stack              |  |  
             | |------------------------------|  |      
             |                                           |
             | |------------------------------|  |0x08048000 Text                          
             | |  executable                  |  |                    Data            
             | |                                      |  |                     ……
             | |                                      |  |
             | |                                      |  |
             | |                                      |  |
             | |------------------------------|  |
             |                                           |
             | |------------------------------|  |0x40000000 ld-linux.so
             | |                                     |  |                     libm.so
             | |  shared    LIB               |  |                     libc.so
             | |                                     |  |          
             | |  Stack                           |  |
             | |                                     |  |
       3GB| |----------------------------- |  |
             |                                           |
             | |------------------------------|  |0xc0000000
             | |                                      |  |
             | | Kernel Code and Data  |  |
             | |                                      |  |
             | |-------------------------------|  |
       4GB|---------------------------------   |0xffffffff

其中0x08048000 ~ 0x40000000 ~ 0xc0000000是这样存在的。

从C角度看的image:

      0x08048000
    |--------------------------------------------------------|
    |   |--------------------------------------------------|  |
    |   |   main()                                                 |  |                              
    |   |           xxxx                    Text                 |  |
    |   |   func1                           (instrction)   |  |  
    |   |           xxxx                                            |  |
    |   |   func2                                                  |  |  
    |   |           xxxx                                           |  |
    |   |-------------------------------------------------|  |
    |                                                                     |
    |   |-------------------------------------------------|  |
    |   |   int global                       Data             |  |
    |   |   static int b(main)  static int b(func1)  |  |
    |   |   static int c(func2)                               |  |
    |   |-------------------------------------------------|  |
    |                                                                     |
    |   |-------------------------------------------------|  |
    |   |   malloc(int)                      Heap            |  |
    |   |-------------------------------------------------|  |
    |                      |                                              |    
    |                      |                                              |
    |                     /|/                                             |                            
    |--------------------------------------------------------|
    |    0x40000000                                              |
    |                                                                      |
    |                                                                      |
    |--------------------------------------------------------|
    |                     /|/                                             |
    |                      |                                               |
    |                      |                                               |
    |    |-------------------------------------------------|  |
    |    |  func2 int c                     Stack  2          |  |
    |    |-------------------------------------------------|  |
    |                                                                      |
    |    |-------------------------------------------------|  |
    |    |  func1 int b                     Stack  1         |  |
    |    |-------------------------------------------------|  |
    |                                                                      |
    |    |-------------------------------------------------|  |
    |    |  main()  argv[0]  argv[1]  …                  |  |
    |    |-------------------------------------------------|  |
    |--------------------------------------------------------|
      0xbfffffff

      所以可以清楚的知道不同变量(global,static or auto)的生命周期(storage class),和不同变量的有效范围(scope)。
 
      Kernel code和data当然存在内存中,所以实际上都还要经过page table 转成实际地址。在0x0~ 0xbfffffff中的page table,每个process有不同page table,但在0xc0000000以下的page table,则都一样。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值