已合并
feat: 新增 LLVM 工具自动发现脚本与步骤零输入校验门禁 #39
fangzy0823创建于 14 天前
feat: 新增 LLVM 工具自动发现脚本与步骤零输入校验门禁 #39
已合并
共 7 个文件变更+315-5
| @@ -51,6 +51,16 @@ metadata: | |||
| 51 | 4. 检查输入是否存在、是否可读。若脚本未识别日志,直接检查原始文件中的 `Reason:`、 | 51 | 4. 检查输入是否存在、是否可读。若脚本未识别日志,直接检查原始文件中的 `Reason:`、 |
| 52 | `Fault thread info:`、`Reason:GWP-ASAN` 和 `*** GWP-ASan detected a memory error ***`。 | 52 | `Fault thread info:`、`Reason:GWP-ASAN` 和 `*** GWP-ASan detected a memory error ***`。 |
| 53 | 53 | ||
| 54 | +5. 日志类型校验:脚本在收集阶段自动识别 CppCrash FaultLog——文件名以 `cppcrash-` 开头, | ||
| 55 | + 或内容含 `Reason:Signal:` / `Reason:GWP-ASAN` / `*** GWP-ASan detected a memory error ***`。 | ||
| 56 | + 不符合上述特征的日志不在本技能范围。 | ||
| 57 | + 若脚本提示"未在 ... 中找到 cppcrash 日志",停止分析并提示用户: | ||
| 58 | + "该日志不是 CppCrash 故障日志,无法使用本技能分析。请提供 Native 层崩溃(CppCrash)的 FaultLog。" | ||
| 59 | + | ||
| 60 | +6. 堆栈信息校验:脚本在解析后检查日志是否含可分析的调用栈——`Fault thread info:` 区段下 | ||
| 61 | + `#NN pc` 格式帧,或 GWP-ASan 报告中的 `#N 0xADDR` 格式帧。两者皆无时停止分析并提示用户: | ||
| 62 | + "该 CppCrash 日志缺少 Native 调用栈信息,无法定位根因。请提供包含完整调用栈的 FaultLog。" | ||
| 63 | + | ||
| 54 | ### 步骤一:提取关键日志 | 64 | ### 步骤一:提取关键日志 |
| 55 | 65 | ||
| 56 | 完整阅读脚本输出,并在以下情况回读原始日志: | 66 | 完整阅读脚本输出,并在以下情况回读原始日志: |
| @@ -163,16 +173,18 @@ reference 发生冲突时,以证据等级更高的规则为准。模块栈模 | |||
| 163 | 173 | ||
| 164 | 仅在材料齐全时执行,不得将其写成无条件必选步骤: | 174 | 仅在材料齐全时执行,不得将其写成无条件必选步骤: |
| 165 | 175 | ||
| 176 | +`ohos_llvm_tools.py` 根据环境变量(`DEVECO_HOME` / `DEVECO_SDK_HOME` / `OHOS_SDK`)自动定位 `llvm-addr2line` 与 `llvm-objdump`;未找到时按报错提示设置上述任一环境变量或把工具加入系统 `PATH`。 | ||
| 177 | + | ||
| 166 | 1. 有匹配 BuildID 的 `.so` 和符号时运行: | 178 | 1. 有匹配 BuildID 的 `.so` 和符号时运行: |
| 167 | 179 | ||
| 168 | ```bash | 180 | ```bash |
| 169 | - llvm-addr2line -pCfie "<so文件>" "<pc相对偏移>" | 181 | + python "<skill-root>/scripts/ohos_llvm_tools.py" addr2line "<so文件>" "<pc相对偏移>" |
| 170 | ``` | 182 | ``` |
| 171 | 183 | ||
| 172 | 2. 行号仍不足以判断多参数调用、虚表、函数指针或访存寄存器时运行: | 184 | 2. 行号仍不足以判断多参数调用、虚表、函数指针或访存寄存器时运行: |
| 173 | 185 | ||
| 174 | ```bash | 186 | ```bash |
| 175 | - llvm-objdump -dS -l -C "<so文件>" > "<so文件名>.objdump" | 187 | + python "<skill-root>/scripts/ohos_llvm_tools.py" objdump "<so文件>" [输出目录] |
| 176 | ``` | 188 | ``` |
| 177 | 189 | ||
| 178 | 3. 用户提供源码后,结合行号上下文检查空值、边界、所有权、线程和回调时序。 | 190 | 3. 用户提供源码后,结合行号上下文检查空值、边界、所有权、线程和回调时序。 |
| @@ -221,7 +221,7 @@ by thread <tid> here: | |||
| 221 | |--------|----------| | 221 | |--------|----------| |
| 222 | | 缺少释放栈 | 标注无法确认释放位置;建议补充完整 GWP-ASan 日志或检查 frame pointer | | 222 | | 缺少释放栈 | 标注无法确认释放位置;建议补充完整 GWP-ASan 日志或检查 frame pointer | |
| 223 | | 缺少申请栈 | 标注无法确认对象创建来源;仍可基于违规访问栈和错误类型定性 | | 223 | | 缺少申请栈 | 标注无法确认对象创建来源;仍可基于违规访问栈和错误类型定性 | |
| 224 | -| 缺少符号 | 输出 `.so+offset`,建议提供符号文件用 `llvm-addr2line` 解析 | | 224 | +| 缺少符号 | 输出 `.so+offset`,建议提供符号文件用 `ohos_llvm_tools.py addr2line` 解析 | |
| 225 | | 只有裸地址 | 结论置信度降级;要求补充 maps / so / BuildID | | 225 | | 只有裸地址 | 结论置信度降级;要求补充 maps / so / BuildID | |
| 226 | | 32 位进程栈不完整 | 标注 GWP-ASan 对 32 位进程分配/释放栈支持可能不足 | | 226 | | 32 位进程栈不完整 | 标注 GWP-ASan 对 32 位进程分配/释放栈支持可能不足 | |
| 227 | 227 | ||
| @@ -42,6 +42,15 @@ def build_report(log_path: str, analyze_all: bool = False) -> str: | |||
| 42 | reports = [] | 42 | reports = [] |
| 43 | for index, path in enumerate(selected, start=1): | 43 | for index, path in enumerate(selected, start=1): |
| 44 | crash = parse_crash_log(path) | 44 | crash = parse_crash_log(path) |
| 45 | + has_gwp_asan_frames = bool(crash.gwp_asan_text) and any( | ||
| 46 | + line.strip().startswith('#') and '0x' in line | ||
| 47 | + for line in crash.gwp_asan_text.splitlines() | ||
| 48 | + ) | ||
| 49 | + if not crash.fault_stack.frames and not has_gwp_asan_frames: | ||
| 50 | + raise ValueError( | ||
| 51 | + f'该日志缺少 Native 调用栈' | ||
| 52 | + f'(无 #NN pc 帧且 GWP-ASan 报告无调用栈),无法分析: {path.name}' | ||
| 53 | + ) | ||
| 45 | if analyze_all and len(selected) > 1: | 54 | if analyze_all and len(selected) > 1: |
| 46 | reports.append(f'【日志 {index}/{len(selected)}:{path.name}】') | 55 | reports.append(f'【日志 {index}/{len(selected)}:{path.name}】') |
| 47 | other_logs = found[1:] if not analyze_all and index == 1 else [] | 56 | other_logs = found[1:] if not analyze_all and index == 1 else [] |
| @@ -0,0 +1,279 @@ | |||
| 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 | +"""OHOS LLVM 工具查找与执行脚本。 | ||
| 16 | + | ||
| 17 | +根据环境变量(DEVECO_HOME / DEVECO_CLI_STUDIO_PATH / DEVECO_SDK_HOME / DEVECO_CLI_CLT_PATH / OHOS_SDK)自动搜索 | ||
| 18 | +llvm-addr2line 和 llvm-objdump,并执行符号解析或反汇编。 | ||
| 19 | + | ||
| 20 | +用法:: | ||
| 21 | + | ||
| 22 | + # 地址解析 | ||
| 23 | + python ohos_llvm_tools.py addr2line <so_file> <offset> | ||
| 24 | + | ||
| 25 | + # 反汇编 | ||
| 26 | + python ohos_llvm_tools.py objdump <so_file> [output_dir] | ||
| 27 | + | ||
| 28 | +搜索策略: | ||
| 29 | + 1. {DEVECO_HOME}/sdk/default/openharmony/native/llvm/bin | ||
| 30 | + 2. {DEVECO_CLI_STUDIO_PATH}/sdk/default/openharmony/native/llvm/bin | ||
| 31 | + 3. {DEVECO_SDK_HOME}/default/openharmony/native/llvm/bin | ||
| 32 | + 4. {DEVECO_CLI_CLT_PATH}/sdk/default/openharmony/native/llvm/bin | ||
| 33 | + 5. {OHOS_SDK}/{platform}/native/llvm/bin 或 {OHOS_SDK}/native/llvm/bin | ||
| 34 | + 6. 从以上各环境变量的根目录开始递归模糊搜索(优先 llvm/bin) | ||
| 35 | + 7. 回退到系统 PATH(shutil.which) | ||
| 36 | +""" | ||
| 37 | + | ||
| 38 | +import argparse | ||
| 39 | +import os | ||
| 40 | +import platform | ||
| 41 | +import shutil | ||
| 42 | +import subprocess | ||
| 43 | +import sys | ||
| 44 | +from pathlib import Path | ||
| 45 | +from typing import List, Optional, Tuple | ||
| 46 | + | ||
| 47 | +# 用于定位 OHOS LLVM 工具的环境变量(按优先级排列) | ||
| 48 | +_ENV_VARS = ('DEVECO_HOME', 'DEVECO_CLI_STUDIO_PATH', 'DEVECO_SDK_HOME', 'DEVECO_CLI_CLT_PATH', 'OHOS_SDK') | ||
| 49 | + | ||
| 50 | +# 递归模糊搜索时跳过的目录名(大小写不敏感),避免遍历无关大目录 | ||
| 51 | +_SKIP_DIRS = frozenset({ | ||
| 52 | + 'node_modules', '.git', '.gradle', '.cxx', 'caches', 'temp', | ||
| 53 | + '.idea', '__pycache__', '.hg', '.svn', 'build-cache', | ||
| 54 | +}) | ||
| 55 | + | ||
| 56 | + | ||
| 57 | +def _exe_suffix() -> str: | ||
| 58 | + """Windows 下可执行文件带 .exe 后缀。""" | ||
| 59 | + return '.exe' if sys.platform == 'win32' else '' | ||
| 60 | + | ||
| 61 | + | ||
| 62 | +def _platform_dir() -> str: | ||
| 63 | + """返回当前系统的 OHOS SDK 平段子目录名。""" | ||
| 64 | + system = platform.system().lower() | ||
| 65 | + return {'windows': 'windows', 'linux': 'linux', 'darwin': 'darwin'}.get(system, system) | ||
| 66 | + | ||
| 67 | + | ||
| 68 | +def _env_roots() -> List[Path]: | ||
| 69 | + """收集所有已设置且存在环境变量的根目录。""" | ||
| 70 | + roots = [] | ||
| 71 | + for name in _ENV_VARS: | ||
| 72 | + value = os.environ.get(name, '').strip() | ||
| 73 | + if value: | ||
| 74 | + root = Path(value) | ||
| 75 | + if root.is_dir(): | ||
| 76 | + roots.append(root) | ||
| 77 | + return roots | ||
| 78 | + | ||
| 79 | + | ||
| 80 | +def _direct_bin_candidates() -> List[Path]: | ||
| 81 | + """根据已知目录结构构造候选 bin 目录列表。 | ||
| 82 | + | ||
| 83 | + 依次对应各 SDK 布局(按优先级): | ||
| 84 | + - DEVECO_HOME: <root>/sdk/default/openharmony/native/llvm/bin | ||
| 85 | + - DEVECO_CLI_STUDIO_PATH: <root>/sdk/default/openharmony/native/llvm/bin | ||
| 86 | + - DEVECO_SDK_HOME: <root>/default/openharmony/native/llvm/bin | ||
| 87 | + - DEVECO_CLI_CLT_PATH: <root>/sdk/default/openharmony/native/llvm/bin | ||
| 88 | + - OHOS_SDK: <root>/<platform>/native/llvm/bin (独立 OHOS SDK) | ||
| 89 | + <root>/native/llvm/bin (command-line-tools) | ||
| 90 | + """ | ||
| 91 | + candidates = [] | ||
| 92 | + deveco_home = os.environ.get('DEVECO_HOME', '').strip() | ||
| 93 | + if deveco_home: | ||
| 94 | + candidates.append( | ||
| 95 | + Path(deveco_home) / 'sdk' / 'default' / 'openharmony' / 'native' / 'llvm' / 'bin') | ||
| 96 | + deveco_cli_studio = os.environ.get('DEVECO_CLI_STUDIO_PATH', '').strip() | ||
| 97 | + if deveco_cli_studio: | ||
| 98 | + candidates.append( | ||
| 99 | + Path(deveco_cli_studio) / 'sdk' / 'default' / 'openharmony' / 'native' / 'llvm' / 'bin') | ||
| 100 | + deveco_sdk_home = os.environ.get('DEVECO_SDK_HOME', '').strip() | ||
| 101 | + if deveco_sdk_home: | ||
| 102 | + candidates.append( | ||
| 103 | + Path(deveco_sdk_home) / 'default' / 'openharmony' / 'native' / 'llvm' / 'bin') | ||
| 104 | + deveco_cli_clt = os.environ.get('DEVECO_CLI_CLT_PATH', '').strip() | ||
| 105 | + if deveco_cli_clt: | ||
| 106 | + candidates.append( | ||
| 107 | + Path(deveco_cli_clt) / 'sdk' / 'default' / 'openharmony' / 'native' / 'llvm' / 'bin') | ||
| 108 | + ohos_sdk = os.environ.get('OHOS_SDK', '').strip() | ||
| 109 | + if ohos_sdk: | ||
| 110 | + sdk_root = Path(ohos_sdk) | ||
| 111 | + # 独立 OHOS SDK 带平台段;command-line-tools 不带平台段,两者均需尝试 | ||
| 112 | + candidates.append(sdk_root / _platform_dir() / 'native' / 'llvm' / 'bin') | ||
| 113 | + candidates.append(sdk_root / 'native' / 'llvm' / 'bin') | ||
| 114 | + return candidates | ||
| 115 | + | ||
| 116 | + | ||
| 117 | +def _find_in_dir(bin_dir: Path, tool: str) -> Optional[Path]: | ||
| 118 | + """检查指定目录下是否存在目标工具(含平台后缀)。""" | ||
| 119 | + candidate = bin_dir / f'{tool}{_exe_suffix()}' | ||
| 120 | + if candidate.is_file(): | ||
| 121 | + return candidate | ||
| 122 | + return None | ||
| 123 | + | ||
| 124 | + | ||
| 125 | +def _collect_fuzzy_candidates(tool: str) -> List[Path]: | ||
| 126 | + """从各环境变量根目录递归模糊搜索,返回所有命中的工具路径。 | ||
| 127 | + | ||
| 128 | + 遍历根目录下所有子目录(跳过 _SKIP_DIRS 中的无关目录),收集名为 | ||
| 129 | + `<tool><suffix>` 的文件。同一文件经 resolve 去重。 | ||
| 130 | + """ | ||
| 131 | + suffix = _exe_suffix() | ||
| 132 | + target = f'{tool}{suffix}' | ||
| 133 | + matches: List[Path] = [] | ||
| 134 | + seen = set() | ||
| 135 | + for root in _env_roots(): | ||
| 136 | + for current, dirnames, filenames in os.walk(root): | ||
| 137 | + # 剪枝:原地修改 dirnames 阻止 os.walk 进入无关目录 | ||
| 138 | + dirnames[:] = [d for d in dirnames if d.lower() not in _SKIP_DIRS] | ||
| 139 | + if target not in filenames: | ||
| 140 | + continue | ||
| 141 | + path = (Path(current) / target).resolve() | ||
| 142 | + if path in seen: | ||
| 143 | + continue | ||
| 144 | + seen.add(path) | ||
| 145 | + matches.append(path) | ||
| 146 | + return matches | ||
| 147 | + | ||
| 148 | + | ||
| 149 | +def _rank_key(path: Path) -> Tuple[int, int, str]: | ||
| 150 | + """模糊搜索结果排序键:llvm/bin 路径优先,其次路径更浅,最后按字典序。""" | ||
| 151 | + parts = [p.lower() for p in path.parent.parts] | ||
| 152 | + is_llvm_bin = 'llvm' in parts and 'bin' in parts | ||
| 153 | + return (0 if is_llvm_bin else 1, len(parts), str(path).lower()) | ||
| 154 | + | ||
| 155 | + | ||
| 156 | +def find_llvm_tool(tool: str) -> Optional[Path]: | ||
| 157 | + """查找 OHOS LLVM 工具。 | ||
| 158 | + | ||
| 159 | + 依次按已知路径、递归模糊搜索、系统 PATH 回退,返回首个命中的可执行文件路径。 | ||
| 160 | + """ | ||
| 161 | + # 1. 已知路径候选 | ||
| 162 | + for bin_dir in _direct_bin_candidates(): | ||
| 163 | + found = _find_in_dir(bin_dir, tool) | ||
| 164 | + if found: | ||
| 165 | + return found | ||
| 166 | + | ||
| 167 | + # 2. 递归模糊搜索 | ||
| 168 | + fuzzy = _collect_fuzzy_candidates(tool) | ||
| 169 | + if fuzzy: | ||
| 170 | + fuzzy.sort(key=_rank_key) | ||
| 171 | + return fuzzy[0] | ||
| 172 | + | ||
| 173 | + # 3. 系统 PATH 回退(shutil.which 已处理平台后缀) | ||
| 174 | + which = shutil.which(tool) | ||
| 175 | + return Path(which) if which else None | ||
| 176 | + | ||
| 177 | + | ||
| 178 | +def run_addr2line(so_file: Path, offset: str) -> int: | ||
| 179 | + """执行 llvm-addr2line 进行地址解析。 | ||
| 180 | + | ||
| 181 | + 命令: llvm-addr2line -pCfie <so_file> <offset> | ||
| 182 | + 解析结果写入标准输出,工具查找信息写入标准错误,互不污染。 | ||
| 183 | + """ | ||
| 184 | + tool = find_llvm_tool('llvm-addr2line') | ||
| 185 | + if not tool: | ||
| 186 | + _error('未找到 llvm-addr2line。请设置环境变量 DEVECO_HOME / DEVECO_SDK_HOME / ' | ||
| 187 | + 'OHOS_SDK,或将 llvm-addr2line 加入系统 PATH。') | ||
| 188 | + return 1 | ||
| 189 | + if not so_file.is_file(): | ||
| 190 | + _error(f'.so 文件不存在: {so_file}') | ||
| 191 | + return 1 | ||
| 192 | + _info(f'使用工具: {tool}') | ||
| 193 | + cmd = [str(tool), '-pCfie', str(so_file), offset] | ||
| 194 | + return _run(cmd) | ||
| 195 | + | ||
| 196 | + | ||
| 197 | +def run_objdump(so_file: Path, output_dir: Optional[Path] = None) -> int: | ||
| 198 | + """执行 llvm-objdump 进行反汇编。 | ||
| 199 | + | ||
| 200 | + 命令: llvm-objdump -dS -l -C <so_file> > <so_name>.objdump | ||
| 201 | + 反汇编结果写入 <output_dir>/<so_name>.objdump,默认输出到当前目录。 | ||
| 202 | + """ | ||
| 203 | + tool = find_llvm_tool('llvm-objdump') | ||
| 204 | + if not tool: | ||
| 205 | + _error('未找到 llvm-objdump。请设置环境变量 DEVECO_HOME / DEVECO_SDK_HOME / ' | ||
| 206 | + 'OHOS_SDK,或将 llvm-objdump 加入系统 PATH。') | ||
| 207 | + return 1 | ||
| 208 | + if not so_file.is_file(): | ||
| 209 | + _error(f'.so 文件不存在: {so_file}') | ||
| 210 | + return 1 | ||
| 211 | + out_dir = output_dir or Path.cwd() | ||
| 212 | + out_dir.mkdir(parents=True, exist_ok=True) | ||
| 213 | + out_file = out_dir / f'{so_file.name}.objdump' | ||
| 214 | + print(f'使用工具: {tool}') | ||
| 215 | + print(f'输出文件: {out_file}') | ||
| 216 | + cmd = [str(tool), '-dS', '-l', '-C', str(so_file)] | ||
| 217 | + with open(out_file, 'w', encoding='utf-8', errors='replace') as stdout: | ||
| 218 | + return _run(cmd, stdout=stdout) | ||
| 219 | + | ||
| 220 | + | ||
| 221 | +def _run(cmd: List[str], stdout=None) -> int: | ||
| 222 | + """运行外部命令并返回退出码;标准输出/错误直接继承当前进程。""" | ||
| 223 | + try: | ||
| 224 | + proc = subprocess.run(cmd, stdout=stdout) | ||
| 225 | + except OSError as exc: | ||
| 226 | + _error(f'执行失败: {exc}') | ||
| 227 | + return 1 | ||
| 228 | + if proc.returncode != 0: | ||
| 229 | + _error(f'命令返回非零退出码 {proc.returncode}') | ||
| 230 | + return proc.returncode | ||
| 231 | + | ||
| 232 | + | ||
| 233 | +def _info(msg: str) -> None: | ||
| 234 | + print(msg, file=sys.stderr) | ||
| 235 | + | ||
| 236 | + | ||
| 237 | +def _error(msg: str) -> None: | ||
| 238 | + print(f'错误: {msg}', file=sys.stderr) | ||
| 239 | + | ||
| 240 | + | ||
| 241 | +def _configure_output_encoding() -> None: | ||
| 242 | + """统一正常输出、帮助和错误信息的编码。""" | ||
| 243 | + for stream in (sys.stdout, sys.stderr): | ||
| 244 | + if hasattr(stream, 'reconfigure'): | ||
| 245 | + stream.reconfigure(encoding='utf-8', errors='replace') | ||
| 246 | + | ||
| 247 | + | ||
| 248 | +def main() -> int: | ||
| 249 | + _configure_output_encoding() | ||
| 250 | + parser = argparse.ArgumentParser( | ||
| 251 | + description='OHOS LLVM 工具查找与执行:自动搜索 llvm-addr2line / llvm-objdump 并执行。', | ||
| 252 | + formatter_class=argparse.RawDescriptionHelpFormatter) | ||
| 253 | + sub = parser.add_subparsers(dest='command', metavar='<command>') | ||
| 254 | + | ||
| 255 | + a2l = sub.add_parser('addr2line', help='运行 llvm-addr2line 解析地址(-pCfie)') | ||
| 256 | + a2l.add_argument('so_file', help='.so 文件路径') | ||
| 257 | + a2l.add_argument('offset', help='pc 相对偏移(十六进制,可带 0x 前缀)') | ||
| 258 | + | ||
| 259 | + od = sub.add_parser('objdump', help='运行 llvm-objdump 反汇编(-dS -l -C)') | ||
| 260 | + od.add_argument('so_file', help='.so 文件路径') | ||
| 261 | + od.add_argument('output_dir', nargs='?', default=None, | ||
| 262 | + help='输出目录(默认当前目录),生成 <so文件名>.objdump') | ||
| 263 | + | ||
| 264 | + args = parser.parse_args() | ||
| 265 | + if not args.command: | ||
| 266 | + parser.print_help() | ||
| 267 | + return 2 | ||
| 268 | + | ||
| 269 | + if args.command == 'addr2line': | ||
| 270 | + return run_addr2line(Path(args.so_file), args.offset) | ||
| 271 | + if args.command == 'objdump': | ||
| 272 | + out_dir = Path(args.output_dir) if args.output_dir else None | ||
| 273 | + return run_objdump(Path(args.so_file), output_dir=out_dir) | ||
| 274 | + parser.print_help() | ||
| 275 | + return 2 | ||
| 276 | + | ||
| 277 | + | ||
| 278 | +if __name__ == '__main__': | ||
| 279 | + sys.exit(main()) | ||
| @@ -119,7 +119,7 @@ def _stack_overflow_line(crash: CppCrashLog) -> List[str]: | |||
| 119 | def _render_fault_stack(crash: CppCrashLog) -> str: | 119 | def _render_fault_stack(crash: CppCrashLog) -> str: |
| 120 | lines = [_title('崩溃线程堆栈'), | 120 | lines = [_title('崩溃线程堆栈'), |
| 121 | '说明:调用方向从栈底(最大编号)到栈顶(#00);' | 121 | '说明:调用方向从栈底(最大编号)到栈顶(#00);' |
| 122 | - '各帧保留 pc 相对偏移,可用 llvm-addr2line -pCfie <so> <偏移> 解析行号'] | 122 | + '各帧保留 pc 相对偏移,可用 ohos_llvm_tools.py addr2line <so> <偏移> 解析行号'] |
| 123 | if not crash.fault_stack.frames: | 123 | if not crash.fault_stack.frames: |
| 124 | lines.append('未解析到崩溃线程堆栈') | 124 | lines.append('未解析到崩溃线程堆栈') |
| 125 | return '\n'.join(lines) | 125 | return '\n'.join(lines) |
| @@ -188,7 +188,7 @@ python 01-fault-analysis/cppcrash-analysis/scripts/extract_hilog.py <faultlog文 | |||
| 188 | 4. **按需知识加载**:根据 `.so`、函数和日志证据,仅加载匹配的 ArkUI、ArkData、ArkWeb、JSRuntime、RenderService、JSVM、Text、内存破坏或多线程 reference。 | 188 | 4. **按需知识加载**:根据 `.so`、函数和日志证据,仅加载匹配的 ArkUI、ArkData、ArkWeb、JSRuntime、RenderService、JSVM、Text、内存破坏或多线程 reference。 |
| 189 | 5. **调用栈分层**:分别输出崩溃帧、首个非运行时调用方和首个应用侧帧,不把运行时帧直接作为业务根因。 | 189 | 5. **调用栈分层**:分别输出崩溃帧、首个非运行时调用方和首个应用侧帧,不把运行时帧直接作为业务根因。 |
| 190 | 6. **证据链分析**:联合信号、故障地址、寄存器、指令、Maps、对象生命周期和线程证据判断第一现场及可信度。 | 190 | 6. **证据链分析**:联合信号、故障地址、寄存器、指令、Maps、对象生命周期和线程证据判断第一现场及可信度。 |
| 191 | -7. **可选深入分析**:仅在符号、二进制或源码可用时执行 `llvm-addr2line`、`llvm-objdump` 和源码分析;HiLog 只作时序辅助证据。 | 191 | +7. **可选深入分析**:仅在符号、二进制或源码可用时通过 `ohos_llvm_tools.py`(自动定位 `llvm-addr2line`/`llvm-objdump`)执行符号解析、反汇编和源码分析;HiLog 只作时序辅助证据。 |
| 192 | 8. **报告生成**:使用 Markdown 表格输出三级故障分类、代码根因、关键证据、责任归属、可信度、竞争解释和修复建议,不输出内部故障编码。 | 192 | 8. **报告生成**:使用 Markdown 表格输出三级故障分类、代码根因、关键证据、责任归属、可信度、竞争解释和修复建议,不输出内部故障编码。 |
| 193 | 193 | ||
| 194 | *** | 194 | *** |
| @@ -1,5 +1,15 @@ | |||
| 1 | # 更新日志 | 1 | # 更新日志 |
| 2 | 2 | ||
| 3 | +## [Unreleased] - 2026-08-26 | ||
| 4 | + | ||
| 5 | +### 新增 | ||
| 6 | +- 新增 `scripts/ohos_llvm_tools.py`:按 `DEVECO_HOME` / `DEVECO_CLI_STUDIO_PATH` / `DEVECO_SDK_HOME` / `DEVECO_CLI_CLT_PATH` / `OHOS_SDK` 环境变量优先级链自动定位并执行 `llvm-addr2line`(`-pCfie`)和 `llvm-objdump`(`-dS -l -C`),替代直接调用裸命令。 | ||
| 7 | +- 新增步骤零输入校验:`scripts/main.py` `build_report` 解析后检查 `fault_stack.frames`(`#NN pc` 帧)或 `gwp_asan_text` 中的真实帧行(`#N 0xADDR`),两者皆空时拒绝分析并提示用户提供完整调用栈。 | ||
| 8 | + | ||
| 9 | +### 变更 | ||
| 10 | +- `SKILL.md` 步骤四、`scripts/report.py` 堆栈提示与 `references/gwp_asan.md` 缺失信息表中的 `llvm-addr2line`/`llvm-objdump` 裸命令改为调用 `ohos_llvm_tools.py`。 | ||
| 11 | +- `SKILL.md` 步骤零新增 items 5-6:描述脚本日志类型识别与堆栈校验逻辑,以及脚本报错后停止分析并提示用户的沟通策略。 | ||
| 12 | + | ||
| 3 | ## [1.3.0] - 2026-09-01 | 13 | ## [1.3.0] - 2026-09-01 |
| 4 | 14 | ||
| 5 | ### 变更 | 15 | ### 变更 |