Arrays Z3上的内存访问建模

Arrays Z3上的内存访问建模,arrays,z3,Arrays,Z3,我正在使用Z3模拟一个程序的内存访问,我对性能有疑问,我想和大家分享一下 我想以一种紧凑的方式建模,比如: memset(dst, 0, 1000); 我的第一次尝试是使用数组理论,但这意味着要么创建一千个术语,比如assert和=select mem 0 0=select mem 1 0。。。或者一千家类似的商店或一个量化公式: (forall (x Int) (implies (and (>= x 0) (< x 1000)) (= (select mem x) 0)) 这在

我正在使用Z3模拟一个程序的内存访问,我对性能有疑问,我想和大家分享一下

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

memset(dst, 0, 1000);
我的第一次尝试是使用数组理论,但这意味着要么创建一千个术语,比如assert和=select mem 0 0=select mem 1 0。。。或者一千家类似的商店或一个量化公式:

(forall (x Int) (implies (and (>= x 0) (< x 1000)) (= (select mem x) 0))
这在功能上是正确的,但表达式的大小和生成的AST的累积速度都非常快,我不确定Z3是否经过优化以检测和处理这种情况

因此,问题是:什么是编码内存操作的最有效方法,可以同时处理上述示例中的大型多个存储和单个存储

谢谢, 巴勃罗


PS:非闭合和非匹配括号:p.

不知道更多关于最终目标的信息,除了建模内存访问(例如,您是否要进行验证、测试用例生成等)之外,回答有点困难,因为您有很多选择。但是,如果您依赖其中一个API,则可以最灵活地控制性能问题。例如,您可以定义自己的内存访问,如下链接到z3py脚本:

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


现在,根据您正在执行的操作,例如,您是否允许对多个地址(如此memset)进行原子写入,还是希望将它们全部建模为单个写入?您可以添加更多功能,如在程序步骤中将地址子集修改为某些值。这将使您能够灵活地权衡建模精度与性能,例如,原子写入内存块与一次修改单个地址,这将遇到性能问题。此外,此实现不需要API,您也可以将其编码为SMT-LIB文件,但您可能会有更大的灵活性,例如,假设您希望与模型交互,以约束未来的sat检查,如果您使用其中一个API。

您好,我发现您的方法很有趣,但不适合我的情况。您几乎是在重做数组理论存储操作,除了新数组的索引i之外,每个元素都与前一个元素相等,但在python API的解算器之外。这对于相对较小的地址空间(即7bit)很有效,但我正在为X86开发一个符号执行引擎。因此,我的地址空间是32位,单个内存写入步骤将需要超过4GB的ram来处理它。不过,您似乎同意,在我的示例中,展开具有1000个断言的内存操作是一条可行的道路。正如我在回答中提到的,建模方法取决于您的最终目标,因此您肯定希望应用一些抽象,例如,我提到的原子写入。是的,这基本上是阵列理论,但你有更多的控制权。另一个改进是在数据更改时只引入新的内存位置,并跟踪修改每个位置的程序的最后一步。如果您要对32位地址空间进行建模,我想您将不得不失去一些性能模型准确性,因为您无法精确地对所有内容进行建模。
 (define-fun newmemfun ((idx Int)) Int (
   ite (and (>= idx 0) (< idx 1000)) 0 (prevmemfun idx)
 ))
mem[0] = 1;
mem[0] = 2;

would be:
(ite (= idx 0) 2 (ite (= idx 0) 1 ...
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