【软件分析/静态分析】学习笔记02——中间表示Intermediate Representation

news/2025/2/12 14:07:28/

🔗 课程链接:李樾老师和谭天老师的:南京大学《软件分析》课程02(Intermediate Representation)_哔哩哔哩_bilibili

目录

第二章 Intermediate Representation

2.1 编译器与静态分析器的关系(Compilers & Static Analzers)

2.2 AST vs. IR

2.3 IR: 三地址码(Three-Address Code, 3AC)

2.4 真实静态分析器中的3AC:Soot

2.5 静态单赋值(Static Single Assignment, SSA)

2.6 控制流分析(Control Flow Analysis)⭐

2.6.1(Basic Blocks, BB)——建立节点

2.6.2 控制流图 (Control Flow Graph, CFG)——添加边

2.7 总结


第二章 Intermediate Representation

2.1 编译器与静态分析器的关系(Compilers & Static Analzers)

编译器的作用:
        将程序员写的Source Code转换成机器可以识别的Machine Code,并在转换中及时报错。

编译器的主体框架:

        

  1. 通过扫描器(Scanner)做词法分析(Lexical Analysis):根据正则表达式(Regular Expression)检查输入字符是否合法(例如如果是翻译一个英语句子,就是检查字符是否是英文的,单词是否正确/合法),如果通过了词法分析,每个单词就会生成Tokens,给下一步进行分析。
  2. 通过解析器(Parser)做语法分析(Syntax Analysis):通过语法规则(上下文无关文法,Context-Free Grammar)检查语法,通过就转换成抽象语法树AST
  3. 通过类型检查(Type Checker)做语义分析(Semantic Analysis):根据属性语法(Atrribute Grammar)检查语义(期望实现的是识别在英语中的例如Apple eats you,这样语法正确但是语义不对的句子,但是实际上实现的是,例如String的值除以int 这种类型不匹配的错误)如果通过,就生成Decorated AST
  4. 为了进行静态分析优化,要将Decorated AST转换为中间表示(IR),这里的IR一般为三地址码,再进行静态分析,最后通过代码生成器将优化后的代码生成机器码传给机器。

        实际上,静态分析就是做code优化的,静态分析器IR的基础上进行分析。

2.2 AST vs. IR

        

        如图所示

ASTIR
语法树形式的,hight-level,更接近程序源码通常是三地址码的形式,low-level,更接近机器编码
通常与语言有关通常与语言无关
\统一、简洁
缺少控制流信息包含控制流信息
适合做快速的类型检测通常被认为是静态分析的基础

        所以,以三地址码为主要形式的IR是更有利于做静态分析的。

2.3 IR: 三地址码(Three-Address Code, 3AC)

什么是3AC?

        三地址码就是最多有三个地址(address)的表达形式,并且由于这个性质,每个三地址码的右侧最多只能有一个运算符。这里的地址(Adress)可以是以下三种形式之一:

  • 变量名称: a, b 
  • 常量Constant: 3
  • 编译器生成的临时变量: t1, t2

例如:

        t2 = a + b + 3

转换为三地址码的形式就是:

        t1 = a + b

        t2 = t1 + 3

一些常见的3AC形式:

       

        如上图所示,3AC的操作符也包含很多种形式可以是很多种形式:

  • bop 代表的普通二进制运算或逻辑运算(+ - * /  ……)     
  • uop 代表的单运算符(取负数,非……)
  • 也可以没有运算符
  • goto L 无条件的跳转
  • if… goto L 有条件的跳转
  • rop 代表条件操作(>,<,==,……)

        当然这些知识简单的3AC形式,还有更复杂的,我们通过soot这个例子来学习,见2.4

2.4 真实静态分析器中的3AC:Soot

Soot是一个Java优化框架。它提供了四种中间表示法,用于分析和转换Java字节码。其中 Jimple是一种适合优化的类型化3地址中间表示法(typed 3-address code)。这一部分通过Jimple对3AC有更好的了解。

https://github.com/Sable/soot

Tutorials · soot-oss/soot Wiki · GitHub

1. For循环

        如下图所示将一个for循环的java代码转成3ac,这个例子比较简单,可以直接看懂,额外表注的还有关于传递参数时候的3AC表示。

2. Do-while 循环

        以下是do-while循环的3AC,理解起来也比较简单

3. 方法调用

        方法调用相对复杂一点,首先了解一些JVM的知识。

         JVM中主要的方法调用:

  • invokespecial:用于调用实例构造器<init>()方法、私有方法和父类中的方法
  • invokevirtual:调用非私有实例方法,比如 public 和 protected,大多数方法调用属于这一种,在调用的过程中会进行派生(vitual dispatch)
  • invokeinterface:调用接口方法,会检查实现这个接口的对象,但是调用的时候不能做一些优化
  • invokestatic:调用静态方法,
     
  • Java7: invokedynamic -> Java 是静态语言,动态解析出需要调用的方法,然后执行。

