在Z3上建模内存访问

时间:2012-11-26 06:05:28

标签: arrays z3

我正在使用Z3对程序的内存访问进行建模,我对性能有疑问,我想分享。

我想以紧凑的方式建模,如:

memset(dst, 0, 1000);

我的第一次尝试是使用数组理论,但这意味着创建一千个术语,如(assert (and (= (select mem 0) 0) (= (select mem 1) 0) ...或一千个类似的商店或量化的公式:

(forall (x Int) (implies (and (>= x 0) (< x 1000)) (= (select mem x) 0))

但我被告知要在使用数组时避免使用量词。

接下来的想法是定义一个UF:

 (define-fun newmemfun ((idx Int)) Int (
   ite (and (>= idx 0) (< idx 1000)) 0 (prevmemfun idx)
 ))

但这意味着我需要为每个内存写操作定义一个新函数(即使对于单个存储操作,也不仅仅是像memset或memcpy这样的多个存储)。这将最终创建一个非常嵌套的ITE结构,甚至可以为同一个索引保存“旧”值。即:

mem[0] = 1;
mem[0] = 2;

would be:
(ite (= idx 0) 2 (ite (= idx 0) 1 ...

哪个功能正确,但表达式的大小(我猜它生成的AST)往往累积得非常快,我不确定Z3是否经过优化以检测和处理这种情况。

所以,问题是:对于内存操作进行编码最有效的方法是什么,它可以同时应对上面的例子和单个商店等大型多个商店。

谢谢, 巴勃罗。

PS:非封闭和不匹配的括号:P。

1 个答案:

答案 0 :(得分:1)

在不了解您的最终目标的情况下,除了建模内存访问(例如,您将要进行验证,测试用例生成等吗?)之外,由于您有很多选择,因此有点难以回答。但是,如果您依赖其中一个API,则可以最灵活地控制性能问题。例如,您可以按如下方式定义自己的内存访问(链接到z3py脚本:http://rise4fun.com/Z3Py/gO6i):

address_bits = 7
data_bits = 8

s = Solver()

# mem is a list of length program step, of a list of length 2^address_bits of bitvectors of size 2^data_bits
mem =[]

# modify a single address addr to value at program step step
def modifyAddr(addr, value, step):
  mem.append([]) # add new step
  for i in range(0,2**address_bits):
    mem[step+1].append( BitVec('m' + str(step + 1) + '_' + str(i), data_bits) )

    if i != addr:
      s.add(mem[step+1][i] == mem[step][i])
    else:
      s.add(mem[step+1][i] == value)

# set all memory addresses to a specified value at program step step
def memSet(value, step):
  mem.append([])
  for i in range(0,2**address_bits):
    mem[step+1].append( BitVec('m' + str(step + 1) + '_' + str(i), data_bits) )
    s.add(mem[step+1][i] == value)

modaddr = 23 # example address
step = -1
# initialize all addresses to 0
memSet(0, step)
step += 1
print s.check()
for i in range(0,step+1): print s.model()[mem[i][modaddr]] # print all step values for modaddr

modifyAddr(modaddr,3,step)
step += 1
print s.check()
for i in range(0,step+1): print s.model()[mem[i][modaddr]]

modifyAddr(modaddr,4,step)
step += 1
print s.check()
for i in range(0,step+1): print s.model()[mem[i][modaddr]]

modifyAddr(modaddr,2**6,step)
step += 1
print s.check()
for i in range(0,step+1): print s.model()[mem[i][modaddr]]

memSet(1,step)
step += 1
print s.check()
for i in range(0,step+1): print s.model()[mem[i][modaddr]]

for a in range(0,2**address_bits): # set all address values to their address number
  modifyAddr(a,a,step)
  step += 1

print s.check()
print "values for modaddr at all steps"
for i in range(0,step+1): print s.model()[mem[i][modaddr]] # print all values at each step for modaddr

print "values at final step"
for i in range(0,2**address_bits): print s.model()[mem[step][i]] # print all memory addresses at final step

这种天真的实现允许您(a)将所有内存地址设置为某个值(如memset),或(b)修改单个内存地址,将所有其他地址限制为具有相同的值。对我来说,运行并编码大约128个128位地址的步骤需要几秒钟,因此它有大约20000个位向量表达式,每个8位。

现在,根据你正在做的事情(例如,你是否允许对像这个memset这样的几个地址进行原子写入,或者你想将它们全部建模为单独的写入?),你可以添加更多的功能,比如修改子集地址到程序步骤中的某些值。这将允许您灵活地权衡建模精度的性能(例如,对内存块的原子写入与一次修改单个地址,这将遇到性能问题)。此外,没有任何关于此实现需要API,您可以将其编码为SMT-LIB文件,但如果您使用,您可能会有更多的灵活性(例如,假设您希望与模型交互以限制未来的坐标检查)其中一个API。