如何使用 C++ 获取 Linux 中 elf 二进制文件所需的所有动态库的列表?
一旦我成功地从二进制文件中提取信息(文件名?),我就可以通过搜索
PATH
找到实际的文件,但我无法找到有关从 ELF 二进制文件中提取未损坏信息的任何信息.
想法?
您可以调用“readelf -d”程序并解析输出:
readelf -d /usr/bin/readelf | grep NEEDED
0x0000000000000001 (NEEDED) Shared library: [libz.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
所需共享对象的列表存储在可执行文件的所谓“动态部分”中。获取必要信息的粗略算法如下:
解析ET_EXEC
或 ET_DYN
)。获取程序头的偏移量和计数 (e_phoff/e_phnum/e_phentsize
PT_DYNAMIC
。还要记住 PT_LOAD
段的虚拟地址 -> 文件偏移映射。找到后,解析DT_NEEDED
和 DT_STRTAB
条目。
d_val
条目的
DT_NEEDED
字段是DT_STRTAB
字符串表的偏移量,这将是所需库的SONAME。请注意,由于 DT_STRTAB
条目是运行时 address而不是字符串表的 offset,因此您需要使用步骤 3 中存储的信息将其映射回文件偏移量。
来执行此操作。请注意,
libelf
有 C API。从他们的教程这里,查看第 4.2 节(或这里)中有关如何获取程序头表的示例。找到 DT_DYNAMIC
部分并从字符串表中读取依赖关系,如第 5.4 节中的示例(或
此处)。 -- 编辑 --
我实际上有机会编写代码。这是我所做的:
#include <assert.h>
#include <fcntl.h>
#include <gelf.h>
#include <stdio.h>
#include <unistd.h>
void print_dt_needed(const char *elf_path) {
assert(elf_version(EV_CURRENT) != EV_NONE);
int fd = open(elf_path, O_RDWR, 0);
assert(fd >= 0);
Elf *elf = elf_begin(fd, ELF_C_READ, NULL);
assert(elf != NULL);
assert(elf_kind(elf) == ELF_K_ELF);
Elf_Scn *scn = NULL;
while ((scn = elf_nextscn(elf, scn)) != NULL) {
GElf_Shdr shdr = {};
assert(gelf_getshdr(scn, &shdr) == &shdr);
if (shdr.sh_type == SHT_DYNAMIC) {
Elf_Data *data = NULL;
data = elf_getdata(scn, data);
assert(data != NULL);
size_t sh_entsize = gelf_fsize(elf, ELF_T_DYN, 1, EV_CURRENT);
for (size_t i = 0; i < shdr.sh_size / sh_entsize; i++) {
GElf_Dyn dyn = {};
assert(gelf_getdyn(data, i, &dyn) == &dyn);
if (dyn.d_tag == DT_NEEDED) {
printf("DT_NEEDED detected: %s\n",
elf_strptr(elf, shdr.sh_link, dyn.d_un.d_val));
}
}
}
}
assert(elf_end(elf) == 0);
assert(close(fd) == 0);
}
int main(int argc, char const *argv[]) {
print_dt_needed(argv[1]);
return 0;
}
#include <stdio.h>
#include <string.h>
#include <elf.h>
int print_dependencies(const char *file_name)
{
Elf64_Ehdr ehdr;
Elf64_Shdr shdr, shdr_shstrtab, shdr_dynstr;
Elf64_Phdr phdr;
Elf64_Dyn dyn;
long int oldpos, dynpos;
int dyncount;
char sname[1000];
FILE *f = fopen(file_name, "r");
if(!f) { return 1; }
if(fseek(f, 0, SEEK_SET) != 0) { fclose(f); return 1; }
if(fread(&ehdr, sizeof(ehdr), 1, f) <= 0) { fclose(f); return 1; }
if(memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0) { fclose(f); return 1; }
if(fseek(f, ehdr.e_shoff + (ehdr.e_shentsize * ehdr.e_shstrndx), SEEK_SET) != 0) { fclose(f); return 1; }
if(fread(&shdr_shstrtab, sizeof(shdr_shstrtab), 1, f) <= 0) { fclose(f); return 1; }
if(fseek(f, ehdr.e_shoff, SEEK_SET) != 0) { fclose(f); return 1; }
for(int i = 0; i < ehdr.e_shnum; i++) {
if(fread(&shdr_dynstr, sizeof(shdr_dynstr), 1, f) <= 0) { shdr_dynstr.sh_type = SHT_NULL; break; }
if(shdr_dynstr.sh_type == SHT_STRTAB) {
oldpos = ftell(f);
sname[8] = 0;
if(fseek(f, shdr_shstrtab.sh_offset + shdr_dynstr.sh_name, SEEK_SET) == 0) {
fgets(sname, 8, f);
}
if(strcmp(sname, ".dynstr") == 0) { break; } else { shdr_dynstr.sh_type = SHT_NULL; }
if(fseek(f, oldpos, SEEK_SET) != 0) { break; }
}
}
if(fseek(f, ehdr.e_shoff, SEEK_SET) == 0) {
for(int i = 0; i < ehdr.e_shnum; i++) {
if(fread(&shdr, sizeof(shdr), 1, f) <= 0) { break; }
if(shdr.sh_type == SHT_DYNAMIC) {
oldpos = ftell(f);
dyncount = shdr.sh_size / shdr.sh_entsize;
if(fseek(f, shdr.sh_offset, SEEK_SET) == 0) {
for(int i = 0; i < dyncount; i++) {
if(fread(&dyn, sizeof(dyn), 1, f) <= 0 || dyn.d_tag == DT_NULL) { break; }
if(dyn.d_tag == DT_NEEDED) {
dynpos = ftell(f);
if(fseek(f, shdr_dynstr.sh_offset + dyn.d_un.d_val, SEEK_SET) == 0) {
sname[sizeof(sname) - 1] = 0;
fgets(sname, sizeof(sname) - 1, f);
printf("shdr.sh_type is SHT_DYNAMIC, dyn.d_tag is DT_NEEDED: %s\n", sname);
}
if(fseek(f, dynpos, SEEK_SET) != 0) { break; }
}
}
}
if(fseek(f, oldpos, SEEK_SET) != 0) { break; }
}
}
}
if(fseek(f, ehdr.e_phoff, SEEK_SET) == 0) {
for(int i = 0; i < ehdr.e_phnum; i++) {
if(fread(&phdr, sizeof(phdr), 1, f) <= 0) { break; }
if(phdr.p_type == PT_DYNAMIC) {
oldpos = ftell(f);
dyncount = phdr.p_filesz / sizeof(dyn);
if(fseek(f, phdr.p_offset, SEEK_SET) == 0) {
for(int i = 0; i < dyncount; i++) {
if(fread(&dyn, sizeof(dyn), 1, f) <= 0 || dyn.d_tag == DT_NULL) { break; }
if(dyn.d_tag == DT_NEEDED) {
dynpos = ftell(f);
if(fseek(f, shdr_dynstr.sh_offset + dyn.d_un.d_val, SEEK_SET) == 0) {
sname[sizeof(sname) - 1] = 0;
fgets(sname, sizeof(sname) - 1, f);
printf("phdr.p_type is PT_DYNAMIC, dyn.d_tag is DT_NEEDED: %s\n", sname);
}
if(fseek(f, dynpos, SEEK_SET) != 0) { break; }
}
}
}
if(fseek(f, oldpos, SEEK_SET) != 0) { break; }
}
}
}
fclose(f);
return 0;
}
int main(int argc, char* argv[])
{
if(argc > 1) {
print_dependencies(argv[1]);
}
return 0;
}
这是一个C代码,我试图让它处理所有错误并且不会导致任何内存泄漏,fgets可以被替换为逐字节读取并仅分配所需字节数以节省一些内存的东西。您可以使用基于 malloc 和 realloc 的数组来存储调用函数要使用的 sname。如果需要的话也可以使用C++,那么你可以使用C++向量以方便的方式存储sname。此代码广泛使用 fseek,如果您不想多次查找和读取文件,可以将文件缓存到内存中并从内存中处理它,或者只缓存文件的相关部分。请注意,如果您需要知道运行应用程序所需的所有文件(如 ldd 那样),则需要在其内部递归地使用 print_dependency ,获取依赖项的依赖项等。