本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

一、hstack工具

   hstack 是 HarmonyOS 开发中用于解析混淆后应用崩溃堆栈的专业工具,能够将 Release 版本应用中经过混淆的崩溃堆栈信息还原为源码对应的堆栈信息,便于开发过程中定位和调试问题。

核心功能

  • 堆栈还原:将混淆后的方法名、文件名、行列号还原为源码信息

  • 多平台支持:Windows、Mac、Linux

  • 多语言支持:ArkTS/JS 和 C++ 堆栈解析

应用

  • 发布后的应用在用户端发生崩溃

  • 混淆后的代码难以直接定位问题

  • 需要分析生产环境的错误日志

二、配置要求

1. 工具路径配置

hstack 工具位于 Command Line Tools 的 bin 目录下,需要将该目录添加到系统的 PATH 环境变量中。

2. 运行环境依赖

  • Node.js:需要安装并配置到环境变量中

  • C++ 解析支持(可选):如需解析 C++ 异常堆栈,需要配置:

    • 将 SDK 中的 native/llvm/bin 目录添加到环境变量

    • 环境变量名设置为 ADDR2LINE_PATH

3. 路径限制

路径参数不支持以下特殊字符:

`~!@#$^&*=|{};,\s[]<>?~!@#¥......&*()------|{}【】';:。,、?

三、命令行参数

参数 简写 说明 必需性
--input -i 指定工程 crash 文件归档目录 与 -c 二选一
--crash -c 指定一条 crash 堆栈字符串 与 -i 二选一
--output -o 指定解析结果输出目录/文件 可选
--sourcemapDir -s 指定 sourcemap 文件归档目录 与 --so 至少一个
--soDir --so 指定 shared object 文件归档目录 与 -s 至少一个
--nameObfuscation -n 指定 nameCache 文件归档目录 可选
--version -v 查看 hstack 版本信息 可选
--help -h 查看命令行帮助信息 可选

四、示例

1. 基础使用方式

方式一:解析 crash 文件目录
hstack -i ./crash_logs -o ./parsed_results -s ./sourcemaps --so ./native_libs -n ./name_caches
方式二:解析单条堆栈
hstack -c "at a (entry|entry|1.0.0|src/main/ets/pages/Index.js:25:3)" -o result.txt -s ./sourcemaps -n ./name_caches

2. 输出说明

  • 使用 -i 输入时:在输出目录生成解析后的文件,以原始文件名加 _ 前缀命名

  • 使用 -c 输入时:可指定输出文件,或不指定直接输出到控制台

3. 文件准备要求

必需文件
  • Crash 文件:应用崩溃时生成的堆栈信息

  • Sourcemap 文件 或 SO 文件:至少提供一种

可选文件
  • nameCache 文件:用于方法名还原(需与 sourcemap 配合使用)

五、Release 版本符号表配置

默认情况下,Release 构建的 SO 文件不包含符号表信息。如需生成包含符号表的 SO 文件,需要在模块级 build-profile.json5 中配置:

{
  "buildOption": {
    "externalNativeOptions": {
      "arguments": "-DCMAKE_BUILD_TYPE=RelWithDebInfo"
    }
  }
}

六、堆栈解析原理

1. 混淆后的堆栈格式

at 混淆方法名 (包名|被引用包名|版本号|源码相对路径:混淆行号:混淆列号)

示例混淆堆栈

at processData (app|utils|1.0.0|src/main/ets/common/DataProcessor.js:45:45)
at b (app|app|1.0.0|src/main/ets/pages/MainPage.ts:30:30)
at anonymous (app|app|1.0.0|src/main/ets/pages/MainPage.ts:18:18)

2. 三步解析过程

步骤1:路径信息解析

根据堆栈中的路径信息定位对应的 sourcemap 文件:

原始路径

app|utils|1.0.0|src/main/ets/common/DataProcessor.js

在 sourcemap 中找到对应信息

{
  "app|utils|1.0.0|src/main/ets/common/DataProcessor.js": {
    "version": 3,
    "file": "DataProcessor.js",
    "sources": [
      "oh_modules/.ohpm/Utils@abc123=/oh_modules/Utils/src/main/ets/common/DataProcessor.js"
    ],
    "mappings": "...",
    "entry-package-info": "app|1.0.0",
    "package-info": "utils|1.0.0"
  }
}

第一次解析结果

at processData (oh_modules/.ohpm/Utils@abc123=/oh_modules/Utils/src/main/ets/common/DataProcessor.js:45:45)
步骤2:二次解析(跨模块)

利用 package-info 字段进行跨模块解析:

截断路径(从最后一个 oh_modules 向下两级):

src/main/ets/common/DataProcessor.js

拼接新路径

utils|utils|1.0.0|src/main/ets/common/DataProcessor.js

在 utils 模块的 sourcemap 中查找

{
  "utils|utils|1.0.0|src/main/ets/common/DataProcessor.js": {
    "version": 3,
    "file": "DataProcessor.ets",
    "sources": ["utils/src/main/ets/common/DataProcessor.ets"],
    "mappings": "...",
    "entry-package-info": "utils|1.0.0"
  }
}

最终路径还原

at processData (utils/src/main/ets/common/DataProcessor.ets:15:2)
步骤3:方法名还原

使用 nameCache 文件还原混淆的方法名:

原始混淆堆栈

at b (app|app|1.0.0|src/main/ets/pages/MainPage.ts:30:30)

路径还原后

at b (app/src/main/ets/pages/MainPage.ets:12:5)

在 nameCache 文件中查找

{
  "app/src/main/ets/pages/MainPage.ets": {
    "IdentifierCache": {
      "MainPage#build#__function": "c",
      "MainPage#build#$2#__function": "d"
    },
    "MemberMethodCache": {
      "loadUserData:10:15": "a",
      "processResponse:12:18": "b",
      "handleError:20:25": "c"
    },
    "obfName": "app/src/main/ets/pages/MainPage.ets"
  }
}

方法名还原

  • 混淆方法名:b

  • 在 MemberMethodCache 中找到:"processResponse:12:18": "b"

  • 还原后行号 12 在 12-18 范围内

  • 最终方法名processResponse

3. 最终还原结果

混淆堆栈

at processData (app|utils|1.0.0|src/main/ets/common/DataProcessor.js:45:45)
at b (app|app|1.0.0|src/main/ets/pages/MainPage.ts:30:30)
at anonymous (app|app|1.0.0|src/main/ets/pages/MainPage.ts:18:18)

还原后堆栈

at processData (utils/src/main/ets/common/DataProcessor.ets:15:2)
at processResponse (app/src/main/ets/pages/MainPage.ets:12:5)
at anonymous (app/src/main/ets/pages/MainPage.ets:8:20)

七、使用流程

1. 开发阶段准备

// build-profile.json5 - 确保生成调试信息
{
  "buildOption": {
    "externalNativeOptions": {
      "arguments": "-DCMAKE_BUILD_TYPE=RelWithDebInfo"
    }
  }
}

2. 构建产物备份

每次发布前必须备份以下文件:

  • sourceMaps.map - 源代码映射信息

  • nameCache.json - 名称混淆映射表

  • SO 文件(如配置了符号表)

3. 崩溃信息收集

从用户设备或日志系统收集崩溃堆栈信息。

4. 堆栈解析执行

# 完整解析命令
hstack -i ./collected_crashes -o ./analysis_results -s ./backup_sourcemaps --so ./backup_native_libs -n ./backup_namecaches

5. 结果分析

检查输出目录中的解析结果文件,获得可读的源码堆栈信息。

八、建议

1. 文件管理

  • 版本对应:确保崩溃堆栈与对应的构建产物版本一致

  • 定期备份:每次发布新版本时备份必要的调试文件

  • 安全存储:调试文件包含源码信息,需安全存储

2. 自动化集成

#!/bin/bash
# 示例自动化解析脚本
BACKUP_DIR="./build_artifacts/v1.2.3"
CRASH_DIR="./crashes/v1.2.3"
OUTPUT_DIR="./analysis/v1.2.3"

hstack -i $CRASH_DIR -o $OUTPUT_DIR -s $BACKUP_DIR/sourcemaps --so $BACKUP_DIR/native_libs -n $BACKUP_DIR/namecaches

3. 故障排查

  • 解析失败:检查文件版本是否匹配

  • 方法名未还原:确认提供了 nameCache 文件

  • C++ 堆栈无法解析:检查 ADDR2LINE_PATH 配置

通过使用 hstack 工具,可以有效地分析和解决生产环境中经过混淆的应用崩溃问题,大大提升问题定位效率。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