- 逆向工程
- 调试器
- 开发工具
- 应用安全
【免费下载链接】x64dbg
An open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.
x64dbg 内置的字符串格式化引擎支持{type;arg1;arg2;argN@expression}语法,而插件可以通过_plugin_registerformatfunction注册自定义格式化函数(format function),让用户在日志、表达式、条件断点等场景直接复用自定义输出逻辑。本文以 registerformatfunction.rst 为骨架,结合 stringformat.cpp、formatfunctions.cpp 等源码,讲解该 API 的完整签名、参数约束、回调契约、返回值语义及底层调用链,并给出可直接套用的 C/C++ 插件示例。
一、API 概览:插件如何接入格式化引擎
格式化函数是 x64dbg 字符串格式化系统的扩展点。内置格式化引擎的完整语法与内置类型(d、u、x、a、i、f、F、mem、winerror、ntstatus、utf8、disasm等)可参考 Formatting.md。插件注册的自定义格式化函数与这些内置类型共用同一套解析与调用机制,语法为:
{type;arg1;arg2;argN@expression}其中type是插件注册的格式化函数名,argN是任意字符串参数(原样传给回调),expression是任意合法表达式,其计算结果作为value传入回调。
函数签名
bool _plugin_registerformatfunction( int pluginHandle, //插件句柄 const char* type, //格式化函数名称 CBPLUGINFORMATFUNCTION cbFunction, //回调函数 void* userdata //用户数据 );- 返回值:注册成功返回
true,失败返回false。 - 配套注销 API:
_plugin_unregisterformatfunction(int pluginHandle, const char* type)用于注销,声明与实现同样位于 src/dbg/_plugins.cpp。
二、参数详解与命名约束
pluginHandle:调用插件的句柄
插件在初始化阶段通过plugin_register获得的句柄,用于身份校验。在底层实现pluginformatfuncregister中,会先调用findPluginName(pluginHandle, plugName)反查插件名,句柄无效时直接返回false,并跳过后续注册逻辑,见 src/dbg/plugin_loader.cpp。
type:格式化函数名称(关键约束)
type是格式化语法中;之前的那部分名称,命名规则为:
- 首字符必须是
_或字母(a-zA-Z或下划线); - 后续字符只能为
_、.、字母或数字; - 名称不能为空。
该规则由FormatFunctions::isValidName强制执行,见 src/dbg/formatfunctions.cpp:
bool FormatFunctions::isValidName(const String & name) { if(!name.length()) return false; if(!(name[0] == '_' || isalpha(name[0]))) return false; for(const auto & ch : name) if(!(isalnum(ch) || ch == '_' || ch == '.')) return false; return true; }从源码看,type相当于格式化引擎内部注册表std::unordered_map<String, FormatFunctions::Function> mFunctions的键(见 formatfunctions.h)。Register时若名称非法或已存在同名函数,都会返回false,即重复注册同一名称不会被覆盖。
cbFunction:回调函数
回调类型定义于 src/dbg/_plugins.h:
typedef FORMATRESULT(*CBPLUGINFORMATFUNCTION)( char* dest, size_t destCount, int argc, char* argv[], duint value, void* userdata);参数含义:
dest/destCount:输出缓冲区及其容量(字节)。回调需把格式化结果以\0结尾的字符串写入dest。argc/argv:格式化语法中;分隔的参数个数与字符串数组。注意argv[0]即type本身,真正的参数从argv[1]开始(见下文Call实现分析)。value:@expression计算出的表达式值(duint,即指针宽度的无符号整数)。userdata:注册时传入的userdata指针,原样回传给回调,可用于携带附加信息。
userdata:透传用户数据
注册时的void* userdata会被存入内部Function结构(formatfunctions.h),回调调用时再原样传回,不经过任何转换,适合传入插件自定义的上下文结构体指针。
三、返回值语义:FORMATRESULT 四态契约
回调必须返回FORMATRESULT枚举,定义于 src/dbg/_plugins.h:
| 枚举值 | 含义 |
|---|---|
FORMAT_ERROR | 通用失败(不产生任何输出消息) |
FORMAT_SUCCESS | 格式化成功,dest中为有效结果 |
FORMAT_ERROR_MESSAGE | 格式化失败,但已在缓冲区中写入错误信息(缓冲区至少保证有 511 字节可用) |
FORMAT_BUFFER_TOO_SMALL | 缓冲区过小,x64dbg 会自动扩大缓冲区并重试 |
FORMAT_BUFFER_TOO_SMALL是最重要的状态:调用方看到该返回值后会倍增缓冲区并重新调用回调,直到成功或返回其他状态。这一重试机制在 formatfunctions.cpp 的FormatFunctions::Call中实现:
bool FormatFunctions::Call(std::vector<char> & dest, const String & type, std::vector<String> & argv, duint value) { SHARED_ACQUIRE(LockFormatFunctions); auto found = mFunctions.find(type); if(found == mFunctions.end()) return false; std::vector<char*> argvn(argv.size()); for(size_t i = 0; i < argv.size(); i++) argvn[i] = (char*)argv[i].c_str(); const auto & f = found->second; if(dest.size() == 0) dest.resize(512, '\0'); fuckthis: auto result = f.cbFunction(dest.data(), dest.size(), int(argv.size()), argvn.data(), value, f.userdata); if(result == FORMAT_BUFFER_TOO_SMALL) { dest.resize(dest.size() * 2, '\0'); goto fuckthis; } return result != FORMAT_ERROR; }细节解读:
- 初始缓冲区为 512 字节,
FORMAT_BUFFER_TOO_SMALL时按 2 倍增长循环重试; - 回调返回
FORMAT_ERROR_MESSAGE时,Call返回true,错误消息会直接作为输出展示; - 返回
FORMAT_ERROR时,Call返回false,上层(stringformat.cpp 的printComplexValue)最终输出[Formatting Error]; - 回调全程在
LockFormatFunctions锁保护下执行,插件回调内不应再调用可能重入格式化引擎的 API,以免死锁。
四、注册与注销的底层链路
_plugin_registerformatfunction的完整调用链为:
_plugin_registerformatfunction [src/dbg/_plugins.cpp#L193-L196] └─ pluginformatfuncregister [src/dbg/plugin_loader.cpp#L1403-L1421] ├─ findPluginName(pluginHandle) //句柄校验,失败即返回 false ├─ FormatFunctions::Register(type, cb, userdata) [src/dbg/formatfunctions.cpp#L275-L288] │ ├─ isValidName(type) //命名规则校验 │ └─ mFunctions[type] = f; //写入全局注册表(查重) ├─ gPluginFormatfunctionList.push_back(...) //记录到插件级列表 └─ dprintf("[PLUGIN, %s] Format function \"%s\" registered!") //日志输出注册成功后,日志区会输出[PLUGIN, <插件名>] Format function "<type>" registered!;失败(如名称非法、重复注册、句柄无效)则输出failed to register...。注销链路pluginformatfuncunregister(plugin_loader.cpp)会从插件级列表与全局注册表中同步移除,并级联注销该函数注册过的别名(RegisterAlias机制,见 formatfunctions.cpp)。
五、格式化引擎如何调用插件函数
当用户输入log {myfmt;arg1;arg2@eax}时,格式化引擎的处理流程位于 src/dbg/stringformat.cpp:
- 解析器扫描格式串,识别
{...}块,遇到{{、}}时按转义输出{、}; - 进入
handleFormatString,进一步拆分为类型与表达式; - 发现
;分隔的复合参数后进入printComplexValue:StringUtils::Split(complexArgs, ';')按;拆分出参数列表split;valfromstring(value, &valuint)计算@后的表达式得到value;- 调用
FormatFunctions::Call(dest, split[0], split, valuint)——split[0]即注册的type,split整体作为argv传入,因此回调中argv[0]是类型名,argv[1]起才是用户参数; - 成功则返回结果字符串,失败返回
[Formatting Error]。
这也解释了命名约束的根源:type作为;之前的分隔片段,必须能被解析器安全识别,故限定为_、.、字母、数字组合。
六、完整插件示例:注册一个自定义格式化函数
以下示例注册一个名为_hexdump的自定义格式化函数,将表达式的值按指定宽度输出为十六进制字节序列(演示argv、value、destCount、FORMAT_BUFFER_TOO_SMALL的完整用法):
#include <windows.h> #include <stdio.h> #include "pluginsdk/x64dbg.h" // 提供 _plugin_registerformatfunction 等导出 // 回调:将 value 按小端序输出为 width 个字节的十六进制串 static FORMATRESULT cbHexDump(char* dest, size_t destCount, int argc, char* argv[], duint value, void* userdata) { int width = (argc > 1) ? atoi(argv[1]) : (int)sizeof(duint); if(width <= 0 || width > (int)sizeof(duint)) { _snprintf_s(dest, destCount, _TRUNCATE, "invalid width: %d", width); return FORMAT_ERROR_MESSAGE; // 写入错误信息并报告失败 } if((size_t)(width * 2 + 1) > destCount) return FORMAT_BUFFER_TOO_SMALL; // 请求更大缓冲区,引擎会自动扩容重试 const unsigned char* bytes = (const unsigned char*)&value; char* p = dest; for(int i = 0; i < width; i++) p += sprintf_s(p, destCount - (p - dest), "%02X", bytes[i]); return FORMAT_SUCCESS; } // 插件初始化时注册 static bool pluginInit(PLUG_INITSTRUCT* initStatus) { _plugin_registerlogprintf(PLUG_LOG, "Example format function loaded\n"); if(!_plugin_registerformatfunction(pluginHandle, "_hexdump", cbHexDump, nullptr)) { _plugin_logprintf("Failed to register format function\n"); return false; } return true; } // 插件卸载时注销 static bool pluginStop() { _plugin_unregisterformatfunction(pluginHandle, "_hexdump"); return true; }注册后在命令栏执行:
log {_hexdump;4@eax}若eax = 0x11223344,将输出44332211(小端字节序)。参数;4对应回调的argv[1],@eax的计算结果作为value传入。
七、设计注意事项
- 命名空间冲突:内置格式化函数(
mem、utf8、winerror、disasm等)已占用注册表,插件不应使用同名type,否则Register返回false。建议统一使用_前缀降低冲突概率(也符合命名规则)。 - 缓冲区契约:不要假设
destCount大小;写入前务必检查剩余空间,空间不足返回FORMAT_BUFFER_TOO_SMALL而非截断或越界,这是避免内存问题的关键。 - 失败语义区分:需要展示错误原因时返回
FORMAT_ERROR_MESSAGE并把消息写入缓冲区;无需展示时返回FORMAT_ERROR(最终呈现为[Formatting Error])。 - 并发与重入:回调在格式化引擎持有锁的状态下执行(见 formatfunctions.cpp),回调体内应避免调用
DbgCmdExec、log等可能再次触发格式化或命令解析的操作。 - 生命周期管理:插件退出前务必调用
_plugin_unregisterformatfunction,否则卸载后残留回调指针可能导致崩溃。
八、小结
_plugin_registerformatfunction让插件以极低成本扩展 x64dbg 的字符串格式化能力:注册一个符合命名规则的类型名与回调,即可在任意支持格式化字符串的场景(log命令、表达式、条件断点消息等)以{type;arg@expr}语法使用。其底层实现(名称校验、查重、缓冲区倍增重试、插件级列表管理)在 src/dbg/formatfunctions.cpp 与 src/dbg/plugin_loader.cpp 中均可直接追溯,是理解 x64dbg 插件体系与格式化引擎交互的优秀范例。
- 逆向工程
- 调试器
- 开发工具
- 应用安全
【免费下载链接】x64dbg
An open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.
相关推荐
LongCat-Flash-Omni-FP8核心技术解密:Shortcut-connected MoE与零计算专家如何实现低延迟交互
LongCat Flash Omni FP8核心技术解密:Shortcut connected MoE与零计算专家如何实现低延迟交互 LongCat Flash
逆向工程调试器开发工具应用安全LXGW WenKai TC核心功能解析:传承字形、UVS支持与多场景应用
LXGW WenKai TC核心功能解析:传承字形、UVS支持与多场景应用 LXGW WenKai TC(霞鹜文楷TC)是一款专为繁体中文用户打造的高质量开源字
WasmEdge 源码构建与插件开发指南:从 CMake 配置到自定义 Host 函数
WasmEdge 源码构建与插件开发指南:从 CMake 配置到自定义 Host 函数 WasmEdge 是一个使用 C++17 编写的轻量级、高性能、可扩展
语言运行时云原生本地部署边缘计算
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考