已关闭
appfreeze skill新增符号解析流程 #42
sunyichao创建于 9月2日关闭于 27 天前
appfreeze skill新增符号解析流程 #42
已关闭
共 3 个文件变更+296-13
| @@ -50,6 +50,8 @@ HarmonyOS 高级开发工程师 / HarmonyOS 架构师 / 系统 DFX 工程师 / | |||
| 50 | 确认以下脚本存在: | 50 | 确认以下脚本存在: |
| 51 | - `<skill-root>/scripts/freeze/main.py` | 51 | - `<skill-root>/scripts/freeze/main.py` |
| 52 | - `<skill-root>/scripts/sample_stack_analyzer.py` | 52 | - `<skill-root>/scripts/sample_stack_analyzer.py` |
| 53 | + - `<skill-root>/scripts/llvm_addr2line.py` | ||
| 54 | + - `<skill-root>/scripts/match_so.py` | ||
| 53 | 55 | ||
| 54 | 3. **依赖检查** | 56 | 3. **依赖检查** |
| 55 | appfreeze Python 脚本仅使用 Python 标准库和本技能内置模块,无需安装第三方 pip 依赖。 | 57 | appfreeze Python 脚本仅使用 Python 标准库和本技能内置模块,无需安装第三方 pip 依赖。 |
| @@ -89,7 +91,7 @@ HarmonyOS 高级开发工程师 / HarmonyOS 架构师 / 系统 DFX 工程师 / | |||
| 89 | 概览中 `resources` 为“有”时执行: | 91 | 概览中 `resources` 为“有”时执行: |
| 90 | 92 | ||
| 91 | ```bash | 93 | ```bash |
| 92 | -python "<skill-root>/scripts/freeze/main.py" -p "<file_path>" --section resources | 94 | + python "<skill-root>/scripts/freeze/main.py" -p "<file_path>" --section resources |
| 93 | ``` | 95 | ``` |
| 94 | 96 | ||
| 95 | 结合概览与资源区段识别: | 97 | 结合概览与资源区段识别: |
| @@ -160,12 +162,50 @@ python "<skill-root>/scripts/freeze/main.py" -p "<file_path>" --section event-qu | |||
| 160 | 162 | ||
| 161 | --- | 163 | --- |
| 162 | 164 | ||
| 163 | -### Step 4 — 故障目标线程 & 关联线程堆栈分析 | 165 | +### Step 4 — 符号解析 & 关联线程堆栈分析 |
| 166 | +未被整机异常定性时,执行下列流程: | ||
| 164 | 167 | ||
| 165 | -未被整机异常定性时,执行: | 168 | +#### 4a. 符号解析(可追溯源码时执行,无源码追溯时跳过此步骤) |
| 169 | +**必要依赖**:源码可达性检测 | ||
| 170 | +- 在当前工作目录(及父目录)中查找以下标志文件:`build-profile.json5`、`oh-package.json5`、`AppScope/app.json5` | ||
| 171 | +- 若找到,读取 `AppScope/app.json5` 中的 `bundleName` 字段,与提取 faultlog 中的故障进程包名对比,若一致即源码可达;若不一致即源码不可达,直接跳过符号解析流程。 | ||
| 172 | + | ||
| 173 | +**触发条件**(满足其一即需解析): | ||
| 174 | +- 栈顶或责任调用链上的栈帧为应用 so,且只有**裸偏移地址 + BuildID**。 | ||
| 175 | +- 例:`#05 pc 000000000000a128 /data/storage/el1/bundle/libs/arm64/libxxx.so(f3991876654443a608c9cc71f20cf4d8caab449f)` | ||
| 176 | +- 栈顶或责任调用链上的关键栈帧的符号信息为「函数名+偏移量」。 | ||
| 177 | + | ||
| 178 | +**不解析的场景**: | ||
| 179 | +- 系统库帧(`libffrt.so`、`libace_compatible.z.so` 等)—— 日志已带符号名,且不在应用侧责任范围。 | ||
| 180 | +- 用户未提供带符号的 SO 文件路径,且当前工程没有找到对应 SO 文件。**若 SO 文件不可用**:跳过本步骤,在报告中标注"无带符号so文件,行号定位不可用" | ||
| 181 | + | ||
| 182 | +**so 文件校验**(地址解析前必须执行): | ||
| 183 | +从 faultlog 栈帧中提取应用 SO 文件名和 BuildID,校验源码工程目录下同名 .so 是否为同一编译产物(若系统只有 `python3` 可用则替换): | ||
| 184 | +```bash | ||
| 185 | + python scripts/match_so.py {so_name} {build_id} -d {search_dir} | ||
| 186 | +``` | ||
| 187 | +- 退出码 0(stdout 输出匹配路径)→ 用该路径执行下方地址解析 | ||
| 188 | +- 退出码非 0 → 跳过地址解析,在报告中标注「未找到与 faultlog 版本匹配的 SO 文件,行号定位不可用」 | ||
| 189 | + | ||
| 190 | +**地址解析**(仅在 so 文件校验通过时执行): | ||
| 191 | +通过 Python 脚本自动定位 `llvm-addr2line` 并对 BuildID 匹配的 .so 文件执行地址解析。(若系统只有 `python3` 可用,则使用 `python3` 替代 `python`) | ||
| 192 | +```bash | ||
| 193 | + python scripts/llvm_addr2line.py -pCfie {matched_so_file} {offset} | ||
| 194 | +``` | ||
| 195 | +其中 `{matched_so_file}` 为 so 文件校验输出的匹配路径。 | ||
| 196 | + | ||
| 197 | +对崩溃栈中关键帧及前后 3 帧应用帧逐一解析,根据解析结果定位源码分析根因。 | ||
| 198 | + | ||
| 199 | +- **若脚本执行失败(退出码非 0)**:说明系统中未找到 `llvm-addr2line` 工具。此时: | ||
| 200 | +- 提示用户:「未找到 llvm-addr2line 地址解析工具,请安装后重试。可通过以下方式安装:」 | ||
| 201 | +- 提供安装指引: | ||
| 202 | +- **推荐**:确保 DevEco Studio 已安装且 HarmonyOS SDK 已配置,工具位于 `<DEVECO_HOME>/sdk/default/openharmony/native/llvm/bin/`。可设置 `DEVECO_HOME` 环境变量指向 DevEco Studio 安装目录后重试。 | ||
| 203 | +- 在报告中标注"符号解析工具不可用,堆栈行号定位未执行",跳过此步骤。 | ||
| 204 | + | ||
| 205 | +#### 4b. 堆栈分析 | ||
| 166 | 206 | ||
| 167 | ```bash | 207 | ```bash |
| 168 | -python "<skill-root>/scripts/freeze/main.py" -p "<file_path>" --section fault-stack | 208 | + python "<skill-root>/scripts/freeze/main.py" -p "<file_path>" --section fault-stack |
| 169 | ``` | 209 | ``` |
| 170 | 210 | ||
| 171 | > 堆栈调用方向:**栈底(最大编号)→ 栈顶(#00)**,栈顶为最后被调用位置。 | 211 | > 堆栈调用方向:**栈底(最大编号)→ 栈顶(#00)**,栈顶为最后被调用位置。 |
| @@ -191,7 +231,7 @@ python "<skill-root>/scripts/freeze/main.py" -p "<file_path>" --section fault-st | |||
| 191 | 仅当故障栈包含 Binder 等待、日志出现 IPC FULL 特征,或此前步骤无法形成证据闭环时执行: | 231 | 仅当故障栈包含 Binder 等待、日志出现 IPC FULL 特征,或此前步骤无法形成证据闭环时执行: |
| 192 | 232 | ||
| 193 | ```bash | 233 | ```bash |
| 194 | -python "<skill-root>/scripts/freeze/main.py" -p "<file_path>" --section binder | 234 | + python "<skill-root>/scripts/freeze/main.py" -p "<file_path>" --section binder |
| 195 | ``` | 235 | ``` |
| 196 | 236 | ||
| 197 | 1. 从 `binder catcher` 信息追踪 IPC 调用链,找到**最终阻塞的对端进程及线程**。 | 237 | 1. 从 `binder catcher` 信息追踪 IPC 调用链,找到**最终阻塞的对端进程及线程**。 |
| @@ -217,7 +257,7 @@ python "<skill-root>/scripts/freeze/main.py" -p "<file_path>" --section binder | |||
| 217 | 概览中 `attachments` 为“有”且需要采样栈或其他 faultlog 路径时,先执行: | 257 | 概览中 `attachments` 为“有”且需要采样栈或其他 faultlog 路径时,先执行: |
| 218 | 258 | ||
| 219 | ```bash | 259 | ```bash |
| 220 | -python "<skill-root>/scripts/freeze/main.py" -p "<file_path>" --section attachments | 260 | + python "<skill-root>/scripts/freeze/main.py" -p "<file_path>" --section attachments |
| 221 | ``` | 261 | ``` |
| 222 | 262 | ||
| 223 | 每一次Snapshot堆栈是一次的采样(最多采样10次),我们统计函数的次数的时候一次采样堆栈里面就算出现多次,也只统计为一次。请对采样栈通过下面的脚本进行分析: | 263 | 每一次Snapshot堆栈是一次的采样(最多采样10次),我们统计函数的次数的时候一次采样堆栈里面就算出现多次,也只统计为一次。请对采样栈通过下面的脚本进行分析: |
| @@ -225,7 +265,7 @@ python "<skill-root>/scripts/freeze/main.py" -p "<file_path>" --section attachme | |||
| 225 | **调用独立采样栈分析脚本**: | 265 | **调用独立采样栈分析脚本**: |
| 226 | 266 | ||
| 227 | ```bash | 267 | ```bash |
| 228 | -python "<skill-root>/scripts/sample_stack_analyzer.py" "<sample_stacks.txt路径>" | 268 | + python "<skill-root>/scripts/sample_stack_analyzer.py" "<sample_stacks.txt路径>" |
| 229 | ``` | 269 | ``` |
| 230 | 270 | ||
| 231 | 这个脚本只会列出采样栈中业务帧的情况,你需要重点关注业务函数分布情况,分析时**必须**给出完整的**函数名称,包含文件位置,行号,列号**。如果脚本结果没有任何信息,不需要列出系统函数。那么你需要直接阅读一下采样栈, | 271 | 这个脚本只会列出采样栈中业务帧的情况,你需要重点关注业务函数分布情况,分析时**必须**给出完整的**函数名称,包含文件位置,行号,列号**。如果脚本结果没有任何信息,不需要列出系统函数。那么你需要直接阅读一下采样栈, |
| @@ -254,7 +294,7 @@ python "<skill-root>/scripts/sample_stack_analyzer.py" "<sample_stacks.txt路径 | |||
| 254 | 3. **根本原因**(详细描述触发路径) | 294 | 3. **根本原因**(详细描述触发路径) |
| 255 | 4. **根因模块**(具体模块名) | 295 | 4. **根因模块**(具体模块名) |
| 256 | 5. **责任领域**(应用 / 系统 / 混合 / 未定,并给出定界依据) | 296 | 5. **责任领域**(应用 / 系统 / 混合 / 未定,并给出定界依据) |
| 257 | -6. **修复建议**(无需输出验证步骤;严格面向已判定的责任领域。系统侧根因只给系统侧代码/架构修改,应用侧根因只给应用侧修改,混合责任分开输出,未定时不强行给修改方案) | 297 | +6. **修复建议**(无需输出验证步骤;严格面向已判定的责任领域。系统侧根因只给系统侧代码/架构修改,应用侧根因只给应用侧修改,源码可达时必须给出源码级修复建议,混合责任分开输出,未定时不强行给修改方案) |
| 258 | 298 | ||
| 259 | --- | 299 | --- |
| 260 | 300 | ||
| @@ -289,9 +329,9 @@ python "<skill-root>/scripts/sample_stack_analyzer.py" "<sample_stacks.txt路径 | |||
| 289 | 原始日志: | 329 | 原始日志: |
| 290 | <直接从 faultlog 中摘取的原始日志片段> | 330 | <直接从 faultlog 中摘取的原始日志片段> |
| 291 | 331 | ||
| 292 | -2. <关键证据2> | 332 | +2. <关键证据2>(源码可达时输出) |
| 293 | - 原始日志: | 333 | + 符号解析结果及源码定位 |
| 294 | - <直接从 faultlog 中摘取的原始日志片段> | 334 | + <符号解析结果及源码定位根因分析> |
| 295 | 335 | ||
| 296 | 3. <…> | 336 | 3. <…> |
| 297 | 337 | ||
| @@ -300,14 +340,14 @@ python "<skill-root>/scripts/sample_stack_analyzer.py" "<sample_stacks.txt路径 | |||
| 300 | |----------|----------|--------------|------| | 340 | |----------|----------|--------------|------| |
| 301 | 341 | ||
| 302 | 【根本原因】 | 342 | 【根本原因】 |
| 303 | -<详细说明导致冻屏的直接原因及其完整触发路径> | 343 | +<结合证据链详细说明导致冻屏的直接原因及其完整触发路径> |
| 304 | 344 | ||
| 305 | 【根因模块】 | 345 | 【根因模块】 |
| 306 | <模块名称,例如:com.example.app / libxxx.z.so / xxx_service> | 346 | <模块名称,例如:com.example.app / libxxx.z.so / xxx_service> |
| 307 | 责任领域 : <应用 / 系统 / 混合 / 未定> | 347 | 责任领域 : <应用 / 系统 / 混合 / 未定> |
| 308 | 定界依据 : <根因实现、调用契约与代码归属证据;不能只看路径> | 348 | 定界依据 : <根因实现、调用契约与代码归属证据;不能只看路径> |
| 309 | 349 | ||
| 310 | -【修复建议】 | 350 | +【修复建议】(源码可达时需给出源码级修复建议) |
| 311 | 1. <针对责任模块直接根因的修改;系统侧根因必须写系统模块的修改,应用侧根因必须写应用代码的修改> | 351 | 1. <针对责任模块直接根因的修改;系统侧根因必须写系统模块的修改,应用侧根因必须写应用代码的修改> |
| 312 | 2. <针对深层设计、锁/队列/生命周期/接口契约的同责任域改进> | 352 | 2. <针对深层设计、锁/队列/生命周期/接口契约的同责任域改进> |
| 313 | ================================================================================ | 353 | ================================================================================ |
| @@ -0,0 +1,123 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +# Copyright (c) 2021-2026 Huawei Device Co., Ltd. | ||
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +# you may not use this file except in compliance with the License. | ||
| 5 | +# You may obtain a copy of the License at | ||
| 6 | +# | ||
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +# | ||
| 9 | +# Unless required by applicable law or agreed to in writing, software | ||
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +# See the License for the specific language governing permissions and | ||
| 13 | +# limitations under the License. | ||
| 14 | + | ||
| 15 | +""" | ||
| 16 | +llvm_addr2line.py | ||
| 17 | +查找 llvm-addr2line 工具并执行地址解析。 | ||
| 18 | + | ||
| 19 | +用法: | ||
| 20 | + python llvm_addr2line.py # 只输出工具路径 | ||
| 21 | + python llvm_addr2line.py -pCfie <so_file> <offset> # 直接执行地址解析 | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + 搜索策略: | ||
| 25 | + 1. DEVECO_HOME 环境变量:{DEVECO_HOME}/sdk/default/openharmony/native/llvm/bin | ||
| 26 | + 2. 各平台已知 DevEco Studio 默认安装路径 | ||
| 27 | + 3. 系统 PATH 中的 llvm-addr2line | ||
| 28 | + | ||
| 29 | + 输出:工具路径(无参数)或地址解析结果(有参数) | ||
| 30 | + 退出码:0 = 成功,非 0 = 失败 | ||
| 31 | +""" | ||
| 32 | + | ||
| 33 | +import os | ||
| 34 | +import sys | ||
| 35 | +import platform | ||
| 36 | +import shutil | ||
| 37 | +import subprocess | ||
| 38 | +from typing import Optional, Tuple | ||
| 39 | + | ||
| 40 | + | ||
| 41 | +def check_path(candidate: str) -> bool: | ||
| 42 | + """检查候选路径是否可执行(Windows 下退而检查文件是否存在)。""" | ||
| 43 | + return os.path.isfile(candidate) or os.access(candidate, os.X_OK) | ||
| 44 | + | ||
| 45 | + | ||
| 46 | +def find_addr2line() -> Tuple[Optional[str], int]: | ||
| 47 | + """按优先级查找 llvm-addr2line 工具,返回 (工具路径, 退出码)。""" | ||
| 48 | + plat = platform.system() | ||
| 49 | + tool_name = "llvm-addr2line.exe" if plat == "Windows" else "llvm-addr2line" | ||
| 50 | + sdk_rel_path = os.path.join("sdk", "default", "openharmony", "native", "llvm", "bin") | ||
| 51 | + deveco_home = os.environ.get("DEVECO_HOME", "") | ||
| 52 | + if deveco_home: | ||
| 53 | + candidate = os.path.join(deveco_home, sdk_rel_path, tool_name) | ||
| 54 | + if check_path(candidate): | ||
| 55 | + return candidate, 0 | ||
| 56 | + deveco_paths: list[str] = [] | ||
| 57 | + if plat == "Darwin": | ||
| 58 | + deveco_paths = [ | ||
| 59 | + "/Applications/DevEco-Studio.app/Contents", | ||
| 60 | + os.path.expanduser("~/Applications/DevEco-Studio.app/Contents"), | ||
| 61 | + ] | ||
| 62 | + elif plat == "Windows": | ||
| 63 | + deveco_paths = [ | ||
| 64 | + r"C:\Program Files\DevEco Studio", | ||
| 65 | + r"C:\Program Files (x86)\DevEco Studio", | ||
| 66 | + os.path.expandvars(r"%LOCALAPPDATA%\Programs\DevEco Studio"), | ||
| 67 | + ] | ||
| 68 | + | ||
| 69 | + for d in deveco_paths: | ||
| 70 | + candidate = os.path.join(d, sdk_rel_path, tool_name) | ||
| 71 | + if check_path(candidate): | ||
| 72 | + return candidate, 0 | ||
| 73 | + found = shutil.which(tool_name) | ||
| 74 | + if found: | ||
| 75 | + return found, 0 | ||
| 76 | + | ||
| 77 | + return None, 1 | ||
| 78 | + | ||
| 79 | + | ||
| 80 | +def main() -> None: | ||
| 81 | + """程序入口:无参数时输出工具路径,有参数时定位工具并执行地址解析。""" | ||
| 82 | + args = sys.argv[1:] | ||
| 83 | + | ||
| 84 | + if args: | ||
| 85 | + path, code = find_addr2line() | ||
| 86 | + if code != 0 or not path: | ||
| 87 | + print("[ERROR] 未找到 llvm-addr2line,无法执行地址解析。", file=sys.stderr) | ||
| 88 | + sys.exit(1) | ||
| 89 | + try: | ||
| 90 | + result = subprocess.run([path] + args, capture_output=True, text=True) | ||
| 91 | + if result.stdout: | ||
| 92 | + print(result.stdout, end="") | ||
| 93 | + if result.stderr: | ||
| 94 | + print(result.stderr, end="", file=sys.stderr) | ||
| 95 | + sys.exit(result.returncode) | ||
| 96 | + except OSError as e: | ||
| 97 | + print(f"[ERROR] 调用 llvm-addr2line 失败: {e}", file=sys.stderr) | ||
| 98 | + sys.exit(1) | ||
| 99 | + | ||
| 100 | + path, code = find_addr2line() | ||
| 101 | + if path: | ||
| 102 | + print(path) | ||
| 103 | + sys.exit(0) | ||
| 104 | + | ||
| 105 | + sdk_rel_path = os.path.join("sdk", "default", "openharmony", "native", "llvm", "bin") | ||
| 106 | + plat = platform.system() | ||
| 107 | + tool_name = "llvm-addr2line.exe" if plat == "Windows" else "llvm-addr2line" | ||
| 108 | + | ||
| 109 | + print("[ERROR] 未找到 llvm-addr2line。", file=sys.stderr) | ||
| 110 | + print("", file=sys.stderr) | ||
| 111 | + print("请确保 DevEco Studio 已安装且 HarmonyOS SDK 已配置。", file=sys.stderr) | ||
| 112 | + print(f"期望路径:<DEVECO_HOME>/{sdk_rel_path}/{tool_name}", file=sys.stderr) | ||
| 113 | + print("", file=sys.stderr) | ||
| 114 | + print("提示:可设置 DEVECO_HOME 环境变量指向 DevEco Studio 安装目录。", file=sys.stderr) | ||
| 115 | + if plat == "Darwin": | ||
| 116 | + print(' macOS 示例: export DEVECO_HOME=/Applications/DevEco-Studio.app/Contents', file=sys.stderr) | ||
| 117 | + elif plat == "Windows": | ||
| 118 | + print(r' Windows 示例: set DEVECO_HOME=C:\Program Files\DevEco Studio', file=sys.stderr) | ||
| 119 | + sys.exit(1) | ||
| 120 | + | ||
| 121 | + | ||
| 122 | +if __name__ == "__main__": | ||
| 123 | + main() | ||
| @@ -0,0 +1,120 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +# Copyright (c) 2021-2026 Huawei Device Co., Ltd. | ||
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | +# you may not use this file except in compliance with the License. | ||
| 5 | +# You may obtain a copy of the License at | ||
| 6 | +# | ||
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | +# | ||
| 9 | +# Unless required by applicable law or agreed to in writing, software | ||
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | +# See the License for the specific language governing permissions and | ||
| 13 | +# limitations under the License. | ||
| 14 | +""" | ||
| 15 | +match_so.py — 校验本地 .so 文件是否与 faultlog 中的 BuildID 匹配。 | ||
| 16 | + | ||
| 17 | +用法: | ||
| 18 | + python match_so.py <so_name> <build_id> [-d <search_dir>] | ||
| 19 | + | ||
| 20 | +示例: | ||
| 21 | + python match_so.py libexample.so f3991876654443a608c9cc71f20cf4d8 | ||
| 22 | + python match_so.py libexample.so f3991876654443a608c9cc71f20cf4d8 -d /path/to/symbols | ||
| 23 | + | ||
| 24 | +退出码: | ||
| 25 | + 0 — 找到匹配文件,stdout 输出其绝对路径 | ||
| 26 | + 1 — 找到同名文件但 BuildID 不一致 | ||
| 27 | + 2 — 未找到同名 .so 文件 | ||
| 28 | + 3 — 无法提取 ELF BuildID(工具缺失或文件损坏) | ||
| 29 | +""" | ||
| 30 | + | ||
| 31 | +import argparse | ||
| 32 | +import os | ||
| 33 | +import platform | ||
| 34 | +import re | ||
| 35 | +import shutil | ||
| 36 | +import subprocess | ||
| 37 | +import sys | ||
| 38 | + | ||
| 39 | + | ||
| 40 | +SDK_REL = os.path.join("sdk", "default", "openharmony", "native", "llvm", "bin") | ||
| 41 | + | ||
| 42 | +DEVECO_PATHS = { | ||
| 43 | + "Darwin": [ | ||
| 44 | + "/Applications/DevEco-Studio.app/Contents", | ||
| 45 | + os.path.expanduser("~/Applications/DevEco-Studio.app/Contents"), | ||
| 46 | + ], | ||
| 47 | + "Windows": [ | ||
| 48 | + r"C:\Program Files\DevEco Studio", | ||
| 49 | + r"C:\Program Files (x86)\DevEco Studio", | ||
| 50 | + ], | ||
| 51 | +} | ||
| 52 | + | ||
| 53 | + | ||
| 54 | +def find_readelf(): | ||
| 55 | + """查找 llvm-readelf,优先级:DEVECO_HOME > 已知安装路径 > PATH。""" | ||
| 56 | + plat = platform.system() | ||
| 57 | + name = "llvm-readelf.exe" if plat == "Windows" else "llvm-readelf" | ||
| 58 | + | ||
| 59 | + for base in [os.environ.get("DEVECO_HOME", "")] + DEVECO_PATHS.get(plat, []): | ||
| 60 | + if not base: | ||
| 61 | + continue | ||
| 62 | + c = os.path.join(base, SDK_REL, name) | ||
| 63 | + if os.path.isfile(c): | ||
| 64 | + return c | ||
| 65 | + | ||
| 66 | + return shutil.which(name) | ||
| 67 | + | ||
| 68 | + | ||
| 69 | +def extract_buildid(so_path): | ||
| 70 | + """从 ELF 文件提取 BuildID(小写 hex),失败返回 None。""" | ||
| 71 | + tool = find_readelf() | ||
| 72 | + if not tool: | ||
| 73 | + return None | ||
| 74 | + try: | ||
| 75 | + out = subprocess.run( | ||
| 76 | + [tool, "-n", so_path], | ||
| 77 | + capture_output=True, text=True, timeout=30, | ||
| 78 | + ).stdout | ||
| 79 | + m = re.search(r"Build ID(?:\[sha1\])?\s*[:=]\s*([0-9a-fA-F]+)", out) | ||
| 80 | + return m.group(1).lower() if m else None | ||
| 81 | + except (subprocess.SubprocessError, OSError): | ||
| 82 | + return None | ||
| 83 | + | ||
| 84 | + | ||
| 85 | +def search_so(so_name, root_dir): | ||
| 86 | + """递归搜索同名 .so,返回路径列表。""" | ||
| 87 | + results = [] | ||
| 88 | + for dirpath, _, filenames in os.walk(root_dir): | ||
| 89 | + if so_name in filenames: | ||
| 90 | + results.append(os.path.join(dirpath, so_name)) | ||
| 91 | + return results | ||
| 92 | + | ||
| 93 | + | ||
| 94 | +def main(): | ||
| 95 | + """程序入口:解析参数,查找同名 .so 并匹配 BuildID,输出匹配文件路径。""" | ||
| 96 | + parser = argparse.ArgumentParser(description=__doc__.split("\n")[1].strip()) | ||
| 97 | + parser.add_argument("so_name", help="栈帧中的 SO 文件名,如 libexample.so") | ||
| 98 | + parser.add_argument("build_id", help="栈帧中记录的 BuildID(hex)") | ||
| 99 | + parser.add_argument("-d", "--dir", default=os.getcwd(), help="搜索根目录(默认当前目录)") | ||
| 100 | + args = parser.parse_args() | ||
| 101 | + | ||
| 102 | + expected = args.build_id.lower() | ||
| 103 | + found = search_so(args.so_name, args.dir) | ||
| 104 | + | ||
| 105 | + if not found: | ||
| 106 | + sys.exit(2) | ||
| 107 | + | ||
| 108 | + for path in found: | ||
| 109 | + actual = extract_buildid(path) | ||
| 110 | + if actual is None: | ||
| 111 | + sys.exit(3) | ||
| 112 | + if actual == expected: | ||
| 113 | + print(path) | ||
| 114 | + sys.exit(0) | ||
| 115 | + | ||
| 116 | + sys.exit(1) | ||
| 117 | + | ||
| 118 | + | ||
| 119 | +if __name__ == "__main__": | ||
| 120 | + main() | ||