欢迎加入【开源鸿蒙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 faileddisasm failed,这是集成中最容易出错的环节。


传统方式的效率瓶颈

cs_open失败

disasm失败

ArkTS报错

工程搭建

库文件部署

CMake 配置

NAPI 桥接

类型声明

UI 页面

多架构验证

阶段 主要痛点 传统耗时
工程搭建 手动建目录、改 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.hARM.hMips.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 个关键细节

  1. MIPS/PPC/SPARC 必须加 CS_MODE_BIG_ENDIAN——默认是小端序,这些架构的机器码是大端
  2. SPARC 64 位用 CS_MODE_V9,不是 CS_MODE_64——后者只对 x86/PPC 有效
  3. 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 failedcs_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 failedcs_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 语法限制?欢迎在评论区分享你的经验。

如果本文对你有帮助,请 点赞、收藏、转发 支持一下~

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