关于ELF文件格式的理解与读取

1、ELF Header

注:以下只讨论64位的情况

该段结构体如下:

typedef struct {
        unsigned char   e_ident[EI_NIDENT]; //16 B (B for bytes)
        Elf64_Half      e_type; // 2 B
        Elf64_Half      e_machine; // 2 B
        Elf64_Word      e_version; // 4 B
        Elf64_Addr      e_entry; // 8 B
        Elf64_Off       e_phoff; // 8 B
        Elf64_Off       e_shoff; // 8 B
        Elf64_Word      e_flags; // 4 B
        Elf64_Half      e_ehsize; // 2 B
        Elf64_Half      e_phentsize; // 2 B
        Elf64_Half      e_phnum; // 2 B
        Elf64_Half      e_shentsize; // 2 B
        Elf64_Half      e_shnum; // 2 B
        Elf64_Half      e_shstrndx; // 2 B
} Elf64_Ehdr; 

我们可以结合使用readelf -as ‘elf文件名’命令来查看:图1
图1
可以看到ELF Header与上述结构体一一对应,其中具体含义可以自己百度,推荐:

https://blog.csdn.net/helowken2/article/details/113739946

上图再往下可以看到Section Header节头表,其中e_shnum表示Number of section headers,为10,如果我们想要读取节头表,我们就需要节头表的字符表.shstrrab,可以知道.shstrrab在最后。而.shstrrab节点的索引号在ehdr中用e_shstrndx来表示,
如图2:
在这里插入图片描述

2、Section Header

结构体如下:

typedef struct {
	Elf64_Word	sh_name; // 4 B (B for bytes)
	Elf64_Word	sh_type; // 4 B
	Elf64_Xword	sh_flags; // 8 B
	Elf64_Addr	sh_addr; // 8 B
	Elf64_Off	sh_offset; // 8 B
	Elf64_Xword	sh_size; // 8 B
	Elf64_Word	sh_link; // 4 B
	Elf64_Word	sh_info; // 4 B
	Elf64_Xword	sh_addralign; // 8 B
	Elf64_Xword	sh_entsize; // 8 B
} Elf64_Shdr

一个结构体,可以用来描述图2中的一个节头表。该结构体元素的具体涵义可以自己百度。
所以推导出获取.shstrrab节点的方法为:

	Elf64_Ehdr ehdr[1];//定义ELF 头
	FILE *fp;
	fp = fopen(argv[1],"rb");//首先使用文件指针打开elf文件
	if(fp==NULL)
		exit(EXIT_FAILURE);
	fread(ehdr, sizeof(Elf64_Ehdr), 1, fp);//然后读取该文件的elf头至ehdr中

	int count = ehdr->e_shnum;    //获取节头表数量
	Elf64_Shdr shdr[count];		//定义节头表数量
	fseek(fp, ehdr->e_shoff, SEEK_SET);	//将指针偏移到节头表位置
	fread(shdr, sizeof(Elf64_Shdr), count, fp);	//读取所有的节头表,本例中共10个节头表

	int shstrtabSize = shdr[ehdr->e_shstrndx].sh_size;	/*先读取.shstrtab段大小,ehdr->e_shstrndx
	表示.shstrtab索引号,shdr[ehdr->e_shstrndx]就表示.shstrtab节点,
	shdr[ehdr->e_shstrndx].sh_size 表示该节点的大小*/
	char shstrtabName[shstrtabSize];
	
	fseek(fp, shdr[ehdr->e_shstrndx].sh_offset, SEEK_SET);//sh_offset表示该节点偏移量,将指针偏移至该节点
	fread(shstrtabName, sizeof(char), shstrtabSize, fp);//将.shstrtab节点内容全部读取到shstrtabName中

通过以上方法,可以获取.shstrtab的内容

3、获取各个节头表名字

上文中已经使用shstrtabName获取到了.shstrtab的内容,该内容为所有节头表的名字,但是需要各个名字的索引。

for(int i=0;i<count;i++){
		char *name;
		name = shstrtabName+shdr[i].sh_name;/*shstrtabName为名字字符串的起始地址(基址),
		shdr[i].sh_name则作为各个节头表名字的所以(偏址),两者相加,得出各个节头表名字*/
		printf("%s\n",name);
	}

如图3:
在这里插入图片描述

4、获取符号表信息

符号表,也就是节头表中.symtab表的内容如下图所示,其中TYPE为FUNC表示调用到的子函数,可以用这一特点来制作ftrace功能。各个表项具体意义可以百度。
符号表如图4:
在这里插入图片描述
符号表属于程序表分类,程序表使用结构体如下:

typedef struct {
	Elf64_Word	st_name; // 4 B (B for bytes)
	unsigned char	st_info; // 1 B
	unsigned char	st_other; // 1 B
	Elf64_Half	st_shndx; // 2 B
	Elf64_Addr	st_value; // 8 B
	Elf64_Xword	st_size; // 8 B
} Elf64_Sym

