[置顶] 泰晓 RISC-V 实验箱,配套 30+ 讲嵌入式 Linux 系统开发公开课
libelf 开源库用法详解
By Wu Daemon of TinyLab.org 2020/2/20
前言
ELF 文件是 Linux 系统下的一类重要文件,可执行文件、共享库文件、coredump 文件、目标文件都是 ELF 格式的文件。我们可以使用 readelf 工具解析,也可使用 libelf 开源库来解析它们。
作为解读 eBPF 系列的一部分,该篇主要介绍 BPF 程序的格式 ELF。
下载编译与安装 libelf 库
libelf 库的编译和安装过程很简单,与众多开源库的安装方法一样,
// 下载源码
$ git clone https://github.com/WolfgangSt/libelf.git
// 切换目录,创建一个安装目录
$ cd libelf && mkdir ../install
// 配置,需要指令库安装的路径
$ ./configure --prefix=/home/wu/work/elf/install
// 编译 安装
$ make && make install
查看安装目录,其中包含 include/
,lib/
, share/
三个目录:
include/
: 头文件,应用开发使用该库需要引用头文件lib/
: 包含so共享库和静态库,应用程序需要动态链接或者静态链接该库share/
: 包含了该库的使用手册
wu@ubuntu:~/work/elf/install$ tree
.
├── include
│ └── libelf
│ ├── elf_repl.h
│ ├── gelf.h
│ ├── libelf.h
│ ├── nlist.h
│ └── sys_elf.h
├── lib
│ ├── libelf.a
│ ├── libelf.so -> libelf.so.0.8.12
│ ├── libelf.so.0 -> libelf.so.0.8.12
│ ├── libelf.so.0.8.12
│ └── pkgconfig
│ └── libelf.pc
└── share
└── locale
└── de
└── LC_MESSAGES
└── libelf.mo
使用 libelf 库解析 .text 段
实验目标
接下来使用 libelf 库的 API 编写解析 ELF 的程序。ELF 文件包含四种,我们解析目标文件。
可执行文件包含了若干个 sections,这里主要用来打印出 .text
段的内容。
代码实现
实验目录:
wu@ubuntu:~/work/elf/demo$ ls
Makefile parse.c parse_elf parse_elf.c parse_elf.o test test.c tracex4_kern.o
核心代码:
#include <stdio.h>
#include <libelf.h>
#include <gelf.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
static int get_sec(Elf *elf, int i, GElf_Ehdr *ehdr, char **shname,GElf_Shdr *shdr, Elf_Data **data)
{
Elf_Scn *scn;
scn = elf_getscn(elf, i); //从elf描述符获取按照节索引获取节接口
if (!scn)
return 1;
if (gelf_getshdr(scn, shdr) != shdr) // 通过节结构复制节表头
return 2;
*shname = elf_strptr(elf, ehdr->e_shstrndx, shdr->sh_name); // 从指定的字符串表中通过偏移获取字符串
if (!*shname || !shdr->sh_size)
return 3;
*data = elf_getdata(scn, 0); //从节中获取节数据(经过了字节序的转换)
if (!*data || elf_getdata(scn, *data) != NULL)
return 4;
return 0;
}
int parse_file(const char *path)
{
Elf *elf;
int fd;
GElf_Ehdr ehdr;
GElf_Shdr shdr;
char *shname, *shname_prog;
Elf_Data *data;
if (elf_version(EV_CURRENT) == EV_NONE)
return 1;
fd = open(path, O_RDONLY, 0); //打开elf文件
if (fd < 0)
{
printf("can not open\n");
return -1;
}
elf = elf_begin(fd, ELF_C_READ, NULL);//获取elf描述符,使用‘读取’的方式
if (!elf)
{
printf("can not get elf desc\n");
return -1;
}
if (gelf_getehdr(elf, &ehdr) != &ehdr)
return 1;
for (int i = 1; i < ehdr.e_shnum; i++) {
if (get_sec(elf, i, &ehdr, &shname, &shdr, &data))
continue;
printf("section %d:%s data %p size %zd link %d flags %d type %d\n",i, shname, data->d_buf, data->d_size,shdr.sh_link, (int) shdr.sh_flags,(int) shdr.sh_type);
if(strcmp(shname,".text")==0)
{
printf(".text data:\n");
unsigned char *p=data->d_buf;
for(int j=0;j<data->d_size;j++)
{
if(j%8==0)
{
printf("\n");
}
printf("%4x",*p++);
}
printf("\n");
}
}
}
int main()
{
parse_file("./parse_elf.o");
return 0;
}
代码管理用到的 Makefile 内容如下,动态链接 libelf 库,遵循一般的链接规则:
BIN = parse_elf
OBJS = parse_elf.o
CC = gcc
INCLUDE := -I /home/wu/work/elf/install/include/
LIBS := -L /home/wu/work/elf/install/lib/ -lelf
$(BIN):$(OBJS)
$(CC) -g $^ -o $@ $(LIBS)
%.o:%.c
$(CC) -g -c $< -o $@ $(INCLUDE)
PHONY:clean
clean:
rm -f $(OBJS) $(BIN)
编译并运行
编译后,可以使用 ldd 查看可执行文件链接到的动态库,可以看到,其中包含 libelf.so 共享库:
wu@ubuntu:~/work/elf/demo$ make
wu@ubuntu:~/work/elf/demo$ ldd parse_elf
linux-vdso.so.1 => (0x00007ffd22546000)
libelf.so.0 => /home/wu/work/elf/install/lib/libelf.so.0 (0x00007f22eca8d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f22ec6c3000)
/lib64/ld-linux-x86-64.so.2 (0x00007f22ecca3000)
执行程序后的信息如下,我们解析出了 .text
段的内容,还有其他段的基本信息,比如属性和大小等:
wu@ubuntu:~/work/elf/demo$ ./parse_elf
wu@ubuntu:~/work/elf/demo$ ./parse_elf
section 1:.text data 0x7f1dbc7f0040 size 94 link 0 flags 6 type 1
.text data:
55 48 89 e5 48 83 ec 10
89 7d fc 83 7d fc 0 75
7 b8 0 0 0 0 eb 14
8b 45 fc 83 e8 1 89 c7
e8 0 0 0 0 89 c2 8b
45 fc 1 d0 c9 c3 55 48
89 e5 48 83 ec 10 bf 3
0 0 0 e8 0 0 0 0
89 45 fc 8b 45 fc 89 c6
bf 0 0 0 0 b8 0 0
0 0 e8 0 0 0 0 b8
0 0 0 0 c9 c3
section 2:.rela.text data 0x7f1dbc7f0270 size 96 link 11 flags 64 type 4
section 5:.rodata data 0x7f1dbc7f009e size 8 link 0 flags 2 type 1
section 6:.comment data 0x7f1dbc7f00a6 size 54 link 0 flags 48 type 1
section 8:.eh_frame data 0x7f1dbc7f00e0 size 88 link 0 flags 2 type 1
section 9:.rela.eh_frame data 0x7f1dbc7f02d0 size 48 link 11 flags 64 type 4
section 10:.shstrtab data 0x7f1dbc7f0300 size 97 link 0 flags 0 type 3
section 11:.symtab data 0x7f1dbc7f0138 size 288 link 12 flags 0 type 2
section 12:.strtab data 0x7f1dbc7f0258 size 24 link 0 flags 0 type 3
使用 readelf 和 objdump 交叉验证
下面使用 readelf 和 objdump 工具来验证结果是否一致。
使用 readelf 看出各个段的信息,与解析的一致:
wu@ubuntu:~/work/elf/demo$ readelf -S parse_elf.o
There are 13 section headers, starting at offset 0x950:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
00000000000002c9 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000006f0
00000000000001b0 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 00000309
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 00000309
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 00000310
0000000000000067 0000000000000000 A 0 0 8
[ 6] .comment PROGBITS 0000000000000000 00000377
0000000000000036 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000003ad
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000003b0
0000000000000078 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 000008a0
0000000000000048 0000000000000018 I 11 8 8
[10] .shstrtab STRTAB 0000000000000000 000008e8
0000000000000061 0000000000000000 0 0 1
[11] .symtab SYMTAB 0000000000000000 00000428
0000000000000228 0000000000000018 12 10 8
[12] .strtab STRTAB 0000000000000000 00000650
0000000000000099 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
使用 objdump 查看 .text
这个段,这个打印出来的和 libelf 打印出来的也是一致的:
wu@ubuntu:~/work/elf/demo$ objdump --section=.text -s parse_elf.o
parse_elf.o: file format elf64-x86-64
Contents of section .text:
0000 554889e5 4883ec40 48897de8 8975e448 UH..H..@H.}..u.H
0010 8955d848 894dd04c 8945c84c 894dc08b .U.H.M.L.E.L.M..
0020 45e44863 d0488b45 e84889d6 4889c7e8 E.Hc.H.E.H..H...
0030 00000000 488945f8 48837df8 00750ab8 ....H.E.H.}..u..
0040 01000000 e9bd0000 00488b55 c8488b45 .........H.U.H.E
0050 f84889d6 4889c7e8 00000000 483b45c8 .H..H.......H;E.
... ...
小结
以上详细介绍了如何使用 libelf 开源库来灵活地解析 ELF 文件,并且通过 readelf 和 objdump 交叉做了验证,跟我们实现的代码效果一致。
如果希望更深入的学习 ELF,进一步理解 Linux 程序的编译、装载和运行原理,可以订阅社区开发的 《360° 剖析 Linux ELF》 视频课程。
猜你喜欢:
- 我要投稿:发表原创技术文章,收获福利、挚友与行业影响力
- 知识星球:独家 Linux 实战经验与技巧,订阅「Linux知识星球」
- 视频频道:泰晓学院,B 站,发布各类 Linux 视频课
- 开源小店:欢迎光临泰晓科技自营店,购物支持泰晓原创
- 技术交流:Linux 用户技术交流微信群,联系微信号:tinylab
支付宝打赏 ¥9.68元 | 微信打赏 ¥9.68元 | |
请作者喝杯咖啡吧 |