目标:读取 64位/32位 elf文件,打印section,segments,sectiom to segments mapping
一,elf文件解析
这部分内容请参考互联网,已经有很多博客说的很清楚了。
二,代码布局
代码非常简单,一个头文件用于声明操作的类,一个cpp文件,用于实现该类,下面先介绍一下头文件的相关声明和组成。
/************************************************************************/
/* AUTHOR : FangJianYang
/* VERSION : 01
/* TIME : 2019-02-17
/* DESC : Some function to read a elf file
/* Email : [email protected]
/************************************************************************/
#include
#include
#include
#include
#include
上面几行代码,主要关注这么几行:
1. elf.h 头文件可能不是所有Linux发行版都提供,因此需要按照对应的系统做调整。可以直接使用whereis 命令去查找,比如在Ubuntu上是这个结果:
xxx@ubuntu:~xxx$ whereis elf
elf: /usr/include/elf.h /usr/share/man/man5/elf.5.gz
2. 我定义了几个宏,用于表示程序的错误码,其中:
WRONG_ARGMENTS 表示:用户输入了错误的参数(本例程并不支持所有参数)
GET_SEC_NAME_ERR 表示:获取section名字失败(关于section的名字,可以搜索elf格式来获取更多详情)
CALC_MAPPING_ERROR 表示:计算section到segments的映射错误(同上,搜索elf格式获取更多详情)
typedef struct
{
string sh_strname;
Elf64_Shdr elf64sehd;
}myElf64_Shdr;
typedef struct
{
string sh_strname;
Elf32_Shdr elf32sehd;
}myElf32_Shdr;
typedef struct sec2SegMapping
{
int index;
string secNameVec;
public:
sec2SegMapping(int id,string arg):index(id),secNameVec(arg){}
}sec2SegMapping;
上面的代码我定义了几个结构体,其中myElf64_Shdr和myElf32_Shdr是为了兼容64/32位系统的elf文件。然后sec2SegMapping是用于保存单个的section序号和名字,后面会看到具体用法。
class CReadElf
{
private:
int osType ;
vector secName;
FILE * pFile;
string filename;
vector mapping;
上面是具体工作类的部分描述,其中
private:
vector sections;
vector segments;
Elf64_Ehdr elfHead64;
private:
vector sections32;
vector segments32;
Elf32_Ehdr elfHead32;
上面两段代码是完全一模一样的,只是为了兼容32/64位操作系统,可以优化(你看出来了,我不怎么想优化)。
public:
CReadElf(char *argc);
virtual ~CReadElf();
int getfd();
public:
int readFile();
int readSection();
int readSegment();
int getSectionName();
int caclSec2Segments();
void showHead();
void showSection();
void showSegments();
void showSec2SegMapping();
};
上面代码声明了一些函数,函数功能可以直接从函数名字上大致获得,不做解析。
下面简要讲解一下cpp代码实现
/***********************************************************************/
/* AUTHOR : FangJianYang
/* VERSION : 01
/* TIME : 2019-02-17
/* DESC : Some function to read a elf file
/* Email : [email protected]
/************************************************************************/
#include "readelf.h"
#include
#include
这段代码是cpp文件的一些准备工作,比如用到了stdio.h库文件,主要是需要用到他的输入和输出。stdlib.h头文件,主要是用到了他的一些字符串操作函数。
CReadElf::CReadElf(char *argc):osType(0),filename(""),pFile(NULL)
{
if (argc!=NULL && strlen(argc) != 0)
{
filename= argc;
}
}
CReadElf::~CReadElf()
{
if(pFile != NULL)
fclose(pFile);
}
构造函数和析构函数的实现。在构造函数内我们检查了一下参数是否正确(是否将要解析的elf文件传递了过来),在析构函数内,简单判断了一下是否需要关闭文件句柄(说句柄可能不太准确,因为Linux没这个概念,但是我没想出来那个正确的叫法)。
int CReadElf::readFile()
{
long lSize;
size_t result;
// obtain file size:
fseek (pFile , 0 , SEEK_END);
lSize = ftell (pFile);
rewind (pFile);
if(lSize < sizeof(Elf32_Ehdr) || lSize < sizeof(Elf64_Ehdr) )
{
printf("not a elf file!\n");
return NOT_A_ELF_FILE;
}
char headbuf[EI_NIDENT] = {0};
result = fread(headbuf,1,EI_NIDENT,pFile);
//judge if this is a elf file
if(
headbuf[0] != 0x7f && headbuf[1] != 0x45 &&
headbuf[2] != 0x4c && headbuf[3] != 0x46
)
{
printf("not a elf file!\n");
return NOT_A_ELF_FILE;
}
rewind(pFile);
if(headbuf[4] == 0x02)
{
fread (&elfHead64,1,sizeof(Elf64_Ehdr),pFile);
osType = 64;
}
else
{
fread (&elfHead32,1,sizeof(Elf32_Ehdr),pFile);
osType = 32;
}
rewind(pFile);
return SUCCESS;
}
上述代码,主要作用是读取用户指定的文件,然后判断是否是一个elf文件,假如是的话,看他对用的是32位的还是64为的elf文件。
int CReadElf::readSection()
{
size_t result;
if(osType == 64)
{
size_t secNum = elfHead64.e_shnum;
int secindex = elfHead64.e_shoff;
if(getSectionName()!= secNum)
{
printf("get section head failed!\n");
return GET_SEC_NAME_ERR;
}
//go to the entry of section
fseek(pFile,secindex,SEEK_SET);
Elf64_Shdr elf64Sec;
for(int i = 0;i < secNum;i++)
{
result = fread(&elf64Sec,1,sizeof(Elf64_Shdr),pFile);
if(result != sizeof(Elf64_Shdr))
{
printf("read file failed\n");
return READ_FILE_FAILED;
}
myElf64_Shdr tmpsec;
tmpsec.sh_strname = secName[i];
tmpsec.elf64sehd = elf64Sec;
sections.push_back(tmpsec);
}
}
else
{
size_t secNum = elfHead32.e_shnum;
int secindex = elfHead32.e_shoff;
if(getSectionName()!= secNum)
{
printf("get section head failed\n");
return GET_SEC_NAME_ERR;
}
//go to the entry of section
fseek(pFile,secindex,SEEK_SET);
Elf32_Shdr elf32Sec;
for(int i = 0;i < secNum;i++)
{
result = fread(&elf32Sec,1,sizeof(Elf32_Shdr),pFile);
if(result != sizeof(Elf32_Shdr))
{
printf("read file failed\n");
return READ_FILE_FAILED;
}
myElf32_Shdr tmpsec;
tmpsec.sh_strname = secName[i];
tmpsec.elf32sehd = elf32Sec;
sections32.push_back(tmpsec);
}
}
rewind(pFile);
return SUCCESS;
}
这个函数,关注任意一个分支即可(要么处理64位ELF要么处理32位,逻辑都一样)。
int CReadElf::getfd()
{
pFile = fopen (filename.c_str() , "rb" );
if (pFile==NULL)
{
printf("File error\n");
return FILE_OPEN_ERR;
}
return SUCCESS;
}
这个函数就没有很多要解释的了。可能函数名改动一下会更合适。
int CReadElf::getSectionName()
{
if (osType == 64)
{
Elf64_Shdr *shdr = new Elf64_Shdr[sizeof(Elf64_Shdr) * elfHead64.e_shnum];
int sz = fseek(pFile, elfHead64.e_shoff, SEEK_SET);
if (sz != 0)
{
printf("file fseek ERROR\n");
delete[] shdr;
return FILE_SEEK_ERROR;
}
sz = fread(shdr, sizeof(Elf64_Shdr) * elfHead64.e_shnum, 1, pFile);
if (sz == 0)
{
printf("file read ERROR \n");
delete[] shdr;
return READ_FILE_FAILED;
}
rewind(pFile);
sz = fseek(pFile, shdr[elfHead64.e_shstrndx].sh_offset, SEEK_SET);
if (sz != 0)
{
printf("file fseek ERROR\n");
delete[] shdr;
return FILE_SEEK_ERROR;
}
char shstrtab[shdr[elfHead64.e_shstrndx].sh_size];
char *temp = NULL;
sz = fread(shstrtab, shdr[elfHead64.e_shstrndx].sh_size, 1, pFile);
if (sz == 0)
{
printf("file fread ERROR\n");
delete[] shdr;
return FILE_SEEK_ERROR;
}
for (int shnum = 0; shnum < elfHead64.e_shnum; shnum++)
{
temp = shstrtab;
temp = temp + shdr[shnum].sh_name;
secName.push_back(string(temp));
}
delete[] shdr;
}
else
{
Elf32_Shdr *shdr = new Elf32_Shdr[sizeof(Elf32_Shdr) * elfHead32.e_shnum];
int sz = fseek(pFile, elfHead32.e_shoff, SEEK_SET);
if (sz != 0)
{
printf("file fseek ERROR\n");
delete[] shdr;
return FILE_SEEK_ERROR;
}
sz = fread(shdr, sizeof(Elf32_Shdr) * elfHead32.e_shnum, 1, pFile);
if (sz == 0)
{
printf("file read ERROR \n");
delete[] shdr;
return READ_FILE_FAILED;
}
rewind(pFile);
sz = fseek(pFile, shdr[elfHead32.e_shstrndx].sh_offset, SEEK_SET);
if (sz != 0)
{
printf("file fseek ERROR\n");
delete[] shdr;
return FILE_SEEK_ERROR;
}
char shstrtab[shdr[elfHead32.e_shstrndx].sh_size];
char *temp = NULL;
sz = fread(shstrtab, shdr[elfHead32.e_shstrndx].sh_size, 1, pFile);
if (sz == 0)
{
printf("file fread ERROR\n");
delete[] shdr;
return FILE_SEEK_ERROR;
}
for (int shnum = 0; shnum < elfHead32.e_shnum; shnum++)
{
temp = shstrtab;
temp = temp + shdr[shnum].sh_name;
secName.push_back(string(temp));
}
delete[] shdr;
}
rewind(pFile);
return secName.size();
}
上面代码依然有两个大的分支,我们关注一个即可(关注os64分支吧)。在该函数中
注意,这个函数,我解释的不是非常清晰,可以参考elf.h上的解释,当时做的时候,这个函数我参考了其他人的博客。特此说明!
int CReadElf::readSegment()
{
if (osType == 64)
{
int phoffset = elfHead64.e_phoff;
int phnum = elfHead64.e_phnum;
int phentsize = elfHead64.e_phentsize;
fseek(pFile, phoffset, SEEK_SET);
for (int i = 0; i < phnum; i++)
{
Elf64_Phdr Pro_header;
if (fread(&Pro_header, 1, phentsize, pFile) != phentsize)
{
printf("read segments err!");
return READ_FILE_FAILED;
}
segments.push_back(Pro_header);
}
}
else
{
int phoffset = elfHead32.e_phoff;
int phnum = elfHead32.e_phnum;
int phentsize = elfHead32.e_phentsize;
fseek(pFile, phoffset, SEEK_SET);
for (int i = 0; i < phnum; i++)
{
Elf32_Phdr Pro_header;
if (fread(&Pro_header, 1, phentsize, pFile) != phentsize)
{
printf("read segments err!");
return READ_FILE_FAILED;
}
segments32.push_back(Pro_header);
}
}
return SUCCESS;
}
这段代码我认为没啥解释的,参考elf.h中关于Elf64_Ehdr/Elf32_Ehdr的解释
/**********************************section to Segment mapping 计算方法:***************************
首先,查看program Headers中的各个字段的地址 virtAddr (或 physAddr 一般这两个字段的值是一样的).
表示这个*segment的开始地址,然后看MenSiz(注意,不是FileSize).表示这个segment可以组装的section的大小.
我们对上面两个值相加,获得这个segment的开始地址和结束地址.比如LOADsegment:
开始地址为: 0x0000000000400000,结束地址为 0x00000000004007dc.
然后明确: elf会将地址连续的section分配到同一个segment,前提是这些section的地址在某个segment的范围内.
比如说:Section Headers 字段中,分布在地址 0x0000000000400000 - 0x00000000004007dc
刚好是 "interp .note.ABI-tag .note.gnu.build*id .gnu.hash .dynsym
.dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt
.text .fini .rodata .eh_frame_hdr .eh_frame"
其中过最后一个section的开始地址为 4006e8 大小为 f4 相加后刚好为:4007DC.
***********************************************************************************************/
int CReadElf::caclSec2Segments()
{
if (osType == 64)
{
if (sections.size() == 0 || segments.size() == 0)
{
printf( "can not cal sectiong to segments mapping\n") ;
return CALC_MAPPING_ERROR;
}
for (vector::iterator pit = segments.begin(); pit != segments.end(); pit++)
{
string belongSec("");
Elf64_Addr startAddr = pit->p_paddr;
Elf64_Addr endAddr = pit->p_paddr + pit->p_memsz;
for (vector::iterator sit = sections.begin(); sit != sections.end(); sit++)
{
Elf64_Addr secBeginAddr = sit->elf64sehd.sh_addr;
Elf64_Addr secEndAddr = sit->elf64sehd.sh_addr + sit->elf64sehd.sh_size;
if (secBeginAddr >= startAddr && secEndAddr <= endAddr)
{
belongSec.append(sit->sh_strname);
belongSec.append(" ");
}
}
sec2SegMapping tmpmap(mapping.size(), belongSec);
mapping.push_back(tmpmap);
}
}
else
{
if (sections32.size() == 0 || segments32.size() == 0)
{
printf("can not cal sectiong to segments mapping\n");
return CALC_MAPPING_ERROR;
}
for (vector::iterator pit = segments32.begin(); pit != segments32.end(); pit++)
{
string belongSec("");
Elf32_Addr startAddr = pit->p_paddr;
Elf32_Addr endAddr = pit->p_paddr + pit->p_memsz;
for (vector::iterator sit = sections32.begin(); sit != sections32.end(); sit++)
{
Elf32_Addr secBeginAddr = sit->elf32sehd.sh_addr;
Elf32_Addr secEndAddr = sit->elf32sehd.sh_addr + sit->elf32sehd.sh_size;
if (secBeginAddr >= startAddr && secEndAddr <= endAddr)
{
belongSec.append(sit->sh_strname);
belongSec.append(" ");
}
}
sec2SegMapping tmpmap(mapping.size(), belongSec);
mapping.push_back(tmpmap);
}
}
return SUCCESS;
}
这段代码是用于整理segments和section的映射的,关键在于代码中的注释说明,他说明了他们的关系是如何组织的。关于他的验证可以手动readelf一个文件后,动手算一算。该函数不在解释,原理懂了就特别简单。
特别说明:注释中举得例子,只是我电脑运行的结果,相关的地址位置,需要结合实际去验证。
void CReadElf::showHead()
{
if (osType == 64)
{
printf("e_type : %x \n", elfHead64.e_type);
printf("e_machine : %x \n", elfHead64.e_machine);
printf("e_version : %x \n", elfHead64.e_version);
printf("e_entry : %x \n", elfHead64.e_entry);
printf("e_phoff : %x \n", elfHead64.e_phoff);
printf("e_shoff : %x \n", elfHead64.e_shoff);
printf("e_flags : %x \n", elfHead64.e_flags);
printf("e_ehsize : %x \n", elfHead64.e_ehsize);
printf("e_phentsize : %x \n", elfHead64.e_phentsize);
printf("e_phnum : %x \n", elfHead64.e_phnum);
printf("e_shentsize : %x \n", elfHead64.e_shentsize);
printf("e_shnum : %x \n", elfHead64.e_shnum);
printf("e_shstrndx : %x \n", elfHead64.e_shstrndx);
}
else
{
printf("e_type : %x \n", elfHead32.e_type);
printf("e_machine : %x \n", elfHead32.e_machine);
printf("e_version : %x \n", elfHead32.e_version);
printf("e_entry : %x \n", elfHead32.e_entry);
printf("e_phoff : %x \n", elfHead32.e_phoff);
printf("e_shoff : %x \n", elfHead32.e_shoff);
printf("e_flags : %x \n", elfHead32.e_flags);
printf("e_ehsize : %x \n", elfHead32.e_ehsize);
printf("e_phentsize : %x \n", elfHead32.e_phentsize);
printf("e_phnum : %x \n", elfHead32.e_phnum);
printf("e_shentsize : %x \n", elfHead32.e_shentsize);
printf("e_shnum : %x \n", elfHead32.e_shnum);
printf("e_shstrndx : %x \n", elfHead32.e_shstrndx);
}
printf("\n\n");
}
void CReadElf::showSection()
{
if (osType == 64)
{
if (sections.size() == 0)
{
printf("empty section map!\n");
return;
}
printf("%-20s %-8s %-16s %-8s %-16s %-16s %-8s %-8s %-8s %-8s\n",
"Name", "Type", "Address", "Offset", "Size", "EntSize", "Flags", "Link", "Info", "Align");
for (vector::iterator pit = sections.begin(); pit != sections.end(); pit++)
{
printf("%-20s %-8x %016x %08x %016x %016x %-8x %-8x %-8x %-8x\n",
pit->sh_strname.c_str(),
pit->elf64sehd.sh_type,
pit->elf64sehd.sh_addr,
pit->elf64sehd.sh_offset,
pit->elf64sehd.sh_size,
pit->elf64sehd.sh_entsize,
pit->elf64sehd.sh_flags,
pit->elf64sehd.sh_link,
pit->elf64sehd.sh_info,
pit->elf64sehd.sh_addralign
);
}
}
else
{
if (sections32.size() == 0)
{
printf("empty section map!\n");
return;
}
printf("%-20s %-8s %-16s %-8s %-16s %-16s %-8s %-8s %-8s %-8s\n",
"Name", "Type", "Address", "Offset", "Size", "EntSize", "Flags", "Link", "Info", "Align");
for (vector::iterator pit = sections32.begin(); pit != sections32.end(); pit++)
{
printf("%-20s %-8x %016x %08x %016x %016x %-8x %-8x %-8x %-8x\n",
pit->sh_strname.c_str(),
pit->elf32sehd.sh_type,
pit->elf32sehd.sh_addr,
pit->elf32sehd.sh_offset,
pit->elf32sehd.sh_size,
pit->elf32sehd.sh_entsize,
pit->elf32sehd.sh_flags,
pit->elf32sehd.sh_link,
pit->elf32sehd.sh_info,
pit->elf32sehd.sh_addralign
);
}
}
printf("\n\n");
}
void CReadElf::showSegments()
{
if (osType == 64)
{
if (segments.size() == 0)
{
printf("segments reads err!\n");
return;
}
printf("%-12s %-18s %-18s %-18s %-18s %-18s %-8s %-8s\n",
"Type", "Offset", "VirtAddr", "PhysAddr", "FileSiz", "MemSiz", "Flags", "Align");
for (vector::iterator pit = segments.begin(); pit != segments.end(); pit++)
{
printf("%-12x 0x%016x 0x%016x 0x%016x 0x%016x 0x%016x %-8x %-8x\n",
pit->p_type,
pit->p_offset,
pit->p_vaddr,
pit->p_paddr,
pit->p_filesz,
pit->p_memsz,
pit->p_flags,
pit->p_align
);
}
}
else
{
if (segments32.size() == 0)
{
printf("segments reads err!\n");
return;
}
printf("%-12s %-18s %-18s %-18s %-18s %-18s %-8s %-8s\n",
"Type", "Offset", "VirtAddr", "PhysAddr", "FileSiz", "MemSiz", "Flags", "Align");
for (vector::iterator pit = segments32.begin(); pit != segments32.end(); pit++)
{
printf("%-12x 0x%016x 0x%016x 0x%016x 0x%016x 0x%016x %-8x %-8x\n",
pit->p_type,
pit->p_offset,
pit->p_vaddr,
pit->p_paddr,
pit->p_filesz,
pit->p_memsz,
pit->p_flags,
pit->p_align
);
}
}
printf("\n\n");
}
void CReadElf::showSec2SegMapping()
{
if(mapping.size() == 0)
{
printf("mapping error!\n");
return;
}
for(vector::iterator pit = mapping.begin();pit != mapping.end();pit++)
{
printf("%d\t%s\n",pit->index,pit->secNameVec.c_str());
}
printf("\n");
}
上述函数,用于输出结果,不赘述。
下面附加一个测试程序:
/************************************************************************/
/* AUTHOR : FangJianYang
/* VERSION : 01
/* TIME : 2019-02-17
/* DESC : Some function to read a elf file
/* Email : [email protected]
/************************************************************************/
#include "readelf.h"
int main(int argc,char** argv)
{
if(argc != 2)
{
cout<<"argments err : you should enter 2 argments!"<
三 ,编译和使用说明:
我手写了一个makefile,各位可以直接运行他即可,也可以用vscode打开,有相应的工程文件。我将这段代码和所有文件都上传到了https://download.csdn.net/download/baijiaheizhiganmao/11293397链接处,该链接里面也有一个readme文件。