• 剖析.o文件ELF组成


    ELF文件结构组成

    ①总共13个节

    ②每个节都有一个编号。从ELF头开始编号,编号从0开始,编号的作用就是用来索引(找到)不同节的。

    ③每个.o的都是这样的结构。链接时要做的就是,将ELF格式的.o全部合成为一个完整的ELF格式可执行文件。

    ④.o中每个节的逻辑地址都是从0开始的

    ELF头

    ELF格式头放什内容?

    放ELF格式所需要的一些基本信息,比如

    ①系统所规定的字的大小。64 OS:字大小是64bit  32 OS:字大小是32bit

    ②字节顺序(字节序)  用于说明系统是大端序的还是小端序的。

    ③其它

    1)ELF格式头的大小
    2)目标文件类型(可重定位目标文件、可执行目标文件、共享目标文件)
    3)CPU架构:X86 或 X86_64、AMD_64
    4)等等

    使用readelf,查看“可重定位目标文件”的ELF头信息

    readelf:读取目标文件的ELF格式信息的,跟-h选项的话,就是查看ELF格式头信息。

    [root@localhost ~]# readelf -h test.o
    ELF Header:
      Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
      Class:                             ELF64
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0
      Type:                              REL (Relocatable file)
      Machine:                           Advanced Micro Devices X86-64
      Version:                           0x1
      Entry point address:               0x0
      Start of program headers:          0 (bytes into file)
      Start of section headers:          664 (bytes into file)
      Flags:                             0x0
      Size of this header:               64 (bytes)
      Size of program headers:           0 (bytes)
      Number of program headers:         0
      Size of section headers:           64 (bytes)
      Number of section headers:         13
      Section header string table index: 12
    View Code

    .text

    存放所有函数的机器指令,不过某些常量也会直接和指令一起存在.text当中。.text:也是只读节

    eg:

    int main(void)
    {
        int a = 20;
        a = a + 100; //表达式中的100会直接和指令放在一起
    }
    View Code

    .rodata

    只读数据节,存放只读数据(放某些常量数据)。

    eg:

    int a = 100;
    printf("%d", a);
                    
    char *p = "hello world";
    View Code

    格式字符串"%d"和"hello world"这两个字符串常量,都放在了.rodata中。

    .data 

    ①初始化了全局变量

    int a = 100; //初始化了的全局变量,a就是在.data节中
                        
    int main(void)
    {
        printf("%d
    ", a);
    }
    View Code

    ②初始化了的静态局部变量

    int main(void)
    {
        static int b = 101;//已经初始化了的静态局部变量
                        
        printf("%d", a);
    }
    View Code

    初始化了的静态局部变量的空间就开辟在.data中。

    注意:如果没有static的话,b就是自动局部变量,b空间开辟于栈中。但是只有程序运行起来后才有栈这个东西,因此作为还处在编译阶段的.o来说,自动局部变量还不存在,或者说还只是以函数代码的形式存在于.text中。当程序运行起来有了栈之后,该函数的代码会在栈中开辟自动局部变量的空间,并将数据101存入开辟的空间。

    .bss

    ①未初始化的全局变量

    int a; //未初始化了的全局变量
                        
    int main(void)
    {
        printf("%d", a);
    }
    View Code

    ②未初始化的静态局部变量

    int main(void)
    {
        static int b;//未初始化的静态局部变量
                            
        printf("%d", a);
    }    
    View Code

    由于没有初始化数据,所以其实不占用空间,因此在.o中,.bss只是一个占位符,只有当程序真正运行起来后,才会在内存上真正的开辟.bss的空间,并在.bss空间中开辟a和b的空间,并制自动初始化为0。所以在.o中,.bss只是一个理论上的存在。

    .o为什么没有开辟.bss空间?

    没有实际要存放的数据,开辟空间只是浪费空间。你要知道.o这个文件是存在我们的电脑硬盘上的,.o如果有.bss空间的话,.bss是要占硬盘空间的。

    .symtab:符号表

    参考:.symtab

    symtab记录什么

    每一个.o文件都有一个符号表,用于存放.o中所定义和引用的全局符号信息(函数和全局变量的符号信息)。

    比如a.c
    int a = 100;
                        
    int fun(int a)
    {
        ...
    }
                        
    extern int b; //定义在了b.c中
                        
    int main(void)
    {
        b = 10000;
                            
        fun2(1000); //fun2定义在了c.c中
    }
                        
    a.c -> a.o
    b.c -> b.o
    c.c -> c.o
    View Code

    a.o的.symtab符号表就记录如下符号的信息。

    ①a.o中定义的符号信息

    a:a.o自己定义的全局变量符号
    fun:a.o自己定义的函数符号
    main:a.o自己定义的函数符号

    ②a.o中引用的符号信息

    b:a.o引用的在b.o中定义的全局变量符号
    fun2:a.o引用的在c.o中定义的fun2函数符号

    注意:.symtab符号表并不记录符号的名字

    .symtab记录符号的基本信息,符号是否有定义,符号对应的空间在哪个节中等,但是符号的名字本身并不存在符号表中。

    .symtab符号表的意义

    众多的.o之所以能被链接在一起,这张符号表所记录的信息功不可没。比如,a.o中引用的b和fun2,被定义在了b.o中,将a.o和b.o链接在一起时,必须查看各个.o中的.symtab表,才能将各自符号的定义和引用关联起来。

    注意:符号表并不记录自动局部变量的符号。

    int fun()
    {
        int a = 100;
        ...
    }
    View Code

    为什么不记录自动局部变量的符号?

    自动局部变量是在程序运行起来有了栈以后才有的,在编译阶段fun函数的int a = 100,在fun函数中只是压栈和弹栈的代码。当程序运行起来有了栈后,当fun函数开始运行时,fun的压栈代码会开辟自动局部变量的空间,fun函数结束时会调用弹栈代码自动释放空间。但是如果是static修饰的静态局部变量的话,符号表中会记录符号,因为在编译阶段就会处理静态局部变量。

    .rel.text

    将多个.o链接到一起时,每个.o的.text会被整合为一个.text,整合.text时就必须依赖.rel.text所记录的一些有关.text中指令的位置信息。

    .rel.data

    将多个.o链接到一起时,每个.o的.data会被整合为一个.data,整合到一起时,就必须要依赖.rel.data所记录的一些有关.data的信息。

    .debug

    符号调试表,记录调试信息,编译时必须加-g选项,编译时才会在.debug节中加入调试信息。

    .line 

    存放代码行号,因为调试的时候往往需要显示源码的行号。只有gcc编译时加了-g选项后,才会加入行号信息。

    .strtab

    字符串表

    .symtab、.debug所用到符号名字、每个节的节名字(比如.text等)、源文件名字(***.c)等,都存在.strtab中

    比如:
    .text.rodata......funmain

    节头部表

    描述目标文件中的每个节的某些相关信息

  • 相关阅读:
    实验三
    第二、三周作业
    实验二
    第一周作业
    学号20182325袁源 实验一《Linux基础与Java开发环境》实验报告
    20182331 2019-2020-1 《数据结构与面向对象程序设计》实验五报告
    20182331 2019-2020-1 《数据结构与面向对象程序设计》实验四报告
    20182331 2019-2020-5《数据结构与面向对象程序设计》第5周学习总结
    20182331 2019-2020-4《数据结构与面向对象程序设计》第4周学习总结
    20182331 2019-2020-1 《数据结构与面向对象程序设计》实验三报告
  • 原文地址:https://www.cnblogs.com/kelamoyujuzhen/p/9413272.html
Copyright © 2020-2023  润新知