实用指南:11.22 脚本 手机termux项目分析(python)

一 场景,适合坐电梯 电梯,蹲坑

二 特点,执行快,效率高

python+源文件,

三 说明,这里不要bash,性能偶尔会翻车。

博主有时间把它集成到个人APP 「已开源」

#!/usr/bin/env python3

# -*- coding: utf-8 -*-

"""

纯 Python · C 源码调用图分析器

依赖:universal-ctags cflow (PATH 中即可)

用法:python3 cflow_analyser.py

"""

import json

import logging

import subprocess

import re

from pathlib import Path

logging.basicConfig(level=logging.INFO, format="%(levelname)s | %(message)s")

log = logging.getLogger("analyser")

# ---------- 工具调用 ----------

def run(cmd: list, cwd=None, check=True) -> str:

"""统一 subprocess 封装,失败时自动打印 stderr"""

log.debug("RUN: %s", " ".join(cmd))

try:

cp = subprocess.run(

cmd, cwd=cwd, capture_output=True, text=True, encoding="utf-8", check=check

)

return cp.stdout

except subprocess.CalledProcessError as e:

log.error("命令失败:%s\nstderr=%s", " ".join(cmd), e.stderr)

if check:

raise

return "" # 允许跳过

# ---------- 1. cflow 文本格式 ----------

def cflow_posix(c_files: list[Path]) -> str:

"""返回 cflow --format=posix --brief 原始文本"""

if not c_files:

return ""

cmd = ["cflow", "--format=posix", "--brief", "--level=2"] + [str(f) for f in c_files]

# GNU 才支撑 --level,旧版报错就砍掉

try:

return run(cmd)

except subprocess.CalledProcessError:

cmd.remove("--level=2")

return run(cmd)

# ---------- 2. 解析 posix ----------

RE_POSIX = re.compile(

r"^(?P<caller>\w+)\(\).*? at .*?:\d+.*?>:$"

) # 匹配 caller 行

def parse_posix(text: str) -> dict[str, list[str]]:

"""把 posix 文本转成 caller->[callee...]"""

call_map: dict[str, list[str]] = {}

current = None

for line in text.splitlines():

line = line.strip()

if not line:

continue

m = RE_POSIX.match(line)

if m: # 新的 caller

current = m.group("caller")

continue

# 缩进行就是 callees

if current and line.startswith("HAL_"):

continue # 过滤 HAL 库噪音

callee = line.split("()")[0].strip()

if callee and callee != current:

call_map.setdefault(current, []).append(callee)

return call_map

# ---------- 3. ctags 拿符号 ----------

def ctags_symbols(c_file: Path) -> list[dict]:

cmd = [

"ctags",

"--output-format=json",

"--fields=+KnzS",

"--kinds-c=+f",

str(c_file),

]

out = run(cmd, check=False)

return [json.loads(l) for l in out.splitlines() if l.strip()]

# ---------- 4. 主流程 ----------

def main():

root = Path(input(" 源码目录: ")).expanduser().resolve()

if not root.is_dir():

log.error("目录不存在"); return

report = root / "callgraph.md"

c_files = sorted(root.rglob("*.c"))

h_files = sorted(root.rglob("*.h"))

# ---- 4.1 调用图 ----

log.info("正在生成调用图...")

posix_txt = cflow_posix(c_files)

call_map = parse_posix(posix_txt)

# ---- 4.2 符号表 ----

log.info("正在提取符号...")

symbols: dict[Path, list[dict]] = {}

for f in c_files + h_files:

symbols[f] = ctags_symbols(f)

# ---- 4.3 写报告 ----

with report.open("w", encoding="utf-8") as md:

md.write("# C 源码分析\n\n")

md.write(f"目录:`{root}` \n")

md.write(f"生成时间:{subprocess.check_output(['date']).decode().strip()}\n\n")

md.write("## 目录树\n```\n")

md.write(run(["tree", "-L", "2"], cwd=root))

md.write("```\n\n")

md.write("## 函数列表\n")

idx = 0

for f, syms in symbols.items():

for sym in syms:

if sym.get("kind") != "function":

continue

idx += 1

name = sym["name"]

line = sym.get("line", 0)

md.write(f"{idx}. `{name}` *{f.relative_to(root)}:{line}*\n")

md.write("\n## 调用关系\n")

for caller, callees in call_map.items():

md.write(f"\n`{caller}()` \n")

for c in callees:

md.write(f"├─▶ `{c}()` \n")

log.info("✅ 报告已生成 → %s", report)

if __name__ == "__main__":

main()

posted @ 2026-01-28 10:52  yangykaifa  阅读(0)  评论(0)    收藏  举报