《CLANG STATIC ANALYZER- A Checker Developer's Guide》值部分

本文介绍了Clang静态分析器中的符号化值(SVal)及其子类,包括MemRegion和SymExpr,解释了如何构建和使用这些类来表示内存区域和符号化表达式。同时,文章还探讨了符号化值在分析过程中的角色,如具体值、符号值和特殊值等,并详细讲解了内存模型中的各个组成部分。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最基本的类是SVal类,可以表达各种符号化值,它有很多子类表达不同类型的符号化值,其中两个主要的子类是MemRegion和SymExpr,分别用于处理内存区域和符号化表达式。
SymExpr类也常被称为symbols,表示未知的数值。
如果分析过程中一个值是已知的,则称为具体值。

MemRegion对象”regions”用于两个目的:
1)在分析器内存模型中作为region的位置存储bindings
2)表示指针值

SVal的子类可以分为两大类:Loc用于左值和NonLoc用于右值
这里写图片描述
构建符号化值:
1)SValBuilder类提供了构建SVal对象的方法,允许构建各种SVal和各种SymExpr,也允许在符号化值上进行计算。任何情况下构建SymExpr时,要用SValBuilder而不是直接访问SymbolManager对象。如果符号要的是整数类型则这些方法返回一个nonloc::SymbolVal,如果要求的是指针类型,则返回一个loc::MemRegionVal包含一个用SymbolicRegion封装的符号。这两种情况都可以调用getAsSymbol()方法对结果SVal取得SymExpr本身。
2)构建MemRegionManager用于构建内存区域,有时用于构建一个sub-region

分析器的内存模型

MemRegion是内存的一个段,存储在一个指针类型的SVal中,表示这个段第一个字节的地址,然而仍要想象MemRegion对象包含了整个段相关的信息。

SVal类的getAsRegion()方法适用于以下SVal类型:
1)loc::MemRegionVal:一个指针值,描述给定区域的第一个字节地址
2)nonloc::LocAsInteger:类似指针值,只是存储在一个整型里,这种SVal类型表示从指针到整型的转换结果

有些内存区域是其他区域的sub-region,一个sub-region是一个段中的子段,继承自SubRegion类,每个sub-region都有一个长度(“extent”),可以通过getExtent(…)方法得到,extent可能是具体的也可能是符号化的。

每个SubRegion都只直属于一个super-region,可以通过getSuperRegion()方法得到。有一个memory space作为直接super-region的子区域称为base regions。如果一个region既不是一个memory space,也不是一个base region,则在它的super-region chain的尽头一定有一个base region。

getMemorySpace()方法可以得到region所属的内存空间,getBaseRegion()可得到任何sub-region的base region。

base regions:内存空间的直接子区域,可以是typede或untyped,一个typed区域存储已知类型的值,untyped区域存储未知类型的值。

内存空间:继承自MemSpaceRegion类,有以下几个:
1. GlobalsSpaceRegion:四种不同内存空间的基类

  • 1) NonStaticGlobalSpaceRegion:为所有non-static全局变量的唯一内存空间,分为三个:
    a. GlobalImmutableSpaceRegion:包含不能被修改的全局变量
    b. GlobalSystemSpaceRegion:包含只能被系统修改的变量,如errno
    c. GlobalInternalSpaceRegion:包含其他全局变量

b. StaticGlobalSpaceRegion:为静态全局变量的内存空间
2)HeapSpaceRegion:维护所有在堆上分配的区域
3)StackSpaceRegion:两种不同内存空间的基类
a. StackArgumentsSpaceRegion:函数调用参数的内存空间
b. StackLocalsSpaceRegion:局部变量的内存空间
和其他内存空间不同的是,可能有很多个StackSpaceRegion实例,每个StackFrameContext有一个
4)UnknownSpaceRegion:当分析器不知道区域实际存储在哪时分到未知空间中

untyped base regions:只有三种untyped regions:
1)AllocaRegion:通过标准C库alloc()函数在栈上分配的区域,因为该函数分配raw data,故为untyped区域
2)SymbolicRegion:由一个指针指向的区域,指针的值是一个符号化表达式。该区域是untyped的,因为在C中指针可以随意cast,也无法确定指向的数据是否与指针类型相匹配。

如果一个sub-region的base region是一个SymbolicRegion,我们称这个region有一个symbolic base,MemRegion的getSymbolicBase()方法可返回一个指向symbolic base region的指针或者如果base不是符号化的则返回空指针。该方法用于判断一个复杂的sub-region是否实际和一个特定指针符号有关。

正常情况下SymbolicRegion应该在UnknownSpaceRegion中的,因为通常不知道指针的本质。但有时知道指针指向的是heap(比如指针是new操作符返回的),那么这个区域就在HeapSpaceRegion中。堆符号化区域由MemRegionManager的getSymbolicHeapRegion()方法创建。

