ohmu
·
2026-10-15
1#include "common/binary.h"
2#include "common/debug.h"
3
4#if defined(FF_HAVE_ELF) || defined(__sun) || (defined(__FreeBSD__) && !defined(__DragonFly__)) || defined(__OpenBSD__) || defined(__NetBSD__)
5
6 #include "common/io.h"
7 #include "common/library.h"
8 #include "common/strutil.h"
9
10 #include <libelf.h> // #1254
11 #include <fcntl.h>
12
13/**
14 * Structure to hold dynamically loaded libelf function pointers
15 */
16struct FFElfData {
17 FF_LIBRARY_SYMBOL(elf_version)
18 FF_LIBRARY_SYMBOL(elf_begin)
19 FF_LIBRARY_SYMBOL(elf_getshdrstrndx)
20 FF_LIBRARY_SYMBOL(elf_nextscn)
21 FF_LIBRARY_SYMBOL(elf64_getshdr)
22 FF_LIBRARY_SYMBOL(elf32_getshdr)
23 FF_LIBRARY_SYMBOL(elf_getdata)
24 FF_LIBRARY_SYMBOL(elf_strptr)
25 FF_LIBRARY_SYMBOL(elf_end)
26
27 bool inited;
28} elfData;
29
30/**
31 * Extracts string literals from an ELF (Linux/Unix) binary file
32 *
33 * This function loads the libelf library dynamically, opens the ELF file,
34 * locates the .rodata section (which contains string literals), and
35 * scans it for valid strings. Each string found is passed to the
36 * callback function for processing.
37 *
38 * The function supports both 32-bit and 64-bit ELF formats.
39 */
40const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
41 // Initialize libelf if not already done
42 if (!elfData.inited) {
43 elfData.inited = true;
44 FF_LIBRARY_LOAD_MESSAGE(libelf, "libelf" FF_LIBRARY_EXTENSION, 1);
45 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_version)
46 if (elfData.ffelf_version(EV_CURRENT) == EV_NONE) {
47 FF_DEBUG("elf_version() failed");
48 return "elf_version() failed";
49 }
50
51 // Load all required libelf functions
52 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_begin)
53 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_getshdrstrndx)
54 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_nextscn)
55 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf64_getshdr)
56 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf32_getshdr)
57 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_getdata)
58 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_strptr)
59 FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_end)
60
61 libelf = nullptr;
62 }
63
64 if (elfData.ffelf_end == nullptr) {
65 FF_DEBUG("load libelf failed");
66 return "load libelf failed";
67 }
68
69 // Open the ELF file
70 FF_AUTO_CLOSE_FD int fd = open(elfFile, O_RDONLY | O_CLOEXEC);
71 if (fd < 0) {
72 FF_DEBUG("open(%s) failed: %s", elfFile, strerror(errno));
73 return "open() failed";
74 }
75
76 Elf* elf = elfData.ffelf_begin(fd, ELF_C_READ, nullptr);
77 if (elf == nullptr) {
78 FF_DEBUG("elf_begin() failed");
79 return "elf_begin() failed";
80 }
81
82 // Get the section header string table index
83 size_t shstrndx = 0;
84 if (elfData.ffelf_getshdrstrndx(elf, &shstrndx) < 0) {
85 FF_DEBUG("elf_getshdrstrndx() failed");
86 elfData.ffelf_end(elf);
87 return "elf_getshdrstrndx() failed";
88 }
89
90 // Iterate through all sections, looking for .rodata which contains string literals
91 Elf_Scn* scn = nullptr;
92 while ((scn = elfData.ffelf_nextscn(elf, scn)) != nullptr) {
93 // Try 64-bit section header first, then 32-bit if that fails
94 Elf64_Shdr* shdr64 = elfData.ffelf64_getshdr(scn);
95 Elf32_Shdr* shdr32 = nullptr;
96 if (shdr64 == nullptr) {
97 shdr32 = elfData.ffelf32_getshdr(scn);
98 if (shdr32 == nullptr) {
99 continue;
100 }
101 }
102
103 // Get the section name and check if it's .rodata
104 const char* name = elfData.ffelf_strptr(elf, shstrndx, shdr64 ? shdr64->sh_name : shdr32->sh_name);
105 if (name == nullptr || !ffStrEquals(name, ".rodata")) {
106 continue;
107 }
108
109 // Get the section data
110 Elf_Data* data = elfData.ffelf_getdata(scn, nullptr);
111 if (data == nullptr) {
112 continue;
113 }
114
115 // Scan the section for string literals
116 for (size_t off = 0; off < data->d_size; ++off) {
117 const char* p = (const char*) data->d_buf + off;
118 if (*p == '\0') {
119 continue;
120 }
121 uint32_t len = (uint32_t) strnlen(p, data->d_size - off);
122 if (len < minLength) {
123 off += len;
124 continue;
125 }
126 // Only process printable ASCII characters
127 if (*p >= ' ' && *p <= '~') // Ignore control characters
128 {
129 if (!cb(p, len, userdata)) {
130 break;
131 }
132 }
133 off += len;
134 }
135
136 break;
137 }
138
139 elfData.ffelf_end(elf);
140 return nullptr;
141}
142
143#else
144
145/**
146 * Fallback implementation when libelf is not available
147 */
148const char* ffBinaryExtractStrings(const char* file, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
149 FF_UNUSED(file, cb, userdata, minLength);
150 FF_DEBUG("Fastfetch was built without libelf support");
151 return "Fastfetch was built without libelf support";
152}
153
154#endif