我正在寻找WP选项/模型,可以使我证明基本的C内存操作,如:
memcpy :我试图证明这个简单的代码:
struct header_src{
char t1;
char t2;
char t3;
char t4;
};
struct header_dest{
short t1;
short t2;
};
/*@ requires 0<=n<=UINT_MAX;
@ requires \valid(dest);
@ requires \valid_read(src);
@ assigns (dest)[0..n-1] \from (src)[0..n-1];
@ assigns \result \from dest;
@ ensures dest[0..n] == src[0..n];
@ ensures \result == dest;
*/
void* Frama_C_memcpy(char *dest, const char *src, uint32_t n);
int main(void)
{
struct header_src p_header_src;
struct header_dest p_header_dest;
p_header_src.t1 = 'e';
p_header_src.t2 = 'b';
p_header_src.t3 = 'c';
p_header_src.t4 = 'd';
p_header_dest.t1 = 0x0000;
p_header_dest.t2 = 0x0000;
//@ assert \valid(&p_header_dest);
Frama_C_memcpy((char*)&p_header_dest, (char*)&p_header_src, sizeof(struct header_src));
//@ assert p_header_dest.t1 == 0x6265;
//@ assert p_header_dest.t2 == 0x6463;
}
,但最后两个断言未由WP验证(使用默认证明者Alt-Ergo)。可以通过值分析来证明这一点,但是我主要希望能够证明该代码不使用抽象解释。
指向int的广播指针:由于我正在编写嵌入式代码,因此我希望能够指定以下内容:
#define MEMORY_ADDR 0x08000000
#define SOME_SIZE 10
struct some_struct {
uint8_t field1[SOME_SIZE];
uint32_t field2[SOME_SIZE];
}
// [...]
// some function body {
struct some_struct *p = (some_struct*)MEMORY_ADDR;
if(p == NULL) {
// Handle error
} else {
// Do something
}
// } body end
我看了一些WP的文档,似乎我使用的frama-c版本(Magnesium-20151002)具有几种内存模型(Hoare,Typed,+ cast,+ ref,...)但以上任何模型均未证明给定示例。在文档中明确指出类型化模型不处理指针到整数的强制转换。我很难理解每个wp模型背后的实际情况。如果我至少能够验证memcpy函数的后置条件,那将对我有很大帮助。
另外,我已经看到this有关空指针的问题,显然至少在Magnesium版本中,WP无法很好地处理它。我还没有尝试过其他版本的frama-c,但是我认为较新的版本可以更好地处理void指针。
非常感谢您的建议!
答案 0 :(得分:4)
memcpy
对memcpy
(或Frama_C_memcpy
)的结果的理性超出了当前WP插件的范围。唯一适用于您的情况的内存模型是Bytes
(Chlorine手册的第13页),但尚未实现。
请独立注意,您在Frama_C_memcpy
中的后置条件不是。您要声明集合dest[0..n]
和src[0..n]
的相等性。首先,您应该在n-1
停下来。其次,更重要的是,这太弱了,实际上不足以证明调用方中的两个断言。您想要的是对所有字节进行量化。参见例如Frama-C的stdlib中的谓词memcmp
或变体\forall int i; 0 <= i < n -> dest[i] == src[i];
顺便说一句,此后置条件仅在dest
和src
正确分开的情况下成立,而这是您的函数不需要的。否则,您应该写dest[i] == \at (src[i], Pre)
。而且您的requires
也太弱了,因为另一个原因,因为您只要求第一个字符有效,而不要求n
的第一个字符有效。
指向int的广播指针
基本上,用WP实现的所有当前模型都无法推理使用多种不兼容类型(通过使用并集或指针强制转换)访问内存的代码。在某些情况下,例如Typed
,将在语法上检测到强制类型转换,并发出警告以警告代码无法分析。 Typed+Cast
模型是Typed
的一种变体,其中接受 some 强制类型转换。如果仅在使用指针之前将其重新铸造为原始类型,则分析仅是正确的。这个想法是允许使用一些需要强制转换为void*
的libc函数。
您的示例再次有所不同,因为很可能MEMORY_ADDR
始终使用类型some_stuct
来寻址。略微更改代码并将功能更改为使用指向该类型的指针是否可以?这样,您可以将隐藏在MEMORY_ADDR
上的强制转换隐藏在未经证实的函数中。
答案 1 :(得分:1)
我在最新版本的Frama-C中尝试了此示例(当然,格式进行了一些修改)。
memcpy
情况断言2失败,但是断言3被成功证明(基本上是因为断言2的失败导致了False假设,这证明了一切)。
实际上,这两个断言都无法证明,与您的问题相同。
这个结论是合理的,因为wp
插件中使用的内存模型(据我所知)对结构中字段之间的关系没有假设,即在header_src
的前两个字段中是8位字符,但它们可能不会像char[2]
一样嵌套在物理内存中。相反,它们之间可能有填充物(有关详细说明,请参考wiki)。因此,当您尝试将这种结构中的位复制到另一个结构中时,Frama-C变得完全混乱,不知道您在做什么。
就我而言,Frama-C不支持任何精确控制内存布局的方法,例如gcc的PACKED强制编译器删除填充。
我正面临着同样的问题,(一点也不优雅)解决方案是改用数组。数组始终是嵌套的,因此,如果您尝试将char[4]
复制到short[2]
,我认为可以证明断言。
Cast pointer to int
情况对于内存模型Typed+cast
,我正在使用的当前版本(Chlorine-20180501
)支持在指针和uint64_t
之间进行强制转换。您可能需要尝试此版本。
此外,强烈建议通过why3调用Z3和CVC4,它们的性能肯定优于Alt-Ergo。