typed base regions with typed values
typed regions都有个共同的祖先TypedValueRegion,有各种类型:
1)VarRegion:变量的区域,对每个AST全局或静态变量声明,都定义一个VarRegion。对栈变量,不同函数调用中的区域是不同的,是不同StackSpaceRegion内存空间的sub-region。一个类的成员变量绝不是base region,也永远不会用一个VarRegion表示。
2)CXXThisRegion
3)CXXTempObjectRegion
4)CompoundLiteralRegion:表示一个initializer-list对象的内存区域
5)StringRegion:string literal的区域

typed base regions with untyped values
有很多类型的regions继承自TypedRegion但不是TypedValueRegion,这些区域定义为”location”(指针)类型,然而它们存储的值的类型是未定义的,这些类型是被指向的位置的类型。
1)BlockDataRegion:表示存储在blocks中的数据的base region,这些regions为block处理代码和数据,实现方法for working with closures
2)CodeTextRegion:表示程序代码的内存区域,两个子类:FunctionTextRegion用于函数代码,常在分析器中表示函数指针值,和BlockTextRegion用于块代码。

sub-regions of base regions
base regions的sub-regions都是typed的,即使base region是untyped的。
1)CXXBaseObjectRegion
2)ElementRegion:一维数组元素区域,该元素的索引是任意array index类型的NonLoc符号化值,要么是具体整型,要么是一个符号,该区域也记录类型信息。相同super-region相同index的不同类型的ElementRegion视作不同的。

ElementRegion也用于表示untyped regions的类型转换。
3)FieldRegion:一个结构或类或联合中的一个域的region,该区域也基于一个AST变量声明

ElementRegion不表示一个指针解引用,SymbolicRegion可以。给指针和数组加下标的处理是完全不同的。
这里写图片描述
数组:a[5]是参数变量a的VarRegion的一个ElementRegion,在StackArgumentsSpaceRegion中
element {a ,5 S32b , int }
指针:p[5]是UnknownSpaceRegion中的一个SymbolicRegion的ElementRegion
element { SymRegion { reg_$0

} ,5 S32b , int

具体值concrete value
具体值是编译时就知道的值。
1)numeric values:最基本的具体值,nonloc::ConcreteInt中维护llvm::APSInt,可通过getValue()获得。
这里写图片描述
不同的nonloc::ConcreteInt类实例有不同的数值、类型大小、符号。然而,这三者的每种组合只有一个nonloc::ConcreteInt表示,不会区分不同来源的两个32比特有符号零。

loc::ConcreteInt是一个具体整型表示已知指针值,内部和nonloc::ConcreteInt相似。通常loc::ConcreteInt实例为指针宽度的无符号整型。由于很难在编译时知道内存地址,所有见到的大多数loc::ConcreteInt是0,表示空指针。
2)compound values
a. nonloc::CompoundVal,表示一个initializer-list或一个字符串的具体右值。
b. nonloc::LazyCompoundVal,是一个右值,表示一个任何结构作为一个整体在分析过程中给定时刻的快照
nonloc::LazyCompoundVal存储两个东西:
1)一个对TypedValueRegion的引用(总是typed的)
2)一个对整个Store对象的拷贝,从创建它的ProgramState中得到

特殊值
1)UndefinedVal:每当分析器核心根据语言标准想要强调某个东西的值是未定义的,就会产生一个UndefinedVal。只有一个UndefinedVal,即你不能区分两个来源不同的UndefinedVal。大多数时候,当出现UndefinedVal时应该有checker警告发生了未定义行为,在core.uninitialized包中有很多抛出这类警告的官方checker。这是唯一在ProgramState的assume(…)方法中被禁止的值。
2)UnknownVal:每当符号执行引擎无法用一个符号表达一个特定值时就会产生UnknownVal,也是唯一的,不能区分两个来源不同的UnknownVal。但是可以区分UndefinedVal和UnknownVal。

UnknownVal可能出现在任何地方任何时候,常在符号化表达式超出了它的复杂度限制时出现。不意味着程序中出现错误,而是大多数意味着分析器核心的失败。

符号化表达式
符号化表达式”symbolic expressions”也称符号”symbols”。
符号是永恒的:符号化值不会再分析过程中改变。一旦符号被创建,整个分析过程中都表示同样的值。然而,随着分析的进行,会收集到关于这个值的新的信息,以range constraints的形式存储在ProgramState中。但不同的符号可能或不会表示不同的值。

RangeConstraintManager:用符号化二元表达式类简化约束条件,如(x+3)>5简化为x>2

污点传播自动从被污染的区域传到表示他们的值的数据符号,通过引用存储在符号中的区域

在任何时候,都可以简单地追踪到符号的origin。如果想要知道复现一个分析器找到的bug哪些条件是必要的,可以通过看program state中的符号

符号值总是有一个类型的,整型或指针。

SVal类的getAsSymbol()方法对如下SVal类起作用:
1)nonloc::SymbolVal:值是符号本身
2)loc::MemRegionVal:如果其中的region是一个SymbolicRegion(即一个符号化指针,因为nonloc::SymbolVal总是NonLoc,该方法用Loc值表示指针),这种情况下回返回被构造的region的符号。如果getAsSymbol()的可选的布尔类型参数被置true,该方法还可以在任意有符号化base的区域工作。
3)nonloc::LocAsInteger:从底层loc::MemRegionVal提取一个符号化指针,如果有的话。

