鸿蒙PC集成Capstone:这5个NAPI坑我替你踩过了(附完整代码)
欢迎加入【开源鸿蒙PC社区】,一起共建鸿蒙化C/C++三方库生态。
欢迎在【PC社区】平台贡献你的项目。
仓库: aquynh/capstone v4.0.0 — 轻量级多架构反汇编框架
集成平台: 鸿蒙PC | 测试SDK: HarmonyOS 6.1.0(23)
源代码仓库地址:https://atomgit.com/allincoding/OHOSCapstoneSample
前置说明
| 项目 | 说明 |
|---|---|
| 集成库 | Capstone v4.0.0 |
| 库类型 | 动态库(.so),零外部依赖 |
| 目标平台 | 鸿蒙PC |
| SDK 版本 | HarmonyOS 6.1.0(23),兼容 6.0.0(20) |
| 开发工具 | DevEco Studio |
| 原生编译器 | BiSheng(build-profile.json5 中 nativeCompiler: BiSheng) |
| 交叉编译工具链 | lycium_plusplus |
| 三方库 .so | libcapstone.so.4.0.0 |
| 支持架构 | x86 / ARM / AArch64 / MIPS / PPC / SPARC / SystemZ |
| NAPI 接口数 | 3 个(disasm / csVersion / csArchList) |
| UI 框架 | ArkTS Stage 模式,三栏桌面布局 |
Capstone 与一般三方库的关键区别:它是多架构反汇编引擎。不同架构的 cs_mode 参数、字节序、示例机器码完全不同,一个 mode 标志写错就 cs_open failed 或 disasm failed,这是集成中最容易出错的环节。
传统方式的效率瓶颈
| 阶段 | 主要痛点 | 传统耗时 |
|---|---|---|
| 工程搭建 | 手动建目录、改 module.json5 | 10分钟 |
| 库文件部署 | 14 个头文件 + .so 双重部署 | 5分钟 |
| CMake 配置 | IMPORTED 目标路径、零依赖较简单 | 10分钟 |
| NAPI 桥接 | hex 解析、7 种架构 mode 映射、JSON 输出 | 40分钟 |
| 类型声明 | Index.d.ts 签名匹配 | 10分钟 |
| UI 页面 | 架构选择、机器码输入、结果表格渲染 | 25分钟 |
| 多架构验证 | 7 种架构逐个测试 mode 参数 | 30-60分钟 |
| 编译排错 | ArkTS 语法限制、mode 标志错误 | 20-60分钟 |
总计 2.5-4.5 小时,其中多架构验证占大头——7 种架构 × 每种可能 2-3 个 mode 组合,手动试错非常耗时。
AtomCode + Skills 全流程
环节 1:工程创建与模板复用
基于 OHOSLibharuSample 模板(已含 .so 双重部署、CMake IMPORTED 等修复):
cp -r /home/hoapp/OHOSLibharuSample /home/hoapp/OHOSCapstoneSample
rm -rf .git entry/build entry/.cxx oh_modules
rm -rf entry/src/main/cpp/thirdparty/libharu entry/libs
选择 Libharu 模板的原因:它已有 .so 双重部署的完整实践(thirdparty + entry/libs),Capstone 同为动态库,直接复用。
环节 2:库文件部署
Capstone 产物来自 lycium_plusplus,零依赖,只需部署一个 .so:
① 头文件 → cpp/thirdparty/capstone/include/capstone/
14 个头文件,核心是 capstone.h,其余是各架构的 X86.h、ARM.h、Mips.h 等。
② .so 双重部署
# CMake 链接期
cp libcapstone.so.4.0.0 entry/src/main/cpp/thirdparty/capstone/libs/arm64-v8a/
# HAP 打包期(必须!)
cp libcapstone.so.4.0.0 entry/libs/arm64-v8a/
cd entry/libs/arm64-v8a && ln -sf libcapstone.so.4.0.0 libcapstone.so
零依赖库的优势:只需部署一个 .so,无需像 libharu 那样处理 zlib+png 传递依赖。
环节 3:CMake 配置
零依赖库的 CMake 极简:
cmake_minimum_required(VERSION 3.5.0)
project(OHOSCapstoneSample)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
set(CS_ROOT ${NATIVERENDER_ROOT_PATH}/thirdparty/capstone)
if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif()
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include
${CS_ROOT}/include)
set(CS_LIB_DIR ${CS_ROOT}/libs/arm64-v8a)
add_library(capstone SHARED IMPORTED)
set_target_properties(capstone PROPERTIES IMPORTED_LOCATION
${CS_LIB_DIR}/libcapstone.so.4.0.0)
add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so)
target_link_libraries(entry PUBLIC capstone)
对比 libharu 的 CMake:少了 zlib/png 两个 IMPORTED 目标,链接顺序也无需考虑传递依赖。
环节 4:NAPI 桥接
3 个接口,核心是 disasm:
disasm — hex 字符串解析 + 7 架构 mode 映射 + JSON 输出
static napi_value Disasm(napi_env env, napi_callback_info info) {
// 读取参数:hexBytes, arch?, mode?
// ...
// hex 字符串 → 字节数组
std::vector<uint8_t> bytes;
std::istringstream iss(hexStr);
std::string token;
while (iss >> token) {
bytes.push_back((uint8_t)strtol(token.c_str(), nullptr, 16));
}
// 架构映射(这是最容易出错的地方!)
cs_arch arch = CS_ARCH_X86;
cs_mode mode = CS_MODE_64;
if (archStr == "x86")
{ arch = CS_ARCH_X86; mode = (modeStr == "32") ? CS_MODE_32 : CS_MODE_64; }
else if (archStr == "arm")
{ arch = CS_ARCH_ARM; mode = (modeStr == "thumb") ? CS_MODE_THUMB : CS_MODE_ARM; }
else if (archStr == "arm64")
{ arch = CS_ARCH_ARM64; mode = CS_MODE_ARM; }
else if (archStr == "mips")
{ arch = CS_ARCH_MIPS; mode = (cs_mode)(CS_MODE_MIPS32 | CS_MODE_BIG_ENDIAN); }
else if (archStr == "ppc")
{ arch = CS_ARCH_PPC; mode = (cs_mode)(CS_MODE_64 | CS_MODE_BIG_ENDIAN); }
else if (archStr == "sparc")
{ arch = CS_ARCH_SPARC; mode = (cs_mode)(CS_MODE_V9 | CS_MODE_BIG_ENDIAN); }
else if (archStr == "systemz")
{ arch = CS_ARCH_SYSZ; mode = CS_MODE_BIG_ENDIAN; }
// 反汇编
csh handle;
cs_open(arch, mode, &handle);
cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON);
cs_insn *insns = nullptr;
size_t count = cs_disasm(handle, bytes.data(), bytes.size(), 0x1000, 0, &insns);
// 输出 JSON 数组
std::ostringstream json;
json << "[";
for (size_t i = 0; i < count; i++) {
json << "{\"addr\":\"0x" << std::hex << insns[i].address << "\","
<< "\"mnem\":\"" << insns[i].mnemonic << "\","
<< "\"op\":\"" << insns[i].op_str << "\"}";
if (i + 1 < count) json << ",";
}
json << "]";
cs_free(insns, count);
cs_close(&handle);
return Str(env, json.str());
}
mode 映射的 3 个关键细节:
- MIPS/PPC/SPARC 必须加
CS_MODE_BIG_ENDIAN——默认是小端序,这些架构的机器码是大端 - SPARC 64 位用
CS_MODE_V9,不是CS_MODE_64——后者只对 x86/PPC 有效 - SystemZ 无
CS_MODE_64定义,只需CS_MODE_BIG_ENDIAN
csVersion / csArchList — 简单查询
static napi_value CsVersion(napi_env env, napi_callback_info info) {
int major, minor;
cs_version(&major, &minor);
return Str(env, std::to_string(major) + "." + std::to_string(minor));
}
环节 5:类型声明
export const disasm: (hexBytes: string, arch?: string, mode?: string) => string;
export const csVersion: () => string;
export const csArchList: () => string;
disasm 返回 JSON 字符串,ArkTS 侧 JSON.parse 后渲染表格。
环节 6:UI 页面
三栏布局(架构选择 + 反汇编/示例/API + API 参考),核心交互:
架构切换 → 自动填入示例机器码 → 反汇编 → 结果表格:
private static readonly EXAMPLES: Record<string, string> = {
'x86:64': 'b8 01 00 00 00 c3',
'arm:arm': '00 00 a0 e3 1e 00 00 eb',
'arm64:arm': '09 00 80 d2 e8 03 00 aa',
'mips:64': '00 02 24 02 00 00 00 00',
'ppc:64': '38 60 00 01 4e 80 00 20',
'sparc:64': '82 10 20 01 81 c3 e0 08',
'systemz:64': 'b9 02 00 24 a7 f4 00 02',
}
private updateArch(arch: string, mode: string): void {
this.archName = arch; this.modeName = mode;
const example = Index.EXAMPLES[`${arch}:${mode}`];
if (example !== undefined && example !== null) {
this.hexInput = example;
}
this.result = '';
}
结果渲染:JSON parse 后用 ForEach 渲染指令表格(地址 / 助记符 / 操作数)。
踩坑专区
坑 1:切换架构后示例机器码不匹配导致 disasm failed
现象:
默认输入 b8 01 00 00 00 c3(x86 的 mov eax,1; ret),切换到 ARM64 后点击反汇编,报 disasm failed。
根因:
x86 机器码字节在 ARM64 模式下不是有效指令,cs_disasm 返回 0。用户不会记住每个架构的示例字节,手动输入极易出错。
修复:
为每个架构预置示例机器码,切换架构时自动填入:
// 错误 —— 固定输入,切换架构后无效
@State hexInput: string = 'b8 01 00 00 00 c3';
// 正确 —— 切换架构时自动填入对应示例
private static readonly EXAMPLES: Record<string, string> = {
'x86:64': 'b8 01 00 00 00 c3',
'arm:arm': '00 00 a0 e3 1e 00 00 eb',
'arm64:arm': '09 00 80 d2 e8 03 00 aa',
// ...
};
设计原则:多架构工具的 UI 必须做到「切换架构即切换示例」,否则用户每次都要查指令编码手册。
坑 2:ArkTS 禁用 in 运算符
现象:
编译报错 "in" operator is not supported (arkts-no-in)。
根因:
ArkTS 严格模式禁用 in 运算符,这是与标准 TypeScript 的关键差异。
修复:
// 错误 —— ArkTS 禁用 in
if (key in Index.EXAMPLES) { ... }
// 正确 —— 用 undefined 判断
const example = Index.EXAMPLES[key];
if (example !== undefined && example !== null) { ... }
ArkTS 与 TypeScript 的差异点还有:禁用
as any、禁用delete、禁用for...in。遇到编译错误先查 ArkTS 限制清单。
坑 3:MIPS/PPC/SPARC 缺少大端序标志导致 disasm failed
现象:
x86/ARM/ARM64 反汇编正常,MIPS/PPC/SPARC 报 disasm failed,cs_disasm 返回 0。
根因:
Capstone 的 CS_MODE_LITTLE_ENDIAN 值为 0(默认),MIPS/PPC/SPARC 的机器码是大端序,不显式加 CS_MODE_BIG_ENDIAN 标志,字节序解析完全错乱。
修复:
// 错误 —— MIPS 默认小端序,反汇编失败
mode = CS_MODE_MIPS64;
// 正确 —— 加大端序标志
mode = (cs_mode)(CS_MODE_MIPS64 | CS_MODE_BIG_ENDIAN);
规则:x86/ARM/ARM64 小端序(默认值 0,无需额外标志);MIPS/PPC/SPARC/SystemZ 大端序,必须显式加
CS_MODE_BIG_ENDIAN。
坑 4:SPARC 用 CS_MODE_64 导致 cs_open failed
现象:
SPARC 报 cs_open failed,cs_open 返回非 CS_ERR_OK。
根因:CS_MODE_64 的值是 1 << 3 = 8,这个标志只对 x86 和 PPC 有意义。SPARC 的 64 位模式用 CS_MODE_V9(值 1 << 4 = 16),传 CS_MODE_64 给 SPARC 是无效 mode,cs_open 直接失败。
修复:
// 错误 —— SPARC 不支持 CS_MODE_64
mode = (cs_mode)(CS_MODE_64 | CS_MODE_BIG_ENDIAN);
// 正确 —— SPARC 64 位用 CS_MODE_V9
mode = (cs_mode)(CS_MODE_V9 | CS_MODE_BIG_ENDIAN);
Capstone mode 标志不是通用的——
CS_MODE_64只对 x86/PPC 有效,SPARC 用CS_MODE_V9,ARM 用CS_MODE_ARM/CS_MODE_THUMB,MIPS 用CS_MODE_MIPS32/CS_MODE_MIPS64。混用直接cs_open failed。
坑 5:重复 aboutToAppear 导致编译错误
现象:
编译报错 Duplicate function implementation。
根因:
重构代码时 aboutToAppear 被写了两次(一次在状态定义后,一次在 updateArch 前),ArkTS 不允许函数重载。
修复:
删除重复定义,保留一个。
这类错误在 AtomCode 驱动的大段代码替换中偶发——替换时旧代码未完全清除。解决方法:替换后立即
grep -n aboutToAppear验证唯一性。
通用集成模板(拿来即用)
零依赖动态库 CMakeLists.txt 模板
适用于 Capstone 这类单 .so 无传递依赖的库:
cmake_minimum_required(VERSION 3.5.0)
project({Project} C CXX)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
set(LIB_ROOT ${NATIVERENDER_ROOT_PATH}/thirdparty/{lib})
if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif()
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include
${LIB_ROOT}/include)
set(LIB_DIR ${LIB_ROOT}/libs/arm64-v8a)
add_library({lib} SHARED IMPORTED)
set_target_properties({lib} PROPERTIES IMPORTED_LOCATION
${LIB_DIR}/lib{lib}.so.{version})
add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so)
target_link_libraries(entry PUBLIC {lib})
Capstone 多架构 mode 映射模板
cs_arch arch = CS_ARCH_X86;
cs_mode mode = CS_MODE_64;
if (archStr == "x86")
{ arch = CS_ARCH_X86; mode = (modeStr == "32") ? CS_MODE_32 : CS_MODE_64; }
else if (archStr == "arm")
{ arch = CS_ARCH_ARM; mode = (modeStr == "thumb") ? CS_MODE_THUMB : CS_MODE_ARM; }
else if (archStr == "arm64")
{ arch = CS_ARCH_ARM64; mode = CS_MODE_ARM; }
else if (archStr == "mips")
{ arch = CS_ARCH_MIPS;
mode = (modeStr == "32")
? (cs_mode)(CS_MODE_MIPS32 | CS_MODE_BIG_ENDIAN)
: (cs_mode)(CS_MODE_MIPS64 | CS_MODE_BIG_ENDIAN); }
else if (archStr == "ppc")
{ arch = CS_ARCH_PPC; mode = (cs_mode)(CS_MODE_64 | CS_MODE_BIG_ENDIAN); }
else if (archStr == "sparc")
{ arch = CS_ARCH_SPARC; mode = (cs_mode)(CS_MODE_V9 | CS_MODE_BIG_ENDIAN); }
else if (archStr == "systemz")
{ arch = CS_ARCH_SYSZ; mode = CS_MODE_BIG_ENDIAN; }
ArkTS 多架构示例机器码模板
private static readonly EXAMPLES: Record<string, string> = {
'x86:64': 'b8 01 00 00 00 c3', // mov eax, 1; ret
'x86:32': 'b8 01 00 00 00 c3',
'arm:arm': '00 00 a0 e3 1e 00 00 eb', // mov r0, #0; bl
'arm:thumb': '00 bf 00 bf', // nop; nop
'arm64:arm': '09 00 80 d2 e8 03 00 aa', // mov x9, #0; mov x8, x0
'mips:64': '00 02 24 02 00 00 00 00', // addiu v0, zero, 2; nop
'ppc:64': '38 60 00 01 4e 80 00 20', // li r3, 1; blr
'sparc:64': '82 10 20 01 81 c3 e0 08', // mov 1, %g1; jmpl %o7+8, %g0
'systemz:64': 'b9 02 00 24 a7 f4 00 02', // lpsw; brcl
}
NAPI JSON 返回模板
// 反汇编结果以 JSON 数组返回,ArkTS 侧 JSON.parse 解析
std::ostringstream json;
json << "[";
for (size_t i = 0; i < count; i++) {
if (i > 0) json << ",";
json << "{\"addr\":\"0x" << std::hex << insns[i].address << "\","
<< "\"mnem\":\"" << insns[i].mnemonic << "\","
<< "\"op\":\"" << insns[i].op_str << "\"}";
}
json << "]";
return Str(env, json.str());
总结
Capstone 集成鸿蒙的难点不在 CMake 链接(零依赖,一个 .so 搞定),而在 7 种架构的 mode 参数精确映射 和 大端序标志的遗漏。CS_MODE_64 不是万能的 64 位标志,SPARC 用 CS_MODE_V9,MIPS 用 CS_MODE_MIPS32|MIPS64,SystemZ 不需要——一个 mode 写错就 cs_open failed,查 capstone.h 的宏定义是唯一解法。
你在 NAPI 集成中遇到过什么奇怪的错误?是 cs_open 失败、反汇编乱码还是 ArkTS 语法限制?欢迎在评论区分享你的经验。
如果本文对你有帮助,请 点赞、收藏、转发 支持一下~
更多推荐



所有评论(0)