获取符号表方式如下:
1、符号表的name全部存储在.strtab节头表中,因此需要先存储.strtab的内容,方式类似第“2、Section Header”段中所述。

for(int i=0;i<count;i++){
		char *name;
		name = shstrtabName+shdr[i].sh_name;
		if(strcmp(".strtab", name) == 0){
			strtabAddr = shdr[i].sh_offset;
			strtabSize = shdr[i].sh_size;
		}
	}

首先通过获取节头表名字来比较该节点是否为.strtab节点,如果是,则记录下其偏移量以及大小

		char *name;
		name = shstrtabName+shdr[i].sh_name;
		if(strcmp(".symtab", name) == 0){
			printf("i = %d\n",i);
			int symtabAddr = shdr[i].sh_offset;
			int symtabSize = shdr[i].sh_size;
			int symbolSize = shdr[i].sh_entsize;
			int symtabNum = symtabSize/symbolSize;

然后再通过同样的方式来找到.symtab节点,并记录下偏移量、.symtab的整体大小、.symtab每一个表项的大小、计算出.symtab共有几个表项。

			Elf64_Sym sym[symtabNum];
			fseek(fp, symtabAddr, SEEK_SET);
			fread(sym, sizeof(Elf64_Sym), symtabNum, fp);

将.symtab全部存储进sym中。

			fseek(fp, strtabAddr, SEEK_SET);
			fread(name, sizeof(char), strtabSize, fp);
			
			for(int j=0;j<symtabNum;j++){
				printf("value:0x%lx\t",sym[j].st_value);
				printf("TYPE:%d\t",sym[j].st_info & 0xf);
				printf("name:%s\t",name+sym[j].st_name);	// name为基址,sym[j].st_name为偏址
				printf("\n");
			}
		}

然后将.strtab内容存储进name中,那么就可以通过name+sym[j].st_name的方式来找到符号表相应表项的名字。

5、总结

如图所示:
在这里插入图片描述
各个不同的结构体表示不同级别的节点。

6、示例代码

#include <stdio.h>
#include <stdlib.h>
#include <elf.h>
#include <string.h>

void symtab64_print(Elf64_Ehdr* ehdr,FILE *fp){
	int count = ehdr->e_shnum;    //节头表数量
	Elf64_Shdr shdr[count];
	
	fseek(fp, ehdr->e_shoff, SEEK_SET);
	int test = fread(shdr, sizeof(Elf64_Shdr), count, fp);
	printf("test=%d,count=%d\n",test,count);
	
	int shstrtabSize = shdr[ehdr->e_shstrndx].sh_size;	//.shstrtab段大小  
	char shstrtabName[shstrtabSize];
	
	fseek(fp, shdr[ehdr->e_shstrndx].sh_offset, SEEK_SET);
	fread(shstrtabName, sizeof(char), shstrtabSize, fp);
	
	int strtabAddr = -1;	//定义.strtab的offset变量
	int strtabSize = -1;
	for(int i=0;i<count;i++){
		char *name;
		name = shstrtabName+shdr[i].sh_name;
		if(strcmp(".strtab", name) == 0){
			strtabAddr = shdr[i].sh_offset;
			strtabSize = shdr[i].sh_size;
		}
	}
	
	for(int i=0;i<count;i++){
		if(strtabAddr==-1 || strtabSize ==-1)
			exit(EXIT_FAILURE);
		char *name;
		name = shstrtabName+shdr[i].sh_name;
		if(strcmp(".symtab", name) == 0){
			printf("i = %d\n",i);
			int symtabAddr = shdr[i].sh_offset;
			int symtabSize = shdr[i].sh_size;
			int symbolSize = shdr[i].sh_entsize;
			int symtabNum = symtabSize/symbolSize;
			
			Elf64_Sym sym[symtabNum];
			fseek(fp, symtabAddr, SEEK_SET);
			fread(sym, sizeof(Elf64_Sym), symtabNum, fp);
			
			fseek(fp, strtabAddr, SEEK_SET);
			fread(name, sizeof(char), strtabSize, fp);
			
			for(int j=0;j<symtabNum;j++){
				printf("value:0x%lx\t",sym[j].st_value);//打印symtab的值
				printf("TYPE:%d\t",sym[j].st_info & 0xf);
				printf("name:%s\t",name+sym[j].st_name);	// name为基址,sym[j].st_name为偏址
				printf("\n");
			}
		}
		else 
			continue;
	}
}

int main(int argc,char *argv[]){
	Elf64_Ehdr ehdr[1];
	if(argc != 2)
		exit(EXIT_FAILURE);
	FILE *fp;
	fp = fopen(argv[1],"rb");
	if(fp==NULL)
		exit(EXIT_FAILURE);
	fread(ehdr, sizeof(Elf64_Ehdr), 1, fp);
	symtab64_print(ehdr,fp);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值