请稍侯

可执行文件详解之ELF目标文件

29 November 2020

可执行文件详解之ELF目标文件

目标文件格式:windows 下的PE(Portable Executable)、Linux 的ELF(Executable Linkable Format)以及 macOS 的Mach-O 格式。

ELF文件类型:可重定位文件(Linux 的.o,Windows 的.obj);可执行文件(如/bin/bash文件,Windows 的.exe);共享目标文件(Linux 的.so,Windows 的DLL);核心转储文件(Linux 下的core dump).

注意:使用gcc -c hello.c,使用-c参数表示只编译,生成的目标文件只编译不链接;

工具

编译器编译源代码生成的文件叫目标文件,Linux使用file命令可以查看文件的文件格式。 使用binutils的工具objdump可以查看目标文件的内部结构,如objdump -h hello.oobjdump的”-h”参数把各个段的基本信息打印出来,”-x”把更多信息打印出来,”-s”参数将所有段的内容以十六进制的方式打印出来,”-d”可以将所有包含指令的段反汇编;
objcopy可以把一个二进制文件插入到目标文件作为一段,如objcopy -I binary -O elf64-i386 -b i386 image.jpg image.o,使用objdump -ht image.o查看;
size命令可以用来查看ELF文件的代码段、数据段和BSS段的长度;
readelf命令可以详细查看ELF文件,如readelf -h hello.oreadelf的”-S”参数查看ELF文件的段表结构,如readelf -S hello.o
macOS下可以使用jtool查看目标文件具体结构,./jtool --pages hello.o

ELF目标文件

文件头: ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台、ABI版本、ELF重定位类型、硬件平台及版本、入口地址、程序头入口及长度段表位置及长度、段表数量等。

段表:readelf -s hello.o 查看,如下 段表的结构是一个以”Elf32_Shdr”结构体为元素的数组,结构体如下:

重定位表: 目标文件中有一个.rel.text的段,它的类型是SHT_REL,这就是重定位表(Relocation Table),主要是记录代码段、数据段中那些对绝对地址的引用的位置相关的重定位信息。

字符串表与段表字符串表: 字符串表保存普通字符串,如符号的名字;段表字符串表保存段表中用的字符串,如段名。

符号

符号是链接的接口,所有的符号都记录在目标文件的符号表中;并且每个定义的符号都有一个对应的符号值,如变量与函数的值就是它们的地址。

函数签名包含了一函数名、它的参数类型、它所在的类和名称空间及其他信息,编译器在将C++源代码编译成目标文件时,会将函数的签名和变量的名称进行修饰,形成符号名,这就是编译器的符号修饰。

extern “C”

这是Visual C++ 用来声明或定义一个C的符号的关键字,它可以让C++的名称修饰机制不起作用。

extern "C" {
    int func(int);
    int var;
}

//单独声明某个函数或变量为C语言的符号
extern "C" int func(int);
extern "C" int varable;

利用Visual C++的名称修饰机制,这里创建一个extern_c.cpp 的文件,使用g++ extern_c.cpp -o extern_c构建一下。

#include <stdio.h>
// g++ extern_c.cpp -o extern_c

namespace myname {
	int var = 18;
}
extern "C" int _ZN6myname3varE;

int main() {
	printf("%d\n",_ZN6myname3varE);
}

因为C语言不支持extern "C"语法,要Linux 的 GCC编译器也兼容,可以使用C++的宏__cplusplus来判断当前编译单元是不是C++代码,以下代码可防止C++编译器将memset的符号修改成_Z6memsetPvii.

#ifdef __cplusplus
extern "C" {
#endif
	
void *memset (void *,int,size_t);
	
#ifdef __cplusplus	
}
#endif

__attribute__ ((weak))

通过GCC的 __attribute__ ((weak))可以定义任何一个强符号为弱符号。
因为默认是强引用,所以在链接时对外部目标文件的符号的引用若没有找到就会报未定义错误;如果是弱引用链接时就不会报错。 基于此,下面设计一个程序在运行时动态判断是否链接到pthread库(是否在编译时有-lpthread选项)从而决定执行多线程版本还是单线程版本。

#include <stdio.h>
#include <pthread.h>

/*
单线程版本:gcc pthread.c  -o pt
多线程版本:gcc pthread.c -lpthread -o pt 

GCC 中可以使用  __attribute__ ((weak)) 这个扩展关键字
来声明对一个外部函数的引用为弱引用.
*/
int pthread_create(pthread_t*, 
        const pthread_attr_t*, 
            void* (*)(void *), 
                      void *) __attribute__ ((weak));

int main(int argc, char *argv[]) {
	if(pthread_create) {
        printf("这是多线程版本");
    }else{
        printf("这是单线程版本");
    }
}