参考:5、JVM中的方法调用 - CarBlack - 博客园 (cnblogs.com)

        ② 方法签名(Method Signature)包含类名、 返回值类型、方法名 (参数1  类型,参数2  类型)

        例如:specialinvoke $r3.<java.lang.StringBuilder: void <init>()>(); 这个构造函数的尖括号里的就是一个方法签名,具体含义就是

  •         方法类型:java.lang.StringBuilder 构造函数
  •         方法名字:默认构造函数没有名字,统一叫<init>
  •         参数:没有参数 () 为空
  •         返回值:没有返回值,void

        再如下图中的3AC意思就是:调用$r3的append 方法,将 r1 加进字符串中

         再看3AC的代码可能就会理解一些了,以下的示例代码是在main函数中调用了foo方法,其中foo方法中有两个参数,返回拼接的字符串,先看foo函数的3AC代码

        main函数的3AC代码

 4. Class 类的3AC

         这里不太明白也没关系,反正再看地址码的时候别怕,仔细分析一定是可以看懂的。

2.5 静态单赋值(Static Single Assignment, SSA)

1. SSA的两个特点:

        ① 如下图,SSA里,会给每个变量不同的名字

        

        ② 但是如果是同一个变量在不同的分支呢?在这种情况下,SSA会在合并的时候,引入一个 \OΦ函数,如下图所示,在引入x2 等于这个 Φ 函数,后续使用x2。这个Φ(x0,x1)会根据流的路径来选择是x0 还是x1然后赋值给x2。

        

2. SSA的优点:

  • 程序流信息可以间接体现在不同的变量名上,通过不同的变量名,流的信息被间接地合并到唯一的变量名中
    •  可能有助于提供一些更简单的分析,例如,流不敏感分析通过SSA定义和使用对显式获得流敏感分析的部分精度。(因为流敏感度分析精度虽然高,但是太耗时了,而流不敏感分析就相当于上下文无关的分析,通过单一赋值制就可以获得很久之前就定义过的信息,知道是具体用了那边信息,就相当于获取到了上下文信息。)
  • Define-and-Use pairs(定义-使用对)明确
    • 在一些按需任务中启用更有效的数据事实存储和传播。
    • 一些优化任务在SSA上表现更好(例如,条件常数传播,全局值编号)。

3. SSA的缺点:

  • 如果程序有太多分叉,则会引入太多变量和Φ函数
  • 在最后还要转为机器码执行,在转换回去的时候由于过多的赋值操作产生性能方面的影响。

2.6 控制流分析(Control Flow Analysis)⭐

  • 在进行控制流分析的时候,通常采用建立控制流图(CFG)
  • CFG是静态分析的基础结构
  • CFG中的节点可以是单个的3AC,不过通常用Basic Blocks(BB),如下图所示

2.6.1(Basic Blocks, BB)——建立节点

1. BB的基本概念:        

        Basic Blocks(BB)是连续的满足以下性质的最大长度三地址指令的组成单元:
           -  这个Block的入口只有一个,就是第一个指令,不能从其他地方进来。
           -  这个Block的出口只有一个,就是最后一条指令,不能从其他地方出去。

         如下图所示,仅能从第一条指令出进去该Block,并且仅能从最后一条指令出去,中间的指令不允许进出,能满足这些性质的、连续的、最大的指令集合就是一个Basic Blocks。
        

2. 举个例子🌰

        如下图所示,怎么样来分代码块呢?
        

  • 首先,程序从(1)入口, (2) 没有其他入口和出口,但是(3) 有额外的入口,即从(11)跳转进入,所以(3) 不能和(1)(2)组合在一起,所以 (1)(2)可以组成一个Basic Block
    ——>得出结论如果一个指令是某个跳转的目标,则该指令只能作为一个BB的入口
  • 在看,(3)作为一个Block的入口,(4)没有入口,有个出口,可以加入,但是(5)不能加入,因为(4)已经有出口了,不能作为一个BB的中间指令,所以(3)(4)可以组成一个Basic Block
    ——>得出结论如果一个指令紧跟着一个跳转,则该指令只能作为一个BB的入口

        

3. 怎么建立BB

        通过上述例子的思考,就可以来尝试设计建立Basic Blocks的算法了,伪代码如下:

INPUT: 一个三地址指令序列 P
OUTPUT: P 的Basic Block列表
Method:(1) 确定 P 的入口 (leaders)· P 的第一句是一个入口· 任何一个跳转的目标指令,是一个入口· 任何一个跳转的下一条指令,是一个入口(2) 建立BBs for P· 每个入口到下一个入口之间就是一个BB

        练习:根据算法的设计思想,再看一下2中的例子,完成程序BBs的划分:

        

4. 将跳转到指令标签替换为跳转到基本块

         经过上述的划分,我们就把一个程序P 分割成了多个BBs,并且由于BB的定义导致跳转目标必定是某个BB的入口指令,所以可以将跳转目标的指令标签的形式,换成BB的编号。如下图所示,将原本的跳转到指令标签(7)替换为跳转到基本块 B4

        

