基础技术-ELF系列(3)-libelf使用

2024-06-02 04:52

本文主要是介绍基础技术-ELF系列(3)-libelf使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

成就更好的自己

本篇是基础技术系列中ELF相关技术的第三篇,也是计划中的最后一篇(后续遇到问题可能还会有后续)。本文将会以上一篇文章中提到的实际问题写一段Demo为例,着重讲解一下libelf库的基本使用。

没有看过之前文章的朋友请回顾一下之前的文章:

基础技术-ELF系列2-ELF文件进阶与libelf库-CSDN博客


目录

 

Demo的基本目的和思路

Demo实现与逐行分析


Demo的基本目的和思路

先回顾一下上一篇中的问题:

需要在不进行编译的条件下,对比几个库之间是否存在符号重复。

所谓符号,常见情况下只有源文件中的函数在编译之后能够形成符号,而变量和常量会转变为地址,没有符号这一概念。因此主要分析ELF文件中存在的函数符号即可。

在先前的文章中,我们大概了解了构成ELF文件的最小单位可以以节进行划分。因此我们的思路大概是这样的:

  1. 解析ELF文件的基本信息
  2. 找到哪些节会存放函数的符号
  3. 找到并解析这些节的节头信息
  4. 根据这些节头信息对这些节进行数据解析
  5. 得到描述符号单位的结构
  6. 判断这个符号是库里定义的实际符号还是引用的外部符号
  7. 对实际符号进行打印显示,并作下一步处理

Demo实现与逐行分析

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <libelf.h>
#include <gelf.h>
#include <dlfcn.h>#include "log.h"int Find_And_Pirnt_Func_Sym(int fd)
{int i = 0;int sym_count = 0;int shdr_index = 0;Elf* elf = NULL;GElf_Ehdr ehdr = {0};GElf_Shdr shdr = {0};GElf_Sym sym = {0};Elf_Scn* scn = {0};Elf_Data* data = {0};//对打开的ELF文件进行初始解析拿到ELF结构句柄elf = elf_begin(fd, ELF_C_READ, NULL);CHECK_NULL(elf, {LOG_ERROR("Can not get elf handle");goto end;});//判断该文件是否为ELF格式CHECK_TRUE(elf_kind(elf) != ELF_K_ELF, {LOG_ERROR("File is not a elf obj");goto end;});//获取并解析ELF文件头结构CHECK_NULL(gelf_getehdr(elf, &ehdr), LOG_ERROR("Can not get elf head");goto end;);LOG_INFO("Get Elf Head Success!");//这里是个循环,需要会从第2个节头开始遍历(因为协议规定第1个节头是空节头),依次获取每个节头的句柄for(shdr_index = 1; (scn = elf_nextscn(elf, scn)) != NULL; shdr_index++){//通过节头句柄获取并解析节头结构CHECK_NULL(gelf_getshdr(scn, &shdr), LOG_ERROR("Can not get section[%d] head", shdr_index);continue;);//判断该节是否为包含函数符号的节,不是则continue到下一个节头CHECK_FAILED(shdr.sh_type == SHT_DYNSYM || shdr.sh_type == SHT_SYMTAB, LOG_INFO("Section[%d:%s] \r\t\t\t\t\t\t\t Type Is %08x, Is Not Function Section!", shdr_index, elf_strptr(elf, ehdr.e_shstrndx, shdr.sh_name), shdr.sh_type);continue;);LOG_INFO("Section[%d : %s] \r\t\t\t\t\t\t\t Type Is %08x, Is Function Section!", shdr_index, elf_strptr(elf, ehdr.e_shstrndx, shdr.sh_name), shdr.sh_type);//获取并解析该节头对应节的数据信息CHECK_NULL((data = elf_getdata(scn, NULL)), LOG_ERROR("Can not get section[%d] data", shdr_index);continue;);//通过节头信息中的节总长度和节中每个成员大小,得到节成员的数量sym_count = shdr.sh_size / shdr.sh_entsize;LOG_INFO("This Sction Have %d Sym", sym_count);//遍历节成员for(i = 0; i < sym_count; i++){//根据成员索引获取每一个符号描述信息CHECK_NULL(gelf_getsym(data, i, &sym), {LOG_ERROR("Can not get sym[%d] head", i);continue;});//打印这个符号的基本信息 名称 对应数据内容存放地址 符号标志 符号数据内容长度 其他LOG_INFO("Sym[%d : %s]: \r\t\t\t\t\t\t\t Addr:0x%08lx Info:%02x Size:%08lx Other:%02x",i, elf_strptr(elf, shdr.sh_link, sym.st_name), sym.st_value, sym.st_info, sym.st_size, sym.st_other);}}end://释放ELF结构句柄CHECK_NONULL(elf, elf_end(elf););return 0;
}int main(void)
{int fd = 0;//选择使用的ELF文件解析版本(该步骤为必须步骤,否则begin的时候会报错)CHECK_TRUE(elf_version(EV_CURRENT) == EV_NONE, {LOG_ERROR("elf_version error!");return 0;});//打开ELF文件fd = open("main.o", O_RDONLY, 0);CHECK_TRUE(fd < 0, {LOG_ERROR("can not open!");return 0;});//查找并打印符号业务Find_And_Pirnt_Func_Sym(fd);//关闭文件close(fd);return 0;
}

