二进制文件描述符(Binary File Descriptor, BFD)库(libbfd)为读取和解析所有二进制格式提供了一个公共接口。
BFD库是GUN项目的一部分,并被binutils套件中的许多应用程序使用,包括objdump、readelf及GDB。该套件为二进制格式中使用的所有常见组件提供通用抽象,如描述二进制文件的目标和属性、节列表、重定位集及符号表等。在Ubuntu上,libbfd是binutils-dev软件包的一部分。
所以先安装
sudo apt update
sduo apt install binutils-dev4.2 一个简单的二进制加载接口
#ifndef LOADER_H
#define LOADER_H
#include <stdint.h> // 提供 uint64_t、uint8_t 等定宽整数类型
#include <string> // 提供 std::string
#include <vector> // 提供 std::vector
// 前向声明,用于类之间的相互引用
class Binary;
class Section;
class Symbol;
/**
* @class Symbol
* @brief 表示二进制文件中的一个符号(例如函数名、全局变量等)
*/
class Symbol {
public:
/**
* @enum SymbolType
* @brief 符号的类型
*/
enum SymbolType {
SYM_TYPE_UKN = 0, ///< 未知类型
SYM_TYPE_FUNC = 1 ///< 函数符号
};
/**
* @brief 默认构造函数,将符号初始化为未知类型,地址为0
*/
Symbol() : type(SYM_TYPE_UKN), name(), addr(0) {}
SymbolType type; ///< 符号类型
std::string name; ///< 符号名称
uint64_t addr; ///< 符号在内存中的虚拟地址
};
/**
* @class Section
* @brief 表示二进制文件中的一个节(Section),例如 .text、.data 等
*/
class Section {
public:
/**
* @enum SectionType
* @brief 节的内容类型
*/
enum SectionType {
SEC_TYPE_NONE = 0, ///< 未定义类型
SEC_TYPE_CODE = 1, ///< 可执行代码节(如 .text)
SEC_TYPE_DATA = 2 ///< 数据节(如 .data、.rodata)
};
/**
* @brief 默认构造函数,将成员初始化为安全默认值
*/
Section() : binary(NULL), type(SEC_TYPE_NONE), vma(0), size(0), bytes(NULL) {}
/**
* @brief 判断给定的虚拟地址是否位于当前节内部
* @param addr 要检查的虚拟地址
* @return 若地址在 [vma, vma+size) 范围内则返回 true,否则返回 false
* @note 原清单中条件表达式缺漏,此处修正为 (addr - vma < size)
*/
bool contains(uint64_t addr) const {
return (addr >= vma) && (addr - vma < size);
}
Binary* binary; ///< 指向所属 Binary 对象的指针
std::string name; ///< 节名称(如 ".text"、".data")
SectionType type; ///< 节类型
uint64_t vma; ///< 节的起始虚拟内存地址(Virtual Memory Address)
uint64_t size; ///< 节的大小(字节数)
uint8_t* bytes; ///< 指向节原始字节内容的指针(可能由加载器分配)
};
/**
* @class Binary
* @brief 表示一个完整的二进制可执行文件(如 ELF、PE)
*/
class Binary {
public:
/**
* @enum BinaryType
* @brief 二进制文件的格式类型
*/
enum BinaryType {
BIN_TYPE_AUTO = 0, ///< 自动检测格式
BIN_TYPE_ELF = 1, ///< ELF 格式(Linux、Unix)
BIN_TYPE_PE = 2 ///< PE 格式(Windows)
};
/**
* @enum BinaryArch
* @brief 二进制文件的目标架构
*/
enum BinaryArch {
ARCH_NONE = 0, ///< 未知架构
ARCH_X86 = 1 ///< x86 / x86-64 架构
};
/**
* @brief 默认构造函数
*/
Binary() : type(BIN_TYPE_AUTO), arch(ARCH_NONE), bits(0), entry(0) {}
/**
* @brief 获取代码节(.text)的指针
* @return 若存在 .text 节则返回其指针,否则返回 NULL
*/
Section* get_text_section() {
for (auto &s : sections) {
if (s.name == ".text")
return &s;
}
return NULL;
}
std::string filename; ///< 二进制文件的路径
BinaryType type; ///< 文件格式类型
std::string type_str; ///< 文件格式的字符串描述(如 "ELF"、"PE")
BinaryArch arch; ///< CPU 架构
std::string arch_str; ///< 架构的字符串描述(如 "x86")
unsigned bits; ///< 字长(32 或 64)
uint64_t entry; ///< 程序入口点的虚拟地址
std::vector<Section> sections; ///< 所有节的列表
std::vector<Symbol> symbols; ///< 所有符号的列表
};
/**
* @brief 加载二进制文件并解析其结构
* @param fname 文件路径(输入)
* @param bin 指向 Binary 对象的指针,解析结果将填充至此对象
* @param type 期望的二进制格式类型(可用 BIN_TYPE_AUTO 自动检测)
* @return 成功返回 0,失败返回非 0 错误码
*/
int load_binary(std::string &fname, Binary *bin, Binary::BinaryType type);
/**
* @brief 释放与已加载二进制文件相关的资源
* @param bin 指向要卸载的 Binary 对象
*/
void unload_binary(Binary *bin);
#endif /* LOADER_H */
主要的类:
Binary类是根类,表示整个二进制文件的抽象类。
Section类和 Symbol类分别表示二进制文件中包含的节和符号。
主要的两个函数:
laod_binary,其使用要加载的二进制文件和名称 fname 指向一个二进制对象,该对象包含加载的二进制文件以及二进制类型描述符 type。函数将请求的二进制加载到bin参数。
unload_binary,其得到指向先前加载Binary对象的指针,然后将其卸载。
4.2.1 Binary类
其中的一些属性包括
std::string filename; ///< 二进制文件的路径
// 双重表示形式,包含数字类型标识符,同时type_str包含二进制类型的字符串表示形式。
BinaryType type; ///< 文件格式类型
std::string type_str; ///< 文件格式的字符串描述(如 "ELF"、"PE")
// 体系结构使用相同的双重表示形式
BinaryArch arch; ///< CPU 架构
std::string arch_str; ///< 架构的字符串描述(如 "x86")
unsigned bits; ///< 字长(32 或 64)
uint64_t entry; ///< 程序入口点的虚拟地址
std::vector<Section> sections; ///< 所有节的列表
std::vector<Symbol> symbols; ///< 所有符号的列表
二进制的有效类型在BinaryType枚举体中定义
/**
* @enum BinaryType
* @brief 二进制有效类型
*/
enum BinaryType {
BIN_TYPE_AUTO = 0, ///< 自动检测格式
BIN_TYPE_ELF = 1, ///< ELF 格式(Linux、Unix)
BIN_TYPE_PE = 2 ///< PE 格式(Windows)
};
/**
* @enum BinaryArch
* @brief 二进制文件的目标架构
*/
enum BinaryArch {
ARCH_NONE = 0, ///< 未知架构
ARCH_X86 = 1 ///< x86 / x86-64 架构
};
在这个文件的例子中,唯一有效的体系结构是ARCH_X86,包括x86和x86-64,两者之间的区别是Binary类的bit成员决定的。对于x86,bit被设置为32为;对于x86-64,bit被设置为64位。
4.2.2 Section类
节由section类型的对象表示。Section类是味道节的主要属性的简单封装
Binary* binary; ///< 指向所属 Binary 对象的指针
std::string name; ///< 节名称(如 ".text"、".data")
SectionType type; ///< 节类型
uint64_t vma; ///< 节的起始虚拟内存地址(Virtual Memory Address)
uint64_t size; ///< 节的大小(字节数)
uint8_t* bytes; ///< 指向节原始字节内容的指针(可能由加载器分配)
其中,节的类型由枚举体SectionType的值表示,该值告诉你该段包含的是代码段还是数据段。
enum SectionType {
SEC_TYPE_NONE = 0, ///< 未定义类型
SEC_TYPE_CODE = 1, ///< 可执行代码节(如 .text)
SEC_TYPE_DATA = 2 ///< 数据节(如 .data、.rodata)
};
4.2.3 Symbol类
二进制文件包含许多组件类型的符号,包括局部和全局变量、函数、重定位表达式及对象等。
为了简单,这里加载器接口只公开一种符号:函数符号,加载器使用Symbol类表示符号,该类包含一个符号类型,表示为SymbolType枚举体,其唯一的有效值为
enum SymbolType {
SYM_TYPE_UKN = 0, ///< 未知类型
SYM_TYPE_FUNC = 1 ///< 函数符号
};
另外,该类还包含符号名,以及符号描述的函数起始地址。
SymbolType type; ///< 符号类型
std::string name; ///< 符号名称
uint64_t addr; ///< 函数起始地址
4.3 实现二进制加载器
所有使用libbfd的程序都需要包含bfd.h头文件,如清单4-2所示,并通过指定链接器标志-lbfd来链接libbfd。除了链接libbfd,加载器还需要包含4.2节中创建接口的头文件。
#include <bfd.h>
#include "loader.h"
下面主要两个函数
int load_bianry(std::string &fname, Binary *bin, Binary::BinaryType type) {
return load_binary_bfd(fname, bin, type);
}
void unload_bianry(Binary *bin) {
size_t i;
Section *sec;
for(i = 0; i < bin->sections.size(); i++) {
sec = &bin->sections[i];
if(sec->bytes) {
free(sec->bytes);
}
}
}
4.3.1 初始化libbfd打开二进制文件。
/**
* @brief 使用 libbfd 打开指定的二进制文件,并验证其格式
* @param fname 文件路径
* @return 成功返回 bfd 句柄,失败返回 NULL
*/
static bfd* open_bfd(std::string &fname) {
static int bfd_inited = 0;
bfd *bfd_h;
// libbfd 只需初始化一次
if (!bfd_inited) {
bfd_init();
bfd_inited = 1;
}
// 打开二进制文件,第二个参数为 NULL 表示让 libbfd 自动检测格式
bfd_h = bfd_openr(fname.c_str(), NULL);
if (!bfd_h) {
fprintf(stderr, "failed to open binary '%s' (%s)\n",
fname.c_str(), bfd_errmsg(bfd_get_error()));
return NULL;
}
// 检查文件是否为一个可识别的对象文件(可执行文件、目标文件或共享库)
if (!bfd_check_format(bfd_h, bfd_object)) {
fprintf(stderr, "file '%s' does not look like an executable (%s)\n",
fname.c_str(), bfd_errmsg(bfd_get_error()));
bfd_close(bfd_h);
return NULL;
}
// 某些版本的 bfd_check_format 会残留错误状态,手动清除以避免后续问题
bfd_set_error(bfd_error_no_error);
// 进一步确认文件格式是否为已知类型(ELF、COFF/PE 等)
if (bfd_get_flavour(bfd_h) == bfd_target_unknown_flavour) {
fprintf(stderr, "unrecognized format for binary '%s' (%s)\n",
fname.c_str(), bfd_errmsg(bfd_get_error()));
bfd_close(bfd_h);
return NULL;
}
return bfd_h;
}
4.3.2 解析基础二进制属性
/**
* @brief 核心加载函数:解析二进制文件并填充 Binary 对象
*/
static int load_binary_bfd(std::string &fname, Binary *bin, Binary::BinaryType type) {
int ret = -1;
bfd *bfd_h = NULL;
const bfd_arch_info_type *bfd_info;
// 打开二进制文件
bfd_h = open_bfd(fname);
if (!bfd_h) {
goto fail;
}
// 复制文件名
bin->filename = std::string(fname);
// 获取程序入口点地址
bin->entry = bfd_get_start_address(bfd_h);
// 获取二进制文件格式的字符串描述(例如 "elf64-x86-64")
bin->type_str = std::string(bfd_h->xvec->name);
// 根据 libbfd 探测到的格式类型设置 Binary::BinaryType
switch (bfd_h->xvec->flavour) {
case bfd_target_elf_flavour:
bin->type = Binary::BIN_TYPE_ELF;
break;
case bfd_target_coff_flavour:
bin->type = Binary::BIN_TYPE_PE;
break;
case bfd_target_unknown_flavour:
default:
fprintf(stderr, "unsupported binary type (%s)\n", bfd_h->xvec->name);
goto fail;
}
// 获取体系结构信息
bfd_info = bfd_get_arch_info(bfd_h);
bin->arch_str = std::string(bfd_info->printable_name);
// 根据机器类型设置架构和位宽
switch (bfd_info->mach) {
case bfd_mach_i386_i386:
bin->arch = Binary::ARCH_X86;
bin->bits = 32;
break;
case bfd_mach_x86_64:
bin->arch = Binary::ARCH_X86;
bin->bits = 64;
break;
default:
fprintf(stderr, "unsupported architecture (%s)\n", bfd_info->printable_name);
goto fail;
}
// 加载符号表(静态和动态),即使失败也尽力而为,不影响主流程
load_symbols_bfd(bfd_h, bin);
load_dynsym_bfd(bfd_h, bin);
// 加载节信息(关键步骤)
if (load_sections_bfd(bfd_h, bin) < 0) {
goto fail;
}
ret = 0; // 成功
fail:
if (bfd_h) {
bfd_close(bfd_h);
}
return ret;
}
4.3.3 加载符号
// ---------- 加载静态符号表 ----------
/**
* @brief 从静态符号表中提取函数符号
*/
static int load_symbols_bfd(bfd *bfd_h, Binary *bin) {
long storage_needed;
asymbol **bfd_symtab = NULL;
long nsyms, i;
// 获取存储所有符号指针所需的字节数
storage_needed = bfd_get_symtab_upper_bound(bfd_h);
if (storage_needed < 0) {
fprintf(stderr, "failed to read symtab (%s)\n", bfd_errmsg(bfd_get_error()));
goto fail;
}
if (storage_needed == 0) {
// 没有符号表,直接返回成功
return 0;
}
// 分配内存
bfd_symtab = (asymbol**)malloc(storage_needed);
if (!bfd_symtab) {
fprintf(stderr, "out of memory\n");
goto fail;
}
// 实际读取符号表
nsyms = bfd_canonicalize_symtab(bfd_h, bfd_symtab);
if (nsyms < 0) {
fprintf(stderr, "failed to read symtab (%s)\n", bfd_errmsg(bfd_get_error()));
goto fail;
}
// 遍历符号表,仅保留函数类型的符号
for (i = 0; i < nsyms; i++) {
if (bfd_symtab[i]->flags & BSF_FUNCTION) {
bin->symbols.push_back(Symbol());
Symbol *sym = &bin->symbols.back();
sym->type = Symbol::SYM_TYPE_FUNC;
sym->name = std::string(bfd_symtab[i]->name);
sym->addr = bfd_asymbol_value(bfd_symtab[i]);
}
}
free(bfd_symtab);
return 0;
fail:
if (bfd_symtab) free(bfd_symtab);
return -1;
}
// ---------- 加载动态符号表 ----------
/**
* @brief 从动态符号表中提取函数符号,流程与静态符号表类似
*/
static int load_dynsym_bfd(bfd *bfd_h, Binary *bin) {
long storage_needed;
asymbol **bfd_dynsym = NULL;
long nsyms, i;
storage_needed = bfd_get_dynamic_symtab_upper_bound(bfd_h);
if (storage_needed < 0) {
fprintf(stderr, "failed to read dynamic symtab (%s)\n", bfd_errmsg(bfd_get_error()));
goto fail;
}
if (storage_needed == 0) {
return 0; // 没有动态符号表
}
bfd_dynsym = (asymbol**)malloc(storage_needed);
if (!bfd_dynsym) {
fprintf(stderr, "out of memory\n");
goto fail;
}
nsyms = bfd_canonicalize_dynamic_symtab(bfd_h, bfd_dynsym);
if (nsyms < 0) {
fprintf(stderr, "failed to read dynamic symtab (%s)\n", bfd_errmsg(bfd_get_error()));
goto fail;
}
for (i = 0; i < nsyms; i++) {
if (bfd_dynsym[i]->flags & BSF_FUNCTION) {
bin->symbols.push_back(Symbol());
Symbol *sym = &bin->symbols.back();
sym->type = Symbol::SYM_TYPE_FUNC;
sym->name = std::string(bfd_dynsym[i]->name);
sym->addr = bfd_asymbol_value(bfd_dynsym[i]);
}
}
free(bfd_dynsym);
return 0;
fail:
if (bfd_dynsym) free(bfd_dynsym);
return -1;
}
4.3.4 加载节信息
// ---------- 加载节(Section)信息 ----------
/**
* @brief 遍历二进制文件的所有节,复制代码节和数据节的内容到 Binary 对象
*/
static int load_sections_bfd(bfd *bfd_h, Binary *bin) {
asection *bfd_sec;
Section::SectionType sectype;
// libbfd 通过链表管理节,从 bfd_h->sections 开始遍历
for (bfd_sec = bfd_h->sections; bfd_sec != NULL; bfd_sec = bfd_sec->next) {
// 获取节的标志位
int bfd_flags = bfd_get_section_flags(bfd_h, bfd_sec);
// 只关心代码段和数据段
if (bfd_flags & SEC_CODE) {
sectype = Section::SEC_TYPE_CODE;
} else if (bfd_flags & SEC_DATA) {
sectype = Section::SEC_TYPE_DATA;
} else {
continue; // 忽略其他类型的节(如调试信息、符号表等)
}
// 获取节的虚拟地址、大小和名称
uint64_t vma = bfd_section_vma(bfd_h, bfd_sec);
uint64_t size = bfd_section_size(bfd_h, bfd_sec);
const char *secname = bfd_section_name(bfd_h, bfd_sec);
if (!secname) secname = "<unnamed>";
// 在 Binary 对象中创建对应的 Section 条目
bin->sections.push_back(Section());
Section *sec = &bin->sections.back();
sec->binary = bin;
sec->name = std::string(secname);
sec->type = sectype;
sec->vma = vma;
sec->size = size;
sec->bytes = (uint8_t*)malloc(size);
if (!sec->bytes) {
fprintf(stderr, "out of memory\n");
return -1;
}
// 将节的原始内容复制到分配的内存中
if (!bfd_get_section_contents(bfd_h, bfd_sec, sec->bytes, 0, size)) {
fprintf(stderr, "failed to read section '%s' (%s)\n",
secname, bfd_errmsg(bfd_get_error()));
return -1;
}
}
return 0;
}
评论(0)
暂无评论