操作符号”operation symbols”
有三种表示对其他符号进行的二元操作:
1)SymIntExpr:表示一个其他符号和一个具体整型间的二元操作,如x+5
2)IntSymExpr:表示一个具体整型和其他符号间的二元操作,如3>x
3)SymSymExpr:表示两个符号间的二元操作,如x*y。分析器非常少创建这种表达式,因为对RangeConstraintManager来说是没用的,无法处理这么复杂的约束。通常是当一个操作数被污染了,为了保留污点信息才会创建SymSymExpr。

还有一个SymbolCast表示从一个其他符号到特定类型的转换结果。

conjured symbols
当其他所有的都失败了SymbolConjured是个备用:分析器对表达式没有做任何反应,想象出某符号至少可以保持路径敏感。常用于分析器没有建模的函数的返回值,因为函数体源代码不可得或其他原因。也可用于invalidation。

region value symbols
1)最原始的原子符号是SymbolRegionValue,表示在分析开始时内存区域中存储的值。包含对该region的引用。
这里写图片描述
如代码片段,分析开始时即2行之前,b是一个UndefinedVal,a是一个SymbolRegionValue类的符号,表示参数变量a的region的值。2行后,b的值变为SymbolRegionValue,表示a的值。3行后,a的值变为值为1的nonloc::ConcreteInt。b还是SymbolRegionValue类型,还是a原始的值。

2)SymbolDerived:表示当另一个符号写入一个直接或间接super-region后一个区域的值。包含对parent symbol和parent region的引用。通常出现在invalidation后:一个特定类型的整个结构用一个SymbolConjured变得破碎,那个conjured symbol的域的值以及域的region就要借助SymbolDerived。

SymbolDerived和SymbolRegionValue类似,都是在分析期间一个特定事件之后指向一个值的,而不是在分析一开始。

Extent Symbols:
想知道内存区域的长度“extent”,这个长度可能已知也可能未知,如果长度未知则用SymbolExtent表示。这个符号包含了一个该区域的引用。如果需要得到一个特定区域的extent,不需要手动创建一个新的SymbolExtent,可以利用SubRegion的getExtent(…)方法

Metadata symbols:
metadata symbols是checker指定意义的符号,与内存区域绑定,checker负责创建、管理他们的生命周期并进行回收,通过check::LiveSymbols回调进行。分析器核心本身是不创建SymbolMetadata的,只有checker创建。

可以用SValBuilder创建一个新的SymbolMetadata,如下取自alpha.unix.cstring.OutOfBounds checker的代码片段,创建一个元数据符号表示字符串长度。
这里写图片描述
SymbolMetadata的有如下元素组成:
1)symbol tag:一个void*类型,唯一识别一类元数据符号的。例子中用的是Checker对象的getTag()方法返回的唯一识别checker本身的标识符。
2)parent region:元数据绑定的parent区域,例子中是定义长度的字符串的区域。
3)AST表达式:符号出现的AST表达式
4)符号的类型:如果类型是Loc则SValBuilder的结果SVal就会为loc::MemRegionVal,承载了一个封装这个符号的SymbolRegion
5)block count:在分析期间CFG基本块被访问的次数。用于区分在相同表达式上为相同区域创建的符号

tainted values
只有符号可以带有污点。传播机制:
1)在如下情况时内存区域被污染:
a. 用一个被污染的指针符号构建的SymbolicRegion
b. 用一个被污染的索引值构建的ElementRegion
c. 从super-region继承污点的任何类型region
2)在如下情况时符号可以继承污点,而不考虑它本身自带的污点信息:
a. SymbolRegionValue可能从parent region继承污点
b. SymbolDerived可能从parent symbol继承污点,但不会从parent region继承
c. 所有操作符号从它们的操作数继承
3)SVal会被污染:当一个符号用getAsSymbol()提取或者一个区域用getAsRegion()提取时被污染

debug打印
如打印出:
reg_2 < element { SymRegion { derived_2 < element { SymRegion { derived_1 { conj_$0 { int } ,a - > ptr }} ,0 S32b , int }

意义:
SVal是一个符号,是一个SymbolRegionValue,用reg_N<...>N<...>封装,之后的N是SymbolManager对象中给每个符号赋的内部符号计数器。
这个符号表示的是一个有符号整型的原始值,在一个和符号化指针关联的特定SymbolicRegion中ElementRegion中,index为0。分析器无法知道指针指向的是一个数组还是单个整型,然而能够确定的是该指针被解引用以得到原始值。
该指针本身是一个int类型的SymbolConjured的SymbolDerived,对应某结构体变量a的ptr域的FieldRegion。SymbolDerived本身是一个SymbolicRegion的base,对指针值很重要,然而,SymbolConjured不是一个指针,很可能是结构体a进行invalidation的结果而出现的。

因此,上述打印就是在invalidation期间a.ptr域指针背后的原始存储的整型值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值