Frama-c:无法理解WP内存模型

时间:2018-06-25 13:14:00

标签: frama-c

我正在寻找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指针。

非常感谢您的建议!

2 个答案:

答案 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];

    顺便说一句,此后置条件仅在destsrc正确分开的情况下成立,而这是您的函数不需要的。否则,您应该写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。