尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
SootTest实战:Java静态分析从字节码到调用图与ICFG
简介SootTest 是一份面向 Java 静态分析与编译优化学习者的实战示例资源基于开源分析框架 Soot演示如何生成调用图与过程间控制流程图ICFG。它适合已掌握 Java 基础、希望深入字节码分析与程序结构理解的开发者用于理解方法调用关系、跨方法数据流与控制流特性为性能优化和缺陷检测打基础。压缩包共 23 个文件约 12.27MB包含 10 个 class 编译产物、3 个 java 源码、2 个 dot 图形输出、1 个依赖 jar 及 Eclipse 工程配置等覆盖从源码到可视化结果的完整链路。项目以 SootTest-master 为目录主体内含 testers 示例模块与 sootOutput 输出目录可直观看到调用图与 ICFG 的生成效果。目前已有 1440 人学习下载适合作为 Soot 入门的练手项目帮助读者快速掌握场景构建、控制流图与调用图 API 的使用方式。1. 从一行命令到一张图SootTest 到底在解决什么问题如果你写过 Java 静态分析大概率经历过这样的场景接手一个十几万行的老项目想搞清楚某个接口到底调了哪些方法、异常从哪一层抛出来、哪些分支永远走不到。靠 IDE 的 Find Usages 一层层点点到第三层就迷路了。SootTest 这个项目就是冲着这个痛点来的——它用 Soot 这个字节码分析框架把编译后的 class 文件直接转成调用图Call Graph和过程间控制流图ICFG让你从「人肉追代码」变成「看图说话」。它适合两类人一类是做代码审计、漏洞挖掘、死代码检测的需要程序依赖关系做底图另一类是在学编译原理或程序分析课程想找一个能跑起来、能改参数的实战 Demo。项目本身不复杂但麻雀虽小Soot 的 Scene、Spark、Jimple 这些核心概念一个不少。下面我按「能干什么 → 怎么跑 → 怎么改」的顺序把这份源码拆开讲一遍。2. 环境搭建与首次运行把 class 文件喂给 Soot2.1 依赖与目录结构SootTest 是一个标准的 Maven 工程核心依赖就两个soot 主包和 slf4j 日志门面。我一般会在pom.xml里锁定 Soot 的版本因为不同版本 API 差异不小尤其是Scene和Options的调用方式。dependencies !-- Soot 核心负责字节码解析与图构建 -- dependency groupIdorg.soot-oss/groupId artifactIdsoot/artifactId version4.4.1/version /dependency !-- 日志门面Soot 内部会打不少调试信息 -- dependency groupIdorg.slf4j/groupId artifactIdslf4j-simple/artifactId version1.7.36/version /dependency /dependencies目录结构很直白src/main/java下放分析入口和工具类src/main/resources放 Soot 的配置文件target/classes是编译产物。如果你要分析的第三方 jar 不在 Maven 仓库里常见做法是在项目根目录建一个libs文件夹运行时通过-cp参数把 jar 路径传进去。2.2 配置 Soot 的入口参数Soot 的入口是soot.Main但更灵活的方式是在代码里直接操作Options和Scene。下面这段是 SootTest 里最核心的初始化逻辑我加了逐行注释// 设置 Soot 的 classpath告诉它去哪里找待分析的类 Options.v().set_soot_classpath( target/classes: // 本项目编译产物 /path/to/your/app.jar // 待分析的目标 jar ); // 指定要分析的类支持通配符比如 com.example.* Options.v().set_process_dir( Collections.singletonList(/path/to/your/app.jar) ); // 开启全程序分析模式这是生成调用图的前提 Options.v().set_whole_program(true); // 设置输出格式为 Jimple方便后续遍历中间表示 Options.v().set_output_format(Options.output_format_jimple); // 允许 Phantom 引用避免因为缺少依赖类而直接崩溃 Options.v().set_allow_phantom_refs(true); // 加载基础类比如 java.lang.*否则连 String 都识别不了 Scene.v().loadNecessaryClasses();这里有几个参数值得展开说。set_whole_program(true)是必须的Soot 默认只做单类分析不开全程序模式就拿不到跨方法的调用边。set_allow_phantom_refs(true)是个后悔药——当你的目标 jar 依赖了某个没传进来的库Soot 会为缺失的类生成一个占位符而不是抛异常代价是这部分调用关系会丢失所以生产环境还是要把依赖补全。loadNecessaryClasses()这一步会触发类加载和 Jimple 体生成耗时跟项目规模成正比十万行级别的项目大概要等十几秒。2.3 生成调用图并导出初始化完成后生成调用图只需要一行// 使用 Spark 算法构建调用图比 CHA 精度高 CallGraph callGraph Scene.v().getCallGraph(); // 遍历所有调用边打印调用者和被调用者 for (EdgeUnit, SootMethod edge : callGraph) { SootMethod src edge.src().getMethod(); // 调用方方法 SootMethod tgt edge.tgt(); // 被调用方方法 System.out.println(src.getSignature() -- tgt.getSignature()); }Scene.v().getCallGraph()默认走的是 CHAClass Hierarchy Analysis精度较低但速度快。如果你在配置里显式设置了Options.v().setPhaseOption(cg.spark, enabled:true)Soot 会切换到 Spark 算法能处理虚方法调用和接口分派精度提升明显代价是内存占用翻倍。我实测过一个 5 万方法左右的项目CHA 跑完 8 秒Spark 要 40 秒左右内存从 1G 涨到 3G。所以如果你的机器内存紧张或者只是做粗粒度筛选CHA 够用了。导出格式上SootTest 默认打印到控制台但更实用的做法是输出成 DOT 格式用 Graphviz 渲染成图片// 将调用图导出为 DOT 文件 soot.util.dot.DotGraph dot new soot.util.dot.DotGraph(callgraph); for (EdgeUnit, SootMethod edge : callGraph) { String src edge.src().getMethod().getSignature(); String tgt edge.tgt().getSignature(); dot.drawEdge(src, tgt); // 添加一条有向边 } dot.plot(output/callgraph.dot); // 写入文件生成的.dot文件用dot -Tpng callgraph.dot -o callgraph.png就能转成图片。不过要注意大项目的调用图节点动辄上万直接渲染会糊成一团常见做法是按包名或类名做过滤只画你关心的子图。3. 核心模块拆解从 Jimple 到 ICFG 的完整链路3.1 Jimple 中间表示Soot 的通用语言Soot 不直接操作 Java 字节码而是先把它转成 Jimple——一种三地址码形式的中间表示。每个方法体在 Jimple 里就是一条条Stmt组成的链每条语句最多涉及一个操作。理解 Jimple 是改 SootTest 的前提因为后面所有图分析都建立在它上面。// 获取某个方法的 Jimple 体 SootMethod method Scene.v().getMethod(com.example.Demo: void run()); Body body method.retrieveActiveBody(); // 遍历方法体中的每一条语句 for (Unit unit : body.getUnits()) { Stmt stmt (Stmt) unit; // 判断语句类型赋值、调用、返回、分支等 if (stmt.containsInvokeExpr()) { // 提取方法调用表达式 InvokeExpr invoke stmt.getInvokeExpr(); System.out.println(调用方法: invoke.getMethod().getSignature()); } }retrieveActiveBody()会触发 Jimple 体的构建如果方法没有方法体比如 native 方法这里会返回 null需要做判空。body.getUnits()返回的是一个链式结构顺序就是字节码的执行顺序。containsInvokeExpr()用来筛选出方法调用语句这是构建调用图的基础。实际改造时你可能会想过滤掉 JDK 内部调用只保留业务代码那就在循环里加一个包名前缀判断。3.2 过程间控制流图把调用边和控制边拼起来ICFG 的本质是把每个方法的 CFG 通过调用边连接起来。Soot 本身不直接提供 ICFG 的现成类SootTest 里的做法是手动组合先拿到每个方法的 CFG再根据调用图把 caller 的调用点和 callee 的入口连起来。// 构建 ICFG 的核心逻辑 UnitGraph cfg new BriefUnitGraph(body); // 单个方法的控制流图 // 遍历 CFG 中的每条边 for (Unit unit : cfg) { // 如果当前节点是方法调用 if (((Stmt) unit).containsInvokeExpr()) { SootMethod callee ((Stmt) unit).getInvokeExpr().getMethod(); // 找到 callee 的入口节点建立跨过程边 if (callee.hasActiveBody()) { Unit entry callee.getActiveBody().getUnits().getFirst(); // 这里记录一条从 unit 到 entry 的边 icfgEdges.add(new Edge(unit, entry)); } } }BriefUnitGraph是最常用的 CFG 实现它只保留必要的分支和跳转边不包含异常边。如果你需要分析异常传播路径得换成ExceptionalUnitGraph它会额外生成 throw 到 catch 的边图规模会大不少。callee.hasActiveBody()这个判断很关键——如果被调用方法没有方法体比如接口方法或 native 方法就没法连边直接跳过。实际项目中接口调用占很大比例Spark 算法能根据类型推断把接口调用解析到具体实现类这也是为什么做 ICFG 时建议开 Spark。3.3 调用图算法选型CHA、Spark 与 VTA 的取舍Soot 内置了多种调用图构建算法SootTest 默认用 CHA但你可以通过参数切换。下面这张表是我在实际项目中总结的对比算法精度速度内存适用场景CHA低快小快速筛选、粗粒度分析Spark高慢大漏洞挖掘、精确依赖分析VTA中中中平衡精度与性能切换方式是在Scene.v().loadNecessaryClasses()之前加一行// 启用 Spark关闭默认的 CHA Options.v().setPhaseOption(cg.spark, enabled:true); // 开启点对点分析精度更高但更慢 Options.v().setPhaseOption(cg.spark, on-fly-cg:true);on-fly-cg:true表示在构建调用图的同时做指向分析能处理更复杂的虚方法分派但耗时也会增加。如果你的目标项目里接口和继承层次很深建议开这个选项如果只是分析工具类之间的调用CHA 足够了。4. 避坑与排查Soot 分析中常见的五类翻车4.1 类找不到导致分析中断现象运行时报Exception in thread main java.lang.RuntimeException: This operation requires resolving level SIGNATURES或者大量Phantom class警告。原因Soot 的 classpath 没配全目标 jar 依赖的第三方库没传进去导致类解析失败。解决把目标项目的所有依赖 jar 都加到set_soot_classpath里。Maven 项目可以用mvn dependency:copy-dependencies把依赖拉到target/dependency然后把这个目录拼进 classpath。如果实在凑不齐至少开set_allow_phantom_refs(true)让分析能跑完但要清楚丢失的那部分调用关系会影响结果。4.2 调用图边数爆炸现象一个几千行的项目调用图边数几十万DOT 文件几百兆Graphviz 直接卡死。原因CHA 算法对虚方法调用做了过度近似把所有可能的实现类都连了边导致图规模膨胀。解决三个方向。一是换 Spark 算法提高精度减少虚假边二是在遍历边的时候加过滤只保留业务包名下的调用三是用Scene.v().getReachableMethods()先做可达性裁剪把不可达的方法排除掉再建图。4.3 内存溢出现象分析大项目时抛OutOfMemoryError: Java heap space。原因Soot 会把所有类的 Jimple 体加载到内存加上 Spark 的指向分析数据结构内存消耗远超预期。解决启动时加-Xmx4g甚至-Xmx8g。如果还是不够可以开Options.v().set_keep_line_number(false)和Options.v().set_keep_offset(false)减少元数据存储。另外分析完一个模块后及时调Scene.v().releaseActiveBodies()释放方法体。4.4 接口调用解析不到实现类现象调用图里只看到interface.method()的边没有具体实现类的边。原因CHA 不做指向分析无法确定接口变量实际指向哪个对象。解决启用 Spark 并开启on-fly-cg:true。如果还是解析不到检查目标类是否在 classpath 里以及接口的实现类是否被set_process_dir包含。4.5 输出格式乱码或缺失现象导出的 DOT 文件里方法签名包含特殊字符Graphviz 渲染报错。原因Java 方法签名里的、、$等字符在 DOT 格式里有特殊含义。解决在写入 DOT 之前对节点名做转义把替换成_替换成_或者用双引号把整个签名包起来。SootTest 里我一般会写一个escapeDot()工具方法统一处理。5. 二次开发改造从调用图到自定义分析规则5.1 扩展点一按包名过滤子图原始项目输出全量调用图实际用的时候往往只关心某几个包。改造思路是在遍历边的时候加一层过滤// 只保留 com.example.service 包下的调用关系 String targetPackage com.example.service; for (EdgeUnit, SootMethod edge : callGraph) { String srcClass edge.src().getMethod().getDeclaringClass().getName(); String tgtClass edge.tgt().getDeclaringClass().getName(); if (srcClass.startsWith(targetPackage) || tgtClass.startsWith(targetPackage)) { dot.drawEdge(escapeDot(srcClass), escapeDot(tgtClass)); } }这样导出的图节点数能降一个数量级渲染出来也看得清。如果你要做分层分析可以按包名把图拆成多个子图分别导出。5.2 扩展点二标记敏感方法调用做安全审计时经常需要定位哪些方法调用了Runtime.exec、ProcessBuilder这类敏感 API。可以在遍历调用边时加一个敏感方法名单// 敏感方法签名列表 SetString sensitiveMethods new HashSet(Arrays.asList( java.lang.Runtime: java.lang.Process exec(java.lang.String), java.lang.ProcessBuilder: java.lang.Process start() )); for (EdgeUnit, SootMethod edge : callGraph) { SootMethod tgt edge.tgt(); if (sensitiveMethods.contains(tgt.getSignature())) { // 标记这条边为高危用红色输出 dot.drawEdge(escapeDot(edge.src().getMethod().getSignature()), escapeDot(tgt.getSignature()), colorred); } }DotGraph.drawEdge支持传属性字符串colorred能让 Graphviz 把这条边画成红色。实际项目中敏感方法名单可以从配置文件读方便随时增删。5.3 扩展点三结合 ICFG 做数据流追踪调用图只告诉你「谁调了谁」ICFG 才能告诉你「数据怎么流」。一个实用的改造是在 ICFG 上做简单的污点传播从 source 方法出发沿着控制流边和调用边往下走看能不能到达 sink 方法。// 简化的污点传播从 source 出发做 BFS QueueUnit worklist new LinkedList(); SetUnit visited new HashSet(); worklist.add(sourceUnit); while (!worklist.isEmpty()) { Unit current worklist.poll(); if (visited.contains(current)) continue; visited.add(current); // 如果到达 sink 方法记录一条污点路径 if (isSink(current)) { System.out.println(发现污点路径: current); } // 沿着 ICFG 边继续传播 for (Unit succ : icfg.getSuccsOf(current)) { worklist.add(succ); } }icfg.getSuccsOf(current)返回当前节点的所有后继包括方法内的控制流后继和跨过程的调用后继。这个 BFS 框架很粗糙没有做别名分析和字段敏感处理但作为二次开发的起点足够了。你可以在此基础上加路径记录、加 sanitizer 判断、加字段访问处理逐步逼近一个可用的污点分析器。5.4 验证改造效果的方法改完代码后怎么确认没跑偏我的习惯是准备一个小型测试用例包含三个类A 调用 BB 调用 CC 里有一个敏感方法。跑完分析后检查三件事调用图里 A→B→C 的边是否完整ICFG 里跨过程边是否正确连接敏感方法是否被正确标记。如果这三项都对再拿真实项目跑一遍对比改造前后的节点数和边数变化确认过滤逻辑生效。从那以后我每次改 Soot 分析逻辑都强制先跑这个小用例确认基础链路没断再上大项目。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

