受导师影响,尚未入门的菜鸟记录一下自己的学习过程。
ACSL简介
"ANSI/ISO C 规范语言"(the ANSI/ISO C Specification Language,简称 ACSL)是一种用于C程序的行为规范语言。作为一种形式化语言,ACSL使得函数契约的精确规范成为可能,这不仅使得规范对人类可读,而且可以被分析工具所操作。
ACSL的一个更强大的功能是给整个函数订立函数合约。例如,可以定义一个函数来计算数组中的最大值,并赋予它相应的函数合约(函数合约是软件工程中用于描述函数行为的一种形式化方法。它定义了函数的预期行为和约束,包括函数的输入、输出以及在函数执行前后的状态)。ACSL与自动化验证工具(如Frama-C)兼容,这些工具能够解析带有ACSL注释的C代码,并使用静态分析方法自动验证代码是否严格遵守了规范的约束,例如:每个功能模块/函数可以对应一组规范,即函数合约,只要可以证明某模块满足其合约,它的调用者就可以直接信赖合约中约定的性质,进而(结合其它子功能模块)实现更复杂的功能,而无需了解各个子模块的具体实现。
Frama-c简介
Frama-C 是一个专为 C 语言设计的高级静态分析工具,它集成了多种先进的分析技术,旨在确保代码的安全性和正确性;Frama-C 使用 ANSI/ISO C 规范语言(ACSL)来提供规范,包括函数合约,即函数的前置条件和后置条件。同时,Frama-c有着多种插件,核心包括Eva 插件,WP插件,E-ACSL插件等。
Frama-c的安装和使用见官网教程,包含ACSL。
Documentation - Frama-Chttps://www.frama-c.com/html/documentation.html
Frama-c基本指令
1、查看版本,笔者安装的是27.1的命令行frama,安装过程中29装完有问题用不了(应该是没装好),26由于why3版本装不上,最后将why3版本降到1.6,通过源码安装才装上27...(注:frama-c开发者在github中写到,26适应的why3版本是>=1.5, <1.6,但是我1.5也装不上26...)。
abc@LAPTOP-I4IH9NJF:/mnt/f/LLM4SE/LLM-Toolset/LLM4Veri$ frama-c --version
27.1 (Cobalt)
2、查看已安装的插件
abc@LAPTOP-I4IH9NJF:/mnt/f/LLM4SE/LLM-Toolset/LLM4Veri$ frama-c -plugins
Aorai verification of behavioral properties (experimental)
(-aorai-h)
Callgraph automatically compute the callgraph of the program. Using
Eva might improve the precision of this plug-in (-cg-h)
Dive An interactive imprecision graph generator for Eva.
(-dive-h)
Dominators Compute postdominators of statements (-dominators-h)
E-ACSL Executable ANSI/ISO C Specification Language --- runtime
assertion checker generator (-e-acsl-h)
Eva automatically computes variation domains for the
variables of the program (-eva-h)
From analysis functional dependencies (-from-h)
Impact impact analysis (-impact-h)
Inout operational, imperative and all kinds of inputs/outputs
(-inout-h)
Instantiate Overrides standard library functions (-instantiate-h)
Loop Find maximum number of iterations in loops (-loop-h)
Markdown report generates a report in markdown format (-mdr-h)
Metrics syntactic metrics (-metrics-h)
Nonterm Warns when definitively non-terminating functions/loops
are detected (e.g. reachable functions with unreachable
returns). (-nonterm-h)
Obfuscator obfuscator for confidential code (-obfuscator-h)
Occurrence automatically computes where variables are used
(-occurrence-h)
Pdg Program Dependence Graph (-pdg-h)
Postdominators computing postdominators of statements
(-postdominators-h)
Reduction Generate ACSL annotations from Eva information (-reduc-h)
Report Properties Status Report (-report-h)
Rtegen generates annotations for runtime error checking (-rte-h)
Scope data dependencies higher level functions (-scope-h)
Security-slicing security slicing (experimental, undocumented)
(-security-slicing-h)
Semantic constant folding propagates constants semantically (-scf-h)
Server Frama-C Request Server (experimental) (-server-h)
Server TypeScript API Generate TypeScript API for Server (-server-tsc-h)
Slicing code slicer (-slicing-h)
Sparecode code cleaner (-sparecode-h)
Studia Tools for Eva case studies (-studia-h)
Users function callees (-users-h)
Variadic Variadic functions translation (-variadic-h)
WP Proof by Weakest Precondition Calculus (-wp-h)
3、其他
-frama-c:这是 Frama-C 的主要可执行文件,用于执行大多数 Frama-C 分析,从解析源代码到运行复杂的分析。
-help 或 -h 或 --help:提供 Frama-C 命令行的简短总结,包括版本信息和主要帮助入口。
-kernel-help 或 -kernel-h:打印 Frama-C 核心(即非插件特定)的其他选项。
-<plug-in shortname>-help 或 -<plug-in shortname>-h:显示已安装插件的选项。
-explain:用于获取 Frama-C 核心或任何已安装插件的某些特定选项的信息。
-frama-c-config:辅助批处理版本,用于快速检索配置信息(例如安装路径)。
-frama-c-script:包含与源代码准备、结果可视化和分析自动化相关的几个实用工具。
-cpp-extra-args:用于向默认预处理命令添加参数,通常通过定义(-D 开关)和头文件目录(-I 开关)来包含。
-machdep <machine architecture name>:定义目标平台,例如 x86_64。
-ulevel <n>:将所有循环展开 n 次,这是一个纯语法操作。
-eva:运行 Eva 插件,这是一个基于抽象解释的分析器,用于验证是否存在运行时错误。
-wp:运行 weakest precondition(WP)插件,这是一个基于最弱前置条件计算的程序证明框架。
-save <file>:保存 Frama-C 会话到指定文件,以便将来加载和恢复分析状态。
-load <file>:加载之前保存的 Frama-C 会话文件,恢复分析状态。
-gui:启动 Frama-C 的图形用户界面版本,允许交互式地选择文件、指定选项、启动分析以及浏览代码和观察分析结果。
ACSL示例+frama-c验证
ACSL的验证逻辑与Hoare三元组相同,如果从前置条件出发,程序经过函数体后的状态满足后置条件,也就是{Preconditon}c{Postconditon}成立,称之为满足函数合约,进而我们认为该函数是满足要求的,是合格的代码段。程序验证(3)- 霍尔逻辑 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/305203034(这里有关于Hoare逻辑的相关介绍)
ACSL 可以描述我们期望程序表现出的行为,即程序的规范。它以特殊注释的形式穿插在代码中(由 @ 符号引导)。最简单的 ACSL 规范可以只是一条插在代码中的断言(assertion),用来对程序执行到此处时的状态进行约束。例如:
……
line1 : int x = -10;
line2 : if(x < 0)
line3 : x = -x;
line4 : //@ assert about_x: x >= 0;
……
这是一个简单的绝对值的判断,该条断言要确保当代码执行到line4时,x的值非负,显然,这个代码段可以满足该条断言,将这个代码文件放入frama-c中,可自动证明该断言成立,从而认为断言之前的代码是合格的。
abc@LAPTOP-I4IH9NJF:/mnt/f/LLM4SE/LLM-Toolset/LLM4Veri/test/hh$ frama-c-gui 1.c
前文已经介绍过,-gui进入图形化界面。
可以看到,右侧框内是我们的源代码,左侧框内已经将我们的ACSL转换成frama-c可验证的格式,此处待验证的性质x >= 0左侧有一个蓝色圆圈,选中assert,右键,有个prove annotation by WP,执行后发现圆圈变绿,即性质成立。若为橙色,代表证明失败(无法证明或超时),一半绿一半橙,代表对该目标的证明成功,但还要证明依赖的目标。
以上是一个非常简单直接的示例,接下来我们将这个绝对值方法以函数的形式写出,引出函数合约及preconditon和postcondition概念。
#include <limits.h>
/*@ requires val > INT_MIN;
ensures positive_value: \result >= 0;
ensures function_result:
(val >= 0 ==> \result == val) &&
(val < 0 ==> \result == -val);
*/
int abs(int val) {
return val < 0 ? -val : val;
}
#include <limits.h>
是 C 语言(以及 C++ 语言)中一个标准的头文件,它定义了各种基本数据类型的最小和最大值,以及一些其他的限制。这个头文件是 C 标准库的一部分,包含了 <limits.h>
中定义的宏,这些宏提供了关于数据类型范围的信息。
对于一个函数来说,他的函数合约需要在函数声明上方的ACSL规范给出,包括requires代表的前置条件precondition和ensures代表的后置条件postcondition。前置条件是指在函数执行前整个程序应提供的状态,后置条件是执行完函数后,程序应该满足的状态。
对于这个abs函数来说,他的前置条件val > INT_MIN是因为int数据仅有32位,以补码形式表出,而补码的范围是左闭右开,即负数可以取到端点值;正数比负数的个数要少一,即取不到端点值,其取值范围是从 −2^31 到 2^31−1。INT_MIN表示的就是这个范围的最小值,即 −2^31。在补码表示法中,INT_MIN的二进制表示是所有位都是1,即 1111111111...。当对INT_MIN 取负时,实际上是在计算它的二进制补码。但是,由于int类型只能表示到 2^31-1,所以 2^31 实际上超出了 int
类型能表示的范围,这会导致整数溢出。在大多数系统中,这会导致结果回绕到 int
类型的最小值,即-2^31,因为 2^31是 unsigned int
类型的最大值加1,在32位补码表示中正好是INT_MIN 的值。因此,在32位系统中,-INT_MIN实际上等于 INT_MIN,即-INT_MIN=INT_MIN。
他的两条后置条件分别规定,(1)返回值(ASCL规定用\result替代)要大于等于0,(2)返回值要与输入的绝对值相同。
基于此,再次用frama来验证函数,以及缺少前置或后置会发生什么。
cpp文件如下:
#include <limits.h>
/*@ requires val > INT_MIN;
ensures positive_value: \result >= 0;
ensures function_result:
(val >= 0 ==> \result == val) &&
(val < 0 ==> \result == -val);
*/
int abs(int val) {
return val < 0 ? -val : val;
}
void main() {
int x = -10;
x = abs(x);
//@ assert about_x: x >= 0;
}
执行frama-c-gui 1.c后,可以发现在左侧的框内有abs和main两个函数,里面有写好的ACSL函数合约,直接prove assert发现成功。
当然,可以直接用frama-c-gui -wp 1.c命令,他可以对所有规范进行wp插件进行分析,wp下次再介绍...
在将两条后置条件删除后,点击file下拉框内的reparse可直接加载修改后的代码,重新prove assert,发现验证失败,在console中查看具体信息。
【kernel] Failure: unregistered property assert about_x:x≥0;:这是失败的核心信息,表示有一个断言(assert)关于变量 x
的属性 x≥0
没有被注册或不被支持。这通常意味着 ACSL 规范中使用了 Frama-C 无法识别或不支持的属性。
如何区别是超时还是证否导致的证明失败,只报timeout可能就是时间不够,可增加时间再尝试,而报这个kernel的错误往往是证否(GPT说的哈,我也不确定这个说法对不对...)。
当删除前置条件,并将x的值改为INT_MIN,执行prove,发现全proved了...这个应该有点问题,我后面学到了再回来解决...(在第三次笔记里有详细说明,已解决哈)
总结
以上是我开始学的第一次内容,包括ACSL和Frame-c的简介,一些基本指令和简单的示例,有很多可能表述不准确或有误的地方,以及frama我也不太会用,ASCL还有一些语句没有介绍,程序验证的概念也需要再补补(完了啥都不会),后面我学到了再回来更正...欢迎大家指正。
以下是学习过程中参考的博客,我写的大部分内容是在这个博客基础上拓展出来的。
ACSL 及Frama-C验证工具简介(一) - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/266701781