翼度科技»论坛 云主机 LINUX 查看内容

Linux中的目标文件

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
一、目标文件的格式

Linux:ELF(Executable Linkable Format)
Windows:PE(Portable Executable)
COFF格式:PE和ELF都是源自COFF格式,Unix最早是a.out文件格式,为了解决共享库问题,引入了COFF格式。

  • 引入了段的机制,不同目标文件可以拥有不同数量的段和类型
  • 定义了调试数据格式
二、目标文件是什么?

目标文件就是与那代码编译后但未能链接的那些中间文件。(Linux中的.o,Windows中的.obj)
4种ELF文件

三、Linux中的目标文件

目标文件是经过了预处理、编译、汇编产生的ELF格式的文件。目标文件将代码、数据以及一些连接时需要的信息,通过 “节”或 “段” 存储。
分段的原因:

  • 数据和指令映射到两个虚存空间,这两个空间的权限不同,可以防止程序指令被改写。
  • 现代CPU缓存一般被设计成数据缓存和指令缓存分离,分段有利于提高程序的局部性。
  • 程序中如果运行着许多副本,内存中只需要保存一份程序的指令部分。
主要分为两种段,代码段(.text)和数据段(.data,.bss)
名称存储内容栈局部变量、函数参数、返回地址堆动态分配内存BSS段未初始化或者初始值为0的全局变量或者局部静态变量数据段(.test)已初始化且初始化值不为0的全局变量或者局部静态变量代码段 (.data)可执行代码、字符串字面值、只读变量
示例代码
  1. #include<stdio.h>
  2. int data1;                //.bss
  3. int data2 = 0;                //.bss
  4. int data3 = 10;                //.data
  5. static int data4;                //.bss
  6. static int data5 = 0;        //.bss
  7. static int data6 = 20;                //.data
  8. int main()                        //.text
  9. {
  10.     int a;                        //.text
  11.     int b = 0;                        //.text
  12.     int c = 10;                        //.text
  13.     static int data7;                //.bss
  14.     static int data8 = 0;        //.bss
  15.     static int data9 = 40;        //.data
  16.     return 0;
  17. }
复制代码

四、深入 .o文件
  1. 编译:gcc -c main.c
  2. 查看文件解构:objdump -h main.o
复制代码

Size:段的长度
File off:段的位置

4.1 代码段
  1. objdump -s -d main.o
复制代码

4.2 数据段
  1. objdump -x -s -d main.o
复制代码
可以清楚的发现,.data段中的前4个字节,从低到高为"0x0a 0x00 0x00 0x00 "这个值刚好是data3的值,10进制的10。

4.3 BSS段
  1. objdump -x -s -d main.o
复制代码

在之前的段表之中发现,.bss和.comment段的起始地址都是一样的。有些编译器会将全局的未初始化的变量放在.bss中,有些只是预留一个未定义的全局变量符号,等到链接的时候再在.bss段中分配空间。
可以在段表中发现,一些变量并未在.bss段中。

4.4 其他段


问题:将一个二进制文件作为目标文件的一个段?

4.5 自定义段
  1. //变量:
  2. __attribute__((section("FUN"))) int x = 10;
  3. //函数:
  4. __attribute__((section("BAR"))) void fun()
  5. {
  6. }
复制代码
五、ELF文件

ELF目标文件格式:


  • ELF文件头:包含着整个文件的基本属性,ELF文件版本、目标机器型号、程序入口地址。
  • 各个段
  • 段表:所有段的基本信息,段名、长度、偏移量、读写权限等
  • 字符表和符号表
    ...
5.1 文件头
  1. 查看文件头:readlef -h main.o
复制代码
文件头格式:


魔数:
文件头的结构和相关常数被定义在“/usr/include/elf.h”中,ELF文件有32为版本和64位版本,区别仅仅是成员大小不一样。

文件类型:
e_type类型表示ELF的文件类型,通常以ET_开头

机器类型:
e_machine
通常以EM_开头

"elf.h"定义了自己的类型:

通过Elf32_Ehdr观察文件头的结构和之前有些相似:

将ELF文件头结构与之前输出的一一对应:

5.2 段表
  1. readelf -S main.o
复制代码
显示ELF的主要段以及其他府逐段,如符号表、字符串表、段名字符串表、重定位表

段表的结构由"Elf32_Shdr"这个结构体数组保存,称为段描述符。
ELF段表数组的第一个元素是无效的段描述符,类型是"NULL",也就是有效段的数量是显示段-1。

这是其中每个段的含义


5.3 重定位表

链接器在处理目标文件的时候,需要对目标文件中某些部位进行重定位,即代码段和数据段中队绝对地址引用的位置。
重定位表的类型是"SHT_REL"。对于每一个需要重定位的代码段和数据段,都会有一个重定位表。
如:.rel.text就是.text的重定位表,因为.text至少有一个绝对地址的引用,就是调用了printf函数。
5.4 字符串表

段名为:.strtab或者.shastrtab
用来保存普通字符串或者用来保存段表中用到的字符串。
六、强符号和弱符号

6.1 强符号和弱符号

强符号:编译器默认函数和初始化的全局变量
弱符号:未初始化的全局变量
注意:强弱符号都是对定义来说的,不是针对符号的引用的
  1. extern int ext;
  2. int weak1;
  3. int strong = 1;
  4. __attribute__((weak)) weak2 = 2;
  5. int main()
  6. {
  7.         return 0;
  8. }
复制代码
weak1 和 weak2 都是弱符号
strong 和 main 都是强符号
ext既不是强符号也不是弱符号,因为它是一个外部变量引用。
强弱符号也有如下规则:

  • 不允许被多次定义(目标文件中不允许有同名的强符号)
  • 一个符号在某目标文件中是强符号,在另一个文件中是弱符号,那么选择强符号
  • 一个符号在目标文件中都是弱符号,那么选择占用空间最大的那一个。
6.2 弱引用和强引用

强引用没有找到符号的定义,链接器就会符号未定义的误。
弱引用

  • 如果没有定义,则链接器不报错
  • 如果该符号有定义,则链接器将该符号的引用决议
  • 对于未定义的弱引用,链接器不认为它是一个错误,一般未定义的弱引用,链接器默认是0,或者是一个特殊的值,以便程序代码能够识别。
  1. __attribute__((weakref))void fun();
  2. int main()
  3. {
  4.         fun();
  5.         return 0;
  6. }
复制代码
使用__attribute__((weakref))声明为弱引用。
链接时并不会报错,但是执行时会报错。
因为fun函的地址为0,发生了访址错误。
总结

什么是目标文件,4种目标文件,Linux中的目标文件
ELF:

  • 文件头
  • 段表
  • 重定位表
  • 字符串表
  • 符号表
  • 调试表
强符号和弱符号
强引用和弱引用

来源:https://www.cnblogs.com/baobaobashi/p/17063448.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具