2.6.2 控制流图 (Control Flow Graph, CFG)——添加边

1. 建立边

        在建立完CFG的节点之后,需要添加边(edge),基本规则如下:

  • A--->B 是跳转过去的,则AB 建立一个边,如第一组红色的AB
  • A--->有条件跳转,如第二组的蓝色的A跳转到第一组红B,且蓝A后还有指令蓝B
    • A--->建立一条边(if  goto)
    • A--->B(A紧接着的下一条指令 B) 也需要建立一条边(条件jump block 天然有两出口)
  • A->B 是第三组的绿A无条件跳转到第一组的红B,其中第三组的绿B按原指令紧挨着第三年组的绿A
    • A--->B 建立一条边 (goto)
    • A-×-> B  不建立绿A 向其代码紧挨着的下一条指令的边

        

        完成节点的划分、编号,最后一步就是再按照顺序以及根据上述示例的规则添加上边,将BB连接起来,最后通常,我们在加上Entry Exit,这样就得到了一个控制流图。

        我们还用2.6.1中的例子,为其添加上边,如下图所示。

        

 

2.7 总结

        这一章先讲了编译器与静态分析器的关系,侧重于了解静态分析处在什么位置,主要运用IR进行静态分析,介绍了AST与IR的不同。

         什么是三地址码,并通过soot更清楚的认识真实分析中的3AC。

        介绍CFG的建立步骤,以及Basic Blocks的划分方法,这些静态分析的基础。


🌻写在最后:

    笔者也是软件分析方向的学生, 这个是跟着课程总结和记录的学习笔记, 如果有错误的地方, 欢迎讨论\批评\指正.

   如果有更多关于软件分析领域的资料\论文\课程\建议, 欢迎留言, 一起学习进步~


http://www.ppmy.cn/news/98276.html

相关文章

js的BOM对象中的window、location使用

说明&#xff1a;BOM的全称是Browser Object Model&#xff0c;浏览器对象模型&#xff0c;有Window&#xff08;浏览器窗口&#xff09;、Navigator&#xff08;浏览器&#xff09;、Screen&#xff08;屏幕&#xff09;、History&#xff08;历史记录&#xff09;和Location&…

【软件开发】Redis 理论篇(一)

Redis 理论篇&#xff08;一&#xff09; 一、概述 1.什么是 Redis&#xff1f; Redis 是一个使用 C 语言写成的&#xff0c;开源的高性能 Key-Value 非关系缓存数据库。它支持存储的 Value 类型相对更多&#xff0c;包括 string&#xff08;字符串&#xff09;、list&#x…

使用【Python+Appium】实现自动化测试

一、环境准备 1.脚本语言&#xff1a;Python3.x IDE&#xff1a;安装Pycharm 2.安装Java JDK 、Android SDK 3.adb环境&#xff0c;path添加E:\Software\Android_SDK\platform-tools 4.安装Appium for windows&#xff0c;官网地址 Redirecting 点击下载按钮会到GitHub的…

【Linux】线程详解之线程互斥与同步

文章目录 Linux线程互斥一、进程线程间的互斥相关概念1.临界资源和临界区2.互斥和原子性 二、互斥量mutex1.抢票程序是否引入互斥量现象观察2.抢票程序原理分析3.互斥量的接口4. 加锁后的程序5.互斥量原理探究 可重入VS线程安全一、概念1.线程安全2.重入 二、常见的线程不安全的…

SpringCloudAlibaba(简介及核心组件使用)

微服务架构常见的问题 一旦采用微服务系统架构&#xff0c;就势必会遇到这样几个问题&#xff1a; 这么多小服务&#xff0c;如何管理他们&#xff1f;服务发现/服务注册---》注册中心 这么多小服务&#xff0c;他们之间如何通讯&#xff1f;Feign -> 基于 http 的微服务调…

scanf读取字符数组的注意点

起因&#xff1a;scanf的%c格式可以读取空格和回车 读取中间无空格隔开的二维字符数组时 #include<bits/stdc.h> using namespace std; char mp[10][10]; signed main() {for(int i1;i<3;i){for(int j1;j<3;j){scanf("%c",&mp[i][j]);}getchar();/…

[元带你学: eMMC协议详解 10] Device 识别流程 与 中断模式

依JEDEC eMMC 5.1及经验辛苦整理&#xff0c;付费内容&#xff0c;禁止转载。 所在专栏 《元带你学: eMMC协议详解》 全文2700字&#xff0c;重点需掌握设备识别过程&#xff08;CMD1 -> CMD2 -> CMD3&#xff09;, 这很常用&#xff0c; 也是最容易出现异常的地方。其他…

python学习-基础知识总结

&#xff08;一&#xff09;基础语法 1.1、注释 程序添加注释&#xff0c;可以用来解释程序某些部分的作用和功能&#xff0c;提高程序的可读性&#xff0c;注释有两种形式&#xff1a; 单行注释&#xff1a;#多行注释&#xff1a;单引号&#xff08;注释内容&#xff09;或双…