其他注意事项:

  1. elf_version(EV_CURRENT),该函数为配置程序接下来使用的ELF格式版本,必须进行指定,否则程序不知道以什么版本解析ELF文件;
  2. gelf_getehdr(elf, &ehdr),该函数执行成功后即可获得ELF文件头的信息,里面的结构成员就是readelf -h读出的内容;
  3. elf_nextscn(elf, scn),该函数的意义在于获取下一节头的句柄,这个下一节头就是以入参scn为参考的下一节头,若函数最后一个入参为空,则永远获取的是第2个节的节头;之所以不是第一个,是因为第1个节头规定为空节头,获取的节头句柄通过返回值返回;
  4. gelf_getshdr(scn, &shdr),该函数通过节头句柄获取并解析节头信息,里面的结构就是readelf -S读出的单个节头的内容;
  5. elf_getdata(scn, NULL),上面获取的这是节头信息,这个节的实际数据内容是通过这个函数进行解析的,这个函数或者这个节内容信息的句柄;
  6. elf_strptr(elf, shdr.sh_link, sym.st_name),这个函数是库中获取字符串的函数,一般有两个用处,一是获取这个节名,二是获取符号名;函数的第二个入参代表存放这些符号的节,第三个入参是在这个节中作的偏移,对应的字符串就在这个偏移上存着。存放节名的节索引一般在ELF头结构中的e_shstrndx成员中存放;存放符号名的节索引一般在该符号所属节的节头结构中的sh_link中存放;(这些内容在上一篇博客中说的很明白)
  7. 打印的符号中有很多是引用的外部符号,不是本函数中定义的函数,若只想查看本库中定义的符号,如何进行区分?可用过符号描述结构中的st_size成员进行区分,该成员存放符号对应程序数据的长度,若该长度为0说明该符号在本库中没有定义,是外部符号(比如print,malloc等)
  8. 若该ELF文件经过某些工具裁剪(例如objcopy),把节头信息或者符号信息给裁掉了,那就不能通过上述方法进行解析,该文件同时失去链接功能。这种操作一般只会对可执行文件这么搞,这样不会影响程序运行,而且可以大大加强程序的反编译难度,增加安全性。

对以上面这段代码为源码编出来的.o执行这段程序,即可得到如下执行结果:

[INFO]: Get Elf Head Success!
[INFO]: Section[1:.text]                                 Type Is 00000001, Is Not Function Section!
[INFO]: Section[2:.rela.text]                            Type Is 00000004, Is Not Function Section!
[INFO]: Section[3:.data]                                 Type Is 00000001, Is Not Function Section!
[INFO]: Section[4:.bss]                                  Type Is 00000008, Is Not Function Section!
[INFO]: Section[5:.rodata]                               Type Is 00000001, Is Not Function Section!
[INFO]: Section[6:.comment]                              Type Is 00000001, Is Not Function Section!
[INFO]: Section[7:.note.GNU-stack]                       Type Is 00000001, Is Not Function Section!
[INFO]: Section[8:.note.gnu.property]                    Type Is 00000007, Is Not Function Section!
[INFO]: Section[9:.eh_frame]                             Type Is 00000001, Is Not Function Section!
[INFO]: Section[10:.rela.eh_frame]                       Type Is 00000004, Is Not Function Section!
[INFO]: Section[11 : .symtab]                            Type Is 00000002, Is Function Section!
[INFO]: This Sction Have 31 Sym
[INFO]: Sym[0 : ]:                                       Addr:0x00000000 Info:00 Size:00000000 Other:00
[INFO]: Sym[1 : main.c]:                                 Addr:0x00000000 Info:04 Size:00000000 Other:00
[INFO]: Sym[2 : ]:                                       Addr:0x00000000 Info:03 Size:00000000 Other:00
[INFO]: Sym[3 : ]:                                       Addr:0x00000000 Info:03 Size:00000000 Other:00
[INFO]: Sym[4 : ]:                                       Addr:0x00000000 Info:03 Size:00000000 Other:00
[INFO]: Sym[5 : ]:                                       Addr:0x00000000 Info:03 Size:00000000 Other:00
[INFO]: Sym[6 : __func__.4318]:                          Addr:0x00000680 Info:01 Size:00000018 Other:00
[INFO]: Sym[7 : __func__.4332]:                          Addr:0x00000698 Info:01 Size:00000005 Other:00
[INFO]: Sym[8 : ]:                                       Addr:0x00000000 Info:03 Size:00000000 Other:00
[INFO]: Sym[9 : ]:                                       Addr:0x00000000 Info:03 Size:00000000 Other:00
[INFO]: Sym[10 : ]:                                      Addr:0x00000000 Info:03 Size:00000000 Other:00
[INFO]: Sym[11 : ]:                                      Addr:0x00000000 Info:03 Size:00000000 Other:00
[INFO]: Sym[12 : Find_And_Pirnt_Func_Sym]:               Addr:0x00000000 Info:12 Size:0000070f Other:00
[INFO]: Sym[13 : _GLOBAL_OFFSET_TABLE_]:                 Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[14 : elf_begin]:                             Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[15 : log_level]:                             Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[16 : printf]:                                Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[17 : elf_kind]:                              Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[18 : gelf_getehdr]:                          Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[19 : puts]:                                  Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[20 : gelf_getshdr]:                          Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[21 : elf_strptr]:                            Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[22 : elf_getdata]:                           Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[23 : gelf_getsym]:                           Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[24 : elf_nextscn]:                           Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[25 : elf_end]:                               Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[26 : __stack_chk_fail]:                      Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[27 : main]:                                  Addr:0x0000070f Info:12 Size:0000013b Other:00
[INFO]: Sym[28 : elf_version]:                           Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[29 : open]:                                  Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Sym[30 : close]:                                 Addr:0x00000000 Info:10 Size:00000000 Other:00
[INFO]: Section[12:.strtab]                              Type Is 00000003, Is Not Function Section!
[INFO]: Section[13:.shstrtab]                            Type Is 00000003, Is Not Function Section!

