链接、装载与库[3]目标文件的结构内容

"本文详细探讨了Windows PE与Linux ELF可执行文件的区别,包括文件头内容、链接接口中的符号系统,ELF符号表结构,以及特殊符号和函数签名。还介绍了C++的extern"C"和弱符号的概念,帮助读者理解程序链接过程中的关键概念。"
摘要由CSDN通过智能技术生成

1.可执行文件的格式

  • Windows平台:Portable Executable(PE)
  • Linux平台:Executable Linkable Format(ELF)
    它们都是COFF(Common File Format)的变种

2.目标文件的内容

  • File Header:文件头。描述了整个文件的文件属性,包括文件是否可执行、是静态链接还是动态链接、入口地址、目标硬件、目标操作系统等信息
  • .text / .code:代码段。
  • .data:数据段。存储全局变量和局部静态变量。
  • .bss:(Block Started by Symbol)为未初始化的全局变量和局部静态变量预留位置。
  • .rodata:只读数据段
  • .comment:注释信息段
  • .note.GNU-stack:堆栈段
  • .strtab:字符串表。保存普通的字符串。
  • .shstrtab:(Section Header String Table)段表字符串表,保存段表中用到的字符串,最常见的就是段名(sh_name)。
  • .rel.text:重定位表(Relocation Table)。链接器在处理目标文件时,需要对目标文件中某些部位进行重定位。

3.链接的接口——符号

  • 将函数和变量统称为符号,函数名或变量名统称为符号名。每个目标文件都会有一个相应的符号表,这个表里面记录了目标文件中所用到的所有符号。每个定义的符号有一个对应的值,叫做符号值。对于变量和函数而言,符号值就是它们的地址。

4.ELF符号表结构

 typedef struct {
		Elf32_Word st_name;  //符号名,包含该符号名在符号表中的下标;
		Elf32_Addr st_value;  //符号值。
		Elf32_Word st_size;    //符号大小,值等于该数据类型的大小
		unsigned char st_info;  //符号类型和绑定信息
		unsigned char st_other;  //该成员目前为0,没用
		Elf32_Half st_shndx;	//表示符号所属的段
}Elf32_Sym
  • 符号类型和绑定信息
    1)STB_LOCAL:值为0。局部符号,对于目标文件的外部不可见。
    2)STB_GLOBAL:值为1。全局符号,外部可见。
    3)STB_WEAK:值为2。弱引用。
  • 符号类型
    1)STT_NOTYPE:值为0。未知类型符号。
    2)STT_OBJECT:值为1。该符号是数据对象,如变量、数组。
    3)STT_FUNC:值为2。该符号是函数或其他可执行代码。
    4)STT_SECTION:值为3。该符号是一个段,这种符号必须是STB_LOCAL。
    5)STT_FILE:值为4。该符号表示文件名,一般是该目标文件对应的源文件名,它一定是STB_LOCAL类型的,并且它的st_shndex一定是SHN_ABS。

5.特殊符号

特殊符号被定义在ld链接器的链接脚本中。

  • __executable_start:程序起始地址,注意不是入口地址。
  • __etext或_etext或etext:代码段最末尾的地址。
  • _edata或edata:数据段最末尾的地址。
  • _end或end:程序结束地址。

6.符号修饰与函数签名

  • 符号修饰
    为了防止类似的符号起冲突,有些编译器会为全局变量和函数名前后添加“—”。例如,foo 或_foo.
  • 函数签名
    函数签名用于识别不同的函数,它包含了一个函数的信息,包括函数名、参数类型所在的类和命名空间及其他信息。

考虑如下函数重载的情况:

int func(int);
float func(float);

class C {
  int func(int);
  class C2 {
  	int func(int);
  }
}
namespace N {
   int func(int);
   class C {
	 int func(int);
   }
}
  • 根据GCC的基本C++名称可得符号名如下。基本规则:所有的符号都以“_Z”开头,对于嵌套的名字后面紧跟“N”,然后是各个名称空间和类的名字,每个名字前是名字字符串长度,再以“E”结尾。对于一个函数来说,它的参数列表紧跟在“E”后面(对于int类型就是i,对于float类型就是f)。
函数前面修饰后名称(符号名)
int func(int)_Z4funci
float func(float)_Z4funcf
int C::func(int)_ZN1C4funcEi
int C:: C2::func(int)
int N::func(int)
int N::C::func(int)

7.extern “C”

  • C++为了与C兼容,在符号的管理上。C++有一个用来声明或定义一个C的符号的“ extern “C” ”关键字用法。此外利用C++的宏“__cplusplus”。如果当前编译单元是C++代码,那么memset会在extern "C"里声明,否则直接声明。
#ifdef __cplusplus
extern "C" {
#endif
void *memset(void *, int, size_t);
#ifdef __cplusplus
}
#endif

8.弱符号与强符号

  • 编译器将默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。同时,也可通过GCC的"__ attribute __((weak))"来定义任何一个强符号为弱符号。
  • 弱符号机制允许同一个符号的定义存在于多个文件中。
extern int ext;

int weak;
int strong = 1;
__attibute__((weak)) weak2 = 2;
  • weakweak2 是弱符号。

3条规则:
1)强符号不可在不同模块被定义多次;
2)若全局变量在一个模块未被初始化,而在另一个模块被初始化了,那么链接器将选择在另一个模块中定义的强规则;
3)弱符号可以被多次定义。

  • 规则1例子:这种情况链接器会报错
//foo2.c
int x = 111;

int main(int argc, char const *argv[])
{
	
	return 0;
}

//bar2.c
int x = 222;

void f(){
	
}
  • 规则2例子:这种情况函数f会将x 的值改为222。这种错误是不易察觉的。
//foo2.c
int x = 111;

int main(int argc, char const *argv[])
{
	f();
	return 0;
}

//bar2.c
int x;

void f(){
	x = 222;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值