BIDK API参考手册:开发者必备的接口和数据结构详解
【免费下载链接】BIDKA low-overhead dynamic binary instrumentation and modification tool for ARM (both AArch32 and AArch64 support) and RISC-V (RV64GC).项目地址: https://gitcode.com/openeuler/BIDK
前往项目官网免费下载:https://ar.openeuler.org/ar/
BIDK是一个针对ARM(AArch32/AArch64)和RISC-V(RV64GC)架构的低开销动态二进制插桩和修改工具。本指南将详细介绍BIDK的核心API接口和数据结构,帮助开发者快速上手并构建强大的二进制分析插件。💪
🔧 BIDK核心架构概述
BIDK采用插件化架构,允许开发者在运行时动态修改和分析二进制代码。其核心组件包括:
- 动态二进制修改引擎:实时重写目标程序的指令流
- 插件支持系统:提供丰富的回调机制和API接口
- 代码缓存管理:高效管理修改后的代码片段
- 线程感知设计:支持多线程环境下的安全操作
📋 核心数据结构详解
mambo_context:插件上下文结构
这是BIDK API中最核心的数据结构,封装了所有插件操作所需的上下文信息:
typedef struct { dbm_thread *thread_data; // 线程相关数据 mambo_cb_idx event_type; // 事件类型 int plugin_id; // 插件ID union { struct code_ctx code; // 代码执行上下文 struct syscall_ctx syscall; // 系统调用上下文 struct vm_ctx vm; // 虚拟内存操作上下文 }; } mambo_context;code_ctx:代码执行上下文
当处理指令相关事件时,使用此结构体:
struct code_ctx { cc_type fragment_type; // 片段类型(基本块/跟踪等) int fragment_id; // 片段ID inst_set inst_type; // 指令集类型 void *read_address; // 原始指令地址 int inst; // 指令编码 mambo_cond cond; // 条件码 void *write_p; // 写入指针 void *data_p; // 数据指针 uint32_t pushed_regs; // 已压栈寄存器 uint32_t available_regs; // 可用寄存器 int plugin_pushed_reg_count; // 插件压栈寄存器计数 char *func_name; // 函数名 bool replace; // 是否替换指令 bool *stop; // 停止扫描标志 };syscall_ctx:系统调用上下文
处理系统调用事件时使用的数据结构:
struct syscall_ctx { uintptr_t number; // 系统调用号 uintptr_t *regs; // 寄存器数组指针 bool replace; // 是否替换系统调用 uintptr_t ret; // 返回值 };🔄 回调机制与事件类型
BIDK提供了丰富的事件回调机制,允许插件在特定时机介入执行流程:
事件类型枚举
typedef enum { PLUGIN_REG, // 插件注册 PRE_INST_C, // 指令执行前 POST_INST_C, // 指令执行后 PRE_BB_C, // 基本块执行前 POST_BB_C, // 基本块执行后 PRE_FRAGMENT_C, // 代码片段执行前 POST_FRAGMENT_C, // 代码片段执行后 PRE_SYSCALL_C, // 系统调用前 POST_SYSCALL_C, // 系统调用后 PRE_THREAD_C, // 线程创建前 POST_THREAD_C, // 线程结束后 PRE_FN_C, // 函数调用前 POST_FN_C, // 函数调用后 EXIT_C, // 程序退出 VM_OP_C, // 虚拟内存操作 CALLBACK_MAX_IDX } mambo_cb_idx;分支类型枚举
typedef enum { BRANCH_NONE = (1 << 0), BRANCH_DIRECT = (1 << 1), // 直接分支 BRANCH_INDIRECT = (1 << 2), // 间接分支 BRANCH_RETURN = (1 << 3), // 返回指令 BRANCH_COND = (1 << 4), // 条件分支 BRANCH_COND_PSR = (1 << 5), // PSR条件分支 BRANCH_COND_CBZ = (1 << 6), // CBZ/CBNZ分支 BRANCH_COND_TBZ = (1 << 7), // TBZ/TBNZ分支(仅A64) BRANCH_COND_IT = (1 << 8), // IT块分支(仅T32) BRANCH_CALL = (1 << 9), // 调用指令 BRANCH_INTERWORKING = (1 << 10), // 指令集切换分支 BRANCH_TABLE = (1 << 11), // 跳转表分支(仅T32) } mambo_branch_type;🚀 核心API函数详解
插件注册与管理
mambo_register_plugin()- 注册新插件
mambo_context *mambo_register_plugin(void);这是插件开发的入口点,每个插件必须在初始化时调用此函数。
回调注册函数- 注册事件处理器
int mambo_register_pre_inst_cb(mambo_context *ctx, mambo_callback cb); int mambo_register_post_inst_cb(mambo_context *ctx, mambo_callback cb); int mambo_register_pre_basic_block_cb(mambo_context *ctx, mambo_callback cb); int mambo_register_post_basic_block_cb(mambo_context *ctx, mambo_callback cb); int mambo_register_pre_syscall_cb(mambo_context *ctx, mambo_callback cb); int mambo_register_post_syscall_cb(mambo_context *ctx, mambo_callback cb);内存管理API
mambo_alloc() / mambo_free()- 插件内存分配
void *mambo_alloc(mambo_context *ctx, size_t size); void mambo_free(mambo_context *ctx, void *ptr);使用BIDK的内存管理机制分配和释放内存,确保线程安全。
插件数据存储- 线程本地存储
int mambo_set_plugin_data(mambo_context *ctx, void *data); void *mambo_get_plugin_data(mambo_context *ctx); int mambo_set_thread_plugin_data(mambo_context *ctx, void *data); void *mambo_get_thread_plugin_data(mambo_context *ctx);这些函数允许插件存储和检索线程特定的数据。
指令操作API
指令信息获取- 分析当前指令
int mambo_get_inst(mambo_context *ctx); // 获取指令编码 inst_set mambo_get_inst_type(mambo_context *ctx); // 获取指令集类型 int mambo_get_fragment_id(mambo_context *ctx); // 获取片段ID cc_type mambo_get_fragment_type(mambo_context *ctx); // 获取片段类型 int mambo_get_inst_len(mambo_context *ctx); // 获取指令长度内存访问检测- 识别加载/存储指令
bool mambo_is_load(mambo_context *ctx); // 是否为加载指令 bool mambo_is_store(mambo_context *ctx); // 是否为存储指令 bool mambo_is_load_or_store(mambo_context *ctx); // 是否为内存访问指令 int mambo_get_ld_st_size(mambo_context *ctx); // 获取内存访问大小分支类型检测- 分析控制流
mambo_branch_type mambo_get_branch_type(mambo_context *ctx);这个函数对于构建控制流分析插件至关重要。
代码生成API
寄存器管理- 临时寄存器分配
int mambo_get_scratch_regs(mambo_context *ctx, int count, ...); int mambo_get_scratch_reg(mambo_context *ctx, int *regp); int mambo_free_scratch_regs(mambo_context *ctx, uint32_t regs); int mambo_free_scratch_reg(mambo_context *ctx, int reg);代码发射函数- 生成新指令
void emit_push(mambo_context *ctx, uint32_t regs); // 寄存器压栈 void emit_pop(mambo_context *ctx, uint32_t regs); // 寄存器出栈 void emit_set_reg(mambo_context *ctx, enum reg reg, uintptr_t value); // 设置寄存器值 void emit_fcall(mambo_context *ctx, void *function_ptr); // 函数调用 int emit_branch(mambo_context *ctx, void *target); // 无条件分支 int emit_branch_cond(mambo_context *ctx, void *target, mambo_cond cond); // 条件分支系统调用处理
系统调用操作- 拦截和修改系统调用
int mambo_syscall_get_no(mambo_context *ctx, uintptr_t *no); // 获取系统调用号 void mambo_syscall_get_args(mambo_context *ctx, uintptr_t **args); // 获取参数 int mambo_syscall_bypass(mambo_context *ctx); // 绕过系统调用 int mambo_syscall_get_return(mambo_context *ctx, uintptr_t *ret); // 获取返回值 int mambo_syscall_set_return(mambo_context *ctx, uintptr_t ret); // 设置返回值🛠️ 实用工具函数
地址计算与映射
mambo_calc_ld_st_addr()- 计算内存访问地址
int mambo_calc_ld_st_addr(mambo_context *ctx, enum reg reg);将加载/存储指令的地址计算到指定寄存器中。
mambo_add_identity_mapping()- 添加地址映射
int mambo_add_identity_mapping(mambo_context *ctx);确保修改后的代码能够正确访问原始代码位置。
符号与堆栈跟踪
符号信息获取- 地址到符号的映射
int get_symbol_info_by_addr(uintptr_t addr, char **sym_name, void **start_addr, char **filename);根据地址获取符号名称、起始地址和文件名信息。
堆栈回溯支持- 生成调用栈
typedef int (*stack_frame_handler)(void *data, void *addr, char *sym_name, void *symbol_start_addr, char *filename); int get_backtrace(stack_frame_t *fp, stack_frame_handler handler, void *ptr);📝 插件开发最佳实践
1. 插件初始化模式
每个BIDK插件都应该遵循标准的初始化模式:
__attribute__((constructor)) void plugin_init() { mambo_context *ctx = mambo_register_plugin(); assert(ctx != NULL); // 注册回调函数 mambo_register_pre_inst_cb(ctx, &pre_inst_handler); mambo_register_post_inst_cb(ctx, &post_inst_handler); // 设置插件数据 mambo_set_plugin_data(ctx, &global_data); }2. 线程安全数据管理
对于多线程插件,使用线程本地存储:
int pre_thread_handler(mambo_context *ctx) { struct thread_data *data = mambo_alloc(ctx, sizeof(struct thread_data)); mambo_set_thread_plugin_data(ctx, data); // 初始化线程数据 } int post_thread_handler(mambo_context *ctx) { struct thread_data *data = mambo_get_thread_plugin_data(ctx); // 清理线程数据 mambo_free(ctx, data); }3. 高效代码注入
使用BIDK的代码生成API实现高效插桩:
int pre_inst_handler(mambo_context *ctx) { if (mambo_is_load_or_store(ctx)) { // 分配临时寄存器 int reg; mambo_get_scratch_reg(ctx, ®); // 计算内存地址 mambo_calc_ld_st_addr(ctx, reg); // 执行自定义逻辑 emit_fcall(ctx, &my_memory_tracer); // 释放寄存器 mambo_free_scratch_reg(ctx, reg); } }🔍 实际应用示例
分支计数插件
查看 plugins/branch_count.c 了解如何统计不同类型的分支指令:
struct br_count { uint64_t direct_branch_count; uint64_t indirect_branch_count; uint64_t return_branch_count; }; int branch_count_pre_inst_handler(mambo_context *ctx) { mambo_branch_type type = mambo_get_branch_type(ctx); if (type & BRANCH_RETURN) { // 处理返回指令 } else if (type & BRANCH_DIRECT) { // 处理直接分支 } else if (type & BRANCH_INDIRECT) { // 处理间接分支 } }内存跟踪插件
查看 plugins/mtrace.c 学习如何跟踪内存访问:
int mtrace_pre_inst_handler(mambo_context *ctx) { bool is_load = mambo_is_load(ctx); bool is_store = mambo_is_store(ctx); if (is_load || is_store) { int size = mambo_get_ld_st_size(ctx); // 记录内存访问信息 } }🎯 性能优化技巧
减少代码缓存压力
- 选择性插桩:只在必要时注入代码
- 批量处理:合并多个操作减少指令数
- 寄存器重用:高效管理临时寄存器
- 条件执行:使用条件分支避免不必要的代码执行
内存访问优化
- 局部性原理:将相关数据放在相邻位置
- 缓存友好:减少缓存未命中
- 预取策略:提前加载可能用到的数据
📚 进一步学习资源
- 官方API文档:api/plugin_support.h - 核心API定义
- 辅助函数库:api/helpers.h - 实用工具函数
- 插件示例:plugins/ - 丰富的插件实现
- 系统调用支持:syscalls.h - 系统调用相关定义
💡 常见问题解答
Q: 如何调试BIDK插件?
A: 使用标准调试工具(如gdb)并设置断点在插件的回调函数中。确保使用正确的编译标志。
Q: 插件会影响程序性能吗?
A: BIDK设计为低开销,但插件的复杂性会影响性能。建议进行性能分析和优化。
Q: 支持哪些架构?
A: BIDK支持ARM(AArch32和AArch64)以及RISC-V(RV64GC)架构。
Q: 如何处理多线程程序?
A: BIDK提供线程感知的API,使用mambo_set_thread_plugin_data()和mambo_get_thread_plugin_data()管理线程本地数据。
通过掌握BIDK的API接口和数据结构,您可以构建强大的二进制分析工具,实现性能分析、安全检测、动态优化等多种功能。🚀
【免费下载链接】BIDKA low-overhead dynamic binary instrumentation and modification tool for ARM (both AArch32 and AArch64 support) and RISC-V (RV64GC).项目地址: https://gitcode.com/openeuler/BIDK
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考