AutoRemesher:3 步完成自动四边形网格重划分,高模秒变低模

AutoRemesher:3 步完成自动四边形网格重划分,高模秒变低模

AutoRemesher:3 步完成自动四边形网格重划分,高模秒变低模 【免费下载链接】autoremesher Automatic quad remeshing tool 项目地址: https://gitcode.com/GitHub_Trending/au/autoremesher AutoRemesher 是一款跨平台自动四边形网格重划分工具&a…

📅 2026/10/20 1:59:16
GEFCOM2014-EPFL负荷数据集:电力时序建模的工业级实战指南

GEFCOM2014-EPFL负荷数据集:电力时序建模的工业级实战指南

简介:本资源是面向数据科学学习者与电力行业建模人员的GEFCOM2014能源负荷预测实战包,聚焦R语言环境下的时间序列建模与预测全流程。资源提供EPFL参与构建的多地区小时级负荷数据集及配套R分析框架,覆盖数据加载、可视化(ggplot2/…

📅 2026/10/20 1:59:16
智能瘦身系统怎么做?SpringBoot+小程序从零到一全流程拆解

智能瘦身系统怎么做?SpringBoot+小程序从零到一全流程拆解

在校招季和毕业季,我经常被问到同一个问题:“智能瘦身系统”这个题目到底该怎么做?后台挂Java和SpringBoot,前端接小程序,名字还挂着“智能”和“健康”,听起来像是个健康类APP,可拿到题目之后很…

📅 2026/10/20 1:59:16
MORE NEWS

更多资讯

📰

RAGE引擎换代:GTA5增强版光追剖析与GTA6渲染革命

RAGE 引擎正在经历它诞生以来最关键的一次换代。2025 年 3 月,《GTA5》增强版终于登陆 PC,光追阴影、光追反射、光追环境光遮蔽这三样东西第一次以官方特性形式出现在洛圣都;而《GTA6》预告片里那些令人印象深刻的云层、海面和植被&#xff0…

📰

【信息科学与工程学】【安全领域】第十一篇 安全产品03安全沙箱

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

专科生降AI率工具与避坑指南:从检测原理到实操全解析

最近好几个专科院校的同学来问我,我明明是自己写的初稿,怎么一查AI率就飙到百分之七八十?也有人花几十块钱去买什么一键降AI服务,回来一看句子根本读不通。这种问题太常见了。所以今天专门写一篇给专科生的降AI率工具与避坑指南&a…

📰

基于EFAST的DHSVM水文模型参数全局敏感性分析实践

简介:EFAST_DHSVM share.zip是一套面向水文建模与参数敏感性分析研究的工具包,聚焦DHSVM动态水文模型的EFAST法(基于快速傅里叶变换的敏感性因子估计)。DHSVM模拟土壤、植被与地下水间水分动态,其参数不确定性对预测结…

📰

用EFAST对DHSVM水文模型做全局敏感性分析:参数率定不再靠玄学

简介:针对DHSVM水文模型参数不确定性带来的预测偏差问题,这套工具包基于EFAST快速傅里叶变换敏感性分析法,系统评估各输入参数对模拟输出的影响程度,适合水文建模人员、水资源管理者和气候影响研究人员开展参数校核与模型诊断。压…

📰

NaCl沙盒全解析:原理、工具链与WebAssembly迁移

简介:面向Chrome插件与Native Client应用开发者,nacl_google是聚焦多Pepper版本编译支持的SDK工具套件,可针对指定最低Chrome版本完成PNaCl、传统NaCl及基于glibc的架构特定应用编译,解决工具链版本匹配与更新维护问题。包体共36个…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