Soot 安装和简单使用

目录

前言

一、Soot 的下载和安装

1.1 在命令行中使用 Soot

1.2 在项目中使用 Soot

二、使用 Soot 生成中间代码 (IR)

三、使用 Soot 进行 Java 类插桩

四、使用 Soot 生成控制流图 (CFG)

4.1 按语句划分的控制流程图

4.2 按基本块划分的控制流程图

五、Graphviz 工具的安装和使用

5.1 Graphviz 工具的安装

5.2 Graphviz 工具的使用

六、其他 CFG 图绘制方法

6.1 使用工具

6.2 使用在线网站


前言

Soot 是 McGill 大学的 Sable 研究小组自 1996 年开始开发的 Java 字节码分析工具,它提供了多种字节码分析和变换功能。通过它可以进行过程内和过程间的分析优化,以及程序流图的生成;还能通过图形化的方式输出,让用户对程序有个直观的了解。尤其是做单元测试的时候,可以很方便的通过这个生成控制流图然后进行测试用例的覆盖,显著提高效率。Soot 项目已经不再继续维护,其最高支持到 Java 9 版本。如果要在更新的项目中使用,请配置项目组最新开发和维护的 SootUp 项目(https://soot-oss.github.io/SootUp/)。

一、Soot 的下载和安装

1.1 在命令行中使用 Soot

Soot 项目在 Github 上的地址为:https://github.com/Sable/soot。目前来说,要使用 Soot 有三种途径,分别是命令行、添加到项目以及 Eclipse 插件(不推荐)。

可以选择使用 Github 上的 Release 或者 Git 克隆项目到本地,然后使用 Maven 或者 IDEA -maven 构建项目。

从 Github 上下载源

可以在这里(https://repo1.maven.org/maven2/org/soot-oss/soot/)下载最新的 soot jar 包,我下载的是 4.4.1 版本中的 sootclasses-trunk-jar-with-dependencies.jar 包。

Soot 免构建版本

这个包应该自带了 soot 所需要的所有依赖。下载完成后使用命令提示符进入 jar 文件所在的文件夹(我的是 D:\programing\sootTest),输入以下命令:

java -cp sootclasses-trunk-jar-with-dependencies.jar soot.Main

输出如下图:

Soot 工具初始化

在输入 -h 命令可以回显帮助信息:

java -cp sootclasses-trunk-jar-with-dependencies.jar soot.Main -h

参数帮助信息

1.2 在项目中使用 Soot

从 Github 上 Soot 项目的简介可知,Soot 一般配合 Maven 来进行部署,相关的 POM 文件依赖添加语句如下(在 <dependencies> 下追加 <dependency>):

<dependencies><dependency><groupId>ca.mcgill.sable</groupId><artifactId>soot</artifactId><version>4.4.1</version></dependency>
</dependencies>

二、使用 Soot 生成中间代码 (IR)

Soot 是 Java 优化框架,提供 4 种中间代码来分析和转换字节码。

Baf:精简的字节码表示,操作简单
Jimple:适用于优化的 3-Address 中间表示
Shimple:Jimple 的 SSA 变体
Grimple:适用于反编译和代码检查的 Jimple 汇总版本。

由于在命令行中调用 Soot 是最为简单的模式,所以后文均以在命令行中使用 Soot 为基准。

我的目标是将 Java 源文件转化为 Jimple 以发现程序编译中的问题和规律。因此本文的重点就在这里,我先在 soot.jar 所在的文件夹下新建了一个 Java 源文件 HelloWorld.java 如下图所示:

// HelloWorld.java
public class HelloWorld {public static void main(String[] args) {System.out.println("hello");}
}

因为我使用的 Java 版本是 JDK1.8(Java 8),根据 Soot 提示,默认输入是 class 文件,所以我先用 javac 命令将 HelloWorld.java 编译为 HelloWorld.class。

javac HelloWorld.java

下面我们尝试将上面得到的 class 文件作为输入传给 soot 。

java -cp sootclasses-trunk-jar-with-dependencies.jar soot.Main -pp -cp .  HelloWorld

得到的结果没有报错,但是也无事发生,这是因为 soot 需要通过 -f 属性指定输出的类型,这里我们将输出类型指定为 Jimple,查询文档之后得知要添加 -f J 以确定输出格式,最终的语句如下:

java -cp sootclasses-trunk-jar-with-dependencies.jar soot.Main -f J -pp -cp .  HelloWorld

该命令在 soot 工具所在目录下生成了一个 sootOutput 文件夹,里面有一个 HelloWorld.jimple 文件,使用 Idea 编辑器打开这个文件,得到的内容如下,这就是一个最基本的 HelloWorld.java文件所形成的 Jimple 码。

public class HelloWorld extends java.lang.Object
{public void <init>(){HelloWorld r0;r0 := @this;specialinvoke r0.<init>();return;}public static void main(java.lang.String[]){java.io.PrintStream $r0;java.lang.String[] r1;r1 := @parameter0;$r0 = java.lang.System.out;$r0.println("hello");return;}
}

上文使用 sootclasses-trunk-jar-with-dependencies.jar 也可以改用 sootclasses-trunk.jar (只不过用这个 sootclasses-trunk.jar 必须配置好 classpath 环境变量),但是我不建议这么做。

三、使用 Soot 进行 Java 类插桩

(该小节摘录自官方教程的机器翻译内容)

我将首先展示一个例子。从这个示例中,您可以了解到如何使用 Soot 修改类文件。然后,我将解释在 Soot 中类、方法和语句的内部表示。
在本教程开始前,建议你先掌握 JVM 指令的相关知识。你还应该学习如何使用 Soot 添加局部变量和字段等。这里的类插桩只修改字节码文件,通过注入性能分析代码,来记录程序运行时的重要信息。

任务:计算在运行一个微小的基准测试 TestInvoke.java 时执行了多少条 InvokeStatic 指令。

class TestInvoke {private static int calls=0;public static void main(String[] args) {for (int i=0; i<10; i++) {foo();}System.out.println("我使用了 " + calls + " 个静态调用");}private static void foo(){calls++;bar();}private static void bar(){calls++;}
}

为了实现计数器,我编写了一个名为 MyCounter 的辅助类:

/* 计数器类 */
public class MyCounter {/* 计数器变量存储,初始化为0 */private static int c = 0;/*** 将计数器增加多少(howmany)* @param howmany :计数器的增量。*/public static synchronized void increase(int howmany) {c += howmany;}/*** 报告计数器内容。*/public static synchronized void report() {System.err.println("计数 : " + c);}
}

现在,我要创建一个包装器类,在 Soot 中添加一个阶段,用于插入分析指令,然后调用 Soot.Main.main()。该驱动程序类的 main 方法将名为" jtp.instrumenter "的转换阶段添加到 Soot " jtp "包中。

 PackManager 是对 Soot 注册的不同阶段的类的包装。当 MainDriver 调用  soot.Main.main 时,  Soot 将从 PackManager 得知注册了一个新阶段,并且标志着一个新阶段的 internalTransform 方法会被 Soot 调用。

MainDriver.java :

/* 用法: java MainDriver [soot-options] appClass*//* 导入必要的 soot 包 */import soot.*;public class MainDriver {public static void main(String[] args) {/* 检查参数 */if (args.length == 0) {System.err.println("用法: java MainDriver [options] classname");System.exit(0);}/* 通过调用 Pack.add 方法添加一个阶段(phase)到 transformer 包 */Pack jtp = PackManager.v().getPack("jtp");jtp.add(new Transform("jtp.instrumenter",new InvokeStaticInstrumenter()));/* 把控制权交给 Soot 来处理所有选项,* InvokeStaticInstrumenter.internalTransform 将被调用。*/soot.Main.main(args);}}

instrumenter(插桩)的实际实现扩展了一个抽象类 BodyTransformer。它实现了 internalTransform 方法,该方法采用方法体(指令)和一些选项。主要操作发生在该方法中。根据您的命令行选项,Soot 构建一个类列表(这也意味着方法列表),并通过传入每个方法的主体来调用InvokeStaticInstrumenter.internalTransform

InvokeStaticInstrumenter.java :

/** InvokeStaticInstrumenter 在程序中的 INVOKESTATIC * 字节码之前插入计数指令。插桩后的程序将* 报告在一次运行中发生了多少静态调用。* * 目标:* 在静态调用指令之前插入计数器指令。* 在程序正常退出点之前报告计数器数值。* * 方法:* 1. 创建一个计数器类,它有一个计数器字段,和* 一个报告方法。* 2. 获取每个方法体,遍历每个指令,并且* 在 INVOKESTATIC 之前插入计数指令。* 3. 调用计数器类的报告生成方法。* * 从这个例子中可以学到的东西:* 1. 如何使用 Soot来测试 Java 类。* 2. 如何在类中插入分析指令。*//* InvokeStaticInstrumenter 扩展了抽象类 BodyTransformer,*   并实现 internalTransform 方法。*/import soot.*;import soot.jimple.*;import soot.util.*;import java.util.*;public class InvokeStaticInstrumenter extends BodyTransformer{/* 一些内部字段 */static SootClass counterClass;static SootMethod increaseCounter, reportCounter;static {counterClass    = Scene.v().loadClassAndSupport("MyCounter");increaseCounter = counterClass.getMethod("void increase(int)");reportCounter   = counterClass.getMethod("void report()");}/* InternalTransform 遍历方法体并将计数器指令插入* 在 INVOKESTATIC 指令之前。*/protected void internalTransform(Body body, String phase, Map options) {// 主体的方法SootMethod method = body.getMethod();// 调试System.out.println("instrumenting method : " + method.getSignature());// 将主体的单元作为一个链(单元链)Chain units = body.getUnits();// 获取单元的快照迭代器,因为我们将在// 迭代链时对其进行变异。//Iterator stmtIt = units.snapshotIterator();// 用于迭代每个语句的典型 while 循环while (stmtIt.hasNext()) {// 回溯一个声明Stmt stmt = (Stmt)stmtIt.next();// 语句有很多种类型,这里只是// 对包含 InvokeStatic 的语句感兴趣// 注意:有两种语句可能包含// invoke 表达式:InvokeStmt 和 AssignStmtif (!stmt.containsInvokeExpr()) {continue;}// 取出 invoke (调用)表达式InvokeExpr expr = (InvokeExpr)stmt.getInvokeExpr();// 现在跳过非静态调用if (! (expr instanceof StaticInvokeExpr)) {continue;}// 现在我们到达真正的指令// 调用 Chain.insertBefore() 在其之前插入指令//// 1. 首先,新建一个 invoke 表达式InvokeExpr incExpr= Jimple.v().newStaticInvokeExpr(increaseCounter.makeRef(),IntConstant.v(1));// 2. 然后,构造一个 invoke 语句Stmt incStmt = Jimple.v().newInvokeStmt(incExpr);// 3. 向链中插入新语句// (我们正在对单元链实施变异操作)。units.insertBefore(incStmt, stmt);}// 不要忘记插入报告计数器的指令// 这只发生在 main 方法的退出点之前。// 1. 通过检查签名来检查这是否是 main 方法String signature = method.getSubSignature();boolean isMain = signature.equals("void main(java.lang.String[])");// 2. 重新迭代主体以查找 return 语句if (isMain) {stmtIt = units.snapshotIterator();while (stmtIt.hasNext()) {Stmt stmt = (Stmt)stmtIt.next();// 检查指令是否是带值/不带值的 returnif ((stmt instanceof ReturnStmt)|| (stmt instanceof ReturnVoidStmt)) {// 1. 构造 MyCounter.report() 的 invoke 表达式InvokeExpr reportExpr= Jimple.v().newStaticInvokeExpr(reportCounter.makeRef());// 2. 然后,构造一个 invoke 语句Stmt reportStmt = Jimple.v().newInvokeStmt(reportExpr);// 3. 向链中插入新语句// (我们正在对单元链实施变异操作)。units.insertBefore(reportStmt, stmt);}}}}}

现在,在正式插桩(instrumentation)之前,需要测试一下 instrumenter

[cochin] [621tutorial] java TestInvoke

我使用了 20 个静态调用

运行该 instrumenter

[cochin] [621tutorial] java MainDriver TestInvoke

Soot started on Tue Feb 12 21:22:59 EST 2002

Transforming TestInvoke... instrumenting method : <TestInvoke: void <init>()>

instrumenting method : <TestInvoke: void main(java.lang.String[])>

instrumenting method : <TestInvoke: void foo()>

instrumenting method : <TestInvoke: void bar()>

instrumenting method : <TestInvoke: void <clinit>()>

 

Soot finished on Tue Feb 12 21:23:02 EST 2002

Soot has run for 0 min. 3 sec.

这会将转换后的 TestInvoke.class 放入 ./sootOutput 中。运行这个新转换的基准测试(注意你现在需要在你的类路径上放置 MyCounter.class 文件)

[cochin] [621tutorial] cd sootOutput

[cochin] [621tutorial] java TestInvoke

Exception in thread "main" java.lang.NoClassDefFoundError: MyCounter

        at TestInvoke.main(TestInvoke.java)

[cochin] [621tutorial] cp ../MyCounter.class .

[cochin] [621tutorial] java TestInvoke

我使用了 20 个静态调用

计数 : 20

比较插桩前后的JIMPLE代码:

插桩前:

class TestInvoke extends java.lang.Object{......public static void main(java.lang.String[] ){......label0:staticinvoke <TestInvoke: void foo()>();i0 = i0 + 1;......return;}private static void foo(){......staticinvoke <TestInvoke: void bar()>();return;}private static void bar(){......return;}......}

插桩后:

class TestInvoke extends java.lang.Object{......// 这是主方法public static void main(java.lang.String[] ){......label0:// 这里插入了计数器静态方法staticinvoke <MyCounter: void increase(int)>(1);staticinvoke <TestInvoke: void foo()>();i0 = i0 + 1;......// 这里插入了报告生成器静态方法staticinvoke <MyCounter: void report()>();return;}private static void foo(){......// 这里插入了计数器静态方法staticinvoke <MyCounter: void increase(int)>(1);staticinvoke <TestInvoke: void bar()>();return;}private static void bar(){......return;}......}

我们看到,在每个 staticinvoke 指令之前添加了对 MyCounter.increase(1) 的方法调用,并且在 main 方法的返回指令之前插入了对 MyCounter.report() 的调用。

关于这一部分更多的讲解可以看以下几篇文章:

1. Soot 知识点整理 | fynch3r 的小窝;

2. Soot 使用记录 | Jckling's Blog;

3. 利用 Soot 对 APK 插桩实践 - 博客园;

4. Soot 生成控制流图 - 博客园;

四、使用 Soot 生成控制流图 (CFG)

Soot 利用 AST (抽象语法树)生成程序的控制流程关系。soot.tools.CFGViewer 分析类中的每个方法的控制流并生成 DOT 语言描述的控制流图。我们使用 Graphviz 工具中的 dot 命令将其转换成可视化图形。

任务:使用 soot.tools.CFGViewer 生成 Triangle.class 的控制流图

首先使用 javac 命令编译此源代码文件:

// Triangle.class
package Soot;public class Test {private double num = 5.0;public double cal(int num, String type){double temp=0;if(type == "sum"){for(int i = 0; i <= num; i++){temp =temp + i;}}else if(type == "average"){for(int i = 0; i <= num; i++){temp = temp + i;}temp = temp / (num -1);}else{System.out.println("Please enter the right type(sum or average)");}return temp;}
}

运行 “sootclasses-trunk-jar-with-dependencies.jar” 时,输入文件 Triangle.class 文件的位置与sootclasses-trunk-jar-with-dependencies.jar 在同一目录下。

soot 生成控制流程关系时,有两种增量方式:语句划分和按基本块划分。

4.1 按语句划分的控制流程图

使用下面的命令执行生成按照语句划分的控制流程关系。

# 命令1:按语句划分
java -cp sootclasses-trunk-jar-with-dependencies.jar soot.tools.CFGViewer -cp . -pp Triangle

其中:
(1)“soot.tools.CFGViewer” 表示使用 soot 的控制流图绘制功能
(2)“-cp .” 表示 soot 指明类路径,“.” 表示类路径为当前路径。
(3)Soot 还必须指明 java.lang.Object,可以用 “-pp”,也可以添加 “rj.jar”。
(4)Triangle 指代 Triangle.class,Soot 默认输入 class 文件;当然,你可以用 "–src-prec" 指定输入文件类型。

4.2 按基本块划分的控制流程图

# 命令2:按基本块划分
java -cp sootclasses-trunk-jar-with-dependencies.jar soot.tools.CFGViewer -cp . -pp --graph=BriefBlockGraph Triangle

其中:“–graph=BriefBlockGraph” 表示按基本块划分。

使用基本块划分将使得最终生成的控制流程图分支更少,调理更清晰。但可能忽略一些块内细节的展示。我们在大多数情况下使用基本块划分模式。

五、Graphviz 工具的安装和使用

Graphviz 是开源图形可视化软件。图形可视化是一种将结构信息表示为抽象图形和网络图的方法。它在网络、生物信息学、软件工程、数据库和网页设计、机器学习以及其他技术领域的可视化界面中具有重要应用。

5.1 Graphviz 工具的安装

它的官网服务器是在国外,所以国内浏览会比较慢。

官网链接:Graphviz。

关于安装教程方面,我暂时也没时间截图重新整理一份。索性在 CSDN 找到两篇写的很好的教程,可以结合着看。

1. https://blog.csdn.net/qq_42294351/article/details/119754109

2. https://blog.csdn.net/qq_42257666/article/details/121688656

5.2 Graphviz 工具的使用

Graphviz 画图只需两步:

  1. 创建 .dot 文本文件, 在其中使用 DOT 语言描述图形;这里我们已经有了由 soot 生成的 DOT 文件,可以直接使用 Graphviz 生成可视化图像了。
  2. 使用命令将 dot 文本内容转换为图片:
dot Triangle.dot -T png -o Triangle.png
# -T指定输出类型, 可以指定jpg, gif, svg等
# -o 指定输出文件名, 不指定则输出到标准输出上# 或者:
# dot -T png -o Triangle.png Triangle.dot
# dot -Tpng -o Triangle.png Triangle.dot

按语句划分的(太长了只能截图上传):

长CFG

按基本块划分的:

支持双精度浮点输入的三角形程序(基本块划分)

经过人工优化代码以及修改 DOT 后生成的简化版:

人工简化版 CFG 图

【注】:因为一些原因,我不能展示完整的图像给大家。 

关于 Graphviz 工具和它的 DOT 语法的使用细节可以看这篇文章:

  • Graphviz 绘图 — Graphviz 笔记 (graphviz-note.readthedocs.io);
  • DOT 用法 — Graphviz 笔记 (graphviz-note.readthedocs.io);

六、其他 CFG 图绘制方法

6.1 使用工具

Visustin 工具是一个老牌的多语言流程图生成工具。这款软件是商业化的,虽然他支持多种自然语言,但演示版仅允许免费使用 30 天,并且功能受到限制。

 Visustin 演示版工具的图标:

Visustin 的图标

支持的自然语言列表:

支持的自然语言

样例程序:

测试样例的效果

Visustin 工具生成的控制流图虽然相对准确,但是很不美观。我们可以截图保存并使用 Viso 工具重新画一个 CFG 图。

6.2 使用在线网站

有很多网站提供简单源代码的 CFG 图生成和编辑功能。比如 Code2Flow 网站(code2flow - online interactive code to flowchart converter)。虽然这些网站生成控制流图较为精美且操作简单,但是大多数都不完全免费。

流程图示例

本文发布于:2024.03.25,更新于:2024.03.25.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/768569.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker jupyter 容器中添加matplotlib 中文支持

本教程基于 jupyter/datascience-notebook&#xff0c;适用其他容器。 # 查看所有 Docker 容器 docker ps -a # 进入已经运行的 Jupyter 容器 docker exec -it CONTAINER_ID bash 本例中CONTAINER_ID为2e # 切换到 matplotlib 的字体目录&#xff08;find / -name "…

HTML5和CSS3新特性

Html新增属性 1.新增语义化标签 <header>&#xff1a;头部标签 <nav>&#xff1a;导航标签 <article>&#xff1a;内容标签 <section>&#xff1a;定义文档某个区域 <aside>&#xff1a;侧边栏标签 <footer>&#xff1a;尾部标签 2.…

力扣hot100:994. 腐烂的橘子(多源BFS)

这是一个典型的多源BFS问题&#xff0c;如果初学数据结构的同学&#xff0c;可能第一次不能想到&#xff0c;但是如果做过一次应该就能运用了。      主要思路大概是初始时&#xff0c;多个点进入队列然后进行BFS。将某一等价集合视作同一个起始点&#xff08;超级源点&…

前端学习之用css和html做一个仿淘宝的导航栏

代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>仿淘宝界面案例</title><style>/* 最外层盒子 */.container{width: 270px;height: 385px;border: 1px solid rgb(255, 208, 0);bord…

学习vue3第十节(插槽v-slot)

本节主要介绍一下 v-slot 插槽指令&#xff0c;以及插槽相关内容 1、定义&#xff1a; 子组件给父组件提供使用的一个位置&#xff0c;使用<slot></slot>表示&#xff0c;父组件可以在这个位置填充任何代码&#xff1b; 2、默认插槽 匿名插槽&#xff1a;会自定…

管道疏通房屋补漏官方网站源码-视频搭建教程

[安全]修复jquery低版本的xss安全漏洞&#xff0c;升级用最新版jquery&#xff1b; [新增]后台的登录页、欢迎页支持自定义模板文件&#xff1b; [新增]后台多语言列表管理支持手工同步文档数据&#xff1b; [新增]后台商品发布时&#xff0c;单规格商品支持会员折扣价的设置…

C语言自定义类型联合体和枚举

union n.工会&#xff1b;联邦&#xff0c;联盟&#xff1b;协会&#xff0c;俱乐部&#xff1b;联合&#xff0c;合并&#xff1b; 结婚&#xff0c;婚姻&#xff1b;美利坚合众国&#xff08;the Union&#xff09;&#xff1b; &#xff08;数&#xff09;并&#xff0c;并集…

交互式QGraphicsView(平移/缩放/旋转)

一 简述 Graphics View提供了一个平台&#xff0c;用于大量自定义 2D 图元的管理与交互&#xff0c;框架包括一个事件传播架构&#xff0c;支持场景 Scene 中的图元 Item 进行精确的双精度交互功能。Item 可以处理键盘事件、鼠标按下、移动、释放和双击事件&#xff0c;同时也…

福昕阅读器 PDF 文档基本操作

福昕阅读器 PDF 文档基本操作 References 转至 PDF 顶部 快捷键&#xff1a;Home. 转至 PDF 顶部 快捷键&#xff1a;End. 打开超链接 文本选择工具 -> 手形工具 (Hand Tool) -> 点击超链接 福昕阅读器 同时在多个窗口中打开多个文件 文件 -> 偏好设置 -> 文…

高中信息技术教资刷题笔记_选择题篇

1.信息技术基础 位与字节的换算 模2除法运算 网页保存 进制之间的计算 教你快速学会二进制、十进制、十六进制之间的转换 - 知乎 (zhihu.com) 原码、补码、反码计算 物联网技术 位运算 按位与&#xff1a;同位置为1&#xff0c;则为1&#xff0c;其他都是0按位或&#xff1a;有…

MRC是谁?- 媒体评级委员会 Media Rating Council

在在线广告的世界里&#xff0c;有许多不同的技术和实践用于提供和衡量广告。对于广告商、出版商和营销人员来说&#xff0c;了解这些技术是如何工作的以及如何有效使用这些技术很重要。在这方面发挥关键作用的一个组织是媒体评级委员会&#xff08;MRC&#xff09;。 1. 了解…

Android 项目新建问题总结

title: Android 项目新建问题总结 search: 2024-03-24 tags: “#Android 项目新建问题总结” Android 项目新建问题总结 一、gradle 项目每次都自动下载依赖包到C盘 背景&#xff1a;idea 首次打开一个 gradle 项目&#xff0c;都会在 C 盘下载项目所需的依赖包&#xff0c;但…

Automatic Prompt Engineering

让大模型自己生成prompt&#xff0c;生成提示&#xff08;prompt&#xff09;存在两种不同的操作方式。第一种方式是在文本空间中进行&#xff0c;这种提示以离散的文本形式存在。第二种方式是将提示抽象成一个向量&#xff0c;在特征空间中进行操作&#xff0c;这种提示是抽象…

React高阶组件(HOC)

高阶组件的基本概念 高阶组件&#xff08;HOC&#xff0c;Higher-Order Components&#xff09;不是组件&#xff0c;而是一个函数&#xff0c;它会接收一个组件作为参数并返回一个经过改造的新组件&#xff1a; const EnhancedComponent higherOrderComponent(WrappedCompo…

Gitee删除自己本地仓库

1、打开自己的本地仓库 2、点击管理 3、选择删除仓库 4、将□的内容复制到⭕里

mysql 存储引擎 基本介绍

目录 一 存储引擎概念介绍 &#xff08;一&#xff09;存储引擎概念 &#xff08;二&#xff09;MySQL常用的存储引擎 &#xff08;三&#xff09;存储引擎运作方式 二 MyISAM 存储引擎介绍 &#xff08;一&#xff09; MyISAM 存储引擎特点 1&#xff0c;不支持…

基于51单片机数控直流电压源proteus仿真LCD显示+程序+设计报告+讲解视频

基于51单片机数控直流电压源proteus仿真LCD显示( proteus仿真程序设计报告讲解视频&#xff09; 仿真图proteus7.8及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0072 讲解视频 基于51单片机数控直流电压源proteus仿真程序…

博途建立S7-1200PLC与HMS AB7013Profinet通讯

1、新建一个博图项目1200PLC .CPU 1214C ACDC/RIY 6ES7 214-1BG31-0x80 2、安装GSD文件 Install general station description fle (GsD) GSDMLV2.3-HMS-ABC PROFINET GSD 3、连接PLC 4、在线访问 5、增加访问子网络 6、设定IP地址 7、增加AnyBus模块 8、设定模块的IP地址及…

大语言模型(Large Language Model,LLM)简介

1. 什么是大语言模型 它是一种基于深度学习的人工智能模型&#xff0c;它从大量来自书籍、文章、网页和图像等来源的数据中学习&#xff0c;以发现语言模式和规则&#xff0c;如处理和生成自然语言文本。通常&#xff0c;大语言模型含数百亿&#xff08;或更多&#xff09;参数…

Spring Cloud Gateway Server MVC

之前你如果要用spring cloud gateway &#xff0c;就必须是webflux 的&#xff0c;也就是必须是异步响应式编程。不能和spring mvc 一起使用。现在spring cloud 新出了一个可以不用webflux的gateway。 具体使用mvc的gateway步骤如下 普通的Eureka Client的项目 如果你只是想测…