# 符号模糊化

> 原文： [https://www.fuzzingbook.org/html/SymbolicFuzzer.html](https://www.fuzzingbook.org/html/SymbolicFuzzer.html)

传统的模糊测试方法的问题之一是它们无法行使系统可能具有的所有可能的行为，尤其是在输入空间很大时。 通常，只有特定的输入才可以执行特定执行分支，这可能只占输入空间的一小部分。 传统的模糊测试方法依靠机会来产生所需的输入。 但是，当要探索的空间很大时，依靠随机性来生成我们想要的值不是一个好主意。 例如，即使一个字符串仅考虑前$ 10 $个字符，但接受字符串的函数已经具有$ 2 ^ {80} $个可能的输入。 如果正在寻找特定的字符串，则即使在一台超级计算机中，值的随机生成也将花费数千年。

先前我们已经看到了condicolic跟踪如何提供一种出路。 我们看到了如何使用Python解释器使用直接信息流来实现condicolic跟踪。 但是，这种方法存在两个问题。 第一个是，对策跟踪取决于样本输入的存在。 如果没有样本输入该怎么办？ 其次，如果程序具有间接信息流（例如基于控制流的信息流），则直接信息流可能不可靠。 在这种情况下，必须进行静态分析以弥合差距。 但是，这就提出了问题。 我们能否通过静态检查程序来确定程序的完整行为，并检查它是否在某些（未知）输入下表现异常或导致意外输出？

符号执行是我们无需执行程序就可以推断程序行为的一种方式。 程序是一种可以视为方程式的系统，可以从给定的输入中获取输出值。 象征性地执行程序（即以数学方式解决这些问题）以及任何指定的目标（例如覆盖特定分支或获得特定输出）将为我们提供可以完成此任务的输入。

在本章中，我们研究如何执行符号执行，以及如何将其用于获取有趣的模糊值。

**前提条件**

*   您应该了解如何在Python中使用[类型注释](https://docs.python.org/3/library/typing.html)。
*   对 [SMT求解器](https://en.wikipedia.org/wiki/Satisfiability_modulo_theories)尤其是 [Z3](https://github.com/Z3Prover/z3) 的工作知识很有用。
*   您应该已经阅读了关于的[一章。](Coverage.html)
*   熟悉有关[的关于概念模糊](ConcolicFuzzer.html)的章节会有所帮助。

## 内容提要

要使用本章中提供的代码来[，请编写](Importing.html)

```py
>>> from [fuzzingbook.SymbolicFuzzer](SymbolicFuzzer.html) import <identifier>

```

然后利用以下功能。

本章提供了符号模糊引擎`AdvancedSymbolicFuzzer`的实现。 模糊器使用符号执行来详尽地探查程序中的路径到有限的深度，并生成将到达这些路径的输入。 给定要探索的程序（此处为`gcd`），模糊器可以如下使用：

```py
>>> gcd_fuzzer = AdvancedSymbolicFuzzer(gcd, max_tries=10, max_iter=10, max_depth=10)
>>> for i in range(10):
>>>     r = gcd_fuzzer.fuzz()
>>>     print(r)
{'a': 7, 'b': 4}
{'a': 8, 'b': 16}
{'a': 2, 'b': 17}
{'a': 6, 'b': 5}
{'a': 3, 'b': 5}
{'a': 1, 'b': 1}
{'a': 5, 'b': 2}
{'a': 4, 'b': 8}
{'a': 9, 'b': 9}
{'a': 12, 'b': 11}

```

## 获取覆盖范围的路径条件

在[解析和重新组合输入](SearchBasedFuzzer.html)的章节中，我们看到了为`process_vehicle()`生成输入的困难-这是一个接受字符串的简单函数。 那里给出的解决方案是依靠预先存在的样本输入。 但是，这种解决方案是不适当的，因为它假定存在样本输入。 如果手头没有样品输入怎么办？

对于一个简单的示例，让我们考虑以下功能。 我们可以生成涵盖所有路径的输入吗？

*注意。* 我们使用类型注释来表示程序的参数类型。 关于发现动态不变量的[一章将讨论如何自动推断这些类型。](DynamicInvariants.html)

```py
def check_triangle(a: int, b: int, c: int) -> int:
    if a == b:
        if a == c:
            if b == c:
                return "Equilateral"
            else:
                return "Isosceles"
        else:
            return "Isosceles"
    else:
        if b != c:
            if a == c:
                return "Isosceles"
            else:
                return "Scalene"
        else:
            return "Isosceles"

```

### 控制流程图

此功能的控制流程图可表示如下：

```py
import [fuzzingbook_utils](https://github.com/uds-se/fuzzingbook/tree/master/notebooks/fuzzingbook_utils)

```

```py
from [ControlFlow](ControlFlow.html) import PyCFG, CFGNode, to_graph, gen_cfg

```

```py
import [inspect](https://docs.python.org/3/library/inspect.html)

```

```py
from [graphviz](https://docs.python.org/3/library/graphviz.html) import Source, Graph

```

```py
def show_cfg(fn, **kwargs):
    return Source(to_graph(gen_cfg(inspect.getsource(fn)), **kwargs))

```

```py
show_cfg(check_triangle)

```

<svg height="488pt" viewBox="0.00 0.00 693.00 488.00" width="693pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 484)"><title>%3</title> <g class="node" id="node1"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="265.5" y="-454.3">1: enter: check_triangle(a, b, c)</text></g> <g class="node" id="node9"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="265.5" y="-377.3">2: if: a == b</text></g> <g class="edge" id="edge7"><title>1->3</title></g> <g class="node" id="node2"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="362.5" y="-18.3">1: exit: check_triangle(a, b, c)</text></g> <g class="node" id="node3"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="148.5" y="-95.3">5: return 'Equilateral'</text></g> <g class="edge" id="edge1"><title>6->2</title></g> <g class="node" id="node4"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="295.5" y="-95.3">7: return 'Isosceles'</text></g> <g class="edge" id="edge2"><title>7->2</title></g> <g class="node" id="node5"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="61.5" y="-149.3">9: return 'Isosceles'</text></g> <g class="edge" id="edge3"><title>8->2</title></g> <g class="node" id="node6"><title>11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="478.5" y="-95.3">13: return 'Isosceles'</text></g> <g class="edge" id="edge4"><title>11->2</title></g> <g class="node" id="node7"><title>12</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="623.5" y="-95.3">15: return 'Scalene'</text></g> <g class="edge" id="edge5"><title>12->2</title></g> <g class="node" id="node8"><title>13</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="379.5" y="-149.3">17: return 'Isosceles'</text></g> <g class="edge" id="edge6"><title>13->2</title></g> <g class="node" id="node10"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="179.5" y="-290.3">3: if: a == c</text></g> <g class="edge" id="edge8"><title>3->4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="233" y="-333.8">T</text></g> <g class="node" id="node12"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="377.5" y="-290.3">11: if: b != c</text></g> <g class="edge" id="edge13"><title>3->9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="331.5" y="-333.8">F</text></g> <g class="edge" id="edge12"><title>4->8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="130.5" y="-246.8">F</text></g> <g class="node" id="node11"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="179.5" y="-203.3">4: if: b == c</text></g> <g class="edge" id="edge9"><title>4->5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="184" y="-246.8">T</text></g> <g class="edge" id="edge10"><title>5->6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="174" y="-149.3">T</text></g> <g class="edge" id="edge11"><title>5->7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="259.5" y="-149.3">F</text></g> <g class="edge" id="edge17"><title>9->13</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="381.5" y="-246.8">F</text></g> <g class="node" id="node13"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="487.5" y="-203.3">12: if: a == c</text></g> <g class="edge" id="edge14"><title>9->10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="443" y="-246.8">T</text></g> <g class="edge" id="edge15"><title>10->11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="488" y="-149.3">T</text></g> <g class="edge" id="edge16"><title>10->12</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="579.5" y="-149.3">F</text></g></g></svg>

程序跟踪的可能执行路径可以表示如下，其中数字表示执行的特定行号。

```py
paths = {
    '<path 1>': ([1, 2, 3, 4, 5], 'Equilateral'),
    '<path 2>': ([1, 2, 3, 4, 7], 'Isosceles'),
    '<path 3>': ([1, 2, 3, 9], 'Isosceles'),
    '<path 4>': ([1, 2, 11, 12, 13], 'Isosceles'),
    '<path 5>': ([1, 2, 11, 12, 15], 'Scalene'),
    '<path 6>': ([1, 2, 11, 17], 'Isosceles'),
}

```

考虑`<path 1>`。 要跟踪此路径，我们需要按顺序执行以下语句。

```py
1: check_triangle(a, b, c)
2: if (a == b) -> True
3: if (a == c) -> True
4: if (b == c) -> True
5: return 'Equilateral'

```

也就是说，任何跟踪此路径的执行都必须从`a`，`b`和`c`的值开始，这些值应遵守行号`2: (a == b)`的约束，其值为`True`，`3: (a == c)`的值为`True`和`4: (b == c)`评估为`True`。 我们是否可以生成满足这些约束的输入？

我们已经从[一章中看到了关于保形模糊](ConcolicFuzzer.html)的内容，如何使用SMT求解器（例如Z3）来获得解决方案。

```py
import [z3](https://docs.python.org/3/library/z3.html)

```

我们需要什么样的符号变量？ 我们可以从函数的类型注释中获取该信息。

```py
def get_annotations(fn):
    sig = inspect.signature(fn)
    return ([(i.name, i.annotation)
             for i in sig.parameters.values()], sig.return_annotation)

```

```py
params, ret = get_annotations(check_triangle)
params, ret

```

```py
([('a', int), ('b', int), ('c', int)], int)

```

我们创建符号变量来表示每个参数

```py
Sym_Vars = {
    int: (
        z3.Int, z3.IntVal), float: (
            z3.Real, z3.RealVal), str: (
                z3.String, z3.StringVal)}

```

```py
def get_symbolicparams(fn):
    params, ret = get_annotations(fn)
    return [Sym_Vars[typ][0](name)
            for name, typ in params], Sym_Vars[ret][0]('__return__')

```

```py
(a, b, c), r = get_symbolicparams(check_triangle)
a, b, c, r

```

```py
(a, b, c, __return__)

```

现在，我们可以要求 *z3* 为我们解方程组，如下所示。

```py
z3.solve(a == b, a == c, b == c)

```

```py
[a = 0, b = 0, c = 0]

```

在这里，我们找到了程序中的第一个问题。 我们的程序似乎不检查边数是否大于零。 现在假设我们没有该限制。 我们的程序是否正确遵循所述路径？

我们可以使用[章节中有关[conceptable fuzzing](ConcolicFuzzer.html) ]的`ArcCoverage`作为示踪剂来可视化该信息，如下所示。

```py
from [ConcolicFuzzer](ConcolicFuzzer.html) import ArcCoverage  # minor dependency

```

首先，我们恢复跟踪。

```py
with ArcCoverage() as cov:
    assert check_triangle(0, 0, 0) == 'Equilateral'
cov._trace, cov.arcs()

```

```py
([('check_triangle', 1),
  ('check_triangle', 2),
  ('check_triangle', 3),
  ('check_triangle', 4),
  ('check_triangle', 5),
  ('__exit__', 24),
  ('__exit__', 25)],
 [(1, 2), (2, 3), (3, 4), (4, 5), (5, 24), (24, 25)])

```

现在我们可以确定所采取的路径。

### CFG的路径为

```py
show_cfg(check_triangle, arcs=cov.arcs())

```

<svg height="420pt" viewBox="0.00 0.00 700.00 420.00" width="700pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 416)"><title>%3</title> <g class="node" id="node1"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="303.5" y="-386.3">1: enter: check_triangle(a, b, c)</text></g> <g class="node" id="node9"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="303.5" y="-310.3">2: if: a == b</text></g> <g class="edge" id="edge7"><title>1->3</title></g> <g class="node" id="node2"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="347.5" y="-18.3">1: exit: check_triangle(a, b, c)</text></g> <g class="node" id="node3"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="155.5" y="-94.3">5: return 'Equilateral'</text></g> <g class="edge" id="edge1"><title>6->2</title></g> <g class="node" id="node4"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="302.5" y="-94.3">7: return 'Isosceles'</text></g> <g class="edge" id="edge2"><title>7->2</title></g> <g class="node" id="node5"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="61.5" y="-166.3">9: return 'Isosceles'</text></g> <g class="edge" id="edge3"><title>8->2</title></g> <g class="node" id="node6"><title>11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="485.5" y="-94.3">13: return 'Isosceles'</text></g> <g class="edge" id="edge4"><title>11->2</title></g> <g class="node" id="node7"><title>12</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="630.5" y="-94.3">15: return 'Scalene'</text></g> <g class="edge" id="edge5"><title>12->2</title></g> <g class="node" id="node8"><title>13</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="391.5" y="-166.3">17: return 'Isosceles'</text></g> <g class="edge" id="edge6"><title>13->2</title></g> <g class="node" id="node10"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="217.5" y="-238.3">3: if: a == c</text></g> <g class="edge" id="edge8"><title>3->4</title></g> <g class="node" id="node12"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="391.5" y="-238.3">11: if: b != c</text></g> <g class="edge" id="edge13"><title>3->9</title></g> <g class="edge" id="edge12"><title>4->8</title></g> <g class="node" id="node11"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="217.5" y="-166.3">4: if: b == c</text></g> <g class="edge" id="edge9"><title>4->5</title></g> <g class="edge" id="edge10"><title>5->6</title></g> <g class="edge" id="edge11"><title>5->7</title></g> <g class="edge" id="edge17"><title>9->13</title></g> <g class="node" id="node13"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="556.5" y="-166.3">12: if: a == c</text></g> <g class="edge" id="edge14"><title>9->10</title></g> <g class="edge" id="edge15"><title>10->11</title></g> <g class="edge" id="edge16"><title>10->12</title></g></g></svg>

如您所见，采用的路径是`<path 1>`。

类似地，为了解决`<path 2>`，我们只需要简单地反转<第2行>的条件：

```py
z3.solve(a == b, a == c, z3.Not(b == c))

```

```py
no solution

```

象征性执行表明没有解决方案。 片刻的反思会让我们相信这确实是事实。 让我们继续其他路径。 可以通过将`<line 4>`处的条件取反来获得`<path 3>`。

```py
z3.solve(a == b, z3.Not(a == c))

```

```py
[c = 0, b = 1, a = 1]

```

```py
with ArcCoverage() as cov:
    assert check_triangle(1, 1, 0) == 'Isosceles'
[i for fn, i in cov._trace if fn == 'check_triangle']

```

```py
[1, 2, 3, 9]

```

```py
paths['<path 3>']

```

```py
([1, 2, 3, 9], 'Isosceles')

```

路径< 4 >？4 >

```py
z3.solve(z3.Not(a == b), b != c, a == c)

```

```py
[b = 0, c = 1, a = 1]

```

如前所述，我们的程序不考虑长度为零或为负的边。 我们可以修改程序以检查零输入和负输入。 但是，我们是否总是必须确保每个功能都必须考虑所有可能的输入？ `check_triangle`可能没有直接暴露给用户，它已经从另一个已经保证输入为正的函数中调用。 在关于动态不变量的[一章中，我们将展示如何发现这样的前提条件和后置条件。](DynamicInvariants.html)

我们可以在这里轻松添加这样的前提条件。

```py
pre_condition = z3.And(a > 0, b > 0, c > 0)

```

```py
z3.solve(pre_condition, z3.Not(a == b), b != c, a == c)

```

```py
[c = 2, b = 1, a = 2]

```

```py
with ArcCoverage() as cov:
    assert check_triangle(1, 2, 1) == 'Isosceles'
[i for fn, i in cov._trace if fn == 'check_triangle']

```

```py
[1, 2, 11, 12, 13]

```

```py
paths['<path 4>']

```

```py
([1, 2, 11, 12, 13], 'Isosceles')

```

继续到路径< 5 >：5 >

```py
z3.solve(pre_condition, z3.Not(a == b), b != c, z3.Not(a == c))

```

```py
[b = 2, a = 1, c = 3]

```

确实是*斜角*三角形。

```py
with ArcCoverage() as cov:
    assert check_triangle(3, 1, 2) == 'Scalene'

```

```py
paths['<path 5>']

```

```py
([1, 2, 11, 12, 15], 'Scalene')

```

最后，对于`<path 6>`，该过程相似。

```py
z3.solve(pre_condition, z3.Not(a == b), z3.Not(b != c))

```

```py
[c = 2, a = 1, b = 2]

```

```py
with ArcCoverage() as cov:
    assert check_triangle(2, 1, 1) == 'Isosceles'
[i for fn, i in cov._trace if fn == 'check_triangle']

```

```py
[1, 2, 11, 17]

```

```py
paths['<path 6>']

```

```py
([1, 2, 11, 17], 'Isosceles')

```

如果我们想要另一个解决方案怎么办？ 我们可以简单地要求求解器再次求解，而不能给我们相同的值。

```py
seen = [z3.And(a == 2, b == 1, c == 1)]

```

```py
z3.solve(pre_condition, z3.Not(z3.Or(seen)), z3.Not(a == b), z3.Not(b != c))

```

```py
[c = 2, a = 1, b = 2]

```

```py
seen.append(z3.And(a == 1, b == 2, c == 2))

```

```py
z3.solve(pre_condition, z3.Not(z3.Or(seen)), z3.Not(a == b), z3.Not(b != c))

```

```py
[c = 1, a = 3, b = 1]

```

也就是说，使用简单的符号计算，我们可以轻松地看到（1）一些路径不可达，（2）一些条件不足-我们需要先决条件。 那么获得的总覆盖率呢？

### 可视化覆盖范围

可视化语句覆盖范围可以通过以下方式实现。

```py
class ArcCoverage(ArcCoverage):
    def show_coverage(self, fn):
        src = fn if isinstance(fn, str) else inspect.getsource(fn)
        covered = set([lineno for method, lineno in self._trace])
        for i, s in enumerate(src.split('\n')):
            print('%s  %2d: %s' % ('#' if i + 1 in covered else ' ', i + 1, s))

```

我们运行在coverage跟踪器下获得的所有输入。

```py
with ArcCoverage() as cov:
    assert check_triangle(0, 0, 0) == 'Equilateral'
    assert check_triangle(1, 1, 0) == 'Isosceles'
    assert check_triangle(1, 2, 1) == 'Isosceles'
    assert check_triangle(3, 1, 2) == 'Scalene'
    assert check_triangle(2, 1, 1) == 'Isosceles'

```

```py
cov.show_coverage(check_triangle)

```

```py
#  1: def check_triangle(a: int, b: int, c: int) -> int:
#  2:     if a == b:
#  3:         if a == c:
#  4:             if b == c:
#  5:                 return "Equilateral"
   6:             else:
   7:                 return "Isosceles"
   8:         else:
#  9:             return "Isosceles"
  10:     else:
# 11:         if b != c:
# 12:             if a == c:
# 13:                 return "Isosceles"
  14:             else:
# 15:                 return "Scalene"
  16:         else:
# 17:             return "Isosceles"
  18: 

```

覆盖范围符合预期。 生成的值似乎覆盖了所有可以覆盖的代码。

我们已经看到了如何推理程序的每个路径。 我们可以将它们组合在一起以产生表示程序行为的单个表达式吗？ 这是我们接下来要讨论的。

### 函数摘要

考虑该方程式以确定绝对值。

```py
def abs_value(x: float) -> float:
    if x < 0:
        v: float = -x
    else:
        v: float = x
    return v

```

```py
show_cfg(abs_value)

```

<svg height="366pt" viewBox="0.00 0.00 271.84 366.00" width="272pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 362)"><title>%3</title> <g class="node" id="node1"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="133.0444" y="-332.3">1: enter: abs_value(x)</text></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="133.0444" y="-255.3">2: if: x < 0</text></g> <g class="edge" id="edge2"><title>1->3</title></g> <g class="node" id="node2"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="133.0444" y="-18.3">1: exit: abs_value(x)</text></g> <g class="node" id="node3"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="133.0444" y="-95.3">6: return v</text></g> <g class="edge" id="edge1"><title>6->2</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="63.0444" y="-168.3">3: v: float = -x</text></g> <g class="edge" id="edge3"><title>3->4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="107.5444" y="-211.8">T</text></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="204.0444" y="-168.3">5: v: float = x</text></g> <g class="edge" id="edge4"><title>3->5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="176.0444" y="-211.8">F</text></g> <g class="edge" id="edge5"><title>4->6</title></g> <g class="edge" id="edge6"><title>5->6</title></g></g></svg>

关于`line: 5`中`v`的值，我们能说什么？ 让我们追踪和查看。 首先，我们在`line: 1`有变量`x`。

```py
(x,), r = get_symbolicparams(abs_value)

```

在`line: 2`，我们在可能的路径上面临分歧。 因此，我们产生两条具有相应约束的路径。

```py
l2_F = x < 0
l2_T = z3.Not(x < 0)

```

对于`line: 3`，我们只需要考虑`If`路径。 但是，我们有一个作业。 所以我们在这里使用一个新变量。 源中指示了*浮点型*类型，其等效的 *z3* 类型为*实型*。

```py
v_0 = z3.Real('v_0')
l3 = z3.And(l2_F, v_0 == -x)

```

同样，对于`line: 5`，我们有一个分配。 （我们可以重用之前的变量`v_0`吗？）

```py
v_1 = z3.Real('v_1')
l5 = z3.And(l2_T, v_1 == x)

```

当我们来到`line: 6`时，我们看到我们有*两个*输入流。 我们有一个选择。 我们可以像以前一样将每个路径分开。

```py
v = z3.Real('v')
for s in [z3.And(l3, v == v_0), z3.And(l5, v == v_1)]:
    z3.solve(x != 0, s)

```

```py
[v_0 = 1/2, v = 1/2, x = -1/2]
[v = 1, v_1 = 1, x = 1]

```

或者，我们可以将它们组合在一起并在`line: 6`处产生一个谓词。

```py
v = z3.Real('v')
l6 = z3.Or(z3.And(l3, v == v_0), z3.And(l5, v == v_1))
z3.solve(l6)

```

```py
[v_0 = -1/2, v = 0, v_1 = 0, x = 0]

```

**注意。** 合并两个传入的执行流可能很简单，尤其是在多次遍历执行路径时（例如循环和递归）。 对于那些感兴趣的人，查找[推断循环不变式](https://www.st.cs.uni-saarland.de/publications/details/galeotti-hvc-2014/)。

我们可以得到此结果来生成`abs()`的任何数量的解决方案，如下所示。

```py
s = z3.Solver()
s.add(l6)
for i in range(5):
    if s.check() == z3.sat:
        m = s.model()
        x_val = m[x]
        print(m)
    else:
        print('no solution')
        break
    s.add(z3.Not(x == x_val))
s

```

```py
[v_0 = -1/2, v = 0, v_1 = 0, x = 0]
[v = 1, v_1 = 1, x = 1]
[v = 1/2, v_1 = 1/2, x = 1/2]
[v = 1/4, v_1 = 1/4, x = 1/4]
[v = 1/8, v_1 = 1/8, x = 1/8]

```

```py
[Or(And(And(x < 0, v_0 == -x), v == v_0),
    And(And(Not(x < 0), v_1 == x), v == v_1)),
 Not(0 == x),
 Not(1 == x),
 Not(1/2 == x),
 Not(1/4 == x),
 Not(1/8 == x)]

```

求解器不是特别随机的。 因此，我们需要一点帮助以产生负范围内的值。

```py
s.add(x < 0)
for i in range(5):
    if s.check() == z3.sat:
        m = s.model()
        x_val = m[x]
        print(m)
    else:
        print('no solution')
        break
    s.add(z3.Not(x == x_val))

s

```

```py
[v_0 = 1, v = 1, x = -1]
[v_0 = 2, v = 2, x = -2]
[v_0 = 3, v = 3, x = -3]
[v_0 = 4, v = 4, x = -4]
[v_0 = 5, v = 5, x = -5]

```

```py
[Or(And(And(x < 0, v_0 == -x), v == v_0),
    And(And(Not(x < 0), v_1 == x), v == v_1)),
 Not(0 == x),
 Not(1 == x),
 Not(1/2 == x),
 Not(1/4 == x),
 Not(1/8 == x),
 x < 0,
 Not(-1 == x),
 Not(-2 == x),
 Not(-3 == x),
 Not(-4 == x),
 Not(-5 == x)]

```

注意，在`line: 6`处产生的单个表达式本质上是`abs_value()`的摘要。

```py
abs_value_summary = l6
abs_value_summary

```

```py
Or(And(And(x < 0, v_0 == -x), v == v_0),
   And(And(Not(x < 0), v_1 == x), v == v_1))

```

*z3* 求解器可以在可能的情况下简化谓词。

```py
z3.simplify(l6)

```

```py
Or(And(Not(0 <= x), v_0 == -1*x, v == v_0),
   And(0 <= x, v_1 == x, v == v_1))

```

当在其他地方使用`abs_value()`时，可以使用此摘要而不是追溯到`abs_value()`。 但是，这给我们带来了一个问题。 可能多次调用同一功能。 在这种情况下，使用相同的变量将导致冲突。 避免这种情况的一种方法是在*前缀*之前将某些调用特定的值赋给变量。

```py
import [ast](https://docs.python.org/3/library/ast.html)
import [astor](https://docs.python.org/3/library/astor.html)

```

方法`prefix_vars()`修改表达式中的变量，以使变量以给定值为前缀。

```py
def prefix_vars(astnode, prefix):
    if isinstance(astnode, ast.BoolOp):
        return ast.BoolOp(astnode.op,
                          [prefix_vars(i, prefix) for i in astnode.values], [])
    elif isinstance(astnode, ast.BinOp):
        return ast.BinOp(
            prefix_vars(astnode.left, prefix), astnode.op,
            prefix_vars(astnode.right, prefix))
    elif isinstance(astnode, ast.UnaryOp):
        return ast.UnaryOp(astnode.op, prefix_vars(astnode.operand, prefix))
    elif isinstance(astnode, ast.Call):
        return ast.Call(prefix_vars(astnode.func, prefix),
                        [prefix_vars(i, prefix) for i in astnode.args],
                        astnode.keywords)
    elif isinstance(astnode, ast.Compare):
        return ast.Compare(
            prefix_vars(astnode.left, prefix), astnode.ops,
            [prefix_vars(i, prefix) for i in astnode.comparators])
    elif isinstance(astnode, ast.Name):
        if astnode.id in {'And', 'Or', 'Not'}:
            return ast.Name('z3.%s' % (astnode.id), astnode.ctx)
        else:
            return ast.Name('%s%s' % (prefix, astnode.id), astnode.ctx)
    elif isinstance(astnode, ast.Return):
        return ast.Return(prefix_vars(astnode.value, env))
    else:
        return astnode

```

为了应用`prefix_vars()`，需要使用Python表达式的AST。 解析Python源代码时，默认情况下将其包装在`Module`中。

```py
ast.parse('x+y')

```

```py
<_ast.Module at 0xa1746ca20>

```

因此，为了更轻松地操作表达式AST，我们定义了`get_expression()`，它可以将其解包并返回表达式的AST表示。

```py
def get_expression(src):
    return ast.parse(src).body[0].value

```

它的用法如下：

```py
e = get_expression('x+y')
e

```

```py
<_ast.BinOp at 0xa1746c898>

```

函数`to_src()`允许我们*解析*表达式。

```py
def to_src(astnode):
    return astor.to_source(astnode).strip()

```

It is used as follows:

```py
to_src(e)

```

```py
'(x + y)'

```

我们可以将两个片段组合在一起以产生一个前缀表达式，如下所示。

```py
abs_value_summary_ast = get_expression(str(abs_value_summary))
print(to_src(prefix_vars(abs_value_summary_ast, 'x1_')))

```

```py
z3.Or(z3.And(z3.And(x1_x < 0, x1_v_0 == -x1_x), x1_v == x1_v_0), z3.And(
    z3.And(z3.Not(x1_x < 0), x1_v_1 == x1_x), x1_v == x1_v_1))

```

#### 获取使用的变量的名称和类型

那使用的声明呢？ 假设我们在 *Z3* 中都有所有方程式，我们可以直接检索此信息。 我们定义`z3_names_and_types()`，它接受 *Z3* 表达式，并提取所需的变量定义。

```py
def z3_names_and_types(z3_ast):
    hm = {}
    children = z3_ast.children()
    if children:
        for c in children:
            hm.update(z3_names_and_types(c))
    else:
        # HACK.. How else to distinguish literals and vars?
        if (str(z3_ast.decl()) != str(z3_ast.sort())):
            hm["%s" % str(z3_ast.decl())] = 'z3.%s' % str(z3_ast.sort())
        else:
            pass
    return hm

```

```py
abs_value_declarations = z3_names_and_types(abs_value_summary)
abs_value_declarations

```

```py
{'x': 'z3.Real', 'v_0': 'z3.Real', 'v': 'z3.Real', 'v_1': 'z3.Real'}

```

但是，`z3_names_and_types()`受限制，因为它需要 *Z3* AST才能运行。 因此，我们还定义了`used_identifiers()`，可以直接从任何Python表达式的字符串表示中提取标识符（包括 *Z3* 约束）。 这里的一个权衡是，我们无法跟踪类型信息。 但是我们稍后会看到如何恢复。

```py
def used_identifiers(src):
    def names(astnode):
        lst = []
        if isinstance(astnode, ast.BoolOp):
            for i in astnode.values:
                lst.extend(names(i))
        elif isinstance(astnode, ast.BinOp):
            lst.extend(names(astnode.left))
            lst.extend(names(astnode.right))
        elif isinstance(astnode, ast.UnaryOp):
            lst.extend(names(astnode.operand))
        elif isinstance(astnode, ast.Call):
            for i in astnode.args:
                lst.extend(names(i))
        elif isinstance(astnode, ast.Compare):
            lst.extend(names(astnode.left))
            for i in astnode.comparators:
                lst.extend(names(i))
        elif isinstance(astnode, ast.Name):
            lst.append(astnode.id)
        elif isinstance(astnode, ast.Expr):
            lst.extend(names(astnode.value))
        elif isinstance(astnode, (ast.Num, ast.Str, ast.Tuple, ast.NameConstant)):
            pass
        elif isinstance(astnode, ast.Assign):
            for t in astnode.targets:
                lst.extend(names(t))
            lst.extend(names(astnode.value))
        elif isinstance(astnode, ast.Module):
            for b in astnode.body:
                lst.extend(names(b))
        else:
            raise Exception(str(astnode))
        return list(set(lst))
    return names(ast.parse(src))

```

```py
used_identifiers(str(abs_value_summary))

```

```py
['x', 'v_0', 'v_1', 'v']

```

现在我们可以注册功能摘要`abs_value`供以后使用。

```py
Function_Summaries = {}
Function_Summaries['abs_value'] = {
    'predicate': str(abs_value_summary),
    'vars': abs_value_declarations}

```

如前所述，我们不想依赖 *Z3* 来提取类型信息。 更好的替代方法是让用户将类型信息指定为注释，然后从程序中提取该信息。 接下来，我们将看到如何实现这一目标。

首先，我们将 *Python类型转换为Z3类型*映射为其等效字符串。

```py
Sym_Vars_Str = {
    k.__name__: ("z3.%s" % v1.__name__, "z3.%s" % v2.__name__)
    for k, (v1, v2) in Sym_Vars.items()
}
Sym_Vars_Str

```

```py
{'int': ('z3.Int', 'z3.IntVal'),
 'float': ('z3.Real', 'z3.RealVal'),
 'str': ('z3.String', 'z3.StringVal')}

```

我们还定义了一种方便的方法`translate_to_z3_name()`，用于访问符号变量的 *Z3* 类型。

```py
def translate_to_z3_name(v):
    return Sym_Vars_Str[v][0]

```

现在，我们定义方法`declarations()`，该方法提取Python *语句*中使用的变量。 该想法是寻找包含带注释的类型信息的扩充分配。 这些被收集并返回。

如果存在`call`节点，则它们表示函数调用。 这些函数调用中使用的变量从相应的函数摘要中恢复。

```py
def declarations(astnode, hm=None):
    if hm is None:
        hm = {}
    if isinstance(astnode, ast.Module):
        for b in astnode.body:
            declarations(b, hm)
    elif isinstance(astnode, ast.FunctionDef):
        #hm[astnode.name + '__return__'] = translate_to_z3_name(astnode.returns.id)
        for a in astnode.args.args:
            hm[a.arg] = translate_to_z3_name(a.annotation.id)
        for b in astnode.body:
            declarations(b, hm)
    elif isinstance(astnode, ast.Call):
        # get declarations from the function summary.
        n = astnode.function
        assert isinstance(n, ast.Name)  # for now.
        name = n.id
        hm.update(dict(Function_Summaries[name]['vars']))
    elif isinstance(astnode, ast.AnnAssign):
        assert isinstance(astnode.target, ast.Name)
        hm[astnode.target.id] = translate_to_z3_name(astnode.annotation.id)
    elif isinstance(astnode, ast.Assign):
        # verify it is already defined
        for t in astnode.targets:
            assert isinstance(t, ast.Name)
            assert t.id in hm
    elif isinstance(astnode, ast.AugAssign):
        assert isinstance(astnode.target, ast.Name)
        assert astnode.target.id in hm
    elif isinstance(astnode, (ast.If, ast.For, ast.While)):
        for b in astnode.body:
            declarations(b, hm)
        for b in astnode.orelse:
            declarations(b, hm)
    elif isinstance(astnode, ast.Return):
        pass
    else:
        raise Exception(str(astnode))
    return hm

```

这样，我们现在可以提取表达式中使用的变量。

```py
declarations(ast.parse('s: int = 3\np: float = 4.0\ns += 1'))

```

```py
{'s': 'z3.Int', 'p': 'z3.Real'}

```

我们将`declarations()`包装在直接在功能对象上运行的方法`used_vars()`中。

```py
def used_vars(fn):
    return declarations(ast.parse(inspect.getsource(fn)))

```

使用方法如下：

```py
used_vars(check_triangle)

```

```py
{'a': 'z3.Int', 'b': 'z3.Int', 'c': 'z3.Int'}

```

```py
used_vars(abs_value)

```

```py
{'x': 'z3.Real', 'v': 'z3.Real'}

```

给定提取的变量及其 *Z3* 类型，我们需要一种在需要时重新实例化它们的方法。 我们定义`define_symbolic_vars()`，将这些描述转换为可以直接`exec()`编辑的形式。

```py
def define_symbolic_vars(fn_vars, prefix):
    sym_var_dec = ', '.join([prefix + n for n in fn_vars])
    sym_var_def = ', '.join(["%s('%s%s')" % (t, prefix, n)
                             for n, t in fn_vars.items()])
    return "%s = %s" % (sym_var_dec, sym_var_def)

```

Here is how it can be used:

```py
define_symbolic_vars(abs_value_declarations, '')

```

```py
"x, v_0, v, v_1 = z3.Real('x'), z3.Real('v_0'), z3.Real('v'), z3.Real('v_1')"

```

接下来，我们定义`gen_fn_summary()`，它使用 *Z3* 以可实例化的形式返回函数摘要。

```py
def gen_fn_summary(prefix, fn):
    summary = Function_Summaries[fn.__name__]['predicate']
    fn_vars = Function_Summaries[fn.__name__]['vars']
    decl = define_symbolic_vars(fn_vars, prefix)
    summary_ast = get_expression(summary)
    return decl, to_src(prefix_vars(summary_ast, prefix))

```

Here is how it can be used:

```py
gen_fn_summary('a_', abs_value)

```

```py
("a_x, a_v_0, a_v, a_v_1 = z3.Real('a_x'), z3.Real('a_v_0'), z3.Real('a_v'), z3.Real('a_v_1')",
 'z3.Or(z3.And(z3.And(a_x < 0, a_v_0 == -a_x), a_v == a_v_0), z3.And(z3.And(\n    z3.Not(a_x < 0), a_v_1 == a_x), a_v == a_v_1))')

```

```py
gen_fn_summary('b_', abs_value)

```

```py
("b_x, b_v_0, b_v, b_v_1 = z3.Real('b_x'), z3.Real('b_v_0'), z3.Real('b_v'), z3.Real('b_v_1')",
 'z3.Or(z3.And(z3.And(b_x < 0, b_v_0 == -b_x), b_v == b_v_0), z3.And(z3.And(\n    z3.Not(b_x < 0), b_v_1 == b_x), b_v == b_v_1))')

```

我们如何使用函数摘要？ 这是使用`abs_value()`的函数`abs_max()`。

```py
def abs_max(a: float, b: float):
    a1: float = abs_value(a)
    b1: float = abs_value(b)
    if a1 > b1:
        c: float = a1
    else:
        c: float = b1
    return c

```

为了象征性地跟踪此功能，我们首先定义两个变量`a`和`b`。

```py
a = z3.Real('a')
b = z3.Real('b')

```

`line: 2`包含`a1`的定义，我们将其定义为符号变量。

```py
a1 = z3.Real('a1')

```

我们还需要调用`abs_value()`，其完成如下。 由于这是对`abs_value()`的首次调用，因此我们将`abs1`作为前缀。

```py
d, v = gen_fn_summary('abs1_', abs_value)
d, v

```

```py
("abs1_x, abs1_v_0, abs1_v, abs1_v_1 = z3.Real('abs1_x'), z3.Real('abs1_v_0'), z3.Real('abs1_v'), z3.Real('abs1_v_1')",
 'z3.Or(z3.And(z3.And(abs1_x < 0, abs1_v_0 == -abs1_x), abs1_v == abs1_v_0),\n    z3.And(z3.And(z3.Not(abs1_x < 0), abs1_v_1 == abs1_x), abs1_v == abs1_v_1))')

```

我们还需要将结果值`<prefix>_v`等于我们先前定义的符号变量`a1`。

```py
l2_src = "l2 = z3.And(a == abs1_x, a1 == abs1_v, %s)" % v
l2_src

```

```py
'l2 = z3.And(a == abs1_x, a1 == abs1_v, z3.Or(z3.And(z3.And(abs1_x < 0, abs1_v_0 == -abs1_x), abs1_v == abs1_v_0),\n    z3.And(z3.And(z3.Not(abs1_x < 0), abs1_v_1 == abs1_x), abs1_v == abs1_v_1)))'

```

同时应用声明和赋值。

```py
exec(d)
exec(l2_src)

```

```py
l2

```

```py
And(a == abs1_x,
    a1 == abs1_v,
    Or(And(And(abs1_x < 0, abs1_v_0 == -abs1_x),
           abs1_v == abs1_v_0),
       And(And(Not(abs1_x < 0), abs1_v_1 == abs1_x),
           abs1_v == abs1_v_1)))

```

我们需要对`line: 3`做同样的事情，但是要以`abs2`作为前缀。

```py
b1 = z3.Real('b1')
d, v = gen_fn_summary('abs2_', abs_value)
l3_src = "l3_ = z3.And(b == abs2_x, b1 == abs2_v, %s)" % v
exec(d)
exec(l3_src)

```

```py
l3_

```

```py
And(b == abs2_x,
    b1 == abs2_v,
    Or(And(And(abs2_x < 0, abs2_v_0 == -abs2_x),
           abs2_v == abs2_v_0),
       And(And(Not(abs2_x < 0), abs2_v_1 == abs2_x),
           abs2_v == abs2_v_1)))

```

要获得`line: 3`的真实谓词集，我们需要从`line: 2`中添加谓词。

```py
l3 = z3.And(l2, l3_)

```

```py
l3

```

```py
And(And(a == abs1_x,
        a1 == abs1_v,
        Or(And(And(abs1_x < 0, abs1_v_0 == -abs1_x),
               abs1_v == abs1_v_0),
           And(And(Not(abs1_x < 0), abs1_v_1 == abs1_x),
               abs1_v == abs1_v_1))),
    And(b == abs2_x,
        b1 == abs2_v,
        Or(And(And(abs2_x < 0, abs2_v_0 == -abs2_x),
               abs2_v == abs2_v_0),
           And(And(Not(abs2_x < 0), abs2_v_1 == abs2_x),
               abs2_v == abs2_v_1))))

```

使用z3可以简化此方程式。

```py
z3.simplify(l3)

```

```py
And(a == abs1_x,
    a1 == abs1_v,
    Or(And(Not(0 <= abs1_x),
           abs1_v_0 == -1*abs1_x,
           abs1_v == abs1_v_0),
       And(0 <= abs1_x,
           abs1_v_1 == abs1_x,
           abs1_v == abs1_v_1)),
    b == abs2_x,
    b1 == abs2_v,
    Or(And(Not(0 <= abs2_x),
           abs2_v_0 == -1*abs2_x,
           abs2_v == abs2_v_0),
       And(0 <= abs2_x,
           abs2_v_1 == abs2_x,
           abs2_v == abs2_v_1)))

```

来到`line: 4`，我们有一个条件。

```py
l4_cond = a1 > b1
l4 = z3.And(l3, l4_cond)

```

对于`line: 5`，假设我们采用 *IF* 分支，则定义符号变量`c_0`。

```py
c_0 = z3.Real('c_0')
l5 = z3.And(l4, c_0 == a1)

```

对于`line: 6`，采用了 *ELSE* 分支。 因此，我们反转了这种条件。

```py
l6 = z3.And(l3, z3.Not(l4_cond))

```

对于`line: 7`，我们定义`c_1`。

```py
c_1 = z3.Real('c_1')
l7 = z3.And(l6, c_1 == b1)

```

```py
s1 = z3.Solver()
s1.add(l5)
s1.check()

```

```py
sat

```

```py
m1 = s1.model()
sorted([(d, m1[d]) for d in m1.decls() if not d.name(
).startswith('abs')], key=lambda x: x[0].name())

```

```py
[(a, 1/2), (a1, 1/2), (b, 0), (b1, 0), (c_0, 1/2)]

```

```py
s2 = z3.Solver()
s2.add(l7)
s2.check()

```

```py
sat

```

```py
m2 = s2.model()
sorted([(d, m2[d]) for d in m2.decls() if not d.name(
).startswith('abs')], key=lambda x: x[0].name())

```

```py
[(a, 0), (a1, 0), (b, 0), (b1, 0), (c_1, 0)]

```

我们真正想做的是使这个过程自动化，因为手工完成既繁琐又容易出错。 本质上，我们希望能够提取程序中的所有路径*，并象征性地执行每个路径，这将生成覆盖程序所有可访问部分所需的输入。*

## SimpleSymbolicFuzzer

我们定义了一个简单的*符号模糊器*，它可以根据以下假设以符号形式生成输入值*：*

*   程序中没有循环
*   该功能是自包含的。
*   没有递归。
*   没有变量的重新分配。

关键思想如下：我们从入口点开始遍历控制流程图，并生成到给定深度的所有可能路径。 然后，我们收集沿路径遇到的约束，并生成将遍历程序的输入。

我们基于类`Fuzzer`构建我们的模糊器。

```py
from [Fuzzer](Fuzzer.html) import Fuzzer

```

我们首先提取传递的函数的控制流程图。 我们还为子类提供了一个挂钩来进行处理。

```py
class SimpleSymbolicFuzzer(Fuzzer):
    def __init__(self, fn, **kwargs):
        self.fn_name = fn.__name__
        py_cfg = PyCFG()
        py_cfg.gen_cfg(inspect.getsource(fn))
        self.fnenter, self.fnexit = py_cfg.functions[self.fn_name]
        self.used_variables = used_vars(fn)
        self.fn_args = list(inspect.signature(fn).parameters)
        self.z3 = z3.Solver()

        self.paths = None
        self.last_path = None

        self.options(kwargs)
        self.process()

    def process(self):
        pass

```

我们需要一些变量来控制我们愿意遍历多少。

`MAX_DEPTH`是应尝试跟踪执行的深度。

```py
MAX_DEPTH = 100

```

`MAX_TRIES`是我们放弃之前尝试产生值的最大尝试次数。

```py
MAX_TRIES = 100

```

`MAX_ITER`是我们将尝试的迭代次数。

```py
MAX_ITER = 100

```

`options()`方法在模糊类中设置这些参数。

```py
class SimpleSymbolicFuzzer(SimpleSymbolicFuzzer):
    def options(self, kwargs):
        self.max_depth = kwargs.get('max_depth', MAX_DEPTH)
        self.max_tries = kwargs.get('max_tries', MAX_TRIES)
        self.max_iter = kwargs.get('max_iter', MAX_ITER)
        self._options = kwargs

```

初始化生成控制流程图，并将其挂接到`fnenter`和`fnexit`。

```py
symfz_ct = SimpleSymbolicFuzzer(check_triangle)

```

```py
symfz_ct.fnenter, symfz_ct.fnexit

```

```py
(id:9 line[1] parents: [] : enter: check_triangle(a, b, c),
 id:10 line[1] parents: [14, 15, 16, 19, 20, 21] : exit: check_triangle(a, b, c))

```

### 生成所有可能的路径

我们可以使用从`fnenter`开始的过程`get_all_paths()`递归地检索函数中的所有路径。

想法如下：从函数入口点`fenter`开始，然后使用CFG递归地跟随子代。 在任何节点上都有一个分支，将有多个子节点。 在其他节点上，将只有一个孩子。 假设一个节点有$ n $个子节点。 这样的节点将导致$ n $路径。 我们将当前节点附加到每个路径的开头，并返回由此生成的所有路径。

```py
class SimpleSymbolicFuzzer(SimpleSymbolicFuzzer):
    def get_all_paths(self, fenter, depth=0):
        if depth > self.max_depth:
            raise Exception('Maximum depth exceeded')
        if not fenter.children:
            return [[(0, fenter)]]

        fnpaths = []
        for idx, child in enumerate(fenter.children):
            child_paths = self.get_all_paths(child, depth + 1)
            for path in child_paths:
                # In a conditional branch, idx is 0 for IF, and 1 for Else
                fnpaths.append([(idx, fenter)] + path)
        return fnpaths

```

可以如下使用。

```py
symfz_ct = SimpleSymbolicFuzzer(check_triangle)
paths = symfz_ct.get_all_paths(symfz_ct.fnenter)
print(len(paths))
paths[1]

```

```py
6

```

```py
[(0, id:24 line[1] parents: [] : enter: check_triangle(a, b, c)),
 (0, id:26 line[2] parents: [24] : _if: a == b),
 (0, id:27 line[3] parents: [26] : _if: a == c),
 (1, id:28 line[4] parents: [27] : _if: b == c),
 (0, id:30 line[7] parents: [28] : return 'Isosceles'),
 (0,
  id:25 line[1] parents: [29, 30, 31, 34, 35, 36] : exit: check_triangle(a, b, c))]

```

我们将`get_all_paths()`挂接到初始化，如下所示。

```py
class SimpleSymbolicFuzzer(SimpleSymbolicFuzzer):
    def process(self):
        self.paths = self.get_all_paths(self.fnenter)
        self.last_path = len(self.paths)

```

### 提取所有约束

对于任何给定的路径，我们定义一个函数`extract_constraints()`来提取约束，以便它们可以直接用 *Z3* 执行。 `idx`代表所采取的特定分支。 因此，如果`False`分支是有条件的，则附加条件的否定。

```py
class SimpleSymbolicFuzzer(SimpleSymbolicFuzzer):
    def extract_constraints(self, path):
        predicates = []
        for (idx, elt) in path:
            if isinstance(elt.ast_node, ast.AnnAssign):
                if elt.ast_node.target.id in {'_if', '_while'}:
                    s = to_src(elt.ast_node.annotation)
                    predicates.append(("%s" if idx == 0 else "z3.Not%s") % s)
                elif isinstance(elt.ast_node.annotation, ast.Call):
                    assert elt.ast_node.annotation.func.id == self.fn_name
                else:
                    node = elt.ast_node
                    t = ast.Compare(node.target, [ast.Eq()], [node.value])
                    predicates.append(to_src(t))
            elif isinstance(elt.ast_node, ast.Assign):
                node = elt.ast_node
                t = ast.Compare(node.targets[0], [ast.Eq()], [node.value])
                predicates.append(to_src(t))
            else:
                pass
        return predicates

```

```py
symfz_ct = SimpleSymbolicFuzzer(check_triangle)
paths = symfz_ct.get_all_paths(symfz_ct.fnenter)
symfz_ct.extract_constraints(paths[0])

```

```py
['(a == b)', '(a == c)', '(b == c)']

```

```py
constraints = symfz_ct.extract_constraints(paths[1])
constraints

```

```py
['(a == b)', '(a == c)', 'z3.Not(b == c)']

```

### 使用简单符号模糊器[进行模糊处理](#Fuzzing-with-Simple-Symbolic-Fuzzer)

为了实际生成解决方案，我们定义`fuzz()`。 为此，我们需要首先提取所有路径。 然后选择一条特定的路径，并提取该路径中的约束，然后使用 *z3* 进行求解。

```py
from [contextlib](https://docs.python.org/3/library/contextlib.html) import contextmanager

```

首先，我们为当前求解器创建一个检查点，以便我们可以检查谓词，并在必要时回滚。

```py
@contextmanager
def checkpoint(z3solver):
    z3solver.push()
    yield z3solver
    z3solver.pop()

```

`use_path()`函数提取单个函数的约束，将其应用于当前的求解器（在检查点下），如果可以找到一些解，则返回结果。 如果找到了解决方案，我们还确保我们永远不会重复使用这些解决方案。

```py
class SimpleSymbolicFuzzer(SimpleSymbolicFuzzer):
    def solve_path_constraint(self, path):
        # re-initializing does not seem problematic.
        # a = z3.Int('a').get_id() remains the same.
        constraints = self.extract_constraints(path)
        decl = define_symbolic_vars(self.used_variables, '')
        exec(decl)

        solutions = {}
        with checkpoint(self.z3):
            st = 'self.z3.add(%s)' % ', '.join(constraints)
            eval(st)
            if self.z3.check() != z3.sat:
                return {}
            m = self.z3.model()
            solutions = {d.name(): m[d] for d in m.decls()}
            my_args = {k: solutions.get(k, None) for k in self.fn_args}
        predicate = 'z3.And(%s)' % ','.join(
            ["%s == %s" % (k, v) for k, v in my_args.items()])
        eval('self.z3.add(z3.Not(%s))' % predicate)
        return my_args

```

我们定义`get_path()`来检索当前路径并更新所使用的路径。

```py
class SimpleSymbolicFuzzer(SimpleSymbolicFuzzer):
    def get_next_path(self):
        self.last_path -= 1
        if self.last_path == -1:
            self.last_path = len(self.paths) - 1
        return self.paths[self.last_path]

```

`fuzz()`方法仅按顺序求解每个路径。

```py
class SimpleSymbolicFuzzer(SimpleSymbolicFuzzer):
    def fuzz(self):
        for i in range(self.max_tries):
            res = self.solve_path_constraint(self.get_next_path())
            if res:
                return res
        return {}

```

模糊器可以如下使用。

```py
a, b, c = None, None, None
symfz_ct = SimpleSymbolicFuzzer(check_triangle)
for i in range(1, 10):
    r = symfz_ct.fuzz()
    v = check_triangle(r['a'].as_long(), r['b'].as_long(), r['c'].as_long())
    print(r, "result:", v)

```

```py
{'a': 0, 'b': 1, 'c': 1} result: Isosceles
{'a': 1, 'b': 2, 'c': 3} result: Scalene
{'a': 2, 'b': 0, 'c': 2} result: Isosceles
{'a': 0, 'b': 0, 'c': 4} result: Isosceles
{'a': 1, 'b': 1, 'c': 1} result: Equilateral
{'a': 2, 'b': 3, 'c': 3} result: Isosceles
{'a': 4, 'b': 5, 'c': 6} result: Scalene
{'a': 4, 'b': 2, 'c': 4} result: Isosceles
{'a': 2, 'b': 2, 'c': 5} result: Isosceles

```

```py
symfz_av = SimpleSymbolicFuzzer(abs_value)
for i in range(1, 10):
    r = symfz_av.fuzz()
    v = abs_value(r['x'].numerator_as_long() / r['x'].denominator_as_long())
    print(r, "result:", v)

```

```py
{'x': 0} result: 0.0
{'x': -1} result: 1.0
{'x': 1} result: 1.0
{'x': -1/2} result: 0.5
{'x': 1/2} result: 0.5
{'x': -3/4} result: 0.75
{'x': 1/4} result: 0.25
{'x': -2} result: 2.0
{'x': 1/8} result: 0.125

```

*SimpleSymbolicFuzzer* 似乎对我们上面检查过的*简单*程序运行良好。

### 简单模糊器的问题

如前所述，`SimpleSymbolicFuzzer`尚无法处理变量重新分配。 此外，它也无法解决任何循环问题。 例如，考虑以下程序。

```py
def gcd(a: int, b: int) -> int:
    if a < b:
        c: int = a
        a = b
        b = c

    while b != 0:
        c: int = a
        a = b
        b = c % b
    return a

```

```py
show_cfg(gcd)

```

<svg height="672pt" viewBox="0.00 0.00 287.52 672.00" width="288pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 668)"><title>%3</title> <g class="node" id="node1"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="175.8437" y="-638.3">1: enter: gcd(a, b)</text></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="175.8437" y="-561.3">2: if: a < b</text></g> <g class="edge" id="edge2"><title>1->3</title></g> <g class="node" id="node2"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="74.8437" y="-91.3">1: exit: gcd(a, b)</text></g> <g class="node" id="node3"><title>11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="74.8437" y="-168.3">11: return a</text></g> <g class="edge" id="edge1"><title>11->2</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="135.8437" y="-474.3">3: c: int = a</text></g> <g class="edge" id="edge3"><title>3->4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="163.3437" y="-517.8">T</text></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="184.8437" y="-255.3">7: while: b != 0</text></g> <g class="edge" id="edge7"><title>3->7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="219.8437" y="-401.3">F</text></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="146.8437" y="-401.3">4: a = b</text></g> <g class="edge" id="edge4"><title>4->5</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="154.8437" y="-328.3">5: b = c</text></g> <g class="edge" id="edge5"><title>5->6</title></g> <g class="edge" id="edge6"><title>6->7</title></g> <g class="edge" id="edge12"><title>7->11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="140.8437" y="-211.8">F</text></g> <g class="node" id="node10"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="184.8437" y="-168.3">8: c: int = a</text></g> <g class="edge" id="edge9"><title>7->8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="189.3437" y="-211.8">T</text></g> <g class="node" id="node9"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="232.8437" y="-14.3">10: b = c % b</text></g> <g class="edge" id="edge8"><title>10->7</title></g> <g class="node" id="node11"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="201.8437" y="-91.3">9: a = b</text></g> <g class="edge" id="edge10"><title>8->9</title></g> <g class="edge" id="edge11"><title>9->10</title></g></g></svg>

```py
from [ExpectError](ExpectError.html) import ExpectError

```

```py
with ExpectError():
    symfz_gcd = SimpleSymbolicFuzzer(gcd, max_depth=1000, max_iter=10)
    for i in range(1, 100):
        r = symfz_gcd.fuzz()
        v = gcd(r['a'].as_long(), r['b'].as_long())
        print(r, v)

```

```py
Traceback (most recent call last):
  File "<ipython-input-122-81ef1b3c9fa1>", line 2, in <module>
    symfz_gcd = SimpleSymbolicFuzzer(gcd, max_depth=1000, max_iter=10)
  File "<ipython-input-99-bfa1b98c03e8>", line 15, in __init__
    self.process()
  File "<ipython-input-108-6bc97a9e8eb0>", line 3, in process
    self.paths = self.get_all_paths(self.fnenter)
  File "<ipython-input-106-3dd7b055ae0f>", line 10, in get_all_paths
    child_paths = self.get_all_paths(child, depth + 1)
  File "<ipython-input-106-3dd7b055ae0f>", line 10, in get_all_paths
    child_paths = self.get_all_paths(child, depth + 1)
  File "<ipython-input-106-3dd7b055ae0f>", line 10, in get_all_paths
    child_paths = self.get_all_paths(child, depth + 1)
  [Previous line repeated 998 more times]
  File "<ipython-input-106-3dd7b055ae0f>", line 4, in get_all_paths
    raise Exception('Maximum depth exceeded')
Exception: Maximum depth exceeded (expected)

```

这里的问题是我们的 *SimpleSymbolicFuzzer* 没有循环和变量重新分配的概念。 接下来，我们将看到如何解决此缺陷。

## 高级符号模糊器

接下来，我们定义`AdvancedSymbolicFuzzer`，它可以处理循环的重新分配和*展开。*

```py
class AdvancedSymbolicFuzzer(SimpleSymbolicFuzzer):
    def options(self, kwargs):
        super().options(kwargs)

```

一旦允许重新分配并展开循环，就必须处理如何调用生成的新变量。 这就是我们接下来要解决的问题。

### 处理重新评估

我们想重命名表达式中存在的所有变量，以便使用其使用计数来注释这些变量。 这使得确定可变的重新分配成为可能。 为此，我们定义`rename_variables()`函数，当给定`env`包含不同变量的当前使用索引时，该函数将使用注释对传入的AST节点中的变量进行重命名，并返回带有修改的副本。 请注意，此处我们不能使用 [NodeTransformer](https://docs.python.org/3/library/ast.html#ast.NodeTransformer) ，因为它会修改AST。

也就是说，如果表达式为`env[v] == 1`，则将`v`重命名为`_v_1`

```py
def rename_variables(astnode, env):
    if isinstance(astnode, ast.BoolOp):
        fn = 'z3.And' if isinstance(astnode.op, ast.And) else 'z3.Or'
        return ast.Call(
            ast.Name(fn, None),
            [rename_variables(i, env) for i in astnode.values], [])
    elif isinstance(astnode, ast.BinOp):
        return ast.BinOp(
            rename_variables(astnode.left, env), astnode.op,
            rename_variables(astnode.right, env))
    elif isinstance(astnode, ast.UnaryOp):
        if isinstance(astnode.op, ast.Not):
            return ast.Call(
                ast.Name('z3.Not', None),
                [rename_variables(astnode.operand, env)], [])
        else:
            return ast.UnaryOp(astnode.op,
                               rename_variables(astnode.operand, env))
    elif isinstance(astnode, ast.Call):
        return ast.Call(astnode.func,
                        [rename_variables(i, env) for i in astnode.args],
                        astnode.keywords)
    elif isinstance(astnode, ast.Compare):
        return ast.Compare(
            rename_variables(astnode.left, env), astnode.ops,
            [rename_variables(i, env) for i in astnode.comparators])
    elif isinstance(astnode, ast.Name):
        if astnode.id not in env:
            env[astnode.id] = 0
        num = env[astnode.id]
        return ast.Name('_%s_%d' % (astnode.id, num), astnode.ctx)
    elif isinstance(astnode, ast.Return):
        return ast.Return(rename_variables(astnode.value, env))
    else:
        return astnode

```

为了验证它是否可以正常工作，我们从环境开始。

```py
env = {'x': 1}

```

```py
ba = get_expression('x == 1 and y == 2')
type(ba)

```

```py
_ast.BoolOp

```

```py
assert to_src(rename_variables(ba, env)) == 'z3.And(_x_1 == 1, _y_0 == 2)'

```

```py
bo = get_expression('x == 1 or y == 2')
type(bo.op)

```

```py
_ast.Or

```

```py
assert to_src(rename_variables(bo, env)) == 'z3.Or(_x_1 == 1, _y_0 == 2)'

```

```py
b = get_expression('x + y')
type(b)

```

```py
_ast.BinOp

```

```py
assert to_src(rename_variables(b, env)) == '(_x_1 + _y_0)'

```

```py
u = get_expression('-y')
type(u)

```

```py
_ast.UnaryOp

```

```py
assert to_src(rename_variables(u, env)) == '(-_y_0)'

```

```py
un = get_expression('not y')
type(un.op)

```

```py
_ast.Not

```

```py
assert to_src(rename_variables(un, env)) == 'z3.Not(_y_0)'

```

```py
c = get_expression('x == y')
type(c)

```

```py
_ast.Compare

```

```py
assert to_src(rename_variables(c, env)) == '(_x_1 == _y_0)'

```

```py
f = get_expression('fn(x,y)')
type(f)

```

```py
_ast.Call

```

```py
assert to_src(rename_variables(f, env)) == 'fn(_x_1, _y_0)'

```

```py
env

```

```py
{'x': 1, 'y': 0}

```

接下来，我们要处理CFG，并正确转换路径。

### 跟踪分配

为了跟踪CFG中的分配，我们定义了存储当前CFG节点的数据结构`PNode`。

```py
class PNode:
    def __init__(self, idx, cfgnode, parent=None, order=0, seen=None):
        self.seen = {} if seen is None else seen
        self.max_iter = MAX_ITER
        self.idx, self.cfgnode, self.parent, self.order = idx, cfgnode, parent, order

    def __repr__(self):
        return "PNode:%d[%s order:%d]" % (self.idx, str(self.cfgnode),
                                          self.order)

```

定义新的`PNode`的步骤如下。

```py
cfg = PyCFG()
cfg.gen_cfg(inspect.getsource(gcd))
gcd_fnenter, _ = cfg.functions['gcd']

```

```py
PNode(0, gcd_fnenter)

```

```py
PNode:0[id:27 line[1] parents: [] : enter: gcd(a, b) order:0]

```

`copy()`方法会为孩子的守门生成一份副本，指示使用了哪个路径（孩子的`order`）。

```py
class PNode(PNode):
    def copy(self, order):
        p = PNode(self.idx, self.cfgnode, self.parent, order, self.seen)
        assert p.order == order
        return p

```

使用复制操作。

```py
PNode(0, gcd_fnenter).copy(1)

```

```py
PNode:0[id:27 line[1] parents: [] : enter: gcd(a, b) order:1]

```

#### 逐步探索路径

我们在使用`SimpleSymbolicFuzzer`时遇到的一个问题是，它在尝试另一个之前就探索了一条完成之路。 但是，这不是最佳的。 可能希望以一种更逐步的方式浏览该图，一次将一个可能的执行扩展到一个步骤。

因此，我们定义`explore()`，它一次探索一个节点的子节点（如果有）。 如果做得很详尽，这将生成从起始节点开始的所有路径，直到不再剩下子代为止。 我们将`PNode`设置为容器类，以便可以从外部驱动此迭代，如果说最大迭代已完成或需要确定某些路径的优先级，则停止该迭代。

```py
class PNode(PNode):
    def explore(self):
        ret = []
        for (i, n) in enumerate(self.cfgnode.children):
            key = "[%d]%s" % (self.idx + 1, n)
            ccount = self.seen.get(key, 0)
            if ccount > self.max_iter:
                continue  # drop this child
            self.seen[key] = ccount + 1
            pn = PNode(self.idx + 1, n, self.copy(i), seen=self.seen)
            ret.append(pn)
        return ret

```

我们可以如下使用`explore()`。

```py
PNode(0, gcd_fnenter).explore()

```

```py
[PNode:1[id:29 line[2] parents: [27] : _if: a < b order:0]]

```

```py
PNode(0, gcd_fnenter).explore()[0].explore()

```

```py
[PNode:2[id:30 line[3] parents: [29] : c: int = a order:0],
 PNode:2[id:33 line[7] parents: [32, 29, 36] : _while: b != 0 order:0]]

```

方法`get_path_to_root()`递归地通过子>父链检索完整链到最高父链。

```py
class PNode(PNode):
    def get_path_to_root(self):
        path = []
        n = self
        while n:
            path.append(n)
            n = n.parent
        return list(reversed(path))

```

```py
p = PNode(0, gcd_fnenter)
[s.get_path_to_root() for s in p.explore()[0].explore()[0].explore()[0].explore()]

```

```py
[[PNode:0[id:27 line[1] parents: [] : enter: gcd(a, b) order:0],
  PNode:1[id:29 line[2] parents: [27] : _if: a < b order:0],
  PNode:2[id:30 line[3] parents: [29] : c: int = a order:0],
  PNode:3[id:31 line[4] parents: [30] : a = b order:0],
  PNode:4[id:32 line[5] parents: [31] : b = c order:0]]]

```

节点的字符串表示形式为`z3`可解形式。

```py
class PNode(PNode):
    def __str__(self):
        path = self.get_path_to_root()
        ssa_path = to_single_assignment_predicates(path)
        return ', '.join([to_src(p) for p in ssa_path])

```

但是，在使用它之前，我们需要注意变量重命名，以便重新分配可以正常工作。

#### 重命名使用的变量

我们需要重命名使用过的变量。 任何变量`v = xxx`都应重命名为`_v_0`，任何更高的赋值（例如`v = v + 1`）都应转换为`_v_1 = _v_0 + 1`，更高的条件（例如`v == x`）应转换为`(_v_1 == _x_0)`。 方法`to_single_assignment_predicates()`针对给定路径执行此操作。

```py
def to_single_assignment_predicates(path):
    env = {}
    new_path = []
    for i, node in enumerate(path):
        ast_node = node.cfgnode.ast_node
        new_node = None
        if isinstance(ast_node, ast.AnnAssign) and ast_node.target.id in {
                'exit'}:
            new_node = None
        elif isinstance(ast_node, ast.AnnAssign) and ast_node.target.id in {'enter'}:
            args = [
                ast.parse(
                    "%s == _%s_0" %
                    (a.id, a.id)).body[0].value for a in ast_node.annotation.args]
            new_node = ast.Call(ast.Name('z3.And', None), args, [])
        elif isinstance(ast_node, ast.AnnAssign) and ast_node.target.id in {'_if', '_while'}:
            new_node = rename_variables(ast_node.annotation, env)
            if node.order != 0:
                assert node.order == 1
                new_node = ast.Call(ast.Name('z3.Not', None), [new_node], [])
        elif isinstance(ast_node, ast.AnnAssign):
            assigned = ast_node.target.id
            val = [rename_variables(ast_node.value, env)]
            env[assigned] = 0 if assigned not in env else env[assigned] + 1
            target = ast.Name('_%s_%d' %
                              (ast_node.target.id, env[assigned]), None)
            new_node = ast.Expr(ast.Compare(target, [ast.Eq()], val))
        elif isinstance(ast_node, ast.Assign):
            assigned = ast_node.targets[0].id
            val = [rename_variables(ast_node.value, env)]
            env[assigned] = 0 if assigned not in env else env[assigned] + 1
            target = ast.Name('_%s_%d' %
                              (ast_node.targets[0].id, env[assigned]), None)
            new_node = ast.Expr(ast.Compare(target, [ast.Eq()], val))
        elif isinstance(ast_node, (ast.Return, ast.Pass)):
            new_node = None
        else:
            s = "NI %s  %s" % (type(ast_node), ast_node.target.id)
            raise Exception(s)
        new_path.append(new_node)
    return new_path

```

Here is how it can be used:

```py
p = PNode(0, gcd_fnenter)
path = p.explore()[0].explore()[0].explore()[0].get_path_to_root()
spath = to_single_assignment_predicates(path)

```

```py
[to_src(s) for s in spath]

```

```py
['z3.And(a == _a_0, b == _b_0)',
 '(_a_0 < _b_0)',
 '_c_0 == _a_0',
 '_a_1 == _b_0']

```

#### 循环之前检查

*圆锥形*执行简化*符号*执行的方式之一是处理循环。 与其尝试确定循环的不变性，不如简单地将*展开*循环数次，直到达到`MAX_DEPTH`极限。 但是，直到达到`MAX_DEPTH`时，才需要展开所有循环。 其中一些可能会在此之前退出。 因此，有必要在继续探索之前检查是否可以满足给定的约束集。

```py
def identifiers_with_types(identifiers, defined):
    with_types = dict(defined)
    for i in identifiers:
        if i[0] == '_':
            nxt = i[1:].find('_', 1)
            name = i[1:nxt + 1]
            assert name in defined
            typ = defined[name]
            with_types[i] = typ
    return with_types

```

`extract_constraints()`从路径生成`z3`约束。 主要工作由`to_single_assignment_predicates()`完成。 `extract_constraints()`然后将AST转换为源。

```py
class AdvancedSymbolicFuzzer(AdvancedSymbolicFuzzer):
    def extract_constraints(self, path):
        return [to_src(p) for p in to_single_assignment_predicates(path) if p]

```

### 解决路径约束

现在，我们更新`solve_path_constraint()`方法，以考虑在重新分配期间创建的新标识符。

```py
class AdvancedSymbolicFuzzer(AdvancedSymbolicFuzzer):
    def solve_path_constraint(self, path):
        # re-initializing does not seem problematic.
        # a = z3.Int('a').get_id() remains the same.
        constraints = self.extract_constraints(path)
        identifiers = [
            c for i in constraints for c in used_identifiers(i)]  # <- changes
        with_types = identifiers_with_types(
            identifiers, self.used_variables)  # <- changes
        decl = define_symbolic_vars(with_types, '')
        exec(decl)

        solutions = {}
        with checkpoint(self.z3):
            st = 'self.z3.add(%s)' % ', '.join(constraints)
            eval(st)
            if self.z3.check() != z3.sat:
                return {}
            m = self.z3.model()
            solutions = {d.name(): m[d] for d in m.decls()}
            my_args = {k: solutions.get(k, None) for k in self.fn_args}
        predicate = 'z3.And(%s)' % ','.join(
            ["%s == %s" % (k, v) for k, v in my_args.items()])
        eval('self.z3.add(z3.Not(%s))' % predicate)
        return my_args

```

### 生成所有路径

`get_all_paths()`现在已类似更新，因此我们仅将循环展开到指定的高度。 它也被转换为迭代探索风格，因此我们以广度优先的方式探索CFG。

```py
class AdvancedSymbolicFuzzer(AdvancedSymbolicFuzzer):
    def get_all_paths(self, fenter):
        path_lst = [PNode(0, fenter)]
        completed = []
        for i in range(self.max_iter):
            new_paths = [PNode(0, fenter)]
            for path in path_lst:
                # explore each path once
                if path.cfgnode.children:
                    np = path.explore()
                    for p in np:
                        if path.idx > self.max_depth:
                            break
                        new_paths.append(p)
                else:
                    completed.append(path)
            path_lst = new_paths
        return completed + path_lst

```

现在，我们可以使用高级符号模糊器获得所有路径，如下所示。

```py
asymfz_gcd = AdvancedSymbolicFuzzer(
    gcd, max_iter=10, max_tries=10, max_depth=10)
paths = asymfz_gcd.get_all_paths(asymfz_gcd.fnenter)
print(len(paths))
paths[37].get_path_to_root()

```

```py
38

```

```py
[PNode:0[id:40 line[1] parents: [] : enter: gcd(a, b) order:0],
 PNode:1[id:42 line[2] parents: [40] : _if: a < b order:1],
 PNode:2[id:46 line[7] parents: [45, 42, 49] : _while: b != 0 order:0],
 PNode:3[id:47 line[8] parents: [46] : c: int = a order:0],
 PNode:4[id:48 line[9] parents: [47] : a = b order:0],
 PNode:5[id:49 line[10] parents: [48] : b = c % b order:0],
 PNode:6[id:46 line[7] parents: [45, 42, 49] : _while: b != 0 order:0],
 PNode:7[id:47 line[8] parents: [46] : c: int = a order:0],
 PNode:8[id:48 line[9] parents: [47] : a = b order:0],
 PNode:9[id:49 line[10] parents: [48] : b = c % b order:0],
 PNode:10[id:46 line[7] parents: [45, 42, 49] : _while: b != 0 order:0]]

```

我们还可以在每个路径中列出谓词。

```py
for s in to_single_assignment_predicates(paths[37].get_path_to_root()):
    if s is not None:
        print(to_src(s))

```

```py
z3.And(a == _a_0, b == _b_0)
z3.Not(_a_0 < _b_0)
(_b_0 != 0)
_c_0 == _a_0
_a_1 == _b_0
_b_1 == _c_0 % _b_0
(_b_1 != 0)
_c_1 == _a_1
_a_2 == _b_1
_b_2 == _c_1 % _b_1
(_b_2 != 0)

```

```py
constraints = asymfz_gcd.extract_constraints(paths[37].get_path_to_root())

```

```py
constraints

```

```py
['z3.And(a == _a_0, b == _b_0)',
 'z3.Not(_a_0 < _b_0)',
 '(_b_0 != 0)',
 '_c_0 == _a_0',
 '_a_1 == _b_0',
 '_b_1 == _c_0 % _b_0',
 '(_b_1 != 0)',
 '_c_1 == _a_1',
 '_a_2 == _b_1',
 '_b_2 == _c_1 % _b_1',
 '(_b_2 != 0)']

```

打印出的约束条件表明，我们的变量重命名方法是成功的。 我们只需要一件就可以完成拼图。 我们的路径仍然是`PNode`。 我们需要修改`get_next_path()`，以便我们返回相应的谓词链。

```py
class AdvancedSymbolicFuzzer(AdvancedSymbolicFuzzer):
    def get_next_path(self):
        self.last_path -= 1
        if self.last_path == -1:
            self.last_path = len(self.paths) - 1
        return self.paths[self.last_path].get_path_to_root()

```

接下来，我们将看到如何使用我们的模糊器进行模糊测试。

### 使用高级符号模糊器[进行模糊处理](#Fuzzing-with-Advanced-Symbolic-Fuzzer)

我们在 *gcd* 上使用了高级的符号模糊器，以生成合理的输入。

```py
asymfz_gcd = AdvancedSymbolicFuzzer(
    gcd, max_tries=10, max_iter=10, max_depth=10)
data = []
for i in range(10):
    r = asymfz_gcd.fuzz()
    data.append((r['a'].as_long(), r['b'].as_long()))
    v = gcd(*data[-1])
    print(r, "result:", repr(v))

```

```py
{'a': 7, 'b': 4} result: 1
{'a': 8, 'b': 16} result: 8
{'a': 2, 'b': 17} result: 1
{'a': 6, 'b': 5} result: 1
{'a': 3, 'b': 5} result: 1
{'a': 1, 'b': 1} result: 1
{'a': 5, 'b': 2} result: 1
{'a': 4, 'b': 8} result: 4
{'a': 9, 'b': 9} result: 9
{'a': 13, 'b': 12} result: 1

```

Theo的输出看起来很合理。 但是，获得的覆盖率是多少？

```py
with ArcCoverage() as cov:
    for a, b in data:
        gcd(a, b)

```

```py
cov.show_coverage(gcd)

```

```py
#  1: def gcd(a: int, b: int) -> int:
#  2:     if a < b:
#  3:         c: int = a
#  4:         a = b
#  5:         b = c
   6: 
#  7:     while b != 0:
#  8:         c: int = a
#  9:         a = b
# 10:         b = c % b
# 11:     return a
  12: 

```

```py
show_cfg(gcd, arcs=cov.arcs())

```

<svg height="636pt" viewBox="0.00 0.00 287.52 636.00" width="288pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 632)"><title>%3</title> <g class="node" id="node1"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="178.8437" y="-602.3">1: enter: gcd(a, b)</text></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="178.8437" y="-526.3">2: if: a < b</text></g> <g class="edge" id="edge2"><title>1->3</title></g> <g class="node" id="node2"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="74.8437" y="-90.3">1: exit: gcd(a, b)</text></g> <g class="node" id="node3"><title>11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="74.8437" y="-166.3">11: return a</text></g> <g class="edge" id="edge1"><title>11->2</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="138.8437" y="-454.3">3: c: int = a</text></g> <g class="edge" id="edge3"><title>3->4</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="184.8437" y="-238.3">7: while: b != 0</text></g> <g class="edge" id="edge7"><title>3->7</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="149.8437" y="-382.3">4: a = b</text></g> <g class="edge" id="edge4"><title>4->5</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="155.8437" y="-310.3">5: b = c</text></g> <g class="edge" id="edge5"><title>5->6</title></g> <g class="edge" id="edge6"><title>6->7</title></g> <g class="edge" id="edge12"><title>7->11</title></g> <g class="node" id="node10"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="184.8437" y="-166.3">8: c: int = a</text></g> <g class="edge" id="edge9"><title>7->8</title></g> <g class="node" id="node9"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="232.8437" y="-14.3">10: b = c % b</text></g> <g class="edge" id="edge8"><title>10->7</title></g> <g class="node" id="node11"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="201.8437" y="-90.3">9: a = b</text></g> <g class="edge" id="edge10"><title>8->9</title></g> <g class="edge" id="edge11"><title>9->10</title></g></g></svg>

实际上，分支和语句覆盖率可视化似乎都表明我们已实现完整覆盖率。 我们如何在实践中利用我们的模糊器？ 我们探索了一个用于解决二次方程根的程序的小案例研究。

#### 示例：二次方程[的根](#Example:-Roots-of-a-Quadratic-Equation)

这是找到二次方程式根的著名方程式。

```py
from [typing](https://docs.python.org/3/library/typing.html) import Tuple

```

```py
def roots(a: float, b: float, c: float) -> Tuple[float, float]:
    d: float = b * b - 4 * a * c
    ax: float = 0.5 * d
    bx: float = 0
    while (ax - bx) > 0.1:
        bx = 0.5 * (ax + d / ax)
        ax = bx
    s: float = bx

    a2: float = 2 * a
    ba2: float = b / a2
    return -ba2 + s / a2, -ba2 - s / a2

```

程序看起来正确吗？ 让我们调查该程序是否合理。 但是在此之前，我们需要一个辅助函数`sym_to_float()`将符号值转换为浮点。

```py
def sym_to_float(v):
    if v is None:
        return math.inf
    elif isinstance(v, z3.IntNumRef):
        return v.as_long()
    return v.numerator_as_long() / v.denominator_as_long()

```

现在我们准备开始模糊测试了。

```py
asymfz_roots = AdvancedSymbolicFuzzer(
    roots,
    max_tries=10,
    max_iter=10,
    max_depth=10)
with ExpectError():
    for i in range(100):
        r = asymfz_roots.fuzz()
        print(r)
        d = [sym_to_float(r[i]) for i in ['a', 'b', 'c']]
        v = roots(*d)
        print(d, v)

```

```py
{'a': 1/2, 'b': 1/8, 'c': -2}
[0.5, 0.125, -2.0] (1.87890625, -2.12890625)
{'a': 1/2, 'b': 1/8, 'c': 1}
[0.5, 0.125, 1.0] (-0.125, -0.125)
{'a': 1/2, 'b': 1/8, 'c': -3}
[0.5, 0.125, -3.0] (2.37890625, -2.62890625)
{'a': 1/2, 'b': 1/8, 'c': 2}
[0.5, 0.125, 2.0] (-0.125, -0.125)
{'a': 1/2, 'b': 1/8, 'c': -4}
[0.5, 0.125, -4.0] (2.87890625, -3.12890625)
{'a': 1/2, 'b': 1/8, 'c': 3}
[0.5, 0.125, 3.0] (-0.125, -0.125)
{'a': 1/2, 'b': 1/8, 'c': 4}
[0.5, 0.125, 4.0] (-0.125, -0.125)
{'a': 1/2, 'b': 1/8, 'c': -5}
[0.5, 0.125, -5.0] (3.37890625, -3.62890625)
{'a': 3/2, 'b': 0, 'c': 0}
[1.5, 0.0, 0.0] (0.0, -0.0)
{'a': 1/2, 'b': 1/8, 'c': -1}
[0.5, 0.125, -1.0] (1.37890625, -1.62890625)
{'a': 1/2, 'b': 1/8, 'c': -3/2}
[0.5, 0.125, -1.5] (1.62890625, -1.87890625)
{'a': 1, 'b': 0, 'c': 1/2}
[1.0, 0.0, 0.5] (0.0, -0.0)
{'a': 0, 'b': 0, 'c': -11/2}

```

```py
Traceback (most recent call last):
  File "<ipython-input-171-be5116409d4d>", line 11, in <module>
    v = roots(*d)
  File "<ipython-input-169-f5c00743e7ce>", line 11, in roots
    ba2: float = b / a2
ZeroDivisionError: float division by zero (expected)

```

我们有一个`ZeroDivisionError`。 我们可以消除它吗？

##### 根-除法前检查

```py
def roots2(a: float, b: float, c: float) -> Tuple[float, float]:
    d: float = b * b - 4 * a * c

    xa: float = 0.5 * d
    xb: float = 0
    while (xa - xb) > 0.1:
        xb = 0.5 * (xa + d / xa)
        xa = xb
    s: float = xb

    if a == 0:
        return -c / b

    a2: float = 2 * a
    ba2: float = b / a2
    return -ba2 + s / a2, -ba2 - s / a2

```

```py
asymfz_roots = AdvancedSymbolicFuzzer(
    roots2,
    max_tries=10,
    max_iter=10,
    max_depth=10)
with ExpectError():
    for i in range(1000):
        r = asymfz_roots.fuzz()
        d = [sym_to_float(r[i]) for i in ['a', 'b', 'c']]
        v = roots2(*d)
        #print(d, v)

```

```py
Traceback (most recent call last):
  File "<ipython-input-173-37c0dadab17b>", line 10, in <module>
    v = roots2(*d)
  File "<ipython-input-172-931666e5c85c>", line 12, in roots2
    return -c / b
ZeroDivisionError: float division by zero (expected)

```

显然，我们的修复不完整。 让我们再试一次。

##### 根-消除零除错误

```py
import [math](https://docs.python.org/3/library/math.html)

```

```py
def roots3(a: float, b: float, c: float) -> Tuple[float, float]:
    d: float = b * b - 4 * a * c

    xa: float = 0.5 * d
    xb: float = 0
    while (xa - xb) > 0.1:
        xb = 0.5 * (xa + d / xa)
        xa = xb
    s: float = xb

    if a == 0:
        if b == 0:
            return math.inf
        return -c / b

    a2: float = 2 * a
    ba2: float = b / a2
    return -ba2 + s / a2, -ba2 - s / a2

```

```py
asymfz_roots = AdvancedSymbolicFuzzer(
    roots3,
    max_tries=10,
    max_iter=10,
    max_depth=10)
for i in range(10):
    r = asymfz_roots.fuzz()
    print(r)
    d = [sym_to_float(r[i]) for i in ['a', 'b', 'c']]
    v = roots3(*d)
    print(d, v)

```

```py
{'a': -1, 'b': 0, 'c': 0}
[-1.0, 0.0, 0.0] (0.0, 0.0)
{'a': 1, 'b': 1/8, 'c': -2}
[1.0, 0.125, -2.0] (1.439453125, -1.564453125)
{'a': -9/8, 'b': 0, 'c': 0}
[-1.125, 0.0, 0.0] (0.0, 0.0)
{'a': 0, 'b': 1/4, 'c': 0}
[0.0, 0.25, 0.0] -0.0
{'a': 0, 'b': 0, 'c': 1}
[0.0, 0.0, 1.0] inf
{'a': 0, 'b': 1, 'c': -1/4}
[0.0, 1.0, -0.25] 0.25
{'a': 1, 'b': 1/8, 'c': 1}
[1.0, 0.125, 1.0] (-0.0625, -0.0625)
{'a': 0, 'b': 1/4, 'c': -1}
[0.0, 0.25, -1.0] 4.0
{'a': 0, 'b': 0, 'c': 2}
[0.0, 0.0, 2.0] inf
{'a': 1/2, 'b': 1/8, 'c': -2}
[0.5, 0.125, -2.0] (1.87890625, -2.12890625)

```

以此证明，我们可以使用 *AdvancedSymbolicFuzzer* 来模糊程序，并且可以帮助识别代码中的问题。

## 限制

该程序存在明显错误。 我们不检查负根。 但是，符号执行似乎没有检测到它。 为什么我们无法检测到负根问题？ 因为我们在预定深度停止执行而不会引发错误。 也就是说，我们的象征性执行是广泛而肤浅的。 可以克服此限制的方法之一是依靠[圆锥形执行](ConcolicFuzzer.html)，它允许人们比纯符号执行更深入。 第二个问题是符号执行必定需要大量计算。 这意味着基于规范的模糊器通常能够生成更大的输入集，并且在不检查魔术字节的程序上连续具有更大的覆盖范围，因此它们提供了合理的探索梯度。

## 经验教训

*   可以使用符号执行来增加探索程序所有特征的输入。
*   符号执行可以是广泛的，但可以是浅层的。
*   符号执行非常适合依赖于输入中存在特定值的程序，但是，当这些值不存在时，它的效用会降低，并且输入空间在覆盖率方面表现出梯度。

## 后续步骤

*   [基于搜索的模糊处理](SearchBasedFuzzer.html)在随机模糊处理无法提供足够结果，但符号模糊处理过于繁重时通常可以作为可接受的中间依据。

## 背景

程序的符号执行最初由King [ [King *等人*，1976年。](https://doi.org/10.1145/360248.360252)]于1976年描述。它广泛用于软件尤其是二进制程序的漏洞分析中。 一些众所周知的符号执行工具包括KLEE [Unresolved citation：KLEE。]，angr [Wang *等人*，2017.，Unresolved citation：stephens2016driller。]和SAGE [Godefroid *et al [* ，2012。]。 Python最著名的符号执行环境是CHEF [Unresolved citation：bucur2014prototyping。]，它通过修改解释器来执行符号执行。

## 练习

### 练习1：*扩展符号模糊器以使用函数摘要*

我们在第一部分中展示了如何生成函数摘要。 可以在需要时扩展`AdvancedSymbolicFuzzer`以使用函数摘要吗？

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/SymbolicFuzzer.ipynb#Exercises) to work on the exercises and see solutions.

### 练习2：*静态检查是否应进一步展开循环*

我们研究了在探索到固定深度时如何展开回路。 但是，并非所有循环都需要完全展开。 一些循环可能仅包含恒定数量的迭代。 例如，考虑下面的循环。

```py
i = 0
while i < 10:
    i += 1

```

此循环需要准确地展开$ 10 $次。 在这种情况下，可以实现以下方法调用的`can_be_satisfied()`方法，以便仅在可以满足路径条件的情况下进一步展开。

```py
class AdvancedSymbolicFuzzer(AdvancedSymbolicFuzzer):
    def get_all_paths(self, fenter):
        path_lst = [PNode(0, fenter)]
        completed = []
        for i in range(self.max_iter):
            new_paths = [PNode(0, fenter)]
            for path in path_lst:
                # explore each path once
                if path.cfgnode.children:
                    np = path.explore()
                    for p in np:
                        if path.idx > self.max_depth:
                            break
                        if self.can_be_satisfied(p):
                            new_paths.append(p)
                        else:
                            break
                else:
                    completed.append(path)
            path_lst = new_paths
        return completed + path_lst

```

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/SymbolicFuzzer.ipynb#Exercises) to work on the exercises and see solutions.

**解决方案。** 这是一个解决方案。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/SymbolicFuzzer.ipynb#Exercises) to work on the exercises and see solutions.

### 练习3：*实现一个共模模糊器*

我们已经在[保形模糊](ConcolicFuzzer.html)的一章中看到了如何使用信息流来跟踪函数concolicaly。 但是，这在某种程度上不是最佳的，因为当信息流是间接的时（如在基于控制流的信息流中），约束可能会丢失。 您是否可以使用我们为符号执行而构建的基础结构来实现圆锥形跟踪？

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/SymbolicFuzzer.ipynb#Exercises) to work on the exercises and see solutions.

**解决方案。** 这是一个可能的解决方案。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/SymbolicFuzzer.ipynb#Exercises) to work on the exercises and see solutions.

实际上，现在所追踪的路径是不同的。 可以将这一过程继续进行必要的次数，以探索所有附近的执行路径。

您能否将这种探索纳入到圆锥形的模糊器中？

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/SymbolicFuzzer.ipynb#Exercises) to work on the exercises and see solutions.