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
可以看到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);