基础技术-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

相关文章

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

python使用try函数详解

《python使用try函数详解》Pythontry语句用于异常处理,支持捕获特定/多种异常、else/final子句确保资源释放,结合with语句自动清理,可自定义异常及嵌套结构,灵活应对错误场景... 目录try 函数的基本语法捕获特定异常捕获多个异常使用 else 子句使用 finally 子句捕获所

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

Python对接支付宝支付之使用AliPay实现的详细操作指南

《Python对接支付宝支付之使用AliPay实现的详细操作指南》支付宝没有提供PythonSDK,但是强大的github就有提供python-alipay-sdk,封装里很多复杂操作,使用这个我们就... 目录一、引言二、准备工作2.1 支付宝开放平台入驻与应用创建2.2 密钥生成与配置2.3 安装ali

C#中lock关键字的使用小结

《C#中lock关键字的使用小结》在C#中,lock关键字用于确保当一个线程位于给定实例的代码块中时,其他线程无法访问同一实例的该代码块,下面就来介绍一下lock关键字的使用... 目录使用方式工作原理注意事项示例代码为什么不能lock值类型在C#中,lock关键字用于确保当一个线程位于给定实例的代码块中时

MySQL 强制使用特定索引的操作

《MySQL强制使用特定索引的操作》MySQL可通过FORCEINDEX、USEINDEX等语法强制查询使用特定索引,但优化器可能不采纳,需结合EXPLAIN分析执行计划,避免性能下降,注意版本差异... 目录1. 使用FORCE INDEX语法2. 使用USE INDEX语法3. 使用IGNORE IND

C# $字符串插值的使用

《C#$字符串插值的使用》本文介绍了C#中的字符串插值功能,详细介绍了使用$符号的实现方式,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录$ 字符使用方式创建内插字符串包含不同的数据类型控制内插表达式的格式控制内插表达式的对齐方式内插表达式中使用转义序列内插表达式中使用

flask库中sessions.py的使用小结

《flask库中sessions.py的使用小结》在Flask中Session是一种用于在不同请求之间存储用户数据的机制,Session默认是基于客户端Cookie的,但数据会经过加密签名,防止篡改,... 目录1. Flask Session 的基本使用(1) 启用 Session(2) 存储和读取 Se

springboot自定义注解RateLimiter限流注解技术文档详解

《springboot自定义注解RateLimiter限流注解技术文档详解》文章介绍了限流技术的概念、作用及实现方式,通过SpringAOP拦截方法、缓存存储计数器,结合注解、枚举、异常类等核心组件,... 目录什么是限流系统架构核心组件详解1. 限流注解 (@RateLimiter)2. 限流类型枚举 (

Java Thread中join方法使用举例详解

《JavaThread中join方法使用举例详解》JavaThread中join()方法主要是让调用改方法的thread完成run方法里面的东西后,在执行join()方法后面的代码,这篇文章主要介绍... 目录前言1.join()方法的定义和作用2.join()方法的三个重载版本3.join()方法的工作原