38 如何根据地址获取函数名

38.1 前言

之前有做过一个通过截取内核信号,获取程序出错所在函数位置信息(如段错误),然后进行栈回溯的功能(之前的blog有写),那个虽然成功了,但仍有一些不合人意的地方。就是手动回溯结果显示的只是函数地址,如果要看是哪个函数,那还要用objdump或addrline工具用地址找到是哪个函数,比较麻烦。最近折腾了两天时间,终于搞定了根据地址自动获取函数名称的功能。

不管gdb还readelf或addrline工具,可以简单轻松的敲一下命令就可以把函数名及地址整齐地打印展现出来,那么它们是怎么实现的呢??这就是本blog将要讲述的。

38.2 ELF文件及数据结构

首先了解EFL可执行文件,因为我们要找的信息就在ELF文件的符号表中。ELF文件,其全称为Executable and Linking Format,中文语义就是可执行链接格式文件,属于一种文件的存储格式。其有四个部分构成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。如下图示。

相信c/c++软件工程师对程序的数据组织结构有一定的了解,如一个程序有.text段、.bss段、.data段等等,但这些只是属于程序文件中的一部分。一个可执行文件真实的数据组织远不止上述几个,其可能包含二十几个信息区甚至更多,如下图示,在一个elf可执行文件中,其包含了28个section,分别存放着不同的数据。

.bss

未初始化的全局或局部变量

.comment

可执行文件的版本及控制描述信息等

.dynamic

程序动态链接信息及相关属性

.hash

符号hash表

.shstrtab

保存所有节的名称信息

.strtab

保存程序中所有的字符串信息包括函数名

.symtab

符号表

刚才讲到了,ELF头(ELF header)、程序头表(Program header table)、节头表(Section header table)和节(Section),下面就来看一下它们的数据结构是怎样的。

数据类型定义:

/* Type for a 16-bit quantity.  */  
typedef uint16_t Elf32_Half;  
 
/* Types for signed and unsigned 32-bit quantities.  */  
typedef uint32_t Elf32_Word;  
typedef int32_t  Elf32_Sword;  
    
/* Type of addresses.  */  
typedef uint32_t Elf32_Addr;  
  
/* Type of file offsets.  */  
typedef uint32_t Elf32_Off;  
  
/* Type for section indices, which are 16-bit quantities.  */  
typedef uint16_t Elf32_Section;  
  
/* Type for version symbol information.  */  
typedef Elf32_Half Elf32_Versym;  

ELF Header结构(52字节):

#define EI_NIDENT 16  
typedef struct {  
unsigned char e_ident[EI_NIDENT]; //Magic  
Elf32_Half e_type; //文件类型 2-可执行文件  
Elf32_Half e_machine; //机器类型 如arm  
Elf32_Word e_version; //文件版本  
Elf32_Addr e_entry; //程序入口地址  
Elf32_Off e_phoff; //程序头table偏移字节大小  
Elf32_Off e_shoff; //section偏移字节大小  
Elf32_Word e_flags; //processor-specific flags  
Elf32_Half e_ehsize; //elf文件头大小  
Elf32_Half e_phentsize; //程序头大小  
Elf32_Half e_phnum; //程序头数量  
Elf32_Half e_shentsize; //section头大小  
Elf32_Half e_shnum; //section数量  
Elf32_Half e_shstrndx; //字符串表索引节头位置  
} Elf32_Ehdr;  

Program Header结构(32字节):