这篇关于基础技术-ELF系列(3)-libelf使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1023077

相关文章

使用Python实现IP地址和端口状态检测与监控

《使用Python实现IP地址和端口状态检测与监控》在网络运维和服务器管理中,IP地址和端口的可用性监控是保障业务连续性的基础需求,本文将带你用Python从零打造一个高可用IP监控系统,感兴趣的小伙... 目录概述:为什么需要IP监控系统使用步骤说明1. 环境准备2. 系统部署3. 核心功能配置系统效果展

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

redis中使用lua脚本的原理与基本使用详解

《redis中使用lua脚本的原理与基本使用详解》在Redis中使用Lua脚本可以实现原子性操作、减少网络开销以及提高执行效率,下面小编就来和大家详细介绍一下在redis中使用lua脚本的原理... 目录Redis 执行 Lua 脚本的原理基本使用方法使用EVAL命令执行 Lua 脚本使用EVALSHA命令

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

使用Python和Pyecharts创建交互式地图

《使用Python和Pyecharts创建交互式地图》在数据可视化领域,创建交互式地图是一种强大的方式,可以使受众能够以引人入胜且信息丰富的方式探索地理数据,下面我们看看如何使用Python和Pyec... 目录简介Pyecharts 简介创建上海地图代码说明运行结果总结简介在数据可视化领域,创建交互式地

Java Stream流使用案例深入详解

《JavaStream流使用案例深入详解》:本文主要介绍JavaStream流使用案例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录前言1. Lambda1.1 语法1.2 没参数只有一条语句或者多条语句1.3 一个参数只有一条语句或者多

Java Spring 中 @PostConstruct 注解使用原理及常见场景

《JavaSpring中@PostConstruct注解使用原理及常见场景》在JavaSpring中,@PostConstruct注解是一个非常实用的功能,它允许开发者在Spring容器完全初... 目录一、@PostConstruct 注解概述二、@PostConstruct 注解的基本使用2.1 基本代

C#使用StackExchange.Redis实现分布式锁的两种方式介绍

《C#使用StackExchange.Redis实现分布式锁的两种方式介绍》分布式锁在集群的架构中发挥着重要的作用,:本文主要介绍C#使用StackExchange.Redis实现分布式锁的... 目录自定义分布式锁获取锁释放锁自动续期StackExchange.Redis分布式锁获取锁释放锁自动续期分布式

springboot使用Scheduling实现动态增删启停定时任务教程

《springboot使用Scheduling实现动态增删启停定时任务教程》:本文主要介绍springboot使用Scheduling实现动态增删启停定时任务教程,具有很好的参考价值,希望对大家有... 目录1、配置定时任务需要的线程池2、创建ScheduledFuture的包装类3、注册定时任务,增加、删

使用Python实现矢量路径的压缩、解压与可视化

《使用Python实现矢量路径的压缩、解压与可视化》在图形设计和Web开发中,矢量路径数据的高效存储与传输至关重要,本文将通过一个Python示例,展示如何将复杂的矢量路径命令序列压缩为JSON格式,... 目录引言核心功能概述1. 路径命令解析2. 路径数据压缩3. 路径数据解压4. 可视化代码实现详解1