typedef struct {
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;

Section Header结构(40字节):

typedef struct {
Elf32_Word sh_name; //section名称索引(含义是在string table总的第几个字节数)
Elf32_Word sh_type; //section类型
Elf32_Word sh_flags; //
Elf32_Addr sh_addr; //section的地址
Elf32_Off sh_offset; //section偏移地址
Elf32_Word sh_size; //大小
Elf32_Word sh_link;
Elf32_Word sh_info; //节头信息
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;

Section中的Symbol Table Entry结构(16字节):

typedef struct {
Elf32_Word st_name; //索引值,值为字符串符号表的偏移字节大小(如果是函数那就是函数名)
Elf32_Addr st_value; //符号地址,如果是函数那就是函数地址
Elf32_Word st_size; //
unsigned char st_info; //符号相关信息,如是否为函数、全局类型等等
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;

//注:info转化方式如下。
#define ELF32_ST_BIND(i) ((info)>>4)
#define ELF32_ST_TYPE(i) ((info)&0xf)
#define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf))

38.3 根据地址寻找函数名称思路

对于一个elf文件,其文件内容及组织形式对于不同的文件可能不一样,但一样的是elf文件的文件头对于一种架构来说是一样的,因此若获取elf文件的符号信息等,需要从elf的文件头开始。

 

(1)据上图所示,节头表的起始地址为5388字节开始(即0x150C);

(2)节头大小40字节(即0x28),节头数量29,节头共大小为29*40=1160(即0x488);

(3)节头表数据位置为:0x150C – 0x1994(0x150C+0x488);

(4)由上表可知字符串表索引头:26,因此可以直接在29个节头中定位到字符串索引表头,并获取其中内容;

 

为什么在ELF头中将字符串索引节点给出,那是因为在节头数据结构中,其节section的名字并不是字符串,而只是一个索引值,所有的节名称统一存放在.shstrtab中,因此如果想知道这个section的名称的话,那就需要去找。

        (5)根据名称匹配,可以知道那个section是.symtab符号表,然后拿到其起始地址和大小;

(6)拿到.symtab表起始地址和大小后,以Symbol Table Entry结构大小遍历全部数据。当查找的函数地址与Symbol Table Entry的value匹配时,此时找到对应函数的入口信息点,然后再根据Symbol Table Entry 的name索引值,到.strtab段找到函数名称,此时完成地址与函数名称的查找。

总结流程: 

 

 

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. gethostbyname函数 gethostbyname函数用于通过主机获取IP地址信息。其函数原型如下: ``` struct hostent *gethostbyname(const char *name); ``` 其中,name为主机,返回值为hostent结构体指针,包含了IP地址、别、官方等信息。 示例代码: ```c++ #include <netdb.h> #include <iostream> using namespace std; int main() { struct hostent *host = gethostbyname("www.baidu.com"); if (host == NULL) { cout << "gethostbyname error" << endl; return 1; } cout << "official name: " << host->h_name << endl; cout << "IP address: " << inet_ntoa(*(struct in_addr *)host->h_addr_list[0]) << endl; return 0; } ``` 输出结果: ``` official name: www.a.shifen.com IP address: 14.215.177.38 ``` 可以看到,通过gethostbyname函数,我们成功地获取了百度的IP地址信息。 2. gethostbyaddr函数 gethostbyaddr函数用于通过IP地址获取主机信息。其函数原型如下: ``` struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type); ``` 其中,addr为IP地址,len为地址长度,type为地址类型(IPv4或IPv6),返回值同样为hostent结构体指针。 示例代码: ```c++ #include <netdb.h> #include <iostream> using namespace std; int main() { struct in_addr addr; inet_pton(AF_INET, "14.215.177.38", &addr); struct hostent *host = gethostbyaddr(&addr, sizeof(addr), AF_INET); if (host == NULL) { cout << "gethostbyaddr error" << endl; return 1; } cout << "official name: " << host->h_name << endl; return 0; } ``` 输出结果: ``` official name: www.a.shifen.com ``` 可以看到,通过gethostbyaddr函数,我们成功地获取了IP地址对应的主机信息。 3. getservbyname函数 getservbyname函数用于通过服务获取端口号信息。其函数原型如下: ``` struct servent *getservbyname(const char *name, const char *proto); ``` 其中,name为服务,proto为协议(通常为tcp或udp),返回值为servent结构体指针,包含了端口号、别、协议等信息。 示例代码: ```c++ #include <netdb.h> #include <iostream> using namespace std; int main() { struct servent *serv = getservbyname("http", "tcp"); if (serv == NULL) { cout << "getservbyname error" << endl; return 1; } cout << "port number: " << ntohs(serv->s_port) << endl; return 0; } ``` 输出结果: ``` port number: 80 ``` 可以看到,通过getservbyname函数,我们成功地获取了http协议对应的端口号信息。 综上所述,我们通过gethostbyname、gethostbyaddr、getservbyname三种地址转换函数,成功地实现了通过主机获取IP地址、通过IP地址获取主机、通过服务获取端口